Initial commit: Online documentation site with CMS, notes, and message board

This commit is contained in:
hjdave
2026-04-04 16:00:52 +08:00
commit 38dc7203d1
34 changed files with 9706 additions and 0 deletions

356
src/lib/db.ts Normal file
View File

@@ -0,0 +1,356 @@
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,
release,
};
}
// 初始化数据库表
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) => ({
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;
}
}