์ธ์คํ๊ทธ๋จ ๊ธฐ๋ฐ์ ํ๋ก์ฐ ์์คํ ์ ์ ์ฉํ๊ฒ ์ต๋๋ค.
Mnay To Many ๊ด๊ณ์์๋ ์ค๊ฐ ํ ์ด๋ธ์ ๋ง๋ค์ด์ผ ํฉ๋๋ค. ํ์ง๋ง Follow์ ๊ฒฝ์ฐ, User User ํํ๊ฐ ๋ฉ๋๋ค. ๋ฐ๋ผ์ ๋ค์๊ณผ ๊ฐ์ด ๋ง๋ค์ด์ผ ํฉ๋๋ค.
User ํ ์ด๋ธ์ด Follwer์ Follwee ์ญํ ์ ๋ด๋นํ๊ฒ ๋ฉ๋๋ค.
.
.
// ๋ด๊ฐ ํ๋ก์ฐ ํ๊ณ ์๋ ์ฌ๋๋ค
@ManyToMany(() => UsersModel, (user) => user.followees)
@JoinTable()
followers: UsersModel[];
// ๋๋ฅผ ํ๋ก์ฐ ํ๊ณ ์๋ ์ฌ๋๋ค
@ManyToMany(() => UsersModel, (user) => user.followers)
followees: UsersModel[];
pgadmin์ผ๋ก ํ์ธ์ ํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์์ฑ์ด ๋ฉ๋๋ค.
ํ๋ก์ฐ๋ฅผ ํ๋ ๋ก์ง์ ์์ฑํ๊ฒ ์ต๋๋ค.
@Post('follow/:id')
async postFollow(
@User() user: UsersModel, // follower
@Param('id', ParseIntPipe) followeeId: number,
) {
await this.usersService.followUser(
user.id,
followeeId
);
return true;
}
async followUser(followerId: number, followeeId: number) {
const user = await this.usersRepository.findOne({
where: {
id: followerId
},
relations: {
followees: true
}
});
if (!user) throw new BadRequestException(`์กด์ฌํ์ง ์๋ ํ๋ก์ ์
๋๋ค. `);
await this.usersRepository.save({
...user,
followees: [
...user.followees,
{
id: followeeId,
}
]
})
}
์ด๋ฒ์๋ ํ๋ก์๋ค์ ๊ฐ์ ธ์ค๋ API๋ฅผ ๋ง๋ค๊ฒ ์ต๋๋ค.
@Get('follow/me')
async getFollow(
@User() user: UsersModel
) {
return this.usersService.getFollowers(user.id);
}
async getFollowers(userId: number): Promise<UsersModel[]> {
const user = await this.usersRepository.findOne({
where: {
id: userId,
},
relations: {
followers: true
}
});
return user.followers;
}
ํฌ์คํธ๋งจ์ผ๋ก ํ ์คํธ๋ฅผ ํ๊ฒ ์ต๋๋ค.
1๋ฒ codefactory
๋ก 2๋ฒ codefactory1
๋ฅผ ํ๋ก์ฐ ํ๊ฒ ์ต๋๋ค. ๋จผ์ 1๋ฒ ์ฌ์ฉ์๋ก ๋ก๊ทธ์ธ์ ํ๊ฒ ์ต๋๋ค. ํ์ 2๋ฒ์ ํ๋ก์ฐ ํ๊ฒ ์ต๋๋ค.
2๋ฒ ์ฌ์ฉ์
๋ก ๋ก๊ทธ์ธ์ ๋ค์ ํ๊ณ , ๋์ ํ๋ก์ ๊ฐ์ ธ์ค๋ API๋ฅผ ํธ์ถํฉ๋๋ค.
[
{
"id": 1,
"updatedAt": "2024-01-26T05:58:10.800Z",
"createdAt": "2024-01-26T05:58:10.800Z",
"nickname": "codefactory",
"email": "codefactory@codefactory.ai",
"role": "USER"
}
]
๋ง์ฝ 3๋ฒ ์ฌ์ฉ์
๋ก 1๋ฒ์ ํ๋ก์ฐํ๊ณ 1๋ฒ ์ฌ์ฉ์
๋ก ๋ก๊ทธ์ธ ํ ๋์ ํ๋ก์ ๊ฐ์ ธ์ค๋ API๋ฅผ ํธ์ถํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๋์ค๊ฒ ๋ฉ๋๋ค.
[
{
"id": 2,
"updatedAt": "2024-01-26T06:48:51.110Z",
"createdAt": "2024-01-26T06:48:51.110Z",
"nickname": "codefactory1",
"email": "codefactory1@codefactory.ai",
"role": "USER"
},
{
"id": 3,
"updatedAt": "2024-01-27T18:34:48.009Z",
"createdAt": "2024-01-27T18:34:48.009Z",
"nickname": "codefactory19",
"email": "codefactory19@codefactory.ai",
"role": "ADMIN"
}
]
๋งค์ฐ ์ ์์ ์ธ ๋ฐฉ๋ฒ์ ๋๋ค. ์ด๋ฐ ๋ฐฉ๋ฒ์ผ๋ก ํ๋ฉด ์ค๊ฐ ํ ์ด๋ธ์ ๋ฐ์ดํฐ๋ฅผ ์ถ๊ฐํ ์ ์๊ณ ์ค๋ก์ง follower์ followee๋ง ์ปฌ๋ผ์ผ๋ก ์ง์ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค๋ฉด ๋ด๊ฐ ํ๋ก์ฐ๋ฅผ ํ์ ๋, ์๋๋ฐฉ์ด confirm์ ํ๋์ง ์ด๋ฐ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ ์๊ฐ ์์ต๋๋ค.
๋ฐ๋ผ์ ์ง์ ์ค๊ฐ ํ ์ด๋ธ์ ๋ง๋ค๊ฒ ์ต๋๋ค. ์ง์ ํ ์ด๋ธ์ ๊ตฌํํด์ ManyToMany ํ์์ผ๋ก ๋ง๋ค๊ฒ ์ต๋๋ค.
import { BaseModel } from "src/common/entity/base.entity";
import { UsersModel } from "./users.entity";
import { Column, Entity, ManyToOne } from "typeorm";
@Entity()
export class UserFollowersModel extends BaseModel {
@ManyToOne(() => UsersModel, (user) => user.followers)
follower: UsersModel;
@ManyToOne(() => UsersModel, (user) => user.followees)
followee: UsersModel;
@Column({
default: false
})
isConfirmed: boolean;
}
.
.
@OneToMany(() => UserFollowersModel, (ufm) => ufm.follower) // ๋ณ๊ฒฝ
followers: UserFollowersModel[];
@OneToMany(() => UserFollowersModel, (ufm) => ufm.followee) // ๋ณ๊ฒฝ
followees: UserFollowersModel[];
๊ทธ๋ฆฌ๊ณ app.module.ts์ UserFollowersModel
๋ฅผ ๋ฑ๋กํฉ๋๋ค.
entities: [
PostsModel,
UsersModel,
ImageModel,
ChatsModel,
MessagesModel,
CommentsModel,
UserFollowersModel // ์ถ๊ฐ
],
์ ์์ ์ธ ๋ฐฉ๋ฒ์ผ๋ก ํ๋ฉด ์ปฌ๋ผ์ 2๊ฐ๋ก ๊ณ ์ ์ด ๋์ง๋ง, ์ง์ ํ ์ด๋ธ์ ์์ฑํ๋ฉด ์ฌ๋ฌ ์ปฌ๋ผ์ ๋ฃ์ ์ ์์ต๋๋ค.
์ด์ ํ๋ก์ฐ๋ฅผ ํ๋ ๊ธฐ๋ฅ์์๋ relation์ด ManyToMany๋ผ๋ ๊ฐ์ ํ์ ์ง์ followee๋ฅผ ๋ฃ์์ต๋๋ค.
async followUser(followerId: number, followeeId: number) {
const user = await this.usersRepository.findOne({
where: {
id: followerId
},
relations: {
followees: true
}
});
if (!user) throw new BadRequestException(`์กด์ฌํ์ง ์๋ ํ๋ก์ ์
๋๋ค. `);
await this.usersRepository.save({
...user,
followees: [
...user.followees,
{
id: followeeId,
}
]
})
}
๋ฐ๋ผ์ ๊ธฐ์กด ์ฝ๋๋ฅผ ์ญ์ ํ๊ณ ์๋ก์ด ์ฝ๋๋ฅผ ์์ฑํ๊ฒ ์ต๋๋ค.
constructor(
@InjectRepository(UsersModel)
private readonly usersRepository: Repository<UsersModel>,
@InjectRepository(UserFollowersModel)
private readonly userFollowersRepository: Repository<UserFollowersModel>,
) {}
.
.
async followUser(followerId: number, followeeId: number) {
const result = await this.userFollowersRepository.save({
follower: {
id: followerId
},
followee: {
id: followeeId
}
});
return true;
}
async getFollowers(userId: number): Promise<UsersModel[]> {
const result = await this.userFollowersRepository.find({
where: {
// ํ๋ก์ฐํ๋ ๋์
followee: {
id: userId
}
},
relations: {
follower: true,
followee: true
}
});
return result.map((x) => x.follower); // follower๋ง ๋ฝ์์ ๋ฆฌ์คํธ๋ง๋ค๊ธฐ
}
ํฌ์คํธ๋งจ์ผ๋ก ํ ์คํธ๋ฅผ ํ๊ฒ ์ต๋๋ค.
2๋ฒ, 3๋ฒ ์ฌ์ฉ์
๋ก 1๋ฒ ์ฌ์ฉ์๋ฅผ ํ๋ก์ฐ ํ๊ฒ ์ต๋๋ค.
[
{
"id": 3,
"updatedAt": "2024-01-27T18:34:48.009Z",
"createdAt": "2024-01-27T18:34:48.009Z",
"nickname": "codefactory19",
"email": "codefactory19@codefactory.ai",
"role": "ADMIN"
},
{
"id": 2,
"updatedAt": "2024-01-26T06:48:51.110Z",
"createdAt": "2024-01-26T06:48:51.110Z",
"nickname": "codefactory1",
"email": "codefactory1@codefactory.ai",
"role": "USER"
}
]
@Patch('follow/:id/confirm') // ๋๋ฅผ ํ๋ก์ฐ ํ๋ ค๋ ์๋ id
async patchFollowConfirm(
@User() user: UsersModel,
@Param('id', ParseIntPipe) followerId: number,
) {
await this.usersService.confirmFollow(followerId, user.id);
return true;
}
async confirmFollow(followerId: number, followeeId: number) {
// ์ค๊ฐํ
์ด๋ธ์ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ๋์ง ํ์ธ
const existing = await this.userFollowersRepository.findOne({
where: {
follower: {
id: followerId
},
followee: {
id: followeeId
}
},
relations: {
follower: true,
followee: true
},
});
if (!existing) throw new BadRequestException(`์กด์ฌํ์ง ์๋ ํ๋ก์ฐ ์์ฒญ์
๋๋ค. `);
// save๊ฐ์ ๋ฃ์ผ๋ฉด, ๋ณ๊ฒฝ๋ ๋ถ๋ถ๋ง updateํ๋ค.
await this.userFollowersRepository.save({
...existing,
isConfirmed: true,
});
return true;
}
๊ทธ๋ฆฌ๊ณ ํ๋ก์ฐ๋ฅผ ๊ฐ์ ธ์ค๋ API์์๋ isConfirmed๊ฐ true์ธ๊ฒ๋ง ๊ฐ์ ธ์์ผํฉ๋๋ค.
async getFollowers(userId: number): Promise<UsersModel[]> {
const result = await this.userFollowersRepository.find({
where: {
followee: {
id: userId,
},
isConfirmed: true, // ์ถ๊ฐ
},
relations: {
follower: true,
followee: true,
},
});
return result.map((x) => x.follower);
}
ํฌ์คํธ๋งจ์ผ๋ก ํ์ธ์ ํ๊ฒ ์ต๋๋ค. 1๋ฒ ์ฌ์ฉ์๋ก ๋ก๊ทธ์ธ์ ํ๊ณ ํ๋ก์๋ฅผ ๊ฐ์ ธ์ค๋ ๊ธฐ๋ฅ์ ํธ์ถํ๋ฉด ์๋ฌด๊ฒ๋ ๋์ค์ง ์์ต๋๋ค.
์๋ํ๋ฉด ์ฐ๋ฆฌ๊ฐ ํ๊ฐํ ํ๋ก์ฐ ์์ฒญ์ด ์๊ธฐ ๋๋ฌธ์ ๋๋ค. ๊ทธ๋ฌ๋ฉด 2๋ฒ๊ณผ 3๋ฒ์ด ํ๋ก์ฐ ์์ฒญ์ ํ ๊ฒ์ confirm์ ํ๋๋ก ํ๊ฒ ์ต๋๋ค.
2๋ฒ ์ฌ์ฉ์๊ฐ ํ๋ก์ฐ ์์ฒญํ ๊ฒ์ ํ๊ฐ
๋ฅผ ํ๋ฉด ํ์ธ์ด ๋๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
[
{
"id": 2,
"updatedAt": "2024-01-26T06:48:51.110Z",
"createdAt": "2024-01-26T06:48:51.110Z",
"nickname": "codefactory1",
"email": "codefactory1@codefactory.ai",
"role": "USER"
}
]
์ด๋ฒ์๋ getFollow()
๋ฅผ ํ๋ก์ฐ ์์ฒญ์ด ์จ ๊ฒ๋ค์ ํฌํจํด์ ๋ณด์ฌ์ฃผ๋ ๊ฒ๊ณผ ํฌํจํ์ง ์๊ณ ๋ณด์ฌ์ฃผ๋ ์ฝ๋๋ก ์์ ํ๊ฒ ์ต๋๋ค.
@Get('follow/me')
async getFollow(
@User() user: UsersModel,
@Query('includeNotConfirmed', new DefaultValuePipe(false), ParseBoolPipe) includeNotConfirmed: boolean
) {
return this.usersService.getFollowers(user.id, includeNotConfirmed);
}
async getFollowers(userId: number, includeNotConfirmed: boolean): Promise<UsersModel[]> {
const where = {
// ํ๋ก์ฐํ๋ ๋์
followee: {
id: userId
}
};
// ํ๊ฐ ํ๊ฒ๋ค๋ง ์ถ๊ฐ๋ฅผ ํด๋ผ!
if (!includeNotConfirmed) {
where['isConfirmed'] = true;
}
const result = await this.userFollowersRepository.find({
where,
relations: {
follower: true,
followee: true
}
});
return result.map((x) => x.follower);
}
ํฌ์คํธ๋งจ์ผ๋ก ํ ์คํธ๋ฅผ ํ๊ฒ ์ต๋๋ค.
[
{
"id": 3,
"updatedAt": "2024-01-27T18:34:48.009Z",
"createdAt": "2024-01-27T18:34:48.009Z",
"nickname": "codefactory19",
"email": "codefactory19@codefactory.ai",
"role": "ADMIN"
},
{
"id": 2,
"updatedAt": "2024-01-26T06:48:51.110Z",
"createdAt": "2024-01-26T06:48:51.110Z",
"nickname": "codefactory1",
"email": "codefactory1@codefactory.ai",
"role": "USER"
}
]
์๋ต ๋ฐ์ดํฐ ํํ๋ฅผ ๋ฐ๊พธ๊ฒ ์ต๋๋ค.
async getFollowers(userId: number, includeNotConfirmed: boolean): Promise<UsersModel[]> {
const where = {
followee: {
id: userId
}
};
if (!includeNotConfirmed) {
where['isConfirmed'] = true;
}
const result = await this.userFollowersRepository.find({
where,
relations: {
follower: true,
followee: true
}
});
return result.map((x) => ({
id: x.follower.id,
nickname: x.follower.nickname,
email: x.follower.email,
isConfirmed: x.isConfirmed,
})); // follower๋ง ๋ฝ์์ ๋ฆฌ์คํธ๋ง๋ค๊ธฐ
}
[
{
"id": 3,
"nickname": "codefactory19",
"email": "codefactory19@codefactory.ai",
"isConfirmed": false
},
{
"id": 2,
"nickname": "codefactory1",
"email": "codefactory1@codefactory.ai",
"isConfirmed": true
}
]
[
{
"id": 2,
"nickname": "codefactory1",
"email": "codefactory1@codefactory.ai",
"isConfirmed": true
}
]
@Delete('follow/:id')
async deleteFollow(
@User() user: UsersModel,
@Param('id', ParseIntPipe) followeeId: number, // ๋ด๊ฐ ํ๋ก์ฐํ๋ ์๋
) {
await this.usersService.deleteFollow(user.id, followeeId);
return true;
}
async deleteFollow(followerId: number, followeeId: number) {
await this.userFollowersRepository.delete({
follower: {
id: followerId,
},
followee: {
id: followeeId,
},
});
return true;
}
ํฌ์คํธ๋งจ์ผ๋ก ํ ์คํธํ๊ฒ ์ต๋๋ค. 2๋ฒ ์ฌ์ฉ์๋ 1๋ฒ ์ฌ์ฉ์๋ฅผ ํ๋ก์ฐ ํ๊ณ ์์ต๋๋ค.
2๋ฒ ์ฌ์ฉ์๋ก ๋ก๊ทธ์ธ์ ํด์ 1๋ฒ ์ฌ์ฉ์๋ฅผ ์ธํ๋ก์ฐ ํด๋ณด๊ฒ ์ต๋๋ค.
[
{
"id": 3,
"nickname": "codefactory19",
"email": "codefactory19@codefactory.ai",
"isConfirmed": false
}
]