From cd3d4fb31a56c1bfa44417de326673b86fbe6d9c Mon Sep 17 00:00:00 2001 From: niamu01 Date: Thu, 14 Dec 2023 15:43:14 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20:sparkles:=20FollowerList=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - #405 feat: :sparkles: MakeFollow: 프론트 테스트용 임시 함수 추가 - #405 refactor: :recycle: 변수명 수정 및 함수 분리 login -> target, FollowListByMe -> FollowList, FollowUserList -> FollowListWithCount, followUser->followLser, cursusUserService에 유저 아이디 찾는 함수 추가, 중복되는 함수 분리 - #405 refactor: :recycle: checkFollowingStatus 함수 활용하도록 변경 - #405 --- app/src/api/cursusUser/cursusUser.service.ts | 43 ++++ app/src/follow/follow.module.ts | 1 - app/src/follow/follow.resolve.ts | 53 +++-- app/src/follow/follow.service.ts | 227 ++++++++++++------- app/src/follow/model/follow.model.ts | 10 +- app/src/schema.gql | 19 +- 6 files changed, 244 insertions(+), 109 deletions(-) diff --git a/app/src/api/cursusUser/cursusUser.service.ts b/app/src/api/cursusUser/cursusUser.service.ts index a7cf18c2..d6f7b94d 100644 --- a/app/src/api/cursusUser/cursusUser.service.ts +++ b/app/src/api/cursusUser/cursusUser.service.ts @@ -77,6 +77,18 @@ export class CursusUserService { return this.cursusUserModel.aggregate(); } + async getuserIdByLogin(login: string): Promise { + const userId = await this.findOneAndLean({ + filter: { 'user.login': login }, + }).then((user) => user?.user.id); + + if (!userId) { + return null; + } + + return userId; + } + async findAllAndLean( queryArgs?: QueryArgs, ): Promise { @@ -116,6 +128,37 @@ export class CursusUserService { })); } + async findOneUserPreviewAndLean( + queryArgs?: Omit, 'select'>, + ): Promise { + const cursusUsers: { + user: { + id: number; + login: string; + image: { + link?: string; + }; + }; + } | null = await this.findOneAndLean({ + ...queryArgs, + select: { + 'user.id': 1, + 'user.login': 1, + 'user.image.link': 1, + }, + }); + + if (!cursusUsers) { + return null; + } + + return { + id: cursusUsers.user.id, + login: cursusUsers.user.login, + imgUrl: cursusUsers.user.image.link, + }; + } + async userFullProfile( filter?: FilterQuery, ): Promise { diff --git a/app/src/follow/follow.module.ts b/app/src/follow/follow.module.ts index 6534f310..c7ed7746 100644 --- a/app/src/follow/follow.module.ts +++ b/app/src/follow/follow.module.ts @@ -11,7 +11,6 @@ import { FollowService } from './follow.service'; CursusUserModule, ], providers: [FollowResolver, FollowService], - //exports: [FollowService], }) // eslint-disable-next-line export class FollowModule {} diff --git a/app/src/follow/follow.resolve.ts b/app/src/follow/follow.resolve.ts index 8cf58c10..e0f1c575 100644 --- a/app/src/follow/follow.resolve.ts +++ b/app/src/follow/follow.resolve.ts @@ -4,7 +4,7 @@ import { MyUserId } from 'src/auth/myContext'; import { StatAuthGuard } from 'src/auth/statAuthGuard'; import { HttpExceptionFilter } from 'src/http-exception.filter'; import { FollowService } from './follow.service'; -import { FollowResult, FollowUserList } from './model/follow.model'; +import { FollowListWithCount, FollowResult } from './model/follow.model'; @UseFilters(HttpExceptionFilter) @UseGuards(StatAuthGuard) @@ -12,47 +12,64 @@ import { FollowResult, FollowUserList } from './model/follow.model'; export class FollowResolver { constructor(private readonly followService: FollowService) {} + @Mutation((_returns) => FollowResult, { + description: '프론트 테스트용 임시 함수', + }) + async MakeFollow( + @Args('to') to: string, + @Args('from') from: string, + @Args('type') type: 'follow' | 'unfollow', + ): Promise { + return await this.followService.MakeFollowUser(to, from, type); + } + @Mutation((_returns) => FollowResult) async followUser( @MyUserId() userId: number, - @Args('login') login: string, + @Args('target') target: string, ): Promise { - return await this.followService.followUser(userId, login); + return await this.followService.followUser(userId, target); } @Mutation((_returns) => FollowResult) - async unFollowUser( + async unfollowUser( @MyUserId() userId: number, - @Args('login') login: string, + @Args('target') target: string, ): Promise { - return await this.followService.unFollowUser(userId, login); + return await this.followService.unfollowUser(userId, target); } - @Mutation((_returns) => FollowUserList) + @Mutation((_returns) => FollowListWithCount) async getFollowerList( @MyUserId() userId: number, - @Args('login') login: string, - ): Promise { - const followUser = await this.followService.getFollowerList(login); - const count = await this.followService.getFollowerCount(login); + @Args('target') target: string, + ): Promise { + const followerList = await this.followService.getFollowerList( + userId, + target, + ); + const count = await this.followService.getFollowerCount(target); return { count, - followUser, + followList: followerList, }; } - @Mutation((_returns) => FollowUserList) + @Mutation((_returns) => FollowListWithCount) async getFollowingList( @MyUserId() userId: number, - @Args('login') login: string, - ): Promise { - const followUser = await this.followService.getFollowingList(userId, login); - const count = await this.followService.getFollowingCount(login); + @Args('target') target: string, + ): Promise { + const followingList = await this.followService.getFollowingList( + userId, + target, + ); + const count = await this.followService.getFollowingCount(target); return { count, - followUser, + followList: followingList, }; } } diff --git a/app/src/follow/follow.service.ts b/app/src/follow/follow.service.ts index 1a40aeed..098c0491 100644 --- a/app/src/follow/follow.service.ts +++ b/app/src/follow/follow.service.ts @@ -1,10 +1,14 @@ -import { Injectable } from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectModel } from '@nestjs/mongoose'; import { Model } from 'mongoose'; import { UserPreview } from 'src/common/models/common.user.model'; +import { + QueryArgs, + findAllAndLean, +} from 'src/database/mongoose/database.mongoose.query'; import { CursusUserService } from '../api/cursusUser/cursusUser.service'; import { follow } from './db/follow.database.schema'; -import { FollowListByMe, FollowResult } from './model/follow.model'; +import { FollowList, FollowResult } from './model/follow.model'; @Injectable() export class FollowService { @@ -14,29 +18,73 @@ export class FollowService { private readonly cursusUserService: CursusUserService, ) {} - // input으로 들어온 유저를 팔로우 함 - async followUser( - followerId: number, - followingLogin: string, + async findAllAndLean(queryArgs?: QueryArgs): Promise { + return await findAllAndLean(this.followModel, queryArgs); + } + + // 프론트 테스트용 임시 함수 + async MakeFollowUser( + to: string, + from: string, + type: 'follow' | 'unfollow', ): Promise { - const follower = await this.cursusUserService + const toId = await this.cursusUserService .findOneAndLean({ - filter: { 'user.id': followerId }, + filter: { 'user.login': to }, }) - .then((follower) => follower?.user.id); + .then((following) => following?.user.id); - const following = await this.cursusUserService + const fromId = await this.cursusUserService .findOneAndLean({ - filter: { 'user.login': followingLogin }, + filter: { 'user.login': from }, }) .then((following) => following?.user.id); - if (!follower || !following || follower === following) { + if (type === 'follow') { + await this.followModel + .create({ userId: fromId, followId: toId }) + .then((result) => result.toObject()); + + return { + message: 'OK', + userId: fromId!, + followId: toId!, + }; + } else if (type === 'unfollow') { + await this.followModel + .deleteOne({ + userId: fromId, + followId: toId, + }) + .then((result) => result.deletedCount); + + return { + message: 'OK', + userId: fromId!, + followId: toId!, + }; + } + return { message: 'fail' }; + } + + async followUser( + userId: number, + target: string, + ): Promise { + const following = await this.cursusUserService.getuserIdByLogin(target); + + const alreadyFollow = await this.followModel.find({ + userId: userId, + followId: following, + }); + + // !following은 throw notfound 이긴 함 + if (!following || userId === following || alreadyFollow.length) { return { message: 'fail' }; } const result = await this.followModel - .create({ userId: follower, followId: following }) + .create({ userId: userId, followId: following }) .then((result) => result.toObject()); return { @@ -46,31 +94,20 @@ export class FollowService { }; } - // input으로 들어온 유저를 언팔로우 함 // todo: unfollow 성공도 같은걸 (상태) 반환해서 이름 다시 지어야함 - async unFollowUser( - followerId: number, - followingLogin: string, + async unfollowUser( + userId: number, + target: string, ): Promise { - const follower = await this.cursusUserService - .findOneAndLean({ - filter: { 'user.id': followerId }, - }) - .then((follower) => follower?.user.id); + const following = await this.cursusUserService.getuserIdByLogin(target); - const following = await this.cursusUserService - .findOneAndLean({ - filter: { 'user.login': followingLogin }, - }) - .then((following) => following?.user.id); - - if (!follower || !following || follower === following) { + if (!following || userId === following) { return { message: 'fail' }; } const deletedCount = await this.followModel .deleteOne({ - userId: follower, + userId: userId, followId: following, }) .then((result) => result.deletedCount); @@ -78,74 +115,90 @@ export class FollowService { if (deletedCount === 1) { return { message: 'OK', - userId: follower, + userId: userId, followId: following, }; - } else { - return { - message: 'fail', - }; } + + return { message: 'fail' }; } - // input 유저 <- 팔로워 목록을 찾아옴 // getFollowerList("yeju") -> yeju를 팔로우 하는 사람들 - async getFollowerList(login: string): Promise { - //login이 A<-B 의 A위치에 있는거 find 후 B들의 UserPreview로 합쳐서 반환 + async getFollowerList(userId: number, target: string): Promise { + //target의 id + const targetId = await this.cursusUserService.getuserIdByLogin(target); + + if (!targetId) { + throw new NotFoundException(); + } + + //target을 팔로우 하는 사람들 + const follower: follow[] = await this.findAllAndLean({ + filter: { followId: targetId }, + }); + + const followerUserPreview: UserPreview[] = await Promise.all( + follower.map(async (follower) => { + //target을 팔로우 하는 사람의 preview + const userPreview = + await this.cursusUserService.findOneUserPreviewAndLean({ + filter: { 'user.id': follower.userId }, + }); + + if (!userPreview) { + throw new NotFoundException(); + } - return []; + return userPreview; + }), + ); + + const followerList = await this.checkFollowingStatus( + userId, + followerUserPreview, + ); + + return followerList; } - // input 유저 -> 팔로잉 목록을 찾아옴 // getFollowingList("yeju") -> yeju(target)가 팔로우 하는 사람들 async getFollowingList( - me: number, + userId: number, target: string, - ): Promise { - //target의 id - const targetId = await this.cursusUserService - .findOneAndLean({ - filter: { 'user.login': target }, - }) - .then((user) => user?.user.id); + ): Promise { + const targetId = await this.cursusUserService.getuserIdByLogin(target); + + if (!targetId) { + throw new NotFoundException(); + } //target이 팔로우 하는 사람들 - const following: follow[] = await this.followModel.find({ - userId: targetId, + const following: follow[] = await this.findAllAndLean({ + filter: { userId: targetId }, }); - const targetFollowingUserPreview: Promise[] = following.map( - async (following) => { - //target이 팔로우 하는 사람의 preview - const user = await this.cursusUserService - .findAllUserPreviewAndLean({ + const followingUserPreview: UserPreview[] = await Promise.all( + following.map(async (following) => { + //target을 팔로우 하는 사람의 preview + const userPreview = + await this.cursusUserService.findOneUserPreviewAndLean({ filter: { 'user.id': following.followId }, - }) - //findOne으로 바꾸기 - .then((a) => a[0]); - - return user; - }, - ); - - const followListByMeArray: Promise[] = - targetFollowingUserPreview.map(async (targetFollowingUser) => { - const user = await targetFollowingUser; - let follow: boolean = true; + }); - const isFollowed = await this.followModel.find({ - userId: me, - followId: user.id, - }); - - if (!isFollowed) { - follow = false; + if (!userPreview) { + throw new NotFoundException(); } - return { follow, user }; - }); + return userPreview; + }), + ); - return await Promise.all(followListByMeArray); + const followingList = await this.checkFollowingStatus( + userId, + followingUserPreview, + ); + + return followingList; } async getFollowerCount(login: string): Promise { @@ -163,4 +216,24 @@ export class FollowService { return await this.followModel.countDocuments({ userId: id?.user.id }); //login filter } + + async checkFollowingStatus( + userId: number, + userPreview: UserPreview[], + ): Promise { + const followList = userPreview.map(async (user) => { + if (!user) { + throw new NotFoundException(); + } + + const isFollowed = await this.followModel.findOne({ + userId: userId, + followId: user.id, + }); + + return { isFollowing: !!isFollowed, user }; + }); + + return Promise.all(followList); + } } diff --git a/app/src/follow/model/follow.model.ts b/app/src/follow/model/follow.model.ts index 5884ebb5..928f7bc4 100644 --- a/app/src/follow/model/follow.model.ts +++ b/app/src/follow/model/follow.model.ts @@ -2,18 +2,18 @@ import { Field, ObjectType, createUnionType } from '@nestjs/graphql'; import { UserPreview } from 'src/common/models/common.user.model'; @ObjectType() -export class FollowListByMe { +export class FollowList { @Field() - follow: boolean; + isFollowing: boolean; @Field() user: UserPreview; } @ObjectType() -export class FollowUserList { - @Field((_type) => [FollowListByMe]) - followUser: FollowListByMe[]; +export class FollowListWithCount { + @Field((_type) => [FollowList]) + followList: FollowList[]; @Field() count: number; diff --git a/app/src/schema.gql b/app/src/schema.gql index 9b8c4666..41945f97 100644 --- a/app/src/schema.gql +++ b/app/src/schema.gql @@ -59,13 +59,13 @@ type UserRankingIndexPaginated { pageNumber: Int! } -type FollowListByMe { - follow: Boolean! +type FollowList { + isFollowing: Boolean! user: UserPreview! } -type FollowUserList { - followUser: [FollowListByMe!]! +type FollowListWithCount { + followList: [FollowList!]! count: Int! } @@ -646,10 +646,13 @@ type Mutation { refreshToken(refreshToken: String!): LoginSuccess! logout: Int! deleteAccount: Int! - followUser(login: String!): FollowResult! - unFollowUser(login: String!): FollowResult! - getFollowerList(login: String!): FollowUserList! - getFollowingList(login: String!): FollowUserList! + + """프론트 테스트용 임시 함수""" + MakeFollow(to: String!, from: String!, type: String!): FollowResult! + followUser(target: String!): FollowResult! + unfollowUser(target: String!): FollowResult! + getFollowerList(target: String!): FollowListWithCount! + getFollowingList(target: String!): FollowListWithCount! } union LoginResult = LoginSuccess | LoginNotLinked