- 后端:Bun + Hono + Drizzle ORM + SQLite - 前端:Vue 3 + Naive UI + ECharts - 项目管理:创建项目 + 绑定 Git 仓库 - OKR 系统:目标/关键结果 CRUD + 进度追踪 - Git 同步:Gitea API 自动同步 commit/PR + 作者关联 - 数据看板:项目 OKR 进度 + KR 状态分布 + 代码活动 - 权限体系:admin/manager/developer/viewer 四级 - Docker 部署:docker-compose + nginx Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
214 lines
6.5 KiB
TypeScript
214 lines
6.5 KiB
TypeScript
import { drizzle } from 'drizzle-orm/bun-sqlite';
|
|
import { Database } from 'bun:sqlite';
|
|
import { migrate } from 'drizzle-orm/bun-sqlite/migrator';
|
|
import * as schema from './schema';
|
|
import { config } from '../config';
|
|
import { mkdirSync, existsSync } from 'fs';
|
|
import { dirname, resolve } from 'path';
|
|
|
|
// Ensure data directory exists
|
|
const dbDir = dirname(config.DATABASE_PATH);
|
|
if (!existsSync(dbDir)) {
|
|
mkdirSync(dbDir, { recursive: true });
|
|
}
|
|
|
|
const sqlite = new Database(config.DATABASE_PATH, { create: true });
|
|
sqlite.exec('PRAGMA journal_mode = WAL');
|
|
sqlite.exec('PRAGMA foreign_keys = ON');
|
|
|
|
export const db = drizzle(sqlite, { schema });
|
|
export { sqlite };
|
|
|
|
/**
|
|
* Run database migrations automatically on startup.
|
|
* Uses CREATE TABLE IF NOT EXISTS semantics (via drizzle migrate)
|
|
* so it is safe to call on every boot -- already-applied migrations
|
|
* are tracked in the drizzle __drizzle_migrations journal table.
|
|
*
|
|
* The migrationsFolder path is resolved relative to the project root
|
|
* (where package.json lives) so it works regardless of cwd.
|
|
*/
|
|
function autoMigrate() {
|
|
try {
|
|
// Resolve the drizzle folder relative to this file's location:
|
|
// src/db/index.ts -> ../../drizzle
|
|
const migrationsFolder = resolve(import.meta.dir, '../../drizzle');
|
|
migrate(db, { migrationsFolder });
|
|
console.info('[DB] Auto-migration completed successfully.');
|
|
} catch (err) {
|
|
console.error('[DB] Auto-migration failed:', err);
|
|
// Do not crash the process -- tables may already exist from a prior run.
|
|
// Fallback: create core tables with raw SQL if drizzle migrations folder is missing.
|
|
try {
|
|
createTablesIfNotExist();
|
|
console.info('[DB] Fallback table creation completed.');
|
|
} catch (fallbackErr) {
|
|
console.error('[DB] Fallback table creation also failed:', fallbackErr);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Fallback: create all required tables using raw SQL CREATE TABLE IF NOT EXISTS.
|
|
* This is only used when the drizzle migrations folder cannot be found.
|
|
*/
|
|
function createTablesIfNotExist() {
|
|
sqlite.exec(`
|
|
CREATE TABLE IF NOT EXISTS users (
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
plane_user_id TEXT,
|
|
display_name TEXT NOT NULL,
|
|
email TEXT NOT NULL UNIQUE,
|
|
git_username TEXT,
|
|
role TEXT NOT NULL,
|
|
password_hash TEXT NOT NULL,
|
|
login_attempts INTEGER DEFAULT 0,
|
|
locked_until INTEGER,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS projects (
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
plane_project_id TEXT NOT NULL UNIQUE,
|
|
name TEXT NOT NULL,
|
|
identifier TEXT,
|
|
last_synced_at INTEGER,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sprint_snapshots (
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
project_id TEXT REFERENCES projects(id),
|
|
plane_cycle_id TEXT NOT NULL,
|
|
name TEXT NOT NULL,
|
|
start_date TEXT,
|
|
end_date TEXT,
|
|
total_points INTEGER DEFAULT 0,
|
|
completed_points INTEGER DEFAULT 0,
|
|
total_issues INTEGER DEFAULT 0,
|
|
completed_issues INTEGER DEFAULT 0,
|
|
burndown_data TEXT,
|
|
status TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS task_snapshots (
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
plane_issue_id TEXT NOT NULL,
|
|
project_id TEXT REFERENCES projects(id),
|
|
sprint_id TEXT REFERENCES sprint_snapshots(id),
|
|
title TEXT NOT NULL,
|
|
status TEXT,
|
|
priority TEXT,
|
|
assignee_id TEXT REFERENCES users(id),
|
|
story_points INTEGER,
|
|
created_at INTEGER,
|
|
completed_at INTEGER,
|
|
due_date TEXT,
|
|
labels TEXT,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS milestones (
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
plane_module_id TEXT NOT NULL,
|
|
project_id TEXT REFERENCES projects(id),
|
|
name TEXT NOT NULL,
|
|
status TEXT,
|
|
target_date TEXT,
|
|
total_issues INTEGER DEFAULT 0,
|
|
completed_issues INTEGER DEFAULT 0,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS git_commits (
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
repo_name TEXT NOT NULL,
|
|
sha TEXT NOT NULL UNIQUE,
|
|
author_email TEXT,
|
|
author_name TEXT,
|
|
user_id TEXT REFERENCES users(id),
|
|
message TEXT,
|
|
additions INTEGER DEFAULT 0,
|
|
deletions INTEGER DEFAULT 0,
|
|
committed_at INTEGER NOT NULL,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS git_prs (
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
repo_name TEXT NOT NULL,
|
|
external_id INTEGER NOT NULL,
|
|
title TEXT,
|
|
user_id TEXT REFERENCES users(id),
|
|
author_username TEXT,
|
|
state TEXT,
|
|
additions INTEGER DEFAULT 0,
|
|
deletions INTEGER DEFAULT 0,
|
|
review_comments INTEGER DEFAULT 0,
|
|
created_at INTEGER,
|
|
merged_at INTEGER,
|
|
merge_time_hours REAL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS objectives (
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
title TEXT NOT NULL,
|
|
owner_id TEXT REFERENCES users(id),
|
|
project_id TEXT REFERENCES projects(id),
|
|
period TEXT NOT NULL,
|
|
progress REAL DEFAULT 0,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS key_results (
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
objective_id TEXT NOT NULL REFERENCES objectives(id),
|
|
title TEXT NOT NULL,
|
|
target_value REAL NOT NULL,
|
|
current_value REAL DEFAULT 0,
|
|
unit TEXT,
|
|
weight REAL DEFAULT 1,
|
|
linked_plane_cycle_id TEXT,
|
|
linked_plane_module_id TEXT,
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS author_mappings (
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
git_email TEXT,
|
|
git_username TEXT,
|
|
user_id TEXT REFERENCES users(id),
|
|
created_at INTEGER NOT NULL,
|
|
updated_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS project_repos (
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
project_id TEXT NOT NULL REFERENCES projects(id),
|
|
repo_name TEXT NOT NULL,
|
|
created_at INTEGER NOT NULL
|
|
);
|
|
|
|
CREATE TABLE IF NOT EXISTS sync_logs (
|
|
id TEXT PRIMARY KEY NOT NULL,
|
|
source TEXT NOT NULL,
|
|
status TEXT NOT NULL,
|
|
message TEXT,
|
|
records_processed INTEGER DEFAULT 0,
|
|
synced_at INTEGER NOT NULL
|
|
);
|
|
`);
|
|
}
|
|
|
|
// Run auto-migration on module load (i.e. on server startup)
|
|
autoMigrate();
|