diff --git a/BE/src/app.module.ts b/BE/src/app.module.ts index 88a88d8..fac6872 100644 --- a/BE/src/app.module.ts +++ b/BE/src/app.module.ts @@ -14,6 +14,7 @@ import { PassportModule } from '@nestjs/passport'; import { AuthModule } from './auth/auth.module'; import { ServeStaticModule } from '@nestjs/serve-static'; import { join } from 'path'; +import { Mates } from './mates/mates.entity'; @Module({ imports: [ @@ -33,7 +34,7 @@ import { join } from 'path'; username: config.get('DATABASE_USERNAME'), password: config.get('DATABASE_PASSWORD'), database: config.get('DATABASE_NAME'), - entities: [StudyLogs, Categories, UsersModel], + entities: [StudyLogs, Categories, UsersModel, Mates], synchronize: true, }), inject: [ConfigService], diff --git a/BE/src/mates/dto/response/mates.dto.ts b/BE/src/mates/dto/response/mates.dto.ts new file mode 100644 index 0000000..035e5a2 --- /dev/null +++ b/BE/src/mates/dto/response/mates.dto.ts @@ -0,0 +1,24 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class MatesDto { + @ApiProperty({ + type: 'number', + example: '1', + description: '친구 관계 id', + }) + id: number; + + @ApiProperty({ + type: 'number', + example: 1, + description: '구독의 주체 id (1이 2를 구독중)', + }) + follower_id: number; + + @ApiProperty({ + type: 'number', + example: 2, + description: '구독 중인 친구 id', + }) + following_id: number; +} diff --git a/BE/src/mates/dto/response/status-message.dto.ts b/BE/src/mates/dto/response/status-message.dto.ts new file mode 100644 index 0000000..0e5f3fd --- /dev/null +++ b/BE/src/mates/dto/response/status-message.dto.ts @@ -0,0 +1,17 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class StatusMessageDto { + @ApiProperty({ + type: 'number', + example: 200, + description: '상태 코드', + }) + statusCode: number; + + @ApiProperty({ + type: 'string', + example: '성공적으로 삭제되었습니다.', + description: '메시지', + }) + message: string; +} diff --git a/BE/src/mates/mates.controller.ts b/BE/src/mates/mates.controller.ts index 5738286..a396cef 100644 --- a/BE/src/mates/mates.controller.ts +++ b/BE/src/mates/mates.controller.ts @@ -1,29 +1,72 @@ -import { Controller, Delete, Get, Param, Post } from '@nestjs/common'; -import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { + Controller, + Delete, + Get, + Param, + Post, + Body, + UseGuards, +} from '@nestjs/common'; +import { + ApiBearerAuth, + ApiCreatedResponse, + ApiOperation, + ApiTags, +} from '@nestjs/swagger'; +import { User } from 'src/users/decorator/user.decorator'; +import { MatesService } from './mates.service'; +import { MatesDto } from './dto/response/mates.dto'; +import { StatusMessageDto } from './dto/response/status-message.dto'; +import { AccessTokenGuard } from 'src/auth/guard/bearer-token.guard'; +import { UsersModel } from 'src/users/entity/users.entity'; @Controller('mates') @ApiTags('소셜 페이지') export class MatesController { + constructor(private readonly matesService: MatesService) {} @Get() - @ApiOperation({ summary: '모든 친구들 조회하기' }) - getMates() {} + @UseGuards(AccessTokenGuard) + @ApiBearerAuth() + @ApiOperation({ summary: '모든 친구들 조회하기 (완)' }) + getMates(@User('id') user_id: number): Promise { + return this.matesService.getMates(user_id); + } @Get('/:mate_id/stats') + @UseGuards(AccessTokenGuard) + @ApiBearerAuth() @ApiOperation({ summary: '특정 친구의 통계 조회하기' }) getMateStats(@Param('mate_id') id: string) { id; } @Post() + @UseGuards(AccessTokenGuard) + @ApiBearerAuth() @ApiCreatedResponse({ description: '친구가 성공적으로 구독되었습니다.', }) - @ApiOperation({ summary: '친구 구독하기' }) - crateMate() {} + @ApiOperation({ summary: '친구 구독하기 (완)' }) + createMate( + @User() user: UsersModel, + @Body('following_nickname') following_nickname: string, + ): Promise { + return this.matesService.addMate(user, following_nickname); + } - @Delete('/:mate_id') - @ApiOperation({ summary: '구독한 친구 구독 취소하기' }) - deleteMate(@Param('mate_id') id: string) { - id; + @Delete('') + @UseGuards(AccessTokenGuard) + @ApiBearerAuth() + @ApiOperation({ summary: '구독한 친구 구독 취소하기 (완)' }) + async deleteMate( + @User() user: UsersModel, + @Body('following_id') following_id: number, + ): Promise { + await this.matesService.deleteMate(user, following_id); + + return { + statusCode: 200, + message: '성공적으로 삭제되었습니다.', + }; } } diff --git a/BE/src/mates/mates.entity.ts b/BE/src/mates/mates.entity.ts new file mode 100644 index 0000000..00c5155 --- /dev/null +++ b/BE/src/mates/mates.entity.ts @@ -0,0 +1,28 @@ +import { + Entity, + PrimaryGeneratedColumn, + Column, + ManyToOne, + JoinColumn, +} from 'typeorm'; +import { UsersModel } from 'src/users/entity/users.entity'; + +@Entity() +export class Mates { + @PrimaryGeneratedColumn() + id: number; + + @ManyToOne(() => UsersModel, (user) => user.follower, { + eager: true, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'follower_id' }) + follower_id: UsersModel; + + @ManyToOne(() => UsersModel, (user) => user.following, { + eager: true, + onDelete: 'CASCADE', + }) + @JoinColumn({ name: 'following_id' }) + following_id: UsersModel; +} diff --git a/BE/src/mates/mates.module.ts b/BE/src/mates/mates.module.ts index 337f98e..d591c89 100644 --- a/BE/src/mates/mates.module.ts +++ b/BE/src/mates/mates.module.ts @@ -1,7 +1,14 @@ import { Module } from '@nestjs/common'; import { MatesController } from './mates.controller'; +import { MatesService } from './mates.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { Mates } from './mates.entity'; +import { AuthModule } from 'src/auth/auth.module'; +import { UsersModule } from 'src/users/users.module'; @Module({ + imports: [TypeOrmModule.forFeature([Mates]), AuthModule, UsersModule], controllers: [MatesController], + providers: [MatesService], }) export class MatesModule {} diff --git a/BE/src/mates/mates.service.spec.ts b/BE/src/mates/mates.service.spec.ts new file mode 100644 index 0000000..7169027 --- /dev/null +++ b/BE/src/mates/mates.service.spec.ts @@ -0,0 +1,18 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { MatesService } from './mates.service'; + +describe('MatesService', () => { + let service: MatesService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [MatesService], + }).compile(); + + service = module.get(MatesService); + }); + + it('should be defined', () => { + expect(service).toBeDefined(); + }); +}); diff --git a/BE/src/mates/mates.service.ts b/BE/src/mates/mates.service.ts new file mode 100644 index 0000000..54fb022 --- /dev/null +++ b/BE/src/mates/mates.service.ts @@ -0,0 +1,84 @@ +import { + Injectable, + NotFoundException, + BadRequestException, +} from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { Mates } from './mates.entity'; +import { Repository } from 'typeorm'; +import { MatesDto } from './dto/response/mates.dto'; +import { UsersModel } from 'src/users/entity/users.entity'; + +@Injectable() +export class MatesService { + constructor( + @InjectRepository(Mates) + private matesRepository: Repository, + @InjectRepository(UsersModel) + private userRepository: Repository, + ) {} + + async getMates(user_id: number): Promise { + const result = await this.matesRepository.find({ + where: { follower_id: { id: user_id } }, + }); + return result.map((mate) => this.entityToDto(mate)); + } + + async addMate( + user: UsersModel, + following_nickname: string, + ): Promise { + const following = await this.userRepository.findOne({ + where: { nickname: following_nickname }, + }); + + if (user.id === following.id) { + throw new BadRequestException('자신을 친구 추가 할 수 없습니다.'); + } + + if (!user || !following) { + throw new NotFoundException('해당 유저는 존재하지 않습니다.'); + } + + const isExist = await this.matesRepository.findOne({ + where: { follower_id: user, following_id: following }, + }); + + if (isExist) { + throw new BadRequestException('이미 친구 관계입니다.'); + } + + const mate = this.matesRepository.create({ + follower_id: user, + following_id: following, + }); + + const result = await this.matesRepository.save(mate); + return this.entityToDto(result); + } + + async deleteMate(user: UsersModel, following_id: number): Promise { + const following = await this.userRepository.findOne({ + where: { id: following_id }, + }); + const result = await this.matesRepository.delete({ + follower_id: user, + following_id: following, + }); + + if (result.affected === 0) { + throw new NotFoundException('해당 친구 관계는 존재하지 않습니다.'); + } + } + + entityToDto(mate: Mates): MatesDto { + const { id, follower_id, following_id } = mate; + const mateDto = { + id: id, + follower_id: follower_id.id, + following_id: following_id.id, + }; + return mateDto; + } +} diff --git a/BE/src/users/entity/users.entity.ts b/BE/src/users/entity/users.entity.ts index fe2b577..e72cd86 100644 --- a/BE/src/users/entity/users.entity.ts +++ b/BE/src/users/entity/users.entity.ts @@ -4,6 +4,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { StudyLogs } from 'src/study-logs/study-logs.entity'; import { Categories } from 'src/categories/categories.entity'; import { AuthTypeEnum } from '../const/auth-type.const'; +import { Mates } from 'src/mates/mates.entity'; @Entity() export class UsersModel { @@ -57,4 +58,10 @@ export class UsersModel { @OneToMany(() => Categories, (category) => category.user_id) categories: Categories[]; + + @OneToMany(() => Mates, (mate) => mate.follower_id) + follower: Mates[]; + + @OneToMany(() => Mates, (mate) => mate.following_id) + following: Mates[]; }