Skip to content

Commit b580552

Browse files
committed
feat: add biome fmt
1 parent 5e8c629 commit b580552

29 files changed

Lines changed: 1758 additions & 1630 deletions

biome.json

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
{
2+
"$schema": "https://biomejs.dev/schemas/2.3.13/schema.json",
3+
"vcs": {
4+
"enabled": true,
5+
"clientKind": "git",
6+
"useIgnoreFile": true
7+
},
8+
"files": {
9+
"ignoreUnknown": false
10+
},
11+
"formatter": {
12+
"enabled": true,
13+
"indentStyle": "tab"
14+
},
15+
"linter": {
16+
"enabled": true,
17+
"rules": {
18+
"recommended": true
19+
}
20+
},
21+
"javascript": {
22+
"formatter": {
23+
"quoteStyle": "double"
24+
}
25+
},
26+
"assist": {
27+
"enabled": true,
28+
"actions": {
29+
"source": {
30+
"organizeImports": "on"
31+
}
32+
}
33+
}
34+
}

package.json

Lines changed: 23 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,25 @@
11
{
2-
"name": "store",
3-
"scripts": {
4-
"dev": "bun run --hot src/index.ts",
5-
"start": "NODE_ENV=production bun run src/index.ts",
6-
"type-check": "tsc --noEmit",
7-
"prisma-deploy": "prisma migrate deploy"
8-
},
9-
"dependencies": {
10-
"@hono/zod-validator": "^0.7.6",
11-
"@prisma/client": "^6.18.0",
12-
"dotenv": "^17.2.3",
13-
"hono": "^4.10.3",
14-
"hono-rate-limiter": "^0.5.3",
15-
"jszip": "^3.10.1",
16-
"zod": "^3.24.1"
17-
},
18-
"devDependencies": {
19-
"@types/bun": "latest",
20-
"prisma": "^6.18.0",
21-
"typescript": "^5.9.3"
22-
},
23-
"type": "module"
2+
"name": "store",
3+
"scripts": {
4+
"dev": "bun run --hot src/index.ts",
5+
"start": "NODE_ENV=production bun run src/index.ts",
6+
"format": "biome format --write",
7+
"type-check": "tsc --noEmit",
8+
"prisma-deploy": "prisma migrate deploy"
9+
},
10+
"dependencies": {
11+
"@hono/zod-validator": "^0.7.6",
12+
"@prisma/client": "^6.18.0",
13+
"dotenv": "^17.2.3",
14+
"hono": "^4.10.3",
15+
"hono-rate-limiter": "^0.5.3",
16+
"jszip": "^3.10.1",
17+
"zod": "^3.24.1"
18+
},
19+
"devDependencies": {
20+
"@types/bun": "latest",
21+
"prisma": "^6.18.0",
22+
"typescript": "^5.9.3"
23+
},
24+
"type": "module"
2425
}

prisma.config.ts

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import { defineConfig, env } from "prisma/config";
2-
import "dotenv/config"
2+
import "dotenv/config";
33

44
export default defineConfig({
5-
schema: "prisma/schema.prisma",
6-
migrations: {
7-
path: "prisma/migrations",
8-
},
9-
engine: "classic",
10-
datasource: {
11-
url: env("DATABASE_URL"),
12-
},
5+
schema: "prisma/schema.prisma",
6+
migrations: {
7+
path: "prisma/migrations",
8+
},
9+
engine: "classic",
10+
datasource: {
11+
url: env("DATABASE_URL"),
12+
},
1313
});

src/constants/platforms.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
export const VALID_PLATFORMS = ['linux', 'macos', 'windows'] as const;
2-
export type Platform = typeof VALID_PLATFORMS[number];
1+
export const VALID_PLATFORMS = ["linux", "macos", "windows"] as const;
2+
export type Platform = (typeof VALID_PLATFORMS)[number];
33

44
export function isValidPlatform(platform: string): platform is Platform {
55
return VALID_PLATFORMS.includes(platform as Platform);

src/db.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import { PrismaClient } from './generated/prisma/client.js';
1+
import { PrismaClient } from "./generated/prisma/client.js";
22

33
export const prisma = new PrismaClient();
44

5-
if (typeof process !== 'undefined') {
6-
process.on('beforeExit', async () => {
7-
await prisma.$disconnect();
8-
});
5+
if (typeof process !== "undefined") {
6+
process.on("beforeExit", async () => {
7+
await prisma.$disconnect();
8+
});
99
}

src/index.ts

Lines changed: 38 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,52 @@
1-
import { Hono } from 'hono'
2-
import type { AppContext } from '@/types/app.js';
3-
import { prisma } from '@/db.js';
4-
import { VALID_PLATFORMS } from '@/constants/platforms.js';
5-
import { createStorageFromEnv, LocalStorageAdapter } from '@/storage/index.js';
6-
import { ipMiddleware } from '@/middleware/ip.js';
7-
import storageRouter from '@/routes/storage.js';
8-
import v1 from '@/routes/v1/index.js';
9-
import { authMiddleware } from './middleware/auth';
10-
import { rateLimiter } from 'hono-rate-limiter';
11-
import { logger } from 'hono/logger'
1+
import { Hono } from "hono";
2+
import type { AppContext } from "@/types/app.js";
3+
import { prisma } from "@/db.js";
4+
import { VALID_PLATFORMS } from "@/constants/platforms.js";
5+
import { createStorageFromEnv, LocalStorageAdapter } from "@/storage/index.js";
6+
import { ipMiddleware } from "@/middleware/ip.js";
7+
import storageRouter from "@/routes/storage.js";
8+
import v1 from "@/routes/v1/index.js";
9+
import { authMiddleware } from "./middleware/auth";
10+
import { rateLimiter } from "hono-rate-limiter";
11+
import { logger } from "hono/logger";
1212

1313
await prisma.$transaction(
14-
VALID_PLATFORMS.map(p => prisma.extensionPlatform.upsert({
15-
create: { id: p },
16-
where: { id: p },
17-
update: {}
18-
})),
14+
VALID_PLATFORMS.map((p) =>
15+
prisma.extensionPlatform.upsert({
16+
create: { id: p },
17+
where: { id: p },
18+
update: {},
19+
}),
20+
),
1921
);
2022

21-
const app = new Hono<AppContext>()
23+
const app = new Hono<AppContext>();
2224
const storage = createStorageFromEnv();
2325

2426
app.use(logger());
25-
app.use('*', ipMiddleware());
26-
app.use('*', rateLimiter<AppContext>({
27-
windowMs: 60 * 1000,
28-
limit: 60,
29-
keyGenerator: (c) => c.get('clientIp'),
30-
}));
31-
app.use('*', authMiddleware());
32-
app.use('*', async (c, next) => {
33-
c.set('storage', storage)
34-
await next()
35-
})
27+
app.use("*", ipMiddleware());
28+
app.use(
29+
"*",
30+
rateLimiter<AppContext>({
31+
windowMs: 60 * 1000,
32+
limit: 60,
33+
keyGenerator: (c) => c.get("clientIp"),
34+
}),
35+
);
36+
app.use("*", authMiddleware());
37+
app.use("*", async (c, next) => {
38+
c.set("storage", storage);
39+
await next();
40+
});
3641

3742
if (storage instanceof LocalStorageAdapter) {
38-
app.route('/', storageRouter);
43+
app.route("/", storageRouter);
3944
}
4045

41-
app.get('/', (c) => {
42-
return c.json({ message: 'Vicinae Backend' })
43-
})
46+
app.get("/", (c) => {
47+
return c.json({ message: "Vicinae Backend" });
48+
});
4449

45-
app.route('/v1', v1);
50+
app.route("/v1", v1);
4651

4752
export default app;

src/middleware/auth.ts

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,23 @@
11
import type { MiddlewareHandler } from "hono";
2-
import type { AppContext } from '@/types/app.js';
2+
import type { AppContext } from "@/types/app.js";
33

44
const API_SECRET = process.env.API_SECRET;
55

66
export const authMiddleware = (): MiddlewareHandler<AppContext> => {
77
return async (c, next) => {
8-
const authorization = c.req.header('Authorization');
9-
const key = authorization?.split(' ')[1] ?? '';
8+
const authorization = c.req.header("Authorization");
9+
const key = authorization?.split(" ")[1] ?? "";
1010

1111
// check but don't block
1212
if (key !== API_SECRET) {
13-
if (key.length != 0) { console.warn(`Got invalid api secret: ${key}`); }
14-
c.set('authenticated', false);
13+
if (key.length != 0) {
14+
console.warn(`Got invalid api secret: ${key}`);
15+
}
16+
c.set("authenticated", false);
1517
return next();
1618
}
1719

18-
c.set('authenticated', true);
20+
c.set("authenticated", true);
1921
return next();
2022
};
2123
};

src/middleware/ip.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
import type { MiddlewareHandler } from 'hono';
2-
import type { AppContext } from '@/types/app.js';
1+
import type { MiddlewareHandler } from "hono";
2+
import type { AppContext } from "@/types/app.js";
33

44
/**
55
* Middleware to extract and store the real client IP in context.
@@ -10,39 +10,39 @@ export const ipMiddleware = (): MiddlewareHandler<AppContext> => {
1010
return async (c, next) => {
1111
// Try X-Forwarded-For first (set by Caddy and most reverse proxies)
1212
// Take the first IP in the chain (original client)
13-
const forwardedFor = c.req.header('X-Forwarded-For');
13+
const forwardedFor = c.req.header("X-Forwarded-For");
1414
if (forwardedFor) {
15-
c.set('clientIp', forwardedFor.split(',')[0].trim());
15+
c.set("clientIp", forwardedFor.split(",")[0].trim());
1616
await next();
1717
return;
1818
}
1919

2020
// Try X-Real-IP (also set by Caddy)
21-
const realIp = c.req.header('X-Real-IP');
21+
const realIp = c.req.header("X-Real-IP");
2222
if (realIp) {
23-
c.set('clientIp', realIp);
23+
c.set("clientIp", realIp);
2424
await next();
2525
return;
2626
}
2727

2828
// Try CF-Connecting-IP (Cloudflare)
29-
const cfIp = c.req.header('CF-Connecting-IP');
29+
const cfIp = c.req.header("CF-Connecting-IP");
3030
if (cfIp) {
31-
c.set('clientIp', cfIp);
31+
c.set("clientIp", cfIp);
3232
await next();
3333
return;
3434
}
3535

3636
// Try True-Client-IP (Cloudflare Enterprise)
37-
const trueClientIp = c.req.header('True-Client-IP');
37+
const trueClientIp = c.req.header("True-Client-IP");
3838
if (trueClientIp) {
39-
c.set('clientIp', trueClientIp);
39+
c.set("clientIp", trueClientIp);
4040
await next();
4141
return;
4242
}
4343

4444
// Fallback to unknown
45-
c.set('clientIp', 'unknown');
45+
c.set("clientIp", "unknown");
4646
await next();
4747
};
4848
};

src/routes/storage.ts

Lines changed: 39 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,47 @@
11
import { Hono } from "hono";
22
import { getMimeType } from "@/utils/mime.js";
3-
import type { AppContext } from '@/types/app.js';
3+
import type { AppContext } from "@/types/app.js";
44

55
const app = new Hono<AppContext>();
66

7-
app.get('/storage/*', async (c) => {
8-
try {
9-
const storage = c.var.storage;
10-
const path = c.req.path.replace('/storage/', '');
11-
const file = await storage.get(path);
12-
13-
const contentType = getMimeType(path);
14-
const filename = path.split('/').pop();
15-
16-
// For images and markdown, use inline display instead of attachment
17-
const isInline = contentType.startsWith('image/') || contentType === 'text/markdown';
18-
19-
// Compute ETag from file buffer for cache validation
20-
const crypto = await import('crypto');
21-
const hash = crypto.createHash('md5').update(file).digest('hex');
22-
const etag = `"${hash}"`;
23-
24-
// Check if client has cached version
25-
const ifNoneMatch = c.req.header('if-none-match');
26-
if (ifNoneMatch === etag) {
27-
return new Response(null, { status: 304 });
28-
}
29-
30-
return new Response(file, {
31-
headers: {
32-
'Content-Type': contentType,
33-
'Content-Disposition': isInline
34-
? `inline; filename="${filename}"`
35-
: `attachment; filename="${filename}"`,
36-
// Cache for 1 year since files are content-addressed (path includes version)
37-
'Cache-Control': 'public, max-age=31536000, immutable',
38-
'ETag': etag,
39-
},
40-
});
41-
} catch (error) {
42-
return c.json({ error: 'File not found' }, 404);
43-
}
7+
app.get("/storage/*", async (c) => {
8+
try {
9+
const storage = c.var.storage;
10+
const path = c.req.path.replace("/storage/", "");
11+
const file = await storage.get(path);
12+
13+
const contentType = getMimeType(path);
14+
const filename = path.split("/").pop();
15+
16+
// For images and markdown, use inline display instead of attachment
17+
const isInline =
18+
contentType.startsWith("image/") || contentType === "text/markdown";
19+
20+
// Compute ETag from file buffer for cache validation
21+
const crypto = await import("crypto");
22+
const hash = crypto.createHash("md5").update(file).digest("hex");
23+
const etag = `"${hash}"`;
24+
25+
// Check if client has cached version
26+
const ifNoneMatch = c.req.header("if-none-match");
27+
if (ifNoneMatch === etag) {
28+
return new Response(null, { status: 304 });
29+
}
30+
31+
return new Response(file, {
32+
headers: {
33+
"Content-Type": contentType,
34+
"Content-Disposition": isInline
35+
? `inline; filename="${filename}"`
36+
: `attachment; filename="${filename}"`,
37+
// Cache for 1 year since files are content-addressed (path includes version)
38+
"Cache-Control": "public, max-age=31536000, immutable",
39+
ETag: etag,
40+
},
41+
});
42+
} catch (error) {
43+
return c.json({ error: "File not found" }, 404);
44+
}
4445
});
4546

46-
4747
export default app;

0 commit comments

Comments
 (0)