好的,让我们来实现博客的分类系统。这将允许我们为文章添加分类,并按分类浏览文章。

第十二部分:实现博客分类系统

  1. 首先,我们需要更新数据库模式。在 src/db/schema.ts 中添加以下内容:
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
 
// ... 其他表定义保持不变
 
export const categories = sqliteTable('categories', {
  id: integer('id').primaryKey(),
  name: text('name').notNull().unique(),
});
 
export const postCategories = sqliteTable('post_categories', {
  postId: integer('post_id').notNull().references(() => posts.id),
  categoryId: integer('category_id').notNull().references(() => categories.id),
});
  1. 创建一个新的迁移文件。运行以下命令:
bun run drizzle-kit generate:sqlite
  1. 在新生成的迁移文件中,确保包含了新表的创建语句。然后运行迁移:
bun run db:migrate
  1. 更新 src/routes/posts.ts 以包含分类功能:
import { Elysia, t } from "elysia";
import { db } from "../db";
import { posts, users, categories, postCategories } from "../db/schema";
import { eq, desc, inArray } from "drizzle-orm";
import { authMiddleware } from "../middleware/auth";
import { generateRSSFeed } from "../utils/rss";
 
export const postRoutes = new Elysia()
  .use(authMiddleware)
  .get("/posts", async ({ query }) => {
    let postsQuery = db.select({
      id: posts.id,
      title: posts.title,
      content: posts.content,
      createdAt: posts.createdAt,
      author: users.username,
    })
      .from(posts)
      .leftJoin(users, eq(posts.authorId, users.id))
      .orderBy(desc(posts.createdAt));
 
    if (query.category) {
      postsQuery = postsQuery
        .innerJoin(postCategories, eq(posts.id, postCategories.postId))
        .innerJoin(categories, eq(postCategories.categoryId, categories.id))
        .where(eq(categories.name, query.category));
    }
 
    const allPosts = await postsQuery.all();
    return allPosts;
  })
  .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,
      categories: categories.name,
    })
      .from(posts)
      .leftJoin(users, eq(posts.authorId, users.id))
      .leftJoin(postCategories, eq(posts.id, postCategories.postId))
      .leftJoin(categories, eq(postCategories.categoryId, categories.id))
      .where(eq(posts.id, parseInt(params.id)))
      .all();
 
    if (post.length === 0) {
      return null;
    }
 
    const categories = post.map(p => p.categories).filter(Boolean);
    return { ...post[0], categories };
  })
  .post("/posts", async ({ body, getCurrentUser }) => {
    const user = await getCurrentUser();
    if (!user) {
      throw new Error("Not authorized");
    }
 
    const { title, content, categories } = body;
 
    const newPost = await db.transaction(async (tx) => {
      const [post] = await tx.insert(posts).values({
        title,
        content,
        authorId: user.id,
      }).returning();
 
      if (categories && categories.length > 0) {
        for (const categoryName of categories) {
          let [category] = await tx.select().from(categories).where(eq(categories.name, categoryName)).limit(1).all();
          if (!category) {
            [category] = await tx.insert(categories).values({ name: categoryName }).returning();
          }
          await tx.insert(postCategories).values({ postId: post.id, categoryId: category.id });
        }
      }
 
      return post;
    });
 
    return newPost;
  })
  // ... 其他路由保持不变
  .get("/categories", async () => {
    const allCategories = await db.select().from(categories).all();
    return allCategories;
  });
  1. 更新 src/views/posts/new.eta 以包含分类选择:
<% layout('../layouts/main.eta', { title: 'New Post' }) %>
 
<h1>Create a New Post</h1>
 
<form action="/posts" method="POST">
    <div>
        <label for="title">Title:</label>
        <input type="text" id="title" name="title" required>
    </div>
    <div>
        <label for="content">Content:</label>
        <textarea id="content" name="content" required></textarea>
    </div>
    <div>
        <label for="categories">Categories (comma-separated):</label>
        <input type="text" id="categories" name="categories">
    </div>
    <button type="submit">Create Post</button>
</form>
  1. 更新 src/views/posts/show.eta 以显示文章分类:
<% layout('../layouts/main.eta', { title: it.post.title }) %>
 
<h1><%= it.post.title %></h1>
<p>By <%= it.post.author %> on <%= new Date(it.post.createdAt).toLocaleDateString() %></p>
 
<% if (it.post.categories && it.post.categories.length > 0) { %>
    <p>Categories: 
        <% it.post.categories.forEach((category, index) => { %>
            <a href="/?category=<%= category %>"><%= category %></a><%= index < it.post.categories.length - 1 ? ', ' : '' %>
        <% }) %>
    </p>
<% } %>
 
<div><%= it.post.content %></div>
 
<!-- ... 评论部分保持不变 -->
  1. 更新 src/views/home.eta 以包含分类列表和按分类过滤:
<% layout('./layouts/main.eta', { title: 'Home' }) %>
 
<h1>Welcome to My Blog</h1>
 
<div class="categories">
    <h2>Categories</h2>
    <ul>
        <li><a href="/">All</a></li>
        <% it.categories.forEach(function(category) { %>
            <li><a href="/?category=<%= category.name %>"><%= category.name %></a></li>
        <% }) %>
    </ul>
</div>
 
<div class="posts">
    <% it.posts.forEach(function(post) { %>
        <div class="post">
            <h2><a href="/posts/<%= post.id %>"><%= post.title %></a></h2>
            <p>By <%= post.author %> on <%= new Date(post.createdAt).toLocaleDateString() %></p>
            <p><%= post.content.substring(0, 200) %>...</p>
        </div>
    <% }) %>
</div>
  1. 更新 src/index.ts 中的主页路由以包含分类:
app.get("/", async ({ query }) => {
  const posts = await postRoutes.handle({ method: 'GET', path: '/posts', query });
  const categories = await postRoutes.handle({ method: 'GET', path: '/categories' });
  return await renderFile(path.join(__dirname, "views", "home.eta"), { posts, categories });
});
  1. 更新 public/styles/main.css 以添加分类样式:
/* ... 前面的样式保持不变 */
 
.categories {
    margin-bottom: 2rem;
}
 
.categories ul {
    list-style-type: none;
    padding: 0;
}
 
.categories li {
    display: inline-block;
    margin-right: 1rem;
}
 
.categories a {
    text-decoration: none;
    color: #007bff;
}
 
.categories a:hover {
    text-decoration: underline;
}

这些更改实现了博客的分类系统:

  1. 创建了新的数据库表来存储分类和文章-分类关系。
  2. 更新了文章创建和查询逻辑以包含分类。
  3. 在文章创建表单中添加了分类输入。
  4. 在文章详情页面显示了分类信息。
  5. 在主页添加了分类列表,并允许按分类过滤文章。
  6. 更新了样式以美化分类显示。

这个功能允许用户为文章添加分类,并按分类浏览文章,提高了博客的组织性和可导航性。

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

  1. 文章搜索功能
  2. 用户头像上传
  3. 社交媒体分享按钮
  4. 文章归档页面

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