Skip to content

Commit

Permalink
✨ [Feat]: 사용자 신고 API 구현
Browse files Browse the repository at this point in the history
  • Loading branch information
eomgerm committed Jan 4, 2024
1 parent 835e406 commit cd3a376
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 13 deletions.
12 changes: 12 additions & 0 deletions src/user/dto/reqeust/report-user-request.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty } from 'class-validator';
import { UserReportReason } from 'src/user/enum/user-report-reason';

export class ReportUserReqeust {
@ApiProperty({ description: '신고 사유', enum: Object.keys(UserReportReason) })
@IsNotEmpty({ message: '신고 사유는 필수입니다.' })
@IsEnum(UserReportReason, {
message: `신고 사유는 ${Object.keys(UserReportReason)} 중 하나입니다.`,
})
readonly reason: string;
}
8 changes: 8 additions & 0 deletions src/user/dto/response/report-user-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ApiProperty } from '@nestjs/swagger';

export class ReportUserResponse {
@ApiProperty({ description: '신고 당한 사용자의 아이디', example: 1 })
reportedUserId: number;
@ApiProperty({ description: '신고 시간', example: new Date() })
reportedAt: Date;
}
20 changes: 20 additions & 0 deletions src/user/entity/user-report.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { BaseEntity } from 'src/global/base/base.entity';
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn } from 'typeorm';
import { User } from './user.entity';

@Entity('user_reports')
export class UserReport extends BaseEntity {
@PrimaryGeneratedColumn()
reportId: number;

@Column()
reason: string;

@ManyToOne(() => User, user => user.userId)
@JoinColumn({ name: 'reporter_id', referencedColumnName: 'userId' })
reporter: User;

@ManyToOne(() => User, user => user.userId)
@JoinColumn({ name: 'target_user_id', referencedColumnName: 'userId' })
targetUser: User;
}
7 changes: 7 additions & 0 deletions src/user/entity/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { PlaceReview } from 'src/place-review/entity/place-review.entity';
import { Column, DeleteDateColumn, Entity, OneToMany, PrimaryGeneratedColumn } from 'typeorm';
import { SocialType } from '../enum/social-type';
import { Password } from './password';
import { UserReport } from './user-report.entity';

@Entity('users')
export class User extends BaseEntity {
Expand Down Expand Up @@ -35,4 +36,10 @@ export class User extends BaseEntity {

@OneToMany(() => PlaceReview, review => review.author)
reviews: PlaceReview[];

@OneToMany(() => UserReport, report => report.reporter)
reports: UserReport[];

@OneToMany(() => UserReport, report => report.targetUser)
targetedReports: UserReport[];
}
5 changes: 5 additions & 0 deletions src/user/enum/user-report-reason.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum UserReportReason {
SCAM = 'SCAM',
INAPPROPRIATE = 'INAPPROPRIATE',
COMMERCIAL = 'COMMERCIAL',
}
11 changes: 8 additions & 3 deletions src/user/exception/user-response-code.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ export const UserResponseCode = {
INVALID_PASSWORD_PATTERN: new ResponseCode(
HttpStatus.BAD_REQUEST,
'USER_001',
'비밀번호 형식이 아닙니',
'비밀번호 형식이 아닙니다',
),
USER_NOT_FOUND: new ResponseCode(HttpStatus.NOT_FOUND, 'USER_002', '해당 유저가 없습니다.'),
FOLLOW_MYSELF: new ResponseCode(
REPORT_SELF: new ResponseCode(
HttpStatus.BAD_REQUEST,
'USER_003',
'나를 팔로우 할 수 없습니다.',
'나 자신을 신고할 수 없습니다.',
),
ALREADY_REPORTED: new ResponseCode(
HttpStatus.BAD_REQUEST,
'USER_004',
'이미 신고된 사용자입니다.',
),
};
36 changes: 33 additions & 3 deletions src/user/user.controller.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
import { Body, Controller, Post, UploadedFile, UseInterceptors } from '@nestjs/common';
import {
Body,
Controller,
Param,
Post,
UploadedFile,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { FileInterceptor } from '@nestjs/platform-express';
import {
ApiBearerAuth,
ApiBody,
ApiConsumes,
ApiCreatedResponse,
Expand All @@ -9,10 +18,15 @@ import {
ApiTags,
getSchemaPath,
} from '@nestjs/swagger';
import { JwtAuthGuard } from 'src/auth/guard/auth.guard';
import { BaseResponse } from 'src/global/base/base-response';
import { GlobalResponseCode } from 'src/global/base/global-respose-code';
import { SignUpRequest } from './dto/sign-up-request.dto';
import { SignUpResponse } from './dto/sign-up-response.dto';
import { ExtractPayload } from 'src/global/decorator/extract-payload.decorator';
import { UserExistsValidationPipe } from 'src/global/validation/pipe/user-exists-validation.pipe';
import { ReportUserReqeust } from './dto/reqeust/report-user-request.dto';
import { SignUpRequest } from './dto/reqeust/sign-up-request.dto';
import { ReportUserResponse } from './dto/response/report-user-response.dto';
import { SignUpResponse } from './dto/response/sign-up-response.dto';
import { UserConverter } from './user.converter';
import { UserService } from './user.service';

Expand Down Expand Up @@ -56,4 +70,20 @@ export class UserController {
const newUser = await this.userService.create(reqeust, profileImage);
return BaseResponse.of(UserConverter.toSignUpResponse(newUser), GlobalResponseCode.CREATED);
}

@Post(':targetUserId/report')
@UseGuards(JwtAuthGuard)
@ApiOperation({
summary: '사용자 신고 API',
})
@ApiBearerAuth()
@ApiCreatedResponse({ type: ReportUserResponse, description: '사용자 신고 성공' })
async reportUser(
@Param('targetUserId', UserExistsValidationPipe) userId: number,
@Body() request: ReportUserReqeust,
@ExtractPayload() reporterId: number,
): Promise<BaseResponse<ReportUserResponse>> {
const userReport = await this.userService.reportUser(request, reporterId, userId);
return BaseResponse.of(UserConverter.toReportUserResponse(userReport));
}
}
23 changes: 21 additions & 2 deletions src/user/user.converter.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Injectable, OnModuleInit } from '@nestjs/common';
import { Builder } from 'builder-pattern';
import { S3Service } from 'src/aws/s3/s3.service';
import { SignUpRequest } from './dto/sign-up-request.dto';
import { SignUpResponse } from './dto/sign-up-response.dto';
import { ReportUserReqeust } from './dto/reqeust/report-user-request.dto';
import { SignUpRequest } from './dto/reqeust/sign-up-request.dto';
import { ReportUserResponse } from './dto/response/report-user-response.dto';
import { SignUpResponse } from './dto/response/sign-up-response.dto';
import { Password } from './entity/password';
import { UserReport } from './entity/user-report.entity';
import { User } from './entity/user.entity';
import { UserReportReason } from './enum/user-report-reason';

@Injectable()
export class UserConverter implements OnModuleInit {
Expand Down Expand Up @@ -33,4 +37,19 @@ export class UserConverter implements OnModuleInit {
public static toSignUpResponse(user: User): SignUpResponse {
return Builder(SignUpResponse).userId(user.userId).build();
}

public static toUserReport(reqeust: ReportUserReqeust, reporter: User, targetUser: User) {
return Builder(UserReport)
.reason(UserReportReason[reqeust.reason])
.reporter(reporter)
.targetUser(targetUser)
.build();
}

public static toReportUserResponse(userReport: UserReport) {
return Builder(ReportUserResponse)
.reportedAt(userReport.createdAt)
.reportedUserId(userReport.reporter.userId)
.build();
}
}
6 changes: 4 additions & 2 deletions src/user/user.module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { S3Service } from 'src/aws/s3/s3.service';
import { UserExistsValidationPipe } from 'src/global/validation/pipe/user-exists-validation.pipe';
import { UserReport } from './entity/user-report.entity';
import { User } from './entity/user.entity';
import { UserController } from './user.controller';
import { UserConverter } from './user.converter';
import { UserService } from './user.service';

@Module({
imports: [TypeOrmModule.forFeature([User])],
providers: [UserService, S3Service, UserConverter],
imports: [TypeOrmModule.forFeature([User, UserReport])],
providers: [UserService, S3Service, UserConverter, UserExistsValidationPipe],
controllers: [UserController],
exports: [UserService],
})
Expand Down
38 changes: 35 additions & 3 deletions src/user/user.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { S3Service } from 'src/aws/s3/s3.service';
import { BaseException } from 'src/global/base/base-exception';
import { Repository } from 'typeorm';
import { SignUpRequest } from './dto/sign-up-request.dto';
import { ReportUserReqeust } from './dto/reqeust/report-user-request.dto';
import { SignUpRequest } from './dto/reqeust/sign-up-request.dto';
import { UserReport } from './entity/user-report.entity';
import { User } from './entity/user.entity';
import { UserResponseCode } from './exception/user-response-code';
import { UserConverter } from './user.converter';

@Injectable()
Expand All @@ -13,12 +16,41 @@ export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
private readonly s3Service: S3Service,
@InjectRepository(UserReport)
private readonly userReportRepository: Repository<UserReport>,
) {}

async isUserExists(userId: number): Promise<boolean> {
return this.userRepository.exist({ where: { userId } });
}

async create(request: SignUpRequest, profileImage: Express.Multer.File): Promise<User> {
const newUser = this.userRepository.save(await UserConverter.toUser(request, profileImage));

return newUser;
}

async reportUser(
request: ReportUserReqeust,
reporterId: number,
targetUserId: number,
): Promise<UserReport> {
if (reporterId === targetUserId) {
throw BaseException.of(UserResponseCode.REPORT_SELF);
}

const isAlreadyReported = await this.userReportRepository.exist({
where: { reporter: { userId: reporterId }, targetUser: { userId: targetUserId } },
});
if (isAlreadyReported) {
throw BaseException.of(UserResponseCode.ALREADY_REPORTED);
}

const reporter = await this.userRepository.findOneBy({ userId: reporterId });
const targetUser = await this.userRepository.findOneBy({ userId: targetUserId });

const userReport = UserConverter.toUserReport(request, reporter, targetUser);

return await this.userReportRepository.save(userReport);
}
}

0 comments on commit cd3a376

Please sign in to comment.