好的,让我们来实现博客的分类系统。这将允许我们为文章添加分类,并按分类浏览文章。
第十二部分:实现博客分类系统
- 首先,我们需要更新数据库模式。在
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),
});- 创建一个新的迁移文件。运行以下命令:
bun run drizzle-kit generate:sqlite- 在新生成的迁移文件中,确保包含了新表的创建语句。然后运行迁移:
bun run db:migrate- 更新
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;
});- 更新
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>- 更新
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>
<!-- ... 评论部分保持不变 -->- 更新
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>- 更新
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 });
});- 更新
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;
}这些更改实现了博客的分类系统:
- 创建了新的数据库表来存储分类和文章-分类关系。
- 更新了文章创建和查询逻辑以包含分类。
- 在文章创建表单中添加了分类输入。
- 在文章详情页面显示了分类信息。
- 在主页添加了分类列表,并允许按分类过滤文章。
- 更新了样式以美化分类显示。
这个功能允许用户为文章添加分类,并按分类浏览文章,提高了博客的组织性和可导航性。
接下来,我们可以考虑添加以下功能:
- 文章搜索功能
- 用户头像上传
- 社交媒体分享按钮
- 文章归档页面
你希望继续哪个方向,或者有其他想法吗?