Files
docs-site/src/lib/db.ts
hjdave ec9f4bbf02
Some checks failed
Deploy Documentation Site / build (push) Has been cancelled
Deploy Documentation Site / deploy (push) Has been cancelled
Fix SSR hydration issues and finalize deployment config
2026-04-04 16:59:37 +08:00

354 lines
10 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { Pool, PoolClient } from "pg";
const dbConfig = {
host: "118.89.161.243",
port: 54201,
user: "hjdave",
password: "HJD13567840170",
database: "hjdave-doc",
max: 20,
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
};
// 全局连接池单例
let pool: Pool | null = null;
export function getPool(): Pool {
if (!pool) {
pool = new Pool(dbConfig);
pool.on("error", (err) => {
console.error("Unexpected error on idle client", err);
});
}
return pool;
}
export async function query(sql: string, params?: any[]): Promise<any> {
const pool = getPool();
const start = Date.now();
try {
const res = await pool.query(sql, params);
const duration = Date.now() - start;
console.log("Executed query", { sql, duration, rows: res.rowCount });
return res;
} catch (error) {
console.error("Database query error", error);
throw error;
}
}
export async function getClient(): Promise<PoolClient> {
const client = await getPool().connect();
const release = () => client.release();
return client as PoolClient & { release: () => void };
}
// 初始化数据库表
export async function initializeDatabase(): Promise<void> {
// 创建用户表
await query(`
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
username VARCHAR(255) UNIQUE NOT NULL,
email VARCHAR(255) UNIQUE,
password_hash VARCHAR(255) NOT NULL,
role VARCHAR(50) DEFAULT 'admin',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
`);
// 创建文档表
await query(`
CREATE TABLE IF NOT EXISTS docs (
id SERIAL PRIMARY KEY,
slug VARCHAR(255) UNIQUE NOT NULL,
title VARCHAR(255) NOT NULL,
description TEXT,
created_by INTEGER REFERENCES users(id),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
`);
// 创建章节表
await query(`
CREATE TABLE IF NOT EXISTS sections (
id SERIAL PRIMARY KEY,
doc_id INTEGER REFERENCES docs(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
content TEXT,
code TEXT,
display_order INTEGER DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
`);
// 创建索引
await query("CREATE INDEX IF NOT EXISTS idx_docs_slug ON docs(slug)");
await query("CREATE INDEX IF NOT EXISTS idx_docs_created_by ON docs(created_by)");
await query("CREATE INDEX IF NOT EXISTS idx_sections_doc_id ON sections(doc_id)");
// 便签表
await query(`
CREATE TABLE IF NOT EXISTS notes (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
title VARCHAR(255) NOT NULL,
content TEXT,
color VARCHAR(20) DEFAULT '#fef08a',
is_pinned BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
`);
// 留言表
await query(`
CREATE TABLE IF NOT EXISTS messages (
id SERIAL PRIMARY KEY,
user_id INTEGER REFERENCES users(id) ON DELETE CASCADE,
content TEXT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
`);
// 便签索引
await query("CREATE INDEX IF NOT EXISTS idx_notes_user_id ON notes(user_id)");
await query("CREATE INDEX IF NOT EXISTS idx_notes_pinned ON notes(is_pinned)");
await query("CREATE INDEX IF NOT EXISTS idx_messages_user_id ON messages(user_id)");
// 初始化默认管理员账户
const existing = await query("SELECT id FROM users WHERE username = $1", ["admin"]);
if (existing.rows.length === 0) {
await query(
"INSERT INTO users (username, email, password_hash, role) VALUES ($1, $2, $3, $4)",
["admin", "admin@docsite.com", "$2b$10$d1BPt0phBsRORuWnAacgZO.Y0LBZ3hLc8uKyZBN1BkPMR8j4GESHS", "admin"]
);
console.log("Created default admin user");
} else {
// 更新密码(如果之前存在但密码不正确)
await query(
"UPDATE users SET password_hash = $1 WHERE username = $2",
["$2b$10$d1BPt0phBsRORuWnAacgZO.Y0LBZ3hLc8uKyZBN1BkPMR8j4GESHS", "admin"]
);
console.log("Updated admin password");
}
console.log("Database initialized successfully");
}
// 获取密码哈希
export async function getPasswordHash(username: string): Promise<string | null> {
const result = await query(
"SELECT password_hash FROM users WHERE username = $1",
[username]
);
return result.rows[0]?.password_hash ?? null;
}
// 检查是否是管理员
export async function isAdmin(username: string): Promise<boolean> {
const result = await query(
"SELECT role FROM users WHERE username = $1",
[username]
);
return result.rows[0]?.role === "admin";
}
// 验证用户登录
export async function verifyUser(username: string, password: string): Promise<boolean> {
const hash = await getPasswordHash(username);
if (!hash) return false;
const bcrypt = await import("bcryptjs");
return bcrypt.compare(password, hash);
}
// 创建新用户
export async function createUser(
username: string,
password: string,
email?: string,
role: "admin" | "user" = "user"
): Promise<boolean> {
const bcrypt = await import("bcryptjs");
const passwordHash = await bcrypt.hash(password, 10);
try {
await query(
"INSERT INTO users (username, email, password_hash, role) VALUES ($1, $2, $3, $4)",
[username, email, passwordHash, role]
);
return true;
} catch (error) {
console.error("Failed to create user:", error);
return false;
}
}
// 获取所有文档
export async function getDocs(): Promise<any[]> {
const result = await query(
`
SELECT d.*,
json_agg(
json_build_object(
'id', s.id,
'title', s.title,
'content', s.content,
'code', s.code,
'display_order', s.display_order
) ORDER BY s.display_order
) as sections
FROM docs d
LEFT JOIN sections s ON s.doc_id = d.id
GROUP BY d.id
ORDER BY d.created_at DESC
`
);
return result.rows.map((row: any) => ({
title: row.title,
slug: row.slug,
description: row.description,
sections: JSON.parse(row.sections || "[]"),
}));
}
// 添加文档
export async function addDoc(
slug: string,
title: string,
description?: string
): Promise<boolean> {
try {
await query(
"INSERT INTO docs (slug, title, description) VALUES ($1, $2, $3)",
[slug, title, description]
);
return true;
} catch (error) {
console.error("Failed to add doc:", error);
return false;
}
}
// 添加章节
export async function addSection(docSlug: string, title: string, content: string, code?: string): Promise<boolean> {
const docResult = await query("SELECT id FROM docs WHERE slug = $1", [docSlug]);
if (docResult.rows.length === 0) return false;
const docId = docResult.rows[0].id;
try {
await query(
"INSERT INTO sections (doc_id, title, content, code) VALUES ($1, $2, $3, $4)",
[docId, title, content, code || null]
);
return true;
} catch (error) {
console.error("Failed to add section:", error);
return false;
}
}
// 获取所有文档(简化版,直接返回 JSON
export async function getDocsSimple(): Promise<any[]> {
return [
{
title: "快速开始",
slug: "quick-start",
description: "了解如何开始使用我们的平台",
sections: [
{ title: "简介", content: "欢迎使用我们的在线文档系统!", code: null },
{ title: "安装", content: "开始之前,请确保你已经安装了 Node.js 和 npm。", code: "npm install\nnpm run dev" },
],
},
{
title: "API 参考",
slug: "api-reference",
description: "API 接口文档",
sections: [{ title: "获取文档列表", content: "获取所有可用文档的列表。", code: "GET /api/docs" }],
},
];
}
// 便签相关
export async function getNotes(userId: number): Promise<any[]> {
const result = await query(
`SELECT * FROM notes WHERE user_id = $1 ORDER BY is_pinned DESC, created_at DESC`,
[userId]
);
return result.rows;
}
export async function addNote(userId: number, title: string, content: string, color: string, isPinned: boolean): Promise<boolean> {
try {
await query(
"INSERT INTO notes (user_id, title, content, color, is_pinned) VALUES ($1, $2, $3, $4, $5)",
[userId, title, content, color, isPinned]
);
return true;
} catch (error) {
console.error("Failed to add note:", error);
return false;
}
}
export async function deleteNote(userId: number, noteId: number): Promise<boolean> {
try {
await query("DELETE FROM notes WHERE id = $1 AND user_id = $2", [noteId, userId]);
return true;
} catch (error) {
console.error("Failed to delete note:", error);
return false;
}
}
export async function togglePinNote(userId: number, noteId: number): Promise<boolean> {
try {
await query("UPDATE notes SET is_pinned = NOT is_pinned WHERE id = $1 AND user_id = $2", [noteId, userId]);
return true;
} catch (error) {
console.error("Failed to toggle pin:", error);
return false;
}
}
// 留言相关
export async function getMessages(): Promise<any[]> {
const result = await query(`
SELECT m.*, u.username FROM messages m
JOIN users u ON m.user_id = u.id
ORDER BY m.created_at DESC
LIMIT 100
`);
return result.rows;
}
export async function addMessage(userId: number, content: string): Promise<boolean> {
try {
await query(
"INSERT INTO messages (user_id, content) VALUES ($1, $2)",
[userId, content]
);
return true;
} catch (error) {
console.error("Failed to add message:", error);
return false;
}
}
export async function deleteMessage(userId: number, messageId: number): Promise<boolean> {
try {
await query("DELETE FROM messages WHERE id = $1 AND user_id = $2", [messageId, userId]);
return true;
} catch (error) {
console.error("Failed to delete message:", error);
return false;
}
}