1. Follow System ์ด๋ก
- ์ธ์คํ๊ทธ๋จ ํ๋ก์ฐ ์์คํ = ์์ฒญ์ด ๋จ๋ฑกํฅ์ธ ํ๋ก์ฐ ์์คํ
- ์ฌ์ฉ์ A์ B๊ฐ ์๋ค๊ณ ๊ฐ์ ํ๋ฉด,
- A๋ B๋ฅผ ํ๋ก์ฐํ ์ ์๊ณ , B๋ A๋ฅผ ํ๋ก์ฐ์ํ ์๋ ์๋ค.
- ๋ฐ๋์ ๊ฒฝ์ฐ๋ ๋ง์ฐฌ๊ฐ์ง.
- ์ฆ, ๋ค์์ ํ๋ก์๋ฅผ ๊ฐ์ง ์๋ ์๊ณ , ๋ค์์ ํ๋ก์ฐ๋ ํ ์ ์๋ค.
- ๋ค์ ๋งํด, Many to Many ๊ด๊ณ์ด๋ค.
- ๊ทธ๋ฐ๋ฐ User๋ฅผ ๋ค๋๋ค ๊ด๊ณ๋ก ํ ๋ ค๋๋ฐ, ๋ ๋ค ์ ์ ํ ์ด๋ธ์ ์ฌ์ฉํ๊ณ ์๋ค.
- ์ด๋ด ๋, ํ๋ก์ฐ ID์ ํ๋ก์ ID๋ฅผ ๊ฐ์ง User Follow ํ ์ด๋ธ์ด๋ ์ค๊ฐ ํ ์ด๋ธ์ ๋ง๋ค๋ฉด ๋๋ค.
๋ค์ ํ ์์๋ฅผ ๋ณด๋ฉด,
| follower_id | followee_id |
|---|---|
| 1 | 2 |
| 2 | 1 |
| 3 | 4 |
| 2 | 4 |
- 1๋ฒ ์ฌ์ฉ์๊ฐ 2๋ฒ์ ํ๋ก์ฐํ๊ณ ์๋ค.
- 2๋ฒ ์ฌ์ฉ์๊ฐ 1๋ฒ์ ํ๋ก์ฐํ๊ณ ์๋ค. (์ด ๊ฒฝ์ฐ 1๋ฒ๊ณผ 2๋ฒ์ ์๋ก ๋งํ๋ก์ฐํ ์ํ)
- 3๋ฒ ์ฌ์ฉ์๊ฐ 4๋ฒ์ ํ๋ก์ฐํ๊ณ ์๋ค.
- 4๋ฒ ์ฌ์ฉ์๋ ์๋ฌด๋ ํ๋ก์ฐํ๊ณ ์์ง์๋ค. (ํ๋ก์๊ฐ 3๋ฒ, 2๋ฒ ํด์ 2๋ช ์๋ค)
2. Followers & Followees ํ๋กํผํฐ ์์ฑ
user ์ํฐํฐ์ ํ๋ก์์ ํ๋ก์ ์์ฑ์ ์ถ๊ฐํ๋ค.
1@Entity()2export class UsersModel extends BaseModel {3// ์๋ต45// ๋ด๊ฐ ํ๋ก์ฐ ํ๊ณ ์๋ ์ฌ๋๋ค6@ManyToMany(() => UsersModel, user => user.followees)7@JoinTable()8followers: UsersModel[]910// ๋๋ฅผ ํ๋ก์ฐ ํ๊ณ ์๋ ์ฌ๋๋ค11@ManyToMany(() => UsersModel, user => user.followers)12followees: UsersModel[]13}
3. Follow ์์คํ ๋ก์ง ์์ฑ ๋ฐ ํ ์คํธ
user ์๋น์ค์ ํ๋ก์ฐ ์์ฒญ, ํ๋ก์ ์กฐํ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ค.
1// **** 4) ํ๋ก์ฐ ์์ฒญ2async followUser(followerId: number, followeeId: number) {3const user = await this.userRepository.findOne({4where: {5id: followerId,6},7relations: {8followees: true,9},10})1112if (!user) {13throw new BadRequestException('์กด์ฌํ์ง ์๋ ํ๋ก์์ ๋๋ค.')14}1516await this.userRepository.save({17...user,18followees: [...user.followees, { id: followeeId }],19})2021return true22}2324// **** 5) ํ๋ก์๋ค ์กฐํ25async getFollowers(userId: number): Promise<UsersModel[]> {26const user = await this.userRepository.findOne({27where: { id: userId },28relations: {29followers: true,30},31})3233return user.followers34}
user ์ปจํธ๋กค๋ฌ์ ํ๋ก์ฐ ์๋ํฌ์ธํธ๋ฅผ ์ถ๊ฐํ๋ค.
1@Get('follow/me')2async getFollow(3@User() user: UsersModel, //4) {5return this.usersService.getFollowers(user.id)6}78@Post('follow/:id')9async postFollow(10@User() user: UsersModel, //11@Param('id', ParseIntPipe) followeeId: number,12) {13await this.usersService.followUser(user.id, followeeId)14return true15}
4. Follow Table ์ง์ ์์ฑ
์๋๋ฐฉ์ด okํ๋์ง, ํ๋ก์ฐ ์์ฒญํ์ ๋, ํ๊ฐ๋ฅผ ๋ด๊ฑฐ๋ ์ธ์ ํ๋ก์ฐ ํ๋์ง์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ๊ณ ์ถ์ ๋๊ฐ ์๋ค.
src/users/entities/user-follow.entity.ts ํ์ผ์ ๋ง๋ ๋ค.
1import { BaseModel } from 'src/common/entity/base.entity'2import { UsersModel } from './users.entity'3import { Column, Entity, ManyToOne } from 'typeorm'45@Entity()6export class UserFollowersModel extends BaseModel {7@ManyToOne(() => UsersModel, user => user.followers)8follower: UsersModel910@ManyToOne(() => UsersModel, user => user.followees)11followee: UsersModel1213@Column({ default: false })14isConfirmed: boolean15}
users/entity/users.entity.ts ํ์ผ์ ํ๋ก์ฐ ๊ด๊ณ๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ค.
1// ๋ด๊ฐ ํ๋ก์ฐ ํ๊ณ ์๋ ์ฌ๋๋ค2@OneToMany(() => UserFollowersModel, ufm => ufm.follower)3followers: UsersModel[]45// ๋๋ฅผ ํ๋ก์ฐ ํ๊ณ ์๋ ์ฌ๋๋ค6@OneToMany(() => UserFollowersModel, ufm => ufm.followee)7followees: UsersModel[]
app ๋ชจ๋์ UserFollowModel์ ์ถ๊ฐํ๋ค.
1// ์๋ต2TypeOrmModule.forRoot({3// ์๋ต4// entitiesํด๋์ ์์ฑํ PostsModel ๊ฐ์ ธ์ค๊ธฐ5entities: [6PostsModel, //7UsersModel,8ImageModel,9ChatsModel,10MessagesModel,11CommentsModel,12UserFollowersModel,13],14synchronize: true,15}),
5. Custom Table์ ๋ง์ถฐ์ ๋ก์ง ๋ณ๊ฒฝ
users/entity/users.entity.ts ํ์ผ์ ํ๋ก์ฐ ๊ด๊ณ๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ค.
1// ๋ด๊ฐ ํ๋ก์ฐ ํ๊ณ ์๋ ์ฌ๋๋ค2@OneToMany(() => UserFollowersModel, ufm => ufm.follower)3followers: UserFollowersModel[]45// ๋๋ฅผ ํ๋ก์ฐ ํ๊ณ ์๋ ์ฌ๋๋ค6@OneToMany(() => UserFollowersModel, ufm => ufm.followee)7followees: UserFollowersModel[]
users ์๋น์ค์ ๊ธฐ๋ฅ์ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ค.
1@Injectable()2export class UsersService {3constructor(4@InjectRepository(UsersModel)5private readonly userRepository: Repository<UsersModel>,6@InjectRepository(UserFollowersModel)7private readonly userFollowersRepository: Repository<UserFollowersModel>,8) {}910// ์๋ต1112// **** 4) ํ๋ก์ฐ ์์ฒญ13async followUser(followerId: number, followeeId: number) {14return await this.userFollowersRepository.save({15follower: {16id: followerId,17},18followee: {19id: followeeId,20},21})22}2324// **** 5) ํ๋ก์๋ค ์กฐํ25async getFollowers(userId: number): Promise<UsersModel[]> {26const result = await this.userFollowersRepository.find({27where: {28followee: {29id: userId,30},31},32relations: {33follower: true,34},35})3637return result.map(userFollow => userFollow.follower)38}39}
users ๋ชจ๋์ UserFollowersModel์ importํ๋ค.
1@Module({2// ์ด ๋ชจ๋ ์์์ UsersModel์ ์ด๋์๋ ์ฌ์ฉ ๊ฐ๋ฅ3imports: [4TypeOrmModule.forFeature([5UsersModel, //6UserFollowersModel,7]),8],9exports: [UsersService],10controllers: [UsersController],11providers: [UsersService],12})13export class UsersModule {}
6. Confirm Follow ๋ก์ง
users ์ปจํธ๋กค๋ฌ ํ๋ก์ฐ ์์ฒญ ์น์ธ ์๋ํฌ์ธํธ๋ฅผ ์ถ๊ฐํ๋ค.
1// **** ๋ด ํ๋ก์๋ค ์กฐํ2@Get('follow/me')3async getFollow(4@User() user: UsersModel, //5@Query('includeNotConfirmed', new DefaultValuePipe(false), ParseBoolPipe) includeNotConfirmed: boolean,6) {7return this.usersService.getFollowers(user.id, includeNotConfirmed)8}910// **** ํ๋ก์ฐ ์์ฒญ ์น์ธ11@Patch('follow/:id/confirm')12async patchFollowConfirm(13@User('id') followeeId: number, //14@Param('id', ParseIntPipe) followerId: number,15) {16await this.usersService.confirmFollow(followerId, followeeId)17return true18}
users ์๋น์ค์ ํ๋ก์ฐ ์์ฒญ ์น์ธ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ค.
1// **** 5) ํ๋ก์๋ค ์กฐํ2async getFollowers(userId: number, includeNotConfirmed: boolean) {3const where = {4followee: { id: userId },5}67if (!includeNotConfirmed) {8where['isConfirmed'] = true9}1011const result = await this.userFollowersRepository.find({12where,13relations: {14follower: true,15followee: true,16},17})1819return result.map(el => ({20id: el.follower.id,21nickname: el.follower.nickname,22email: el.follower.email,23isConfirmed: el.isConfirmed,24}))25}2627// **** ํ๋ก์ฐ ์์ฒญ ์น์ธ28async confirmFollow(followerId: number, followeeId: number) {29const existing = await this.userFollowersRepository.findOne({30where: {31follower: { id: followerId },32followee: { id: followeeId },33},34relations: {35follower: true,36followee: true,37},38})3940if (!existing) {41throw new BadRequestException('์กด์ฌํ์ง ์๋ ํ๋ก์ฐ ์์ฒญ์ ๋๋ค!')42}4344await this.userFollowersRepository.save({45...existing,46isConfirmed: true,47})4849return true50}
7. ํ๋ก์ฐ ์ทจ์ ์์ฒญ
users ์ปจํธ๋กค๋ฌ ํ๋ก์ฐ ์์ฒญ ์ทจ์ ์๋ํฌ์ธํธ๋ฅผ ์ถ๊ฐํ๋ค.
1// **** ํ๋ก์ฐ ์์ฒญ ์ญ์ 2@Delete('follow/:id')3async deleteFollow(4@User() user: UsersModel, //5@Param('id', ParseIntPipe) followeeId: number,6) {7await this.usersService.deleteFollow(user.id, followeeId)8return true9}
users ์๋น์ค์ ํ๋ก์ฐ ์์ฒญ ์ทจ์ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ค.
1// **** ํ๋ก์ฐ ์์ฒญ ์ญ์ 2async deleteFollow(followerId: number, followeeId: number) {3await this.userFollowersRepository.delete({4follower: { id: followerId },5followee: { id: followeeId },6})78return true9}