非常好,让我们依次实现这三个功能,它们将大大提升博客系统的安全性和管理能力。
第二十四部分:细粒度权限控制
- 首先,我们需要在数据库中添加一个新表来存储文章权限。更新
src/db/schema.ts:
export const postPermissions = sqliteTable("post_permissions", {
id: integer("id").primaryKey(),
postId: integer("post_id").notNull().references(() => posts.id),
userId: integer("user_id").notNull().references(() => users.id),
canEdit: integer("can_edit").notNull().default(0),
canDelete: integer("can_delete").notNull().default(0),
});- 创建新的迁移文件
src/db/migrations/0008_add_post_permissions.ts:
export async function up(db) {
await db.run(sql`
CREATE TABLE post_permissions (
id INTEGER PRIMARY KEY,
post_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
can_edit INTEGER NOT NULL DEFAULT 0,
can_delete INTEGER NOT NULL DEFAULT 0,
FOREIGN KEY (post_id) REFERENCES posts(id),
FOREIGN KEY (user_id) REFERENCES users(id)
);
`);
}
export async function down(db) {
await db.run(sql`DROP TABLE post_permissions;`);
}- 运行迁移:
bun run migrate- 更新
src/middleware/auth.ts文件,添加检查特定文章权限的函数:
import { db } from "../db";
import { postPermissions } from "../db/schema";
import { and, eq } from "drizzle-orm";
export async function canEditPost(userId: number, postId: number): Promise<boolean> {
const permission = await db
.select()
.from(postPermissions)
.where(and(eq(postPermissions.userId, userId), eq(postPermissions.postId, postId)))
.get();
return !!permission && permission.canEdit === 1;
}
export async function canDeletePost(userId: number, postId: number): Promise<boolean> {
const permission = await db
.select()
.from(postPermissions)
.where(and(eq(postPermissions.userId, userId), eq(postPermissions.postId, postId)))
.get();
return !!permission && permission.canDelete === 1;
}- 更新
src/routes/posts.ts文件,使用新的权限检查:
import { canEditPost, canDeletePost } from "../middleware/auth";
export const postRoutes = new Elysia()
// ... 其他路由保持不变
.get("/posts/:id/edit", async ({ params, session, set, render }) => {
if (!session.user) {
set.status = 401;
return "Unauthorized";
}
const postId = parseInt(params.id);
const canEdit = await canEditPost(session.user.id, postId);
if (!canEdit && session.user.role !== "admin") {
set.status = 403;
return "Forbidden";
}
// 获取文章并渲染编辑页面
})
.delete("/posts/:id", async ({ params, session, set }) => {
if (!session.user) {
set.status = 401;
return "Unauthorized";
}
const postId = parseInt(params.id);
const canDelete = await canDeletePost(session.user.id, postId);
if (!canDelete && session.user.role !== "admin") {
set.status = 403;
return "Forbidden";
}
// 删除文章的逻辑
});第二十五部分:用户审核机制
- 更新
src/db/schema.ts,在 users 表中添加 status 字段:
export const users = sqliteTable("users", {
// ... 其他字段保持不变
status: text("status").notNull().default("pending"),
});- 创建新的迁移文件
src/db/migrations/0009_add_user_status.ts:
export async function up(db) {
await db.run(sql`
ALTER TABLE users ADD COLUMN status TEXT NOT NULL DEFAULT 'pending';
`);
}
export async function down(db) {
await db.run(sql`
ALTER TABLE users DROP COLUMN status;
`);
}- 运行迁移:
bun run migrate- 更新
src/routes/auth.ts文件,修改注册逻辑:
export const authRoutes = new Elysia()
// ... 其他路由保持不变
.post("/register", async ({ body, set }) => {
const { username, email, password } = body;
const hashedPassword = await Bun.password.hash(password);
await db.insert(users).values({
username,
email,
password: hashedPassword,
role: "reader",
status: "pending",
});
set.redirect = "/login?message=Registration successful. Please wait for admin approval.";
});- 更新
src/routes/users.ts文件,添加用户审核功能:
export const userRoutes = new Elysia()
// ... 其他路由保持不变
.get("/users/pending", adminOnly, async ({ render }) => {
const pendingUsers = await db.select().from(users).where(eq(users.status, "pending")).all();
return render("users/pending", { users: pendingUsers });
})
.post("/users/:id/approve", adminOnly, async ({ params, set }) => {
await db.update(users)
.set({ status: "active" })
.where(eq(users.id, parseInt(params.id)));
set.redirect = "/users/pending";
})
.post("/users/:id/reject", adminOnly, async ({ params, set }) => {
await db.delete(users).where(eq(users.id, parseInt(params.id)));
set.redirect = "/users/pending";
});- 创建
src/views/users/pending.eta文件:
<% layout('../layouts/main.eta', { title: 'Pending Users' }) %>
<h1>Pending Users</h1>
<table>
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<% it.users.forEach(user => { %>
<tr>
<td><%= user.username %></td>
<td><%= user.email %></td>
<td>
<form action="/users/<%= user.id %>/approve" method="POST" style="display: inline;">
<button type="submit">Approve</button>
</form>
<form action="/users/<%= user.id %>/reject" method="POST" style="display: inline;">
<button type="submit">Reject</button>
</form>
</td>
</tr>
<% }) %>
</tbody>
</table>第二十六部分:用户活动日志
- 在
src/db/schema.ts中添加新表:
export const activityLogs = sqliteTable("activity_logs", {
id: integer("id").primaryKey(),
userId: integer("user_id").notNull().references(() => users.id),
action: text("action").notNull(),
details: text("details"),
createdAt: integer("created_at", { mode: "timestamp" }).notNull().default(sql`CURRENT_TIMESTAMP`),
});- 创建新的迁移文件
src/db/migrations/0010_add_activity_logs.ts:
export async function up(db) {
await db.run(sql`
CREATE TABLE activity_logs (
id INTEGER PRIMARY KEY,
user_id INTEGER NOT NULL,
action TEXT NOT NULL,
details TEXT,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
`);
}
export async function down(db) {
await db.run(sql`DROP TABLE activity_logs;`);
}- 运行迁移:
bun run migrate- 创建一个新文件
src/utils/logger.ts:
import { db } from "../db";
import { activityLogs } from "../db/schema";
export async function logActivity(userId: number, action: string, details?: string) {
await db.insert(activityLogs).values({
userId,
action,
details,
createdAt: new Date(),
});
}- 在各个路由文件中使用 logActivity 函数记录重要操作。例如,在
src/routes/posts.ts中:
import { logActivity } from "../utils/logger";
export const postRoutes = new Elysia()
// ... 其他路由保持不变
.post("/posts", async ({ body, session, set }) => {
// ... 创建文章的逻辑
await logActivity(session.user.id, "create_post", `Created post: ${newPost.title}`);
})
.post("/posts/:id", async ({ params, body, session, set }) => {
// ... 更新文章的逻辑
await logActivity(session.user.id, "update_post", `Updated post: ${params.id}`);
})
.delete("/posts/:id", async ({ params, session, set }) => {
// ... 删除文章的逻辑
await logActivity(session.user.id, "delete_post", `Deleted post: ${params.id}`);
});- 添加一个新路由来查看活动日志。在
src/routes/users.ts中:
export const userRoutes = new Elysia()
// ... 其他路由保持不变
.get("/activity-logs", adminOnly, async ({ render }) => {
const logs = await db
.select({
id: activityLogs.id,
username: users.username,
action: activityLogs.action,
details: activityLogs.details,
createdAt: activityLogs.createdAt,
})
.from(activityLogs)
.innerJoin(users, eq(activityLogs.userId, users.id))
.orderBy(desc(activityLogs.createdAt))
.limit(100)
.all();
return render("users/activity-logs", { logs });
});- 创建
src/views/users/activity-logs.eta文件:
<% layout('../layouts/main.eta', { title: 'Activity Logs' }) %>
<h1>Activity Logs</h1>
<table>
<thead>
<tr>
<th>User</th>
<th>Action</th>
<th>Details</th>
<th>Time</th>
</tr>
</thead>
<tbody>
<% it.logs.forEach(log => { %>
<tr>
<td><%= log.username %></td>
<td><%= log.action %></td>
<td><%= log.details %></td>
<td><%= new Date(log.createdAt).toLocaleString() %></td>
</tr>
<% }) %>
</tbody>
</table>这些更改实现了三个新功能:
- 细粒度的权限控制:现在可以为特定用户分配对特定文章的编辑和删除权限。
- 用户审核机制:新注册的用户需要管理员批准才能激活账户。
- 用户活动日志:系统现在会记录重要的用户操作,管理员可以查看这些日志。
主要改进包括:
- 增强了系统的安全性和灵活性。
- 提供了更好的用户管理工具。
- 增加了系统的可追踪性和问责制。
这些功能大大提升了博客系统的管理能力和安全性。管理员现在可以更精细地控制用户权限,审核新用户,并监控系统活动。
接下来,我们可以考虑以下方向来进一步完善系统:
- 实现文章的协作编辑功能。
- 添加文章版本控制系统。
- 实现更高级的搜索功能,如全文搜索。
- 添加用户通知系统。
你希望继续哪个方向,或者有其他想法吗?