From 929d92235b0e42cbedd775461df7ca126fae9c01 Mon Sep 17 00:00:00 2001 From: npmrun <1549469775@qq.com> Date: Thu, 14 May 2026 10:51:43 +0800 Subject: [PATCH] add --- .env.example | 5 +- build-files/migrate/migrate-pg.js | 50 ---------- build-files/migrate/migrate-sqlite.js | 51 ----------- build-files/migrate/pg.js | 50 ++++++++++ build-files/migrate/sqlite3.js | 51 +++++++++++ build-files/run.sh | 3 +- build-files/seed/sqlite3.js | 117 ++++++++++++++++++++++++ bun.lock | 8 +- package.json | 13 +-- packages/drizzle-pkg/database/pg/db.ts | 11 --- packages/drizzle-pkg/database/pg/schema/auth.ts | 16 ---- packages/drizzle-pkg/database/sqlite/db.ts | 10 -- packages/drizzle-pkg/env.ts | 1 + packages/drizzle-pkg/lib/db.ts | 24 ++++- packages/drizzle-pkg/lib/paths.ts | 33 +++++++ packages/drizzle-pkg/lib/resolve-sqlite-url.ts | 15 +++ packages/drizzle-pkg/lib/schema/auth.ts | 26 +++++- packages/drizzle-pkg/seed.ts | 7 +- scripts/deploy-gitea.sh | 50 ++++++++++ scripts/deploy-gitee.sh | 50 ++++++++++ scripts/migrate-test.sh | 2 +- scripts/mv.sh | 11 +++ server/constants/cloud-probes.ts | 42 +++++++++ server/constants/upload.ts | 56 ++++++++++++ server/middleware/00.cloud-probe.ts | 25 +++++ server/middleware/00.public.ts | 88 ------------------ server/middleware/01.public.ts | 91 ++++++++++++++++++ server/plugins/00.global.ts | 8 -- server/plugins/00.req-time.ts | 36 ++++++++ server/plugins/01.req-time.ts | 36 -------- server/plugins/01.well-known-ignore.ts | 14 +++ server/plugins/02.error-logger.ts | 67 ++++++++++++++ server/plugins/02.well-known-ignore.ts | 14 --- server/plugins/03.error-logger.ts | 43 --------- static/.gitignore | 0 static/upload/a.txt | 1 + 36 files changed, 779 insertions(+), 346 deletions(-) delete mode 100644 build-files/migrate/migrate-pg.js delete mode 100644 build-files/migrate/migrate-sqlite.js create mode 100644 build-files/migrate/pg.js create mode 100644 build-files/migrate/sqlite3.js create mode 100644 build-files/seed/sqlite3.js delete mode 100644 packages/drizzle-pkg/database/pg/db.ts delete mode 100644 packages/drizzle-pkg/database/pg/schema/auth.ts delete mode 100644 packages/drizzle-pkg/database/sqlite/db.ts create mode 100644 packages/drizzle-pkg/lib/paths.ts create mode 100644 packages/drizzle-pkg/lib/resolve-sqlite-url.ts create mode 100644 scripts/deploy-gitea.sh create mode 100644 scripts/deploy-gitee.sh create mode 100644 scripts/mv.sh create mode 100644 server/constants/cloud-probes.ts create mode 100644 server/constants/upload.ts create mode 100644 server/middleware/00.cloud-probe.ts delete mode 100644 server/middleware/00.public.ts create mode 100644 server/middleware/01.public.ts delete mode 100644 server/plugins/00.global.ts create mode 100644 server/plugins/00.req-time.ts delete mode 100644 server/plugins/01.req-time.ts create mode 100644 server/plugins/01.well-known-ignore.ts create mode 100644 server/plugins/02.error-logger.ts delete mode 100644 server/plugins/02.well-known-ignore.ts delete mode 100644 server/plugins/03.error-logger.ts create mode 100644 static/.gitignore create mode 100644 static/upload/a.txt diff --git a/.env.example b/.env.example index 67b4e98..6529a7c 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,4 @@ -DATABASE_URL=postgresql://postgres:xxxxxx@localhost:6666/postgres \ No newline at end of file +DATABASE_URL=file:./db.sqlite +STATIC_DIR=static +UPLOAD_SUBDIR=upload +NITRO_PORT=3399 \ No newline at end of file diff --git a/build-files/migrate/migrate-pg.js b/build-files/migrate/migrate-pg.js deleted file mode 100644 index 885fdbe..0000000 --- a/build-files/migrate/migrate-pg.js +++ /dev/null @@ -1,50 +0,0 @@ -import { drizzle } from 'drizzle-orm/node-postgres' -import { migrate } from 'drizzle-orm/node-postgres/migrator' -import { Pool } from 'pg' -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -const argv = process.argv.slice(2) -const migrationsFolderRelative = argv[0] - -if (!migrationsFolderRelative) { - throw new Error('migrations 文件夹未设置') -} - - -export async function runMigrations() { - const databaseUrl = process.env.DATABASE_URL - if (!databaseUrl) { - throw new Error('DATABASE_URL 未设置') - } - - const pool = new Pool({ connectionString: databaseUrl }) - const db = drizzle(pool) - - try { - console.log('🚀 开始执行 PostgreSQL 迁移...') - const migrationsFolder = path.resolve(process.cwd(), migrationsFolderRelative) - - await migrate(db, { - migrationsFolder, - }) - - console.log('✅ PostgreSQL 迁移完成!') - } catch (err) { - console.log('❌ 迁移失败:', err) - throw err - } finally { - await pool.end() - } -} - -const isMain = - process.argv[1] && - path.resolve(process.argv[1]) === fileURLToPath(import.meta.url) - -if (isMain) { - runMigrations().catch((err) => { - console.error(err) - process.exit(1) - }) -} diff --git a/build-files/migrate/migrate-sqlite.js b/build-files/migrate/migrate-sqlite.js deleted file mode 100644 index fe336c7..0000000 --- a/build-files/migrate/migrate-sqlite.js +++ /dev/null @@ -1,51 +0,0 @@ -import { drizzle } from 'drizzle-orm/better-sqlite3' -import { migrate } from 'drizzle-orm/better-sqlite3/migrator' -import Database from 'better-sqlite3' -import path from 'node:path' -import { fileURLToPath } from 'node:url' - -const argv = process.argv.slice(2) -const migrationsFolderRelative = argv[0] - -if (!migrationsFolderRelative) { - throw new Error('migrations 文件夹未设置') -} - -export async function runMigrations() { - const dbUrl = process.env.DATABASE_URL || '' - const sqlitePath = dbUrl.startsWith('file:') ? dbUrl.slice(5) : dbUrl - - if (!sqlitePath) { - throw new Error('DATABASE_URL 未设置,且未提供有效的 SQLite 文件路径') - } - - const sqlite = new Database(sqlitePath) - const db = drizzle(sqlite) - - try { - console.log(`🚀 开始执行 SQLite 迁移... (${sqlitePath})`) - const migrationsFolder = path.resolve(process.cwd(), migrationsFolderRelative) - - await migrate(db, { - migrationsFolder, - }) - - console.log('✅ SQLite 迁移完成!') - } catch (err) { - console.log('❌ 迁移失败:', err) - throw err - } finally { - sqlite.close() - } -} - -const isMain = - process.argv[1] && - path.resolve(process.argv[1]) === fileURLToPath(import.meta.url) - -if (isMain) { - runMigrations().catch((err) => { - console.error(err) - process.exit(1) - }) -} diff --git a/build-files/migrate/pg.js b/build-files/migrate/pg.js new file mode 100644 index 0000000..885fdbe --- /dev/null +++ b/build-files/migrate/pg.js @@ -0,0 +1,50 @@ +import { drizzle } from 'drizzle-orm/node-postgres' +import { migrate } from 'drizzle-orm/node-postgres/migrator' +import { Pool } from 'pg' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const argv = process.argv.slice(2) +const migrationsFolderRelative = argv[0] + +if (!migrationsFolderRelative) { + throw new Error('migrations 文件夹未设置') +} + + +export async function runMigrations() { + const databaseUrl = process.env.DATABASE_URL + if (!databaseUrl) { + throw new Error('DATABASE_URL 未设置') + } + + const pool = new Pool({ connectionString: databaseUrl }) + const db = drizzle(pool) + + try { + console.log('🚀 开始执行 PostgreSQL 迁移...') + const migrationsFolder = path.resolve(process.cwd(), migrationsFolderRelative) + + await migrate(db, { + migrationsFolder, + }) + + console.log('✅ PostgreSQL 迁移完成!') + } catch (err) { + console.log('❌ 迁移失败:', err) + throw err + } finally { + await pool.end() + } +} + +const isMain = + process.argv[1] && + path.resolve(process.argv[1]) === fileURLToPath(import.meta.url) + +if (isMain) { + runMigrations().catch((err) => { + console.error(err) + process.exit(1) + }) +} diff --git a/build-files/migrate/sqlite3.js b/build-files/migrate/sqlite3.js new file mode 100644 index 0000000..fe336c7 --- /dev/null +++ b/build-files/migrate/sqlite3.js @@ -0,0 +1,51 @@ +import { drizzle } from 'drizzle-orm/better-sqlite3' +import { migrate } from 'drizzle-orm/better-sqlite3/migrator' +import Database from 'better-sqlite3' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const argv = process.argv.slice(2) +const migrationsFolderRelative = argv[0] + +if (!migrationsFolderRelative) { + throw new Error('migrations 文件夹未设置') +} + +export async function runMigrations() { + const dbUrl = process.env.DATABASE_URL || '' + const sqlitePath = dbUrl.startsWith('file:') ? dbUrl.slice(5) : dbUrl + + if (!sqlitePath) { + throw new Error('DATABASE_URL 未设置,且未提供有效的 SQLite 文件路径') + } + + const sqlite = new Database(sqlitePath) + const db = drizzle(sqlite) + + try { + console.log(`🚀 开始执行 SQLite 迁移... (${sqlitePath})`) + const migrationsFolder = path.resolve(process.cwd(), migrationsFolderRelative) + + await migrate(db, { + migrationsFolder, + }) + + console.log('✅ SQLite 迁移完成!') + } catch (err) { + console.log('❌ 迁移失败:', err) + throw err + } finally { + sqlite.close() + } +} + +const isMain = + process.argv[1] && + path.resolve(process.argv[1]) === fileURLToPath(import.meta.url) + +if (isMain) { + runMigrations().catch((err) => { + console.error(err) + process.exit(1) + }) +} diff --git a/build-files/run.sh b/build-files/run.sh index 6dda8a2..f5f29ec 100644 --- a/build-files/run.sh +++ b/build-files/run.sh @@ -4,5 +4,6 @@ if [ -f .env ]; then export $(grep -v '^#' .env | xargs) fi -node server/migrate-pg.js migrations +node server/migrate/sqlite3.js migrations +node server/seed/sqlite3.js node server/index.mjs \ No newline at end of file diff --git a/build-files/seed/sqlite3.js b/build-files/seed/sqlite3.js new file mode 100644 index 0000000..51d2714 --- /dev/null +++ b/build-files/seed/sqlite3.js @@ -0,0 +1,117 @@ +/** + * 部署后首次启动前执行:若库中尚无 admin,则用环境变量创建首个管理员。 + * 逻辑对齐 `packages/drizzle-pkg/seed.ts`,使用 SQLite(与 `migrate-sqlite.js` 相同)。 + * + * 环境变量(与 seed.ts 一致): + * - DATABASE_URL:SQLite 路径,可为 `file:/path/to/db.sqlite` 或裸路径 + * - BOOTSTRAP_ADMIN_USERNAME / BOOTSTRAP_ADMIN_PASSWORD:可选;未设置或校验失败则跳过 + */ +import Database from 'better-sqlite3' +import { hash } from 'bcryptjs' +import { mkdirSync } from 'node:fs' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +/** 与 `server/service/auth/index.ts` 一致 */ +const USERNAME_REGEX = /^[a-zA-Z0-9_]{3,20}$/ +const MIN_PASSWORD_LENGTH = 6 + +/** 与 seed.ts 一致:仅当小写用户名本身符合 slug 规则时写入 public_slug */ +const PUBLIC_SLUG_REGEX = /^[a-z0-9]{3,20}$/ + +function derivePublicSlug(username) { + const lower = username.toLowerCase() + return PUBLIC_SLUG_REGEX.test(lower) ? lower : null +} + +/** + * 与 `migrate-sqlite.js` 一致:相对路径相对 `process.cwd()`(例如 `.output/run.sh` 下与迁移写入同一文件)。 + * Nitro 内 `resolveSqliteDatabaseUrl` 在无法锚定 drizzle-pkg 时也会回退到 cwd,避免「迁移/seed 成功、服务 CANTOPEN」。 + */ +function resolveSqliteFilePath(dbUrl) { + const stripped = dbUrl.startsWith('file:') ? dbUrl.slice('file:'.length) : dbUrl + if (!stripped) { + throw new Error('DATABASE_URL 未设置,且未提供有效的 SQLite 文件路径') + } + if (path.isAbsolute(stripped)) { + return stripped + } + return path.resolve(process.cwd(), stripped) +} + +function openSqlite() { + const dbUrl = process.env.DATABASE_URL || '' + const sqlitePath = resolveSqliteFilePath(dbUrl) + mkdirSync(path.dirname(sqlitePath), { recursive: true }) + return new Database(sqlitePath) +} + +async function main() { + const db = openSqlite() + try { + const existingAdmin = db + .prepare(`SELECT id FROM users WHERE role = ? LIMIT 1`) + .get('admin') + if (existingAdmin) { + console.log('Bootstrap skipped: admin exists') + return + } + + const username = process.env.BOOTSTRAP_ADMIN_USERNAME + const password = process.env.BOOTSTRAP_ADMIN_PASSWORD + + if (!username || !password) { + console.warn( + 'Bootstrap skipped: set BOOTSTRAP_ADMIN_USERNAME and BOOTSTRAP_ADMIN_PASSWORD', + ) + return + } + + if (!USERNAME_REGEX.test(username) || password.length < MIN_PASSWORD_LENGTH) { + console.warn( + 'Bootstrap skipped: invalid BOOTSTRAP_ADMIN_USERNAME or BOOTSTRAP_ADMIN_PASSWORD', + ) + return + } + + const passwordHash = await hash(password, 10) + const { maxId } = db.prepare(`SELECT COALESCE(MAX(id), 0) AS maxId FROM users`).get() + const userId = (maxId ?? 0) + 1 + const publicSlug = derivePublicSlug(username) + + try { + db.prepare( + ` + INSERT INTO users (id, username, password, role, status, public_slug) + VALUES (@id, @username, @password, @role, @status, @public_slug) + `, + ).run({ + id: userId, + username, + password: passwordHash, + role: 'admin', + status: 'active', + public_slug: publicSlug, + }) + console.log('Bootstrap complete: admin user created') + } catch (err) { + console.warn( + 'Bootstrap skipped: could not insert admin (unique conflict or DB error)', + err, + ) + } + } finally { + db.close() + } +} + +const isMain = + process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url) + +if (isMain) { + // 成功时自然退出(退出码 0);仅未捕获错误时 process.exit(1)。 + main().catch((e) => { + console.error(e) + process.exit(1) + }) +} diff --git a/bun.lock b/bun.lock index 2e8960f..e7dec4e 100644 --- a/bun.lock +++ b/bun.lock @@ -5,8 +5,8 @@ "": { "name": "person-panel", "dependencies": { - "@nuxt/ui": "^4.6.1", - "better-sqlite3": "^12.9.0", + "@nuxt/ui": "4.6.1", + "better-sqlite3": "12.9.0", "dotenv": "17.4.1", "drizzle-orm": "0.45.2", "drizzle-pkg": "workspace:*", @@ -18,14 +18,14 @@ "multer": "2.1.1", "nuxt": "4.4.2", "pg": "8.20.0", - "tailwindcss": "^4.2.2", + "tailwindcss": "4.2.2", "ufo": "1.6.3", "vue": "3.5.32", "vue-router": "5.0.4", "zod": "4.3.6", }, "devDependencies": { - "@types/better-sqlite3": "^7.6.13", + "@types/better-sqlite3": "7.6.13", "@types/multer": "2.1.0", "@types/pg": "8.20.0", "drizzle-kit": "0.31.10", diff --git a/package.json b/package.json index eb0778a..85b200f 100644 --- a/package.json +++ b/package.json @@ -9,18 +9,19 @@ "scripts": { "build": "nuxt build && bun run cp:db && bun --elide-lines=0 --filter drizzle-pkg build", "dev": "nuxt dev", - "cp:db": "cp build-files/run.sh .output/run.sh && cp .env.example .output/.env && cp -r build-files/migrate/* .output/server/", + "deploy": "bash scripts/deploy-gitea.sh", + "cp:db": "bash scripts/mv.sh", "migrate:test": "sh scripts/migrate-test.sh", "db:migrate": "bun --elide-lines=0 --filter drizzle-pkg migrate", - "db:generate": "bun --elide-lines=0 --filter drizzle-pkg generate --name", + "db:generate": "bun --elide-lines=0 --filter drizzle-pkg generate", "db:seed": "bun --elide-lines=0 --filter drizzle-pkg seed", "generate": "nuxt generate", "preview": "nuxt preview", "postinstall": "nuxt prepare" }, "dependencies": { - "@nuxt/ui": "^4.6.1", - "better-sqlite3": "^12.9.0", + "@nuxt/ui": "4.6.1", + "better-sqlite3": "12.9.0", "dotenv": "17.4.1", "drizzle-orm": "0.45.2", "drizzle-pkg": "workspace:*", @@ -32,14 +33,14 @@ "multer": "2.1.1", "nuxt": "4.4.2", "pg": "8.20.0", - "tailwindcss": "^4.2.2", + "tailwindcss": "4.2.2", "ufo": "1.6.3", "vue": "3.5.32", "vue-router": "5.0.4", "zod": "4.3.6" }, "devDependencies": { - "@types/better-sqlite3": "^7.6.13", + "@types/better-sqlite3": "7.6.13", "@types/multer": "2.1.0", "@types/pg": "8.20.0", "drizzle-kit": "0.31.10", diff --git a/packages/drizzle-pkg/database/pg/db.ts b/packages/drizzle-pkg/database/pg/db.ts deleted file mode 100644 index b52f3e6..0000000 --- a/packages/drizzle-pkg/database/pg/db.ts +++ /dev/null @@ -1,11 +0,0 @@ - -import { drizzle } from "drizzle-orm/node-postgres"; - -if (process.env.NODE_ENV === 'production') { - // 打包时需要保证migrator被引入 - import('drizzle-orm/node-postgres/migrator') -} - -const _db = drizzle(process.env.DATABASE_URL!); - -export { _db as dbGlobal } \ No newline at end of file diff --git a/packages/drizzle-pkg/database/pg/schema/auth.ts b/packages/drizzle-pkg/database/pg/schema/auth.ts deleted file mode 100644 index db1b2e9..0000000 --- a/packages/drizzle-pkg/database/pg/schema/auth.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { sql } from "drizzle-orm"; -import { integer, pgTable, timestamp, varchar } from "drizzle-orm/pg-core"; - -export const users = pgTable("users", { - id: integer().primaryKey(), - username: varchar().notNull().unique(), - email: varchar(), - nickname: varchar(), - password: varchar().notNull(), - avatar: varchar(), - createdAt: timestamp('created_at').defaultNow().notNull(), - updatedAt: timestamp('updated_at') - .defaultNow() - .$onUpdate(() => sql`CURRENT_TIMESTAMP`) - .notNull(), -}); \ No newline at end of file diff --git a/packages/drizzle-pkg/database/sqlite/db.ts b/packages/drizzle-pkg/database/sqlite/db.ts deleted file mode 100644 index 375f1f2..0000000 --- a/packages/drizzle-pkg/database/sqlite/db.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { drizzle } from 'drizzle-orm/libsql'; - -if (process.env.NODE_ENV === 'production') { - // 打包时需要保证migrator被引入 - import('drizzle-orm/better-sqlite3/migrator') -} - -const _db = drizzle(process.env.DATABASE_URL!); - -export { _db as dbGlobal } \ No newline at end of file diff --git a/packages/drizzle-pkg/env.ts b/packages/drizzle-pkg/env.ts index 92a311f..fcbc531 100644 --- a/packages/drizzle-pkg/env.ts +++ b/packages/drizzle-pkg/env.ts @@ -2,3 +2,4 @@ import { config } from 'dotenv'; config({ path: '../../.env' }); +console.log(process.env); diff --git a/packages/drizzle-pkg/lib/db.ts b/packages/drizzle-pkg/lib/db.ts index dda9669..5456678 100644 --- a/packages/drizzle-pkg/lib/db.ts +++ b/packages/drizzle-pkg/lib/db.ts @@ -1 +1,23 @@ -export { dbGlobal } from '../database/pg/db' \ No newline at end of file +import { drizzle } from "drizzle-orm/better-sqlite3"; +import { resolveSqliteDatabaseUrl } from "./resolve-sqlite-url"; + +if (process.env.NODE_ENV === "production") { + // 打包时需要保证migrator被引入 + import("drizzle-orm/better-sqlite3/migrator"); +} + +const rawUrl = process.env.DATABASE_URL; +if (!rawUrl) { + throw new Error("DATABASE_URL 未设置"); +} +const resolvedUrl = resolveSqliteDatabaseUrl(rawUrl); +process.env.DATABASE_URL = resolvedUrl; + +// better-sqlite3 需要裸文件路径;`file:` 前缀仍保留在 DATABASE_URL 供 drizzle-kit 等使用 +const sqlitePath = resolvedUrl.startsWith("file:") + ? resolvedUrl.slice("file:".length) + : resolvedUrl; + +const _db = drizzle(sqlitePath); + +export { _db as dbGlobal }; diff --git a/packages/drizzle-pkg/lib/paths.ts b/packages/drizzle-pkg/lib/paths.ts new file mode 100644 index 0000000..a91277a --- /dev/null +++ b/packages/drizzle-pkg/lib/paths.ts @@ -0,0 +1,33 @@ +import { existsSync, readFileSync } from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; + +function isDrizzlePkgRoot(dir: string): boolean { + const pkg = path.join(dir, "package.json"); + if (!existsSync(pkg)) { + return false; + } + try { + const { name } = JSON.parse(readFileSync(pkg, "utf8")) as { name?: string }; + return name === "drizzle-pkg"; + } catch { + return false; + } +} + +/** + * `drizzle-pkg` 包根目录(与 `package.json`、`db.sqlite` 同级)。 + * - 开发:用 `import.meta` 锚定,避免 cwd 变化把 `file:db.sqlite` 指到错误文件(只读 / DBMOVED)。 + * - 生产:打包后 chunk 路径不可靠,回退到 `cwd/packages/drizzle-pkg`;再不行则回退 `process.cwd()`(与 `.output` 下 migrate/seed 的相对 `DATABASE_URL` 一致)。 + */ +export function getDrizzlePkgRoot(): string { + const fromMeta = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + if (isDrizzlePkgRoot(fromMeta)) { + return fromMeta; + } + const fromCwd = path.resolve(process.cwd(), "packages/drizzle-pkg"); + if (isDrizzlePkgRoot(fromCwd)) { + return fromCwd; + } + return process.cwd(); +} diff --git a/packages/drizzle-pkg/lib/resolve-sqlite-url.ts b/packages/drizzle-pkg/lib/resolve-sqlite-url.ts new file mode 100644 index 0000000..de59c62 --- /dev/null +++ b/packages/drizzle-pkg/lib/resolve-sqlite-url.ts @@ -0,0 +1,15 @@ +import path from "node:path"; +import { getDrizzlePkgRoot } from "./paths"; + +/** 将 `file:` 相对路径解析为绝对路径(相对 drizzle-pkg 根目录) */ +export function resolveSqliteDatabaseUrl(url: string): string { + if (!url.startsWith("file:")) { + return url; + } + let filePath = url.slice("file:".length); + if (path.isAbsolute(filePath)) { + return `file:${filePath}`; + } + const root = getDrizzlePkgRoot(); + return `file:${path.resolve(root, filePath)}`; +} diff --git a/packages/drizzle-pkg/lib/schema/auth.ts b/packages/drizzle-pkg/lib/schema/auth.ts index a342588..b031189 100644 --- a/packages/drizzle-pkg/lib/schema/auth.ts +++ b/packages/drizzle-pkg/lib/schema/auth.ts @@ -1 +1,25 @@ -export { users } from '../../database/pg/schema/auth' \ No newline at end of file +import { index, integer, sqliteTable, text } from "drizzle-orm/sqlite-core"; + +export const users = sqliteTable("users", { + id: integer().primaryKey(), + username: text().notNull().unique(), + email: text(), + nickname: text(), + password: text().notNull(), + avatar: text(), + role: text().notNull().default("user"), + status: text().notNull().default("active"), + publicSlug: text("public_slug").unique(), + bioMarkdown: text("bio_markdown"), + bioVisibility: text("bio_visibility").notNull().default("private"), + socialLinksJson: text("social_links_json").notNull().default("[]"), + avatarVisibility: text("avatar_visibility").notNull().default("private"), + discoverVisible: integer("discover_visible", { mode: "boolean" }).notNull().default(true), + discoverLocation: text("discover_location"), + discoverShowLocation: integer("discover_show_location", { mode: "boolean" }).notNull().default(false), + createdAt: integer("created_at", { mode: "timestamp_ms" }).defaultNow().notNull(), + updatedAt: integer("updated_at", { mode: "timestamp_ms" }) + .defaultNow() + .$onUpdate(() => new Date()) + .notNull(), +}); diff --git a/packages/drizzle-pkg/seed.ts b/packages/drizzle-pkg/seed.ts index 6ed7f28..47ae7d3 100644 --- a/packages/drizzle-pkg/seed.ts +++ b/packages/drizzle-pkg/seed.ts @@ -1,11 +1,11 @@ import './env'; import { seed } from "drizzle-seed"; -import { usersTable } from "./lib/schema/auth"; +import { users } from "./lib/schema/auth"; import { dbGlobal } from "./lib/db"; async function main() { - await seed(dbGlobal, { usersTable }).refine((f) => ({ - usersTable: { + await seed(dbGlobal, { users }).refine((f) => ({ + users: { columns: { name: f.fullName(), age: f.int({ minValue: 18, maxValue: 60 }), @@ -22,4 +22,3 @@ main().catch(e => { console.error(e); process.exit(1); }); - diff --git a/scripts/deploy-gitea.sh b/scripts/deploy-gitea.sh new file mode 100644 index 0000000..65dd28d --- /dev/null +++ b/scripts/deploy-gitea.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env sh + +# 配置区(只改这里) +GIT_REPO_URL="ssh://root@git.xieyaxin.top:8892/topuser/nuxt4-demo.git" +PROD_BRANCH="deploy" # 你要存放产物的分支名(会自动创建) +BUILD_FOLDER=".output" # 打包产物目录 +ARCHIVE_NAME="build-output.tar.gz" +ARCHIVE_PATH=".tmp_${ARCHIVE_NAME}" +COMMIT_MSG="deploy: build at $(date +'%Y-%m-%d %H:%M:%S')" + +# 1. 先打包 +echo "📦 构建项目..." +bun run build + +# 2. 克隆远程产物分支到临时目录 +echo "⬇️ 拉取产物分支..." +git clone --single-branch --branch $PROD_BRANCH $GIT_REPO_URL .tmp_dist || { + echo "🆕 分支不存在,创建新分支..." + mkdir .tmp_dist + cd .tmp_dist + git init + git checkout -b $PROD_BRANCH + git remote add origin $GIT_REPO_URL + cd .. +} + +# 3. 压缩构建产物 +echo "🗜️ 压缩构建目录..." +tar -czf "$ARCHIVE_PATH" -C "$BUILD_FOLDER" . + +# 4. 删除旧产物,复制新产物 +echo "♻️ 更新产物文件..." +rm -rf .tmp_dist/* +cp -r $BUILD_FOLDER/.drone.yml .tmp_dist/.drone.yml +cp "$ARCHIVE_PATH" ".tmp_dist/$ARCHIVE_NAME" + +# 5. 提交并推送 +cd .tmp_dist +git add -A +git commit -m "$COMMIT_MSG" + +echo "🚀 推送到远程分支 $PROD_BRANCH..." +git push origin $PROD_BRANCH + +# 6. 清理临时文件 +cd .. +rm -rf .tmp_dist +rm -f "$ARCHIVE_PATH" + +echo "✅ 发布完成!" \ No newline at end of file diff --git a/scripts/deploy-gitee.sh b/scripts/deploy-gitee.sh new file mode 100644 index 0000000..a5f0055 --- /dev/null +++ b/scripts/deploy-gitee.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env sh + +# 配置区(只改这里) +GIT_REPO_URL="git@gitee.com:xieyaxin/nuxt4-demo.git" +PROD_BRANCH="deploy" # 你要存放产物的分支名(会自动创建) +BUILD_FOLDER=".output" # 打包产物目录 +ARCHIVE_NAME="build-output.tar.gz" +ARCHIVE_PATH=".tmp_${ARCHIVE_NAME}" +COMMIT_MSG="deploy: build at $(date +'%Y-%m-%d %H:%M:%S')" + +# 1. 先打包 +echo "📦 构建项目..." +bun run build + +# 2. 克隆远程产物分支到临时目录 +echo "⬇️ 拉取产物分支..." +git clone --single-branch --branch $PROD_BRANCH $GIT_REPO_URL .tmp_dist || { + echo "🆕 分支不存在,创建新分支..." + mkdir .tmp_dist + cd .tmp_dist + git init + git checkout -b $PROD_BRANCH + git remote add origin $GIT_REPO_URL + cd .. +} + +# 3. 压缩构建产物 +echo "🗜️ 压缩构建目录..." +tar -czf "$ARCHIVE_PATH" -C "$BUILD_FOLDER" . + +# 4. 删除旧产物,复制新产物 +echo "♻️ 更新产物文件..." +rm -rf .tmp_dist/* +cp -r $BUILD_FOLDER/.drone.yml .tmp_dist/.drone.yml +cp "$ARCHIVE_PATH" ".tmp_dist/$ARCHIVE_NAME" + +# 5. 提交并推送 +cd .tmp_dist +git add -A +git commit -m "$COMMIT_MSG" + +echo "🚀 推送到远程分支 $PROD_BRANCH..." +git push origin $PROD_BRANCH + +# 6. 清理临时文件 +cd .. +rm -rf .tmp_dist +rm -f "$ARCHIVE_PATH" + +echo "✅ 发布完成!" \ No newline at end of file diff --git a/scripts/migrate-test.sh b/scripts/migrate-test.sh index f2a4a7a..079adc4 100644 --- a/scripts/migrate-test.sh +++ b/scripts/migrate-test.sh @@ -4,4 +4,4 @@ fi echo "DATABASE_URL: $DATABASE_URL" -node build-files/migrate/migrate-pg.js packages/drizzle-pkg/migrations +node build-files/migrate/sqlite3.js packages/drizzle-pkg/migrations diff --git a/scripts/mv.sh b/scripts/mv.sh new file mode 100644 index 0000000..3d499c4 --- /dev/null +++ b/scripts/mv.sh @@ -0,0 +1,11 @@ +if [ -f .env.prod ]; then + cp .env.prod .output/.env +else + cp .env.example .output/.env +fi + +cp build-files/run.sh .output/run.sh +mkdir .output/server/migrate +cp -r build-files/migrate/* .output/server/migrate/ +mkdir .output/server/seed +cp -r build-files/* .output/server/seed/ \ No newline at end of file diff --git a/server/constants/cloud-probes.ts b/server/constants/cloud-probes.ts new file mode 100644 index 0000000..719f08c --- /dev/null +++ b/server/constants/cloud-probes.ts @@ -0,0 +1,42 @@ +/** + * 各云平台 / 容器编排常见 HTTP 探针路径(不含 `/`,避免误伤站点根)。 + * 可按负载均衡控制台实际配置增删;若与业务路由同名请从列表中移除对应项。 + */ +export const CLOUD_PROBE_PATHS = [ + // 通用 / Kubernetes + "/health", + "/healthz", + "/livez", + "/readyz", + "/liveness", + "/readiness", + "/startup", + "/health/live", + "/health/ready", + "/health/startup", + // Spring Actuator(经网关暴露时偶见) + "/actuator/health", + "/actuator/health/liveness", + "/actuator/health/readiness", + // Azure App Service 相关默认探测 + "/robots933456.txt", + // 国内云控制台常见示例静态页 + "/check.html", + "/status.html", + "/ping", + "/status", + "/alive", +] as const; + +/** 阿里云等:健康检查为 `/rpc` 或带后缀路径(如控制台填写的 `/rpc/...`),按前缀匹配 */ +export const CLOUD_PROBE_PATH_PREFIXES = ["/rpc"] as const; + +export const CLOUD_PROBE_PATH_SET = new Set(CLOUD_PROBE_PATHS); + +export function isCloudProbePath(pathname: string): boolean { + if (CLOUD_PROBE_PATH_SET.has(pathname)) return true; + for (const prefix of CLOUD_PROBE_PATH_PREFIXES) { + if (pathname === prefix || pathname.startsWith(`${prefix}/`)) return true; + } + return false; +} diff --git a/server/constants/upload.ts b/server/constants/upload.ts new file mode 100644 index 0000000..6fee1b9 --- /dev/null +++ b/server/constants/upload.ts @@ -0,0 +1,56 @@ +import path from "node:path"; + +function trimSlashes(input: string): string { + return input.trim().replace(/^\/+|\/+$/g, ""); +} + +function hasParentSegment(input: string): boolean { + return input + .split("/") + .map((part) => part.trim()) + .some((part) => part === ".."); +} + +/** 允许相对项目根或绝对路径;禁止含 `..` 的路径片段(防止配置逃逸)。 */ +function ensureConfigurableDir(input: string, fallback: string, envName: string): string { + const raw = input.trim(); + if (!raw) { + return fallback; + } + const normalized = path.normalize(raw); + const segments = normalized.split(path.sep); + if (segments.some((part) => part === "..")) { + throw new Error(`${envName} must not contain ".." path segments`); + } + return normalized; +} + +function ensureSafeSubdir(input: string, fallback: string, envName: string): string { + const value = trimSlashes(input); + if (!value) { + return fallback; + } + if (hasParentSegment(value)) { + throw new Error(`${envName} must not contain ".." segments`); + } + return value; +} + +/** 静态资源 URL 前缀固定为 `/static`,不允许通过环境变量覆写。 */ +export const STATIC_PUBLIC_PREFIX = "/static"; + +/** 静态资源根目录(相对项目根或绝对路径),默认 `static` */ +export const STATIC_DIR = ensureConfigurableDir(process.env.STATIC_DIR ?? "static", "static", "STATIC_DIR"); + +/** 媒体上传子目录(相对 STATIC_DIR),默认 `upload` */ +export const UPLOAD_SUBDIR = ensureSafeSubdir( + process.env.UPLOAD_SUBDIR ?? "upload", + "upload", + "UPLOAD_SUBDIR", +); + +/** 媒体上传目录(与 STATIC_DIR 同为相对或绝对),默认 `static/upload` */ +export const RELATIVE_ASSETS_DIR = path.join(STATIC_DIR, UPLOAD_SUBDIR); + +/** 与 `media` 返回及静态路径一致,无前导 host */ +export const POST_MEDIA_PUBLIC_PREFIX = `${STATIC_PUBLIC_PREFIX}/${UPLOAD_SUBDIR}/`; diff --git a/server/middleware/00.cloud-probe.ts b/server/middleware/00.cloud-probe.ts new file mode 100644 index 0000000..4cacf1c --- /dev/null +++ b/server/middleware/00.cloud-probe.ts @@ -0,0 +1,25 @@ +import { getRequestURL } from "h3"; +import { isCloudProbePath } from "#server/constants/cloud-probes"; + +const METHODS = new Set(["GET", "HEAD"]); + +export default eventHandler((event) => { + if (!METHODS.has(event.method)) return; + + const pathname = getRequestURL(event).pathname; + if (!isCloudProbePath(pathname)) return; + + setHeader(event, "content-type", "text/plain; charset=utf-8"); + setHeader( + event, + "cache-control", + "no-store, no-cache, must-revalidate, proxy-revalidate", + ); + + if (event.method === "HEAD") { + setResponseStatus(event, 200); + return; + } + + return "OK"; +}); diff --git a/server/middleware/00.public.ts b/server/middleware/00.public.ts deleted file mode 100644 index 88a6eb4..0000000 --- a/server/middleware/00.public.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { resolve, join, sep, extname } from "node:path"; -import { promises as fsp } from "node:fs"; -import { - decodePath, - withLeadingSlash, - withoutTrailingSlash, - parseURL, -} from "ufo"; -import type { H3Event } from "h3"; -import mime from "mime"; - -const METHODS = new Set(["HEAD", "GET"]); -const SAFE_BASE_DIR = resolve("public"); - -// 缓存配置 -const CACHE_CONTROL = "public, max-age=31536000, immutable"; -const NOT_MODIFIED = 304; -const FORBIDDEN = 403; -const NOT_FOUND = 404; -const SERVER_ERROR = 500; - -export default eventHandler(async (event: H3Event) => { - if (!event.path.startsWith("/public")) return; - - const { req, res } = event.node; - const method = req.method; - - if (method && !METHODS.has(method)) return; - - try { - // 安全解析路径 - const url = event.path.replace(/^\/public/, ""); - const pathname = decodePath( - withLeadingSlash(withoutTrailingSlash(parseURL(url).pathname)) - ); - - const targetPath = join(SAFE_BASE_DIR, pathname); - const resolvedPath = resolve(targetPath); - - // 安全校验 - if (!resolvedPath.startsWith(SAFE_BASE_DIR + sep)) { - res.statusCode = FORBIDDEN; - return "Forbidden"; - } - - const stat = await fsp.stat(resolvedPath); - if (!stat.isFile()) { - res.statusCode = NOT_FOUND; - return "Not Found"; - } - - // ====================== 缓存逻辑 ====================== - const mtime = stat.mtime.toUTCString(); - const etag = `"${stat.mtime.getTime().toString(16)}-${stat.size.toString(16)}"`; - - const contentType = mime.getType(resolvedPath) || "application/octet-stream"; - - // 设置缓存头 - res.setHeader("Cache-Control", CACHE_CONTROL); - res.setHeader("ETag", etag); - res.setHeader("Last-Modified", mtime); - res.setHeader("Content-Type", contentType); - res.setHeader("Content-Length", stat.size); - - // 禁用 keep-alive - res.setHeader("Connection", "close"); - - // 304 协商缓存 - const ifNoneMatch = req.headers["if-none-match"]; - const ifModifiedSince = req.headers["if-modified-since"]; - if (ifNoneMatch === etag || (ifModifiedSince && ifModifiedSince === mtime)) { - res.statusCode = NOT_MODIFIED; - return ""; - } - // ====================================================== - - if (method === "HEAD") return ""; - return fsp.readFile(resolvedPath); - - } catch (err: any) { - if (err.code === "ENOENT") { - res.statusCode = NOT_FOUND; - return "Not Found"; - } - res.statusCode = SERVER_ERROR; - return "Server Error"; - } -}); \ No newline at end of file diff --git a/server/middleware/01.public.ts b/server/middleware/01.public.ts new file mode 100644 index 0000000..58a7d06 --- /dev/null +++ b/server/middleware/01.public.ts @@ -0,0 +1,91 @@ +import { resolve, join, relative } from "node:path"; +import { promises as fsp } from "node:fs"; +import { + decodePath, + withLeadingSlash, + withoutTrailingSlash, + parseURL, +} from "ufo"; +import type { H3Event } from "h3"; +import mime from "mime"; +import { STATIC_DIR, STATIC_PUBLIC_PREFIX } from "#server/constants/upload"; + +const METHODS = new Set(["HEAD", "GET"]); +const SAFE_BASE_DIR = resolve(STATIC_DIR); + +// 缓存配置 +const CACHE_CONTROL = "public, max-age=31536000, immutable"; +const NOT_MODIFIED = 304; +const FORBIDDEN = 403; +const NOT_FOUND = 404; +const SERVER_ERROR = 500; + +export default eventHandler(async (event: H3Event) => { + if (!event.path.startsWith(STATIC_PUBLIC_PREFIX)) return; + + const { req, res } = event.node; + const method = req.method; + + if (method && !METHODS.has(method)) return; + + try { + // 安全解析路径 + const url = event.path.replace(STATIC_PUBLIC_PREFIX, ""); + + const pathname = decodePath( + withLeadingSlash(withoutTrailingSlash(parseURL(url).pathname)) + ); + + const targetPath = join(SAFE_BASE_DIR, pathname); + const resolvedPath = resolve(targetPath); + + // 安全校验(支持 STATIC_DIR 为绝对路径;relative 在越界时为 `..` 开头) + const rel = relative(SAFE_BASE_DIR, resolvedPath); + if (rel.startsWith("..") || rel === "..") { + res.statusCode = FORBIDDEN; + return "Forbidden"; + } + + const stat = await fsp.stat(resolvedPath); + if (!stat.isFile()) { + res.statusCode = NOT_FOUND; + return "Not Found"; + } + + // ====================== 缓存逻辑 ====================== + const mtime = stat.mtime.toUTCString(); + const etag = `"${stat.mtime.getTime().toString(16)}-${stat.size.toString(16)}"`; + + const contentType = mime.getType(resolvedPath) || "application/octet-stream"; + + // 设置缓存头 + res.setHeader("Cache-Control", CACHE_CONTROL); + res.setHeader("ETag", etag); + res.setHeader("Last-Modified", mtime); + res.setHeader("Content-Type", contentType); + res.setHeader("Content-Length", stat.size); + + // 禁用 keep-alive + res.setHeader("Connection", "close"); + + // 304 协商缓存 + const ifNoneMatch = req.headers["if-none-match"]; + const ifModifiedSince = req.headers["if-modified-since"]; + if (ifNoneMatch === etag || (ifModifiedSince && ifModifiedSince === mtime)) { + res.statusCode = NOT_MODIFIED; + return ""; + } + // ====================================================== + + if (method === "HEAD") return ""; + return fsp.readFile(resolvedPath); + + } catch (err: any) { + if (err.code === "ENOENT") { + res.statusCode = NOT_FOUND; + return "Not Found"; + } + res.statusCode = SERVER_ERROR; + return "Server Error"; + } +}); \ No newline at end of file diff --git a/server/plugins/00.global.ts b/server/plugins/00.global.ts deleted file mode 100644 index 220db0d..0000000 --- a/server/plugins/00.global.ts +++ /dev/null @@ -1,8 +0,0 @@ - -if (import.meta.dev) { - console.log("plugin: 00.global"); -} - -export default defineNitroPlugin(async () => { - -}) diff --git a/server/plugins/00.req-time.ts b/server/plugins/00.req-time.ts new file mode 100644 index 0000000..d201118 --- /dev/null +++ b/server/plugins/00.req-time.ts @@ -0,0 +1,36 @@ +import log4js from "logger"; +import { randomUUID } from "crypto"; + +const logger = log4js.getLogger("APP") + +if (import.meta.dev) { + console.log("plugin: 00.req-time"); +} + +declare module "http" { + interface IncomingMessage { + $beginTime: number; + $requestId: string; + } +} + +export default defineNitroPlugin((nitroApp) => { + nitroApp.hooks.hook('request', async (event) => { + const incoming = event.node.req.headers["x-request-id"]; + const requestId = + typeof incoming === "string" && incoming.trim().length > 0 + ? incoming.trim() + : randomUUID(); + + event.node.req.$requestId = requestId; + event.node.req.$beginTime = new Date().getTime(); + event.node.res.setHeader("X-Request-Id", requestId); + logger.info(`[${requestId}]`, `[${event.method}-${event.path}]`, "开始请求"); + }) + nitroApp.hooks.hook('afterResponse', async (event) => { + const requestId = event.node.req.$requestId ?? "(no-request-id)"; + let curTime = new Date().getTime() + let offsetTime = curTime - event.node.req.$beginTime + logger.info(`[${requestId}]`, `[${event.method}-${event.path}]`, "请求结束,花费了", offsetTime, "ms"); + }) +}) diff --git a/server/plugins/01.req-time.ts b/server/plugins/01.req-time.ts deleted file mode 100644 index 81a9819..0000000 --- a/server/plugins/01.req-time.ts +++ /dev/null @@ -1,36 +0,0 @@ -import log4js from "logger"; -import { randomUUID } from "crypto"; - -const logger = log4js.getLogger("APP") - -if (import.meta.dev) { - console.log("plugin: 1.error-handler"); -} - -declare module "http" { - interface IncomingMessage { - $beginTime: number; - $requestId: string; - } -} - -export default defineNitroPlugin((nitroApp) => { - nitroApp.hooks.hook('request', async (event) => { - const incoming = event.node.req.headers["x-request-id"]; - const requestId = - typeof incoming === "string" && incoming.trim().length > 0 - ? incoming.trim() - : randomUUID(); - - event.node.req.$requestId = requestId; - event.node.req.$beginTime = new Date().getTime(); - event.node.res.setHeader("X-Request-Id", requestId); - logger.info(`[${requestId}]`, `[${event.method}-${event.path}]`, "开始请求"); - }) - nitroApp.hooks.hook('afterResponse', async (event) => { - const requestId = event.node.req.$requestId ?? "(no-request-id)"; - let curTime = new Date().getTime() - let offsetTime = curTime - event.node.req.$beginTime - logger.info(`[${requestId}]`, `[${event.method}-${event.path}]`, "请求结束,花费了", offsetTime, "ms"); - }) -}) diff --git a/server/plugins/01.well-known-ignore.ts b/server/plugins/01.well-known-ignore.ts new file mode 100644 index 0000000..9dd4587 --- /dev/null +++ b/server/plugins/01.well-known-ignore.ts @@ -0,0 +1,14 @@ +if (import.meta.dev) { + console.log("plugin: 01.well-known-ignore"); +} + +export default defineNitroPlugin(() => { + const originalWarn = console.warn; + console.warn = (...args) => { + const msg = args.join(' '); + if (msg.includes('/.well-known/appspecific/com.chrome.devtools.json')) { + return; + } + originalWarn(...args); + }; +}); \ No newline at end of file diff --git a/server/plugins/02.error-logger.ts b/server/plugins/02.error-logger.ts new file mode 100644 index 0000000..bb1bc87 --- /dev/null +++ b/server/plugins/02.error-logger.ts @@ -0,0 +1,67 @@ +import log4js from "logger"; + +const logger = log4js.getLogger("ERROR"); + +/** 路径/路由类「找不到」:只打控制台,不落盘(避免扫描、错 URL 等刷爆 running.log) */ +function isPathNotFoundLikeError(error: unknown): boolean { + if (!error || typeof error !== "object") return false; + const e = error as NodeJS.ErrnoException & { statusCode?: number; statusMessage?: string }; + const code = typeof e.code === "string" ? e.code : ""; + if (code === "ENOENT" || code === "ENOTDIR") return true; + + if (e.statusCode !== 404) return false; + const sm = (e.statusMessage ?? "").trim(); + if (!sm) return true; + if (/^not found$/i.test(sm)) return true; + if (/page not found/i.test(sm)) return true; + if (/^cannot (get|post|put|delete|patch|head)\b/i.test(sm)) return true; + if (/no route found|cannot find.*route|static asset.*not found/i.test(sm)) return true; + return false; +} + +const processHandlersKey = "__personPanelErrorLoggerProcessHandlers"; + +function installProcessErrorLogging() { + const g = globalThis as typeof globalThis & { [processHandlersKey]?: boolean }; + if (g[processHandlersKey]) return; + g[processHandlersKey] = true; + + process.on("unhandledRejection", (reason) => { + if (reason instanceof Error) { + logger.error("[unhandledRejection]", reason.message, reason.stack ?? ""); + } else { + logger.error("[unhandledRejection]", String(reason)); + } + }); + + process.on("uncaughtException", (err) => { + logger.error("[uncaughtException]", err.message, err.stack ?? ""); + }); +} + +if (import.meta.dev) { + console.log("plugin: 02.error-logger"); +} + +export default defineNitroPlugin((nitroApp) => { + installProcessErrorLogging(); + + nitroApp.hooks.hook("error", (error, context) => { + const event = context.event; + const tags = Array.isArray(context.tags) ? (context.tags as string[]).join(",") : ""; + const prefix = [ + event?.method ?? "", + event?.path ?? "(no request)", + tags ? `[${tags}]` : "", + ] + .filter(Boolean) + .join(" "); + + if (isPathNotFoundLikeError(error)) { + console.error(prefix || "(error)", "\n", error); + return; + } + + logger.error(prefix, "\n", error); + }); +}); diff --git a/server/plugins/02.well-known-ignore.ts b/server/plugins/02.well-known-ignore.ts deleted file mode 100644 index 9dd4587..0000000 --- a/server/plugins/02.well-known-ignore.ts +++ /dev/null @@ -1,14 +0,0 @@ -if (import.meta.dev) { - console.log("plugin: 01.well-known-ignore"); -} - -export default defineNitroPlugin(() => { - const originalWarn = console.warn; - console.warn = (...args) => { - const msg = args.join(' '); - if (msg.includes('/.well-known/appspecific/com.chrome.devtools.json')) { - return; - } - originalWarn(...args); - }; -}); \ No newline at end of file diff --git a/server/plugins/03.error-logger.ts b/server/plugins/03.error-logger.ts deleted file mode 100644 index c0385fe..0000000 --- a/server/plugins/03.error-logger.ts +++ /dev/null @@ -1,43 +0,0 @@ -import log4js from "logger"; - -const logger = log4js.getLogger("ERROR"); - -const processHandlersKey = "__personPanelErrorLoggerProcessHandlers"; - -function installProcessErrorLogging() { - const g = globalThis as typeof globalThis & { [processHandlersKey]?: boolean }; - if (g[processHandlersKey]) return; - g[processHandlersKey] = true; - - process.on("unhandledRejection", (reason) => { - if (reason instanceof Error) { - logger.error("[unhandledRejection]", reason.message, reason.stack ?? ""); - } else { - logger.error("[unhandledRejection]", String(reason)); - } - }); - - process.on("uncaughtException", (err) => { - logger.error("[uncaughtException]", err.message, err.stack ?? ""); - }); -} - -if (import.meta.dev) { - console.log("plugin: 03.error-logger"); -} - -export default defineNitroPlugin((nitroApp) => { - installProcessErrorLogging(); - - nitroApp.hooks.hook("error", (error, context) => { - const event = context.event; - const tags = Array.isArray(context.tags) ? (context.tags as string[]).join(",") : ""; - logger.error( - event?.method ?? "", - event?.path ?? "(no request)", - tags ? `[${tags}]` : "", - "\n", - error - ); - }); -}); diff --git a/static/.gitignore b/static/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/static/upload/a.txt b/static/upload/a.txt new file mode 100644 index 0000000..43e4382 --- /dev/null +++ b/static/upload/a.txt @@ -0,0 +1 @@ +sadsadsad \ No newline at end of file