Files
learn-languages/src/modules/follow/follow-repository.ts
goddonebianu 683a4104ec feat: 添加用户关注功能
- 新增 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
2026-03-10 14:58:43 +08:00

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,
}));
}