好的,让我们来实现评论系统。这将允许用户在帖子下面留下评论,并查看其他用户的评论。
第九部分:实现评论系统
- 首先,我们需要更新数据库模式。在
src/db/schema.ts中添加以下内容:
import { sqliteTable, text, integer } from "drizzle-orm/sqlite-core";
// ... 现有的表定义保持不变
export const comments = sqliteTable("comments", {
id: integer("id").primaryKey(),
content: text("content").notNull(),
postId: integer("post_id").notNull().references(() => posts.id),
authorId: integer("author_id").notNull().references(() => users.id),
createdAt: integer("created_at", { mode: "timestamp" }).notNull().default(sql`CURRENT_TIMESTAMP`),
});- 创建一个新的迁移文件来添加评论表。在
drizzle目录中创建一个新文件,例如0004_add_comments.sql:
CREATE TABLE comments (
id INTEGER PRIMARY KEY,
content TEXT NOT NULL,
post_id INTEGER NOT NULL,
author_id INTEGER NOT NULL,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts(id),
FOREIGN KEY (author_id) REFERENCES users(id)
);
CREATE INDEX idx_comments_post_id ON comments(post_id);
CREATE INDEX idx_comments_author_id ON comments(author_id);- 创建一个新的路由文件
src/routes/comments.ts:
import { Elysia, t } from "elysia";
import { db } from "../db";
import { comments, users } from "../db/schema";
import { eq } from "drizzle-orm";
import { authMiddleware } from "../middleware/auth";
export const commentRoutes = new Elysia()
.use(authMiddleware)
.post("/comments", async ({ body, set, getCurrentUser }) => {
const user = await getCurrentUser();
if (!user) {
set.status = 401;
return { error: "Not authorized" };
}
const { content, postId } = body;
const comment = await db.insert(comments).values({
content,
postId,
authorId: user.id,
}).returning().get();
return comment;
})
.get("/posts/:id/comments", async ({ params }) => {
const postComments = await db.select({
id: comments.id,
content: comments.content,
createdAt: comments.createdAt,
author: users.username,
})
.from(comments)
.leftJoin(users, eq(comments.authorId, users.id))
.where(eq(comments.postId, parseInt(params.id)))
.orderBy(comments.createdAt)
.all();
return postComments;
});- 更新
src/routes/posts.ts以包含评论:
// ... 前面的导入保持不变
export const postRoutes = new Elysia()
.use(authMiddleware)
// ... 其他路由保持不变
.get("/posts/:id", async ({ params }) => {
const post = await db.select({
id: posts.id,
title: posts.title,
content: posts.content,
createdAt: posts.createdAt,
author: users.username,
authorId: users.id,
})
.from(posts)
.leftJoin(users, eq(posts.authorId, users.id))
.where(eq(posts.id, parseInt(params.id)))
.get();
if (!post) {
return null;
}
const postTags = await db.select({
name: tags.name,
})
.from(postTags)
.leftJoin(tags, eq(postTags.tagId, tags.id))
.where(eq(postTags.postId, post.id))
.all();
return { ...post, tags: postTags.map(tag => tag.name) };
});- 更新
src/views/post.eta以显示评论和添加评论表单:
<% layout('./layouts/main.eta', { title: it.post.title }) %>
<article id="post-<%= it.post.id %>">
<h1><%= it.post.title %></h1>
<p>By <a href="/users/<%= it.post.authorId %>"><%= it.post.author %></a> on <%= new Date(it.post.createdAt).toLocaleDateString() %></p>
<div><%= it.post.content %></div>
<% if (it.post.tags && it.post.tags.length > 0) { %>
<div class="tags">
Tags:
<% it.post.tags.forEach(function(tag) { %>
<a href="/?tag=<%= tag %>" class="tag"><%= tag %></a>
<% }) %>
</div>
<% } %>
</article>
<section id="comments">
<h2>Comments</h2>
<div id="comment-list"></div>
<% if (it.currentUser) { %>
<form hx-post="/comments" hx-swap="afterbegin" hx-target="#comment-list">
<input type="hidden" name="postId" value="<%= it.post.id %>">
<textarea name="content" required></textarea>
<button type="submit">Add Comment</button>
</form>
<% } else { %>
<p>Please <a href="/login">log in</a> to leave a comment.</p>
<% } %>
</section>
<script>
htmx.ajax('GET', '/posts/<%= it.post.id %>/comments', {target: '#comment-list', swap: 'innerHTML'});
</script>- 创建一个新的视图
src/views/comment.eta来渲染单个评论:
<div class="comment">
<p><%= it.content %></p>
<p class="comment-meta">By <%= it.author %> on <%= new Date(it.createdAt).toLocaleString() %></p>
</div>- 更新
src/index.ts以包含新的评论路由:
import { Elysia } from "elysia";
import { html } from "@elysiajs/html";
import { staticPlugin } from "@elysiajs/static";
import { authRoutes, auth } from "./routes/auth";
import { postRoutes } from "./routes/posts";
import { userRoutes } from "./routes/users";
import { commentRoutes } from "./routes/comments";
import { migrate } from "drizzle-orm/libsql/migrator";
import { db } from "./db";
import { renderFile } from "eta";
import path from "path";
// ... 前面的代码保持不变
const app = new Elysia()
.use(html())
.use(staticPlugin())
.use(auth)
.use(authRoutes)
.use(postRoutes)
.use(userRoutes)
.use(commentRoutes)
// ... 其他路由保持不变
.get("/posts/:id", async ({ params, set, getCurrentUser }) => {
const postData = await postRoutes.handle({ method: 'GET', path: `/posts/${params.id}` });
if (!postData) {
set.status = 404;
return "Post not found";
}
const currentUser = await getCurrentUser();
set.headers['Content-Type'] = 'text/html';
return await renderFile(path.join(__dirname, "views", "post.eta"), { post: postData, currentUser });
})
.get("/posts/:id/comments", async ({ params }) => {
const comments = await commentRoutes.handle({ method: 'GET', path: `/posts/${params.id}/comments` });
return comments.map(comment => renderFile(path.join(__dirname, "views", "comment.eta"), comment)).join('');
})
.listen(3000);
console.log(`Server is running at ${app.server?.hostname}:${app.server?.port}`);- 最后,更新
public/styles/main.css以添加评论样式:
/* ... 前面的样式保持不变 */
#comments {
margin-top: 2rem;
border-top: 1px solid #ccc;
padding-top: 1rem;
}
.comment {
background-color: #f9f9f9;
border: 1px solid #e0e0e0;
border-radius: 4px;
padding: 1rem;
margin-bottom: 1rem;
}
.comment-meta {
font-size: 0.8rem;
color: #666;
margin-top: 0.5rem;
}
#comments form {
margin-top: 1rem;
}
#comments textarea {
width: 100%;
height: 100px;
margin-bottom: 0.5rem;
}这些更改实现了评论系统:
- 用户可以在帖子页面上查看所有评论。
- 登录用户可以添加新评论。
- 评论以时间顺序显示,包括作者和时间戳。
- 使用 HTMX 实现了无刷新添加评论和加载评论的功能。
这个功能大大增强了博客的互动性。用户现在可以就帖子内容进行讨论,分享他们的想法和反馈。
接下来,我们可以考虑添加以下功能:
- 评论编辑和删除功能
- 评论分页
- 评论回复功能
- 评论通知系统
您希望继续哪个方向,或者有其他想法吗?