diff --git a/src/controllers/auth.controller.ts b/src/controllers/auth.controller.ts index 8dcba51..e105e43 100644 --- a/src/controllers/auth.controller.ts +++ b/src/controllers/auth.controller.ts @@ -26,7 +26,6 @@ import { RegisterRequestDto } from 'src/services/auth/dto/register.dto'; import { LoginAuthGuard } from 'src/services/auth/guard/login.guard'; import { AccessPayload } from 'src/services/auth/payload/access.payload'; import { UserService } from 'src/services/user/user.service'; -import { WhitelistService } from 'src/services/whitelist/whitelist.service'; @Controller('') @ApiTags('Auth') @@ -34,7 +33,6 @@ export class AuthController { constructor( private readonly authService: AuthService, private readonly userService: UserService, - private readonly whitelistService: WhitelistService, ) {} @Post('/login') diff --git a/src/controllers/badge.controller.ts b/src/controllers/badge.controller.ts new file mode 100644 index 0000000..1165db1 --- /dev/null +++ b/src/controllers/badge.controller.ts @@ -0,0 +1,139 @@ +import { + Body, + Controller, + Delete, + Get, + Param, + Post, + Query, + Req, + UseGuards, +} from '@nestjs/common'; +import { ApiCreatedResponse, ApiOperation, ApiTags } from '@nestjs/swagger'; +import { Request } from 'express'; +import { Badge } from 'src/entities/badge.entity'; +import { GetUsersResponseDto } from 'src/services/auth/dto/get-users.dto'; +import { LoginAuthGuard } from 'src/services/auth/guard/login.guard'; +import { AccessPayload } from 'src/services/auth/payload/access.payload'; +import { BadgeService } from 'src/services/badge/badge.service'; +import { PostBadgeRequestDto } from 'src/services/badge/dto/post-badge.dto'; +import { GrantRequestBadgeDto } from 'src/services/badge/dto/post-grant.dto'; +import { UserService } from 'src/services/user/user.service'; + +@Controller('badge') +@ApiTags('badge') +export class BadgeController { + constructor( + private readonly badgeService: BadgeService, + private readonly userService: UserService, + ) {} + + @Post('/post') + @ApiOperation({ + summary: 'Create Badge', + description: 'Create Badge', + }) + @ApiCreatedResponse({ + description: 'Badge created', + }) + @UseGuards(LoginAuthGuard) + async createBadge( + @Req() req: Request, + @Body() postBadgeRequestDto: PostBadgeRequestDto, + ): Promise { + const userId = (req.user as AccessPayload).userId; + if (await this.userService.checkBadgeForAuth(userId, 'admin')) { + await this.badgeService.createBadge( + postBadgeRequestDto.title, + postBadgeRequestDto.description, + postBadgeRequestDto.icon, + postBadgeRequestDto.key, + ); + } + } + + @Post('/grant') + @ApiOperation({ + summary: 'Grant Badge', + description: 'Grant Badge', + }) + @ApiCreatedResponse({ + description: 'Badge granted', + }) + @UseGuards(LoginAuthGuard) + async grantBadge( + @Req() req: Request, + @Body() grantRequestBadgeDto: GrantRequestBadgeDto, + ): Promise { + const userId = (req.user as AccessPayload).userId; + if (await this.userService.checkBadgeForAuth(userId, 'admin')) { + return await this.badgeService.grantBadgeToUser( + grantRequestBadgeDto.badgeIds, + grantRequestBadgeDto.userId, + ); + } + } + + @Get('') + @ApiOperation({ + summary: 'Get Badges', + description: 'Get Badges', + }) + @ApiCreatedResponse({ + description: 'Badges', + }) + async getBadges(@Req() req: Request): Promise { + const userId = (req.user as AccessPayload).userId; + if (await this.userService.checkBadgeForAuth(userId, 'admin')) { + return this.badgeService.getBadges(); + } + } + + @Get('/:userId') + @ApiOperation({ + summary: 'Get Badges By UserId', + description: 'Get Badges By UserId', + }) + @ApiCreatedResponse({ + description: 'Badges', + }) + async getBadgesByUserId(userId: string): Promise { + return this.badgeService.getBadgesByUserId(userId); + } + + @Delete('/:userId') + @ApiOperation({ + summary: 'Deprive Badge', + description: 'Deprive Badge', + }) + @ApiCreatedResponse({ + description: 'Badge deprived', + }) + @UseGuards(LoginAuthGuard) + async depriveBadge( + @Req() req: Request, + @Param('userId') userId: string, + @Query('badgeIds') badgeIds: string[], + ): Promise { + const adminId = (req.user as AccessPayload).userId; + if (await this.userService.checkBadgeForAuth(adminId, 'admin')) { + await this.badgeService.depriveBadgeFromUser(badgeIds, userId); + } + } + // todo: badge delete를 하는 기능 해당 뱃지를 가진 user_badge table의 해당 뱃지를 가진 row를 삭제하는 기능이 필요 + // @Delete('/:badgeId') + // @ApiOperation({ + // summary: 'Delete Badge', + // description: 'Delete Badge', + // }) + // @ApiCreatedResponse({ + // description: 'Badge deleted', + // }) + // @UseGuards(LoginAuthGuard) + // async deleteBadge(@Req() req: Request, badgeId: string): Promise { + // const userId = (req.user as AccessPayload).userId; + // if (await this.userService.checkBadgeForAuth(userId, 'admin')) { + // await this.badgeService.deleteBadge(badgeId); + // } + // } +} diff --git a/src/controllers/index.ts b/src/controllers/index.ts index 11fa903..b0e3c8d 100644 --- a/src/controllers/index.ts +++ b/src/controllers/index.ts @@ -1,13 +1,13 @@ import { AboutController } from 'src/controllers/about.controller'; import { ApplyController } from 'src/controllers/apply.controller'; import { AuthController } from 'src/controllers/auth.controller'; +import { BadgeController } from 'src/controllers/badge.controller'; import { BoardController } from 'src/controllers/board.controller'; import { EmailController } from 'src/controllers/email.controller'; import { PostController } from 'src/controllers/post.controller'; +import { TeamController } from 'src/controllers/team.controller'; import { WhitelistController } from 'src/controllers/whitelist.controller'; -import { TeamController } from './team.controller'; - export default [ AuthController, AboutController, @@ -17,4 +17,5 @@ export default [ BoardController, PostController, TeamController, + BadgeController, ]; diff --git a/src/entities/badge.entity.ts b/src/entities/badge.entity.ts index 690184d..a875171 100644 --- a/src/entities/badge.entity.ts +++ b/src/entities/badge.entity.ts @@ -14,7 +14,7 @@ import { @Entity({ name: 'badge' }) export class Badge extends BaseEntity { - @Column({ name: '_key', unique: true }) + @Column({ name: '_key', unique: true, nullable: false }) _key: string; @Column({ name: 'title' }) @@ -23,7 +23,7 @@ export class Badge extends BaseEntity { @Column({ name: 'description' }) description: string; - @OneToOne(() => File, (file) => file.badge) + @OneToOne(() => File, (file) => file.badge, { nullable: true }) @JoinColumn({ name: 'icon' }) icon: File; diff --git a/src/services/auth/auth.service.ts b/src/services/auth/auth.service.ts index 1b7b8b5..a693254 100644 --- a/src/services/auth/auth.service.ts +++ b/src/services/auth/auth.service.ts @@ -9,14 +9,14 @@ import ERROR from 'src/common/error'; import { Email } from 'src/entities/email.entity'; import { User } from 'src/entities/user.entity'; import { Whitelist } from 'src/entities/whitelist.entity'; +import { GetUsersResponseDto } from 'src/services/auth/dto/get-users.dto'; import { RegisterRequestDto } from 'src/services/auth/dto/register.dto'; import { AccessPayload } from 'src/services/auth/payload/access.payload'; import { RefreshPayload } from 'src/services/auth/payload/refresh.payload'; +import { BadgeService } from 'src/services/badge/badge.service'; +import { EmailService } from 'src/services/email/email.service'; import { Repository } from 'typeorm'; -import { GetUsersResponseDto } from './dto/get-users.dto'; -import { EmailService } from '../email/email.service'; - @Injectable() export class AuthService { constructor( @@ -32,6 +32,7 @@ export class AuthService { private readonly configService: ConfigService, private readonly jwtService: JwtService, private readonly emailService: EmailService, + private readonly badgeService: BadgeService, ) {} async validateUser(email: string, password: string): Promise { diff --git a/src/services/badge/badge.service.ts b/src/services/badge/badge.service.ts new file mode 100644 index 0000000..65e50f3 --- /dev/null +++ b/src/services/badge/badge.service.ts @@ -0,0 +1,136 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import ERROR from 'src/common/error'; +import { BadgeLog } from 'src/entities/badge-log.entity'; +import { Badge } from 'src/entities/badge.entity'; +import { File } from 'src/entities/file.entity'; +import { User } from 'src/entities/user.entity'; +import { GetUsersResponseDto } from 'src/services/auth/dto/get-users.dto'; +import { In, Repository } from 'typeorm'; + +@Injectable() +export class BadgeService { + constructor( + @InjectRepository(Badge) + private readonly badgeRepository: Repository, + @InjectRepository(File) + private readonly fileRepository: Repository, + @InjectRepository(User) + private readonly userRepository: Repository, + @InjectRepository(BadgeLog) + private readonly badgeLogRepository: Repository, + ) {} + + async createBadge( + title: string, + description: string, + icon: string = null, + key: string, + ): Promise { + const badge = new Badge(); + badge.title = title; + badge.description = description; + badge._key = key; + if (icon) { + const file = await this.fileRepository.findOne({ where: { id: icon } }); + badge.icon = file; + } else { + badge.icon = null; + } + return await this.badgeRepository.save(badge); + } + + async getBadges(): Promise { + return this.badgeRepository.find(); + } + + async getBadgesByUserId(userId: string): Promise { + const userWithBadges = await this.userRepository.findOne({ + where: { id: userId }, + relations: ['badges'], + }); + + return userWithBadges.badges; + } + + async grantBadgeToUser( + badgeIds: string[], + userId: string, + ): Promise { + const badges = await this.badgeRepository.find({ + where: { id: In(badgeIds) }, + }); + + if (badges.length !== badgeIds.length) { + throw ERROR.NOT_FOUND; + } + + const user = await this.userRepository.findOne({ + where: { id: userId }, + relations: ['badges'], + }); + + if (!user) { + throw ERROR.NOT_FOUND; + } + + user.badges = Array.isArray(user.badges) + ? [...user.badges, ...badges] + : badges; + + const grantedUser = await this.userRepository.save(user); + return { + id: grantedUser.id, + username: grantedUser.username, + badges: grantedUser.badges, + email: grantedUser.email, + phoneNumber: grantedUser.phoneNumber, + studentId: grantedUser.studentId, + activation: grantedUser.activation, + createdAt: grantedUser.createdAt, + updatedAt: grantedUser.updatedAt, + }; + } + // todo: badge delete를 하는 기능 해당 뱃지를 가진 user_badge table의 해당 뱃지를 가진 row를 삭제하는 기능이 필요 + // async deleteBadge(badgeId: string): Promise { + // const badge = await this.badgeRepository.findOne({ + // where: { id: badgeId }, + // }); + + // if (!badge) { + // throw ERROR.NOT_FOUND; + // } + + // await this.badgeRepository.manager.connection + // .createQueryBuilder() + // .delete() + // .from('user_badge') + // .where('badgeId = :badgeId', { badgeId }) + // .execute(); + + // await this.badgeRepository.delete({ id: badgeId }); + // } + + async depriveBadgeFromUser( + badgeIds: string[], + userId: string, + ): Promise { + const user = await this.userRepository.findOne({ + where: { id: userId }, + }); + + const badge = await this.badgeRepository.find({ + where: { id: In(badgeIds) }, + }); + + if (!user || !badge) { + throw ERROR.NOT_FOUND; + } + + await this.userRepository + .createQueryBuilder() + .relation(User, 'badges') + .of(user) + .remove(badge); + } +} diff --git a/src/services/badge/dto/post-badge.dto.ts b/src/services/badge/dto/post-badge.dto.ts new file mode 100644 index 0000000..c381bd5 --- /dev/null +++ b/src/services/badge/dto/post-badge.dto.ts @@ -0,0 +1,6 @@ +export class PostBadgeRequestDto { + title: string; + description: string; + icon?: string; + key: string; +} diff --git a/src/services/badge/dto/post-grant.dto.ts b/src/services/badge/dto/post-grant.dto.ts new file mode 100644 index 0000000..47d99e4 --- /dev/null +++ b/src/services/badge/dto/post-grant.dto.ts @@ -0,0 +1,4 @@ +export class GrantRequestBadgeDto { + userId: string; + badgeIds: string[]; +} diff --git a/src/services/index.ts b/src/services/index.ts index 50265e9..10e7f36 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -4,14 +4,14 @@ import { JwtService } from '@nestjs/jwt'; import { AboutService } from 'src/services/about/about.service'; import { ApplyService } from 'src/services/apply/apply.service'; import { AuthService } from 'src/services/auth/auth.service'; +import { BadgeService } from 'src/services/badge/badge.service'; import { BoardService } from 'src/services/board/board.service'; import { EmailService } from 'src/services/email/email.service'; import { PostService } from 'src/services/post/post.service'; +import { TeamService } from 'src/services/team/team.service'; import { UserService } from 'src/services/user/user.service'; import { WhitelistService } from 'src/services/whitelist/whitelist.service'; -import { TeamService } from './team/team.service'; - export default [ AboutService, ApplyService, @@ -24,4 +24,5 @@ export default [ BoardService, PostService, TeamService, + BadgeService, ];