post.service.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240
  1. import { DataSource, Repository, In } from 'typeorm';
  2. import debug from 'debug';
  3. import { z } from '@hono/zod-openapi';
  4. import { PostEntity as Post } from './post.entity';
  5. import { FollowEntity } from '../follows/follow.entity';
  6. import { UserEntity as User } from '../users/user.entity';
  7. import { UserService } from '../users/user.service';
  8. import type { CreatePostDto, UpdatePostDto } from './post.entity';
  9. import { HTTPException } from 'hono/http-exception';
  10. import { DeleteStatus } from '@/share/types';
  11. const logger = debug('backend:posts:service');
  12. export class PostService {
  13. private postRepository: Repository<Post>;
  14. private userRepository: Repository<User>;
  15. private userService: UserService;
  16. constructor(dataSource: DataSource) {
  17. this.postRepository = dataSource.getRepository(Post);
  18. this.userRepository = dataSource.getRepository(User);
  19. this.userService = new UserService(dataSource);
  20. }
  21. /**
  22. * 创建帖子
  23. */
  24. async createPost(userId: number, data: z.infer<typeof CreatePostDto>): Promise<Post> {
  25. // 验证用户是否存在
  26. const user = await this.userRepository.findOneBy({ id: userId });
  27. if (!user) {
  28. throw new Error('用户不存在');
  29. }
  30. const post = this.postRepository.create({
  31. userId,
  32. ...data,
  33. likesCount: 0,
  34. commentsCount: 0
  35. });
  36. const savedPost = await this.postRepository.save(post);
  37. return savedPost as unknown as Post;
  38. }
  39. /**
  40. * 获取帖子详情
  41. */
  42. async getPostById(id: number): Promise<Post | null> {
  43. const post = await this.postRepository.findOne({
  44. where: { id, isDeleted: DeleteStatus.NOT_DELETED },
  45. relations: ['user']
  46. });
  47. return post;
  48. }
  49. /**
  50. * 更新帖子
  51. */
  52. async updatePost(postId: number, userId: number, data: z.infer<typeof UpdatePostDto>): Promise<Post | null> {
  53. // 验证帖子是否存在且属于当前用户
  54. const post = await this.postRepository.findOneBy({
  55. id: postId,
  56. userId,
  57. isDeleted: DeleteStatus.NOT_DELETED
  58. });
  59. if (!post) {
  60. throw new Error('帖子不存在或没有权限');
  61. }
  62. Object.assign(post, data);
  63. return this.postRepository.save(post);
  64. }
  65. /**
  66. * 删除帖子(软删除)
  67. */
  68. async deletePost(postId: number, userId: number): Promise<boolean> {
  69. const result = await this.postRepository.update(
  70. { id: postId, userId, isDeleted: DeleteStatus.NOT_DELETED },
  71. { isDeleted: DeleteStatus.DELETED }
  72. );
  73. return (result.affected || 0) > 0;
  74. }
  75. /**
  76. * 获取用户帖子列表
  77. */
  78. async getUserPosts(userId: number, page: number = 1, pageSize: number = 10): Promise<[Post[], number]> {
  79. const skip = (page - 1) * pageSize;
  80. const [posts, total] = await this.postRepository.findAndCount({
  81. where: { userId, isDeleted: DeleteStatus.NOT_DELETED },
  82. relations: ['user'],
  83. skip,
  84. take: pageSize,
  85. order: { createdAt: 'DESC' }
  86. });
  87. return [posts, total];
  88. }
  89. /**
  90. * 获取关注用户的帖子流
  91. */
  92. async getFollowingFeed(followerId: number, page: number = 1, pageSize: number = 10): Promise<[Post[], number]> {
  93. const skip = (page - 1) * pageSize;
  94. // 直接查询关注用户的帖子,假设follow表存在且有关联
  95. logger('Building following feed query for followerId: %d, page: %d, pageSize: %d', followerId, page, pageSize);
  96. // 创建主查询用于获取分页数据
  97. const query = this.postRepository
  98. .createQueryBuilder('post')
  99. .leftJoin('follows', 'f', 'f.following_id = post.user_id')
  100. .where('f.follower_id = :followerId', { followerId })
  101. .andWhere('post.is_deleted = :notDeleted', { notDeleted: DeleteStatus.NOT_DELETED })
  102. .leftJoinAndSelect('post.user', 'user')
  103. .orderBy('post.created_at', 'DESC')
  104. .skip(skip)
  105. .take(pageSize);
  106. // 创建计数查询
  107. const countQuery = this.postRepository
  108. .createQueryBuilder('post')
  109. .leftJoin('follows', 'f', 'f.following_id = post.user_id')
  110. .where('f.follower_id = :followerId', { followerId })
  111. .andWhere('post.is_deleted = :notDeleted', { notDeleted: DeleteStatus.NOT_DELETED });
  112. // 记录生成的SQL和参数
  113. const sql = query.getSql();
  114. const parameters = query.getParameters();
  115. logger('Following feed query: %s', sql);
  116. logger('Query parameters: %o', parameters);
  117. const [posts, total] = await Promise.all([
  118. query.getRawMany(),
  119. countQuery.getCount()
  120. ]);
  121. logger('Following feed results: %d posts, total: %d', posts.length, total);
  122. return [posts, total];
  123. }
  124. /**
  125. * 点赞帖子
  126. */
  127. async likePost(postId: number): Promise<Post | null> {
  128. const post = await this.postRepository.findOneBy({
  129. id: postId,
  130. isDeleted: DeleteStatus.NOT_DELETED
  131. });
  132. if (!post) {
  133. throw new Error('帖子不存在');
  134. }
  135. post.likesCount += 1;
  136. return this.postRepository.save(post);
  137. }
  138. /**
  139. * 取消点赞帖子
  140. */
  141. async unlikePost(postId: number): Promise<Post | null> {
  142. const post = await this.postRepository.findOneBy({
  143. id: postId,
  144. isDeleted: DeleteStatus.NOT_DELETED
  145. });
  146. if (!post) {
  147. throw new Error('帖子不存在');
  148. }
  149. if (post.likesCount > 0) {
  150. post.likesCount -= 1;
  151. return this.postRepository.save(post);
  152. }
  153. return post;
  154. }
  155. /**
  156. * 根据用户ID列表获取帖子
  157. */
  158. async getPostsByUserIds(userIds: number[], page: number = 1, pageSize: number = 10): Promise<[Post[], number]> {
  159. const skip = (page - 1) * pageSize;
  160. const [posts, total] = await this.postRepository.findAndCount({
  161. where: {
  162. userId: In(userIds),
  163. isDeleted: DeleteStatus.NOT_DELETED
  164. },
  165. relations: ['user'],
  166. skip,
  167. take: pageSize,
  168. order: { createdAt: 'DESC' }
  169. });
  170. return [posts, total];
  171. }
  172. /**
  173. * 获取推荐用户的帖子
  174. */
  175. async getRecommendedPosts(currentUserId: number, page: number = 1, pageSize: number = 10): Promise<[Post[], number]> {
  176. // 获取推荐用户
  177. const recommendedUsers = await this.userService.getRecommendedUsers(currentUserId);
  178. const recommendedUserIds = recommendedUsers.map(u => u.id);
  179. if (recommendedUserIds.length > 0) {
  180. return this.getPostsByUserIds(recommendedUserIds, page, pageSize);
  181. }
  182. return [[], 0];
  183. }
  184. /**
  185. * 获取热门帖子(按点赞数和创建时间排序)
  186. */
  187. async getPopularPosts(page: number = 1, pageSize: number = 10): Promise<[Post[], number]> {
  188. const skip = (page - 1) * pageSize;
  189. const [posts, total] = await this.postRepository.findAndCount({
  190. where: { isDeleted: DeleteStatus.NOT_DELETED },
  191. relations: ['user'],
  192. skip,
  193. take: pageSize,
  194. order: {
  195. likesCount: 'DESC',
  196. createdAt: 'DESC'
  197. }
  198. });
  199. return [posts, total];
  200. }
  201. }