feat: implement database initialization in StorageService

This commit is contained in:
shuaiplus
2026-02-09 23:00:23 +08:00
parent 866ffb8390
commit 70a58aeb04
3 changed files with 143 additions and 2 deletions
+16 -1
View File
@@ -1,9 +1,24 @@
import { Env } from './types';
import { handleRequest } from './router';
import { StorageService } from './services/storage';
// Global flag to track if database has been initialized in this worker instance
let dbInitialized = false;
export default {
async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
// Auto-initialize database on first request
if (!dbInitialized) {
try {
const storage = new StorageService(env.DB);
await storage.initializeDatabase();
dbInitialized = true;
} catch (error) {
console.error('Failed to initialize database:', error);
// Continue anyway - the error will surface when actual DB operations are attempted
}
}
return handleRequest(request, env);
return handleRequest(request, env);
},
};
+126
View File
@@ -9,6 +9,132 @@ import { User, Cipher, Folder, Attachment } from '../types';
export class StorageService {
constructor(private db: D1Database) {}
// --- Database initialization ---
async initializeDatabase(): Promise<void> {
// Check if database is already initialized by looking for the config table
try {
const result = await this.db
.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='config'")
.first<{ name: string }>();
if (result?.name === 'config') {
// Database already initialized
return;
}
} catch (e) {
// If error occurs, assume database needs initialization
console.log('Initializing database...');
}
// Execute initialization SQL
const initSQL = `
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS config (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS users (
id TEXT PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
name TEXT,
master_password_hash TEXT NOT NULL,
key TEXT NOT NULL,
private_key TEXT,
public_key TEXT,
kdf_type INTEGER NOT NULL,
kdf_iterations INTEGER NOT NULL,
kdf_memory INTEGER,
kdf_parallelism INTEGER,
security_stamp TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);
CREATE TABLE IF NOT EXISTS user_revisions (
user_id TEXT PRIMARY KEY,
revision_date TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS ciphers (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
type INTEGER NOT NULL,
folder_id TEXT,
name TEXT,
notes TEXT,
favorite INTEGER NOT NULL DEFAULT 0,
data TEXT NOT NULL,
reprompt INTEGER,
key TEXT,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
deleted_at TEXT,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_ciphers_user_updated ON ciphers(user_id, updated_at);
CREATE INDEX IF NOT EXISTS idx_ciphers_user_deleted ON ciphers(user_id, deleted_at);
CREATE TABLE IF NOT EXISTS folders (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_folders_user_updated ON folders(user_id, updated_at);
CREATE TABLE IF NOT EXISTS attachments (
id TEXT PRIMARY KEY,
cipher_id TEXT,
file_name TEXT NOT NULL,
file_size INTEGER NOT NULL,
key TEXT,
data TEXT NOT NULL,
FOREIGN KEY (cipher_id) REFERENCES ciphers(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_attachments_cipher ON attachments(cipher_id);
CREATE TABLE IF NOT EXISTS refresh_tokens (
token TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
expires_at INTEGER NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
);
CREATE INDEX IF NOT EXISTS idx_refresh_tokens_user ON refresh_tokens(user_id);
CREATE TABLE IF NOT EXISTS login_attempts (
email TEXT PRIMARY KEY,
attempts INTEGER NOT NULL,
locked_until INTEGER,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS api_rate_limits (
identifier TEXT NOT NULL,
window_start INTEGER NOT NULL,
count INTEGER NOT NULL,
PRIMARY KEY (identifier, window_start)
);
CREATE INDEX IF NOT EXISTS idx_api_rate_window ON api_rate_limits(window_start);
`.trim();
// Split by semicolon and execute each statement
const statements = initSQL.split(';').filter(s => s.trim().length > 0);
for (const stmt of statements) {
if (stmt.trim()) {
await this.db.prepare(stmt).run();
}
}
console.log('Database initialized successfully');
}
// --- Config / setup ---
async isRegistered(): Promise<boolean> {