- 新增 Follow 表和 User.bio 字段 (Prisma schema)
- 创建 follow 模块 (action-service-repository)
- 新增 FollowButton/FollowStats/UserList 组件
- 用户页面显示 bio、粉丝/关注数、关注按钮
- 新增 /users/[username]/followers 和 following 页面
- 添加 en-US/zh-CN i18n 翻译
⚠️ 需要运行: prisma migrate dev --name add_follow_and_bio
143 lines
3.3 KiB
TypeScript
143 lines
3.3 KiB
TypeScript
import { prisma } from "@/lib/db";
|
|
import { createLogger } from "@/lib/logger";
|
|
import type {
|
|
RepoInputCheckFollow,
|
|
RepoInputCreateFollow,
|
|
RepoInputDeleteFollow,
|
|
RepoInputGetFollowers,
|
|
RepoInputGetFollowing,
|
|
RepoOutputFollowerCount,
|
|
RepoOutputFollowUser,
|
|
RepoOutputFollowWithUser,
|
|
RepoOutputFollowingCount,
|
|
RepoOutputIsFollowing,
|
|
} from "./follow-repository-dto";
|
|
|
|
const log = createLogger("follow-repository");
|
|
|
|
export async function repoCreateFollow(
|
|
dto: RepoInputCreateFollow
|
|
): Promise<RepoOutputFollowUser> {
|
|
log.debug("Creating follow", { followerId: dto.followerId, followingId: dto.followingId });
|
|
|
|
const follow = await prisma.follow.create({
|
|
data: {
|
|
followerId: dto.followerId,
|
|
followingId: dto.followingId,
|
|
},
|
|
});
|
|
|
|
log.info("Follow created", { followId: follow.id });
|
|
return follow;
|
|
}
|
|
|
|
export async function repoDeleteFollow(
|
|
dto: RepoInputDeleteFollow
|
|
): Promise<void> {
|
|
log.debug("Deleting follow", { followerId: dto.followerId, followingId: dto.followingId });
|
|
|
|
await prisma.follow.delete({
|
|
where: {
|
|
followerId_followingId: {
|
|
followerId: dto.followerId,
|
|
followingId: dto.followingId,
|
|
},
|
|
},
|
|
});
|
|
|
|
log.info("Follow deleted");
|
|
}
|
|
|
|
export async function repoCheckFollow(
|
|
dto: RepoInputCheckFollow
|
|
): Promise<RepoOutputIsFollowing> {
|
|
const follow = await prisma.follow.findUnique({
|
|
where: {
|
|
followerId_followingId: {
|
|
followerId: dto.followerId,
|
|
followingId: dto.followingId,
|
|
},
|
|
},
|
|
});
|
|
|
|
return !!follow;
|
|
}
|
|
|
|
export async function repoGetFollowersCount(userId: string): Promise<RepoOutputFollowerCount> {
|
|
return prisma.follow.count({
|
|
where: { followingId: userId },
|
|
});
|
|
}
|
|
|
|
export async function repoGetFollowingCount(userId: string): Promise<RepoOutputFollowingCount> {
|
|
return prisma.follow.count({
|
|
where: { followerId: userId },
|
|
});
|
|
}
|
|
|
|
export async function repoGetFollowers(
|
|
dto: RepoInputGetFollowers
|
|
): Promise<RepoOutputFollowWithUser[]> {
|
|
const { userId, page = 1, limit = 20 } = dto;
|
|
const skip = (page - 1) * limit;
|
|
|
|
const follows = await prisma.follow.findMany({
|
|
where: { followingId: userId },
|
|
include: {
|
|
follower: {
|
|
select: {
|
|
id: true,
|
|
username: true,
|
|
displayUsername: true,
|
|
image: true,
|
|
bio: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: { createdAt: "desc" },
|
|
skip,
|
|
take: limit,
|
|
});
|
|
|
|
return follows.map((f) => ({
|
|
id: f.id,
|
|
followerId: f.followerId,
|
|
followingId: f.followingId,
|
|
createdAt: f.createdAt,
|
|
user: f.follower,
|
|
}));
|
|
}
|
|
|
|
export async function repoGetFollowing(
|
|
dto: RepoInputGetFollowing
|
|
): Promise<RepoOutputFollowWithUser[]> {
|
|
const { userId, page = 1, limit = 20 } = dto;
|
|
const skip = (page - 1) * limit;
|
|
|
|
const follows = await prisma.follow.findMany({
|
|
where: { followerId: userId },
|
|
include: {
|
|
following: {
|
|
select: {
|
|
id: true,
|
|
username: true,
|
|
displayUsername: true,
|
|
image: true,
|
|
bio: true,
|
|
},
|
|
},
|
|
},
|
|
orderBy: { createdAt: "desc" },
|
|
skip,
|
|
take: limit,
|
|
});
|
|
|
|
return follows.map((f) => ({
|
|
id: f.id,
|
|
followerId: f.followerId,
|
|
followingId: f.followingId,
|
|
createdAt: f.createdAt,
|
|
user: f.following,
|
|
}));
|
|
}
|