非常好,让我们依次实现这三个功能:用户头像上传、文章归档功能和评论系统。这些功能将大大提升博客的用户体验和互动性。
第二十部分:用户头像上传
- 首先,更新
src/db/schema.ts文件,在 users 表中添加 avatar 字段:
export const users = sqliteTable("users", {
// ... 其他字段保持不变
avatar: text("avatar"),
});- 创建一个新的迁移文件
src/db/migrations/0005_add_user_avatar.ts:
export async function up(db) {
await db.run(sql`
ALTER TABLE users ADD COLUMN avatar TEXT;
`);
}
export async function down(db) {
await db.run(sql`
ALTER TABLE users DROP COLUMN avatar;
`);
}- 运行迁移:
bun run migrate- 安装文件上传处理库:
bun add @elysiajs/files- 更新
src/routes/users.ts文件,添加头像上传功能:
import { Elysia } from "elysia";
import { db } from "../db";
import { users } from "../db/schema";
import { eq } from "drizzle-orm";
import { files } from "@elysiajs/files";
export const userRoutes = new Elysia()
.use(files())
.get("/profile", async ({ session, render }) => {
if (!session.user) {
return { redirect: "/login" };
}
const user = await db.select().from(users).where(eq(users.id, session.user.id)).get();
return render("users/profile", { user });
})
.post("/profile/avatar", async ({ session, files, set }) => {
if (!session.user) {
set.status = 401;
return { error: "Unauthorized" };
}
const avatar = files.avatar;
if (!avatar) {
set.status = 400;
return { error: "No file uploaded" };
}
const fileName = `${Date.now()}_${avatar.name}`;
await Bun.write(`public/uploads/${fileName}`, avatar.buffer);
await db.update(users).set({ avatar: `/uploads/${fileName}` }).where(eq(users.id, session.user.id));
set.redirect = "/profile";
});- 创建
src/views/users/profile.eta文件:
<% layout('../layouts/main.eta', { title: 'User Profile' }) %>
<h1>User Profile</h1>
<% if (it.user.avatar) { %>
<img src="<%= it.user.avatar %>" alt="User Avatar" style="width: 100px; height: 100px; object-fit: cover;">
<% } %>
<form action="/profile/avatar" method="POST" enctype="multipart/form-data">
<input type="file" name="avatar" accept="image/*">
<button type="submit">Upload Avatar</button>
</form>
<h2>Username: <%= it.user.username %></h2>
<p>Email: <%= it.user.email %></p>第二十一部分:文章归档功能
- 更新
src/routes/posts.ts文件,添加归档路由:
import { sql } from "drizzle-orm";
export const postRoutes = new Elysia()
// ... 保留现有的路由
.get("/archive", async ({ render }) => {
const archives = await db
.select({
year: sql<number>`strftime('%Y', ${posts.createdAt})`.as('year'),
month: sql<number>`strftime('%m', ${posts.createdAt})`.as('month'),
count: sql<number>`count(*)`.as('count'),
})
.from(posts)
.groupBy(sql`year, month`)
.orderBy(sql`year DESC, month DESC`)
.all();
return render("posts/archive", { archives });
})
.get("/archive/:year/:month", async ({ params, render }) => {
const { year, month } = params;
const archivePosts = await db
.select({
id: posts.id,
title: posts.title,
createdAt: posts.createdAt,
})
.from(posts)
.where(sql`strftime('%Y', ${posts.createdAt}) = ${year} AND strftime('%m', ${posts.createdAt}) = ${month}`)
.orderBy(posts.createdAt)
.all();
return render("posts/archive-month", { year, month, posts: archivePosts });
});- 创建
src/views/posts/archive.eta文件:
<% layout('../layouts/main.eta', { title: 'Blog Archive' }) %>
<h1>Blog Archive</h1>
<ul>
<% it.archives.forEach(archive => { %>
<li>
<a href="/archive/<%= archive.year %>/<%= archive.month.padStart(2, '0') %>">
<%= new Date(archive.year, archive.month - 1).toLocaleString('default', { month: 'long', year: 'numeric' }) %>
(<%= archive.count %> posts)
</a>
</li>
<% }) %>
</ul>- 创建
src/views/posts/archive-month.eta文件:
<% layout('../layouts/main.eta', { title: `Archive for ${it.month}/${it.year}` }) %>
<h1>Archive for <%= new Date(it.year, it.month - 1).toLocaleString('default', { month: 'long', year: 'numeric' }) %></h1>
<ul>
<% it.posts.forEach(post => { %>
<li>
<a href="/posts/<%= post.id %>"><%= post.title %></a>
<small>(<%= new Date(post.createdAt).toLocaleDateString() %>)</small>
</li>
<% }) %>
</ul>
<a href="/archive">Back to Archive</a>第二十二部分:评论系统
- 更新
src/db/schema.ts文件,添加评论表:
export const comments = sqliteTable("comments", {
id: integer("id").primaryKey(),
postId: integer("post_id").notNull().references(() => posts.id),
userId: integer("user_id").notNull().references(() => users.id),
content: text("content").notNull(),
createdAt: integer("created_at", { mode: "timestamp" }).notNull(),
});- 创建一个新的迁移文件
src/db/migrations/0006_add_comments.ts:
export async function up(db) {
await db.run(sql`
CREATE TABLE comments (
id INTEGER PRIMARY KEY,
post_id INTEGER NOT NULL,
user_id INTEGER NOT NULL,
content TEXT NOT NULL,
created_at TIMESTAMP NOT NULL,
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 comments;`);
}- 运行迁移:
bun run migrate- 更新
src/routes/posts.ts文件,添加评论相关的路由:
import { comments } from "../db/schema";
export const postRoutes = new Elysia()
// ... 保留现有的路由
.get("/posts/:id", async ({ params, render, session }) => {
const post = await 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))
.where(eq(posts.id, parseInt(params.id)))
.get();
const postComments = await db
.select({
id: comments.id,
content: comments.content,
createdAt: comments.createdAt,
author: users.username,
})
.from(comments)
.leftJoin(users, eq(comments.userId, users.id))
.where(eq(comments.postId, parseInt(params.id)))
.orderBy(comments.createdAt)
.all();
return render("posts/show", { post, comments: postComments, user: session.user });
})
.post("/posts/:id/comments", async ({ params, body, session, set }) => {
if (!session.user) {
set.status = 401;
return { error: "Unauthorized" };
}
const { content } = body;
await db.insert(comments).values({
postId: parseInt(params.id),
userId: session.user.id,
content,
createdAt: new Date(),
});
set.redirect = `/posts/${params.id}`;
});- 更新
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>
<div><%= it.post.content %></div>
<h2>Comments</h2>
<% if (it.comments.length > 0) { %>
<ul>
<% it.comments.forEach(comment => { %>
<li>
<p><%= comment.content %></p>
<small>By <%= comment.author %> on <%= new Date(comment.createdAt).toLocaleString() %></small>
</li>
<% }) %>
</ul>
<% } else { %>
<p>No comments yet.</p>
<% } %>
<% if (it.user) { %>
<h3>Add a comment</h3>
<form action="/posts/<%= it.post.id %>/comments" method="POST">
<textarea name="content" required></textarea>
<button type="submit">Submit Comment</button>
</form>
<% } else { %>
<p><a href="/login">Log in to comment</a></p>
<% } %>- 更新
public/styles/main.css文件,添加一些评论相关的样式:
/* ... 前面的样式保持不变 */
.comments-list {
list-style-type: none;
padding: 0;
}
.comment {
border: 1px solid #ddd;
padding: 10px;
margin-bottom: 10px;
}
.comment-meta {
font-size: 0.8em;
color: #666;
}
.comment-form textarea {
width: 100%;
height: 100px;
margin-bottom: 10px;
}这些更改实现了三个新功能:
- 用户头像上传:用户现在可以上传和更新他们的头像。
- 文章归档功能:提供了一个按年月组织的文章归档页面。
- 评论系统:用户可以在文章下方查看和发表评论。
主要改进包括:
- 增强了用户个人资料的个性化。
- 提供了更好的内容组织和导航方式。
- 增加了用户互动和参与度。
这些功能大大提升了博客的功能性和用户体验。用户现在可以更个性化地展示自己,更容易找到过去的文章,并且可以与其他读者进行交流。
接下来,我们可以考虑添加以下功能来进一步完善博客系统:
- 相关文章推荐
- 社交媒体分享按钮
- 文章阅读计数器
- 用户权限管理(管理员/作者/读者)
你希望继续哪个方向,或者有其他想法吗?