好的,让我们来实现评论系统。这将允许用户在帖子下面留下评论,并查看其他用户的评论。

第九部分:实现评论系统

  1. 首先,我们需要更新数据库模式。在 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`),
});
  1. 创建一个新的迁移文件来添加评论表。在 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);
  1. 创建一个新的路由文件 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;
  });
  1. 更新 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) };
  });
  1. 更新 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>
  1. 创建一个新的视图 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>
  1. 更新 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}`);
  1. 最后,更新 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;
}

这些更改实现了评论系统:

  1. 用户可以在帖子页面上查看所有评论。
  2. 登录用户可以添加新评论。
  3. 评论以时间顺序显示,包括作者和时间戳。
  4. 使用 HTMX 实现了无刷新添加评论和加载评论的功能。

这个功能大大增强了博客的互动性。用户现在可以就帖子内容进行讨论,分享他们的想法和反馈。

接下来,我们可以考虑添加以下功能:

  1. 评论编辑和删除功能
  2. 评论分页
  3. 评论回复功能
  4. 评论通知系统

您希望继续哪个方向,或者有其他想法吗?