Skip to content

Commit

Permalink
feat(ranking-api): ranking api 부분 완성 (#264)
Browse files Browse the repository at this point in the history
* feat(ranking-api): ranking api 완성

* remove unnecessary comments and add api tags

* feat(app): stack trace limit Infinity

* lint(eslint): unused var allowed with _

* merge main

* recover baseUrl (jest fixed)

* isNil 삭제: 아직 필요하지 않음

* add login dto

* refactor(authController): clean up

* feat(utils): add isNil

* setting(package.json): passWithNoTests

* refactor(room-user): reorder imports

* RankingResponseDto id 삭제

* validateMockUser는 mock유저만 가능하도록 수정

* room service import ordering

* add submission ranking endpoint

* any 타입을 강타입으로

* implement two ranking api

* remove unused endpoint room/:code/ranking

* fix(submission.service): runtime query error addSelect -> select

* fix(jest): add moduleNameMapper to make it work

* refactor(submissionStatDto): do not use it anymore

* refactor: 안 쓰는 datasource 삭제
  • Loading branch information
vimkim authored Feb 10, 2024
1 parent 5f6d95d commit 3694f3e
Show file tree
Hide file tree
Showing 19 changed files with 214 additions and 49 deletions.
6 changes: 5 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"lint:dryrun": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"test": "jest",
"test": "jest --passWithNoTests",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
Expand Down Expand Up @@ -86,6 +86,10 @@
"ts"
],
"rootDir": "src",
"moduleNameMapper": {
"src/(.*)$": "<rootDir>/$1",
"test/(.*)$": "<rootDir>/$1"
},
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
Expand Down
7 changes: 4 additions & 3 deletions server/src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import {
Req,
UseGuards,
} from '@nestjs/common';
import { AppService } from './app.service';
import { ApiTags } from '@nestjs/swagger';
import { Request } from 'express';
import { AppService } from './app.service';
import { SessionAuthGuard } from './auth/auth.guard';
import User from './entities/user.entity';
import { RoomService } from './room/room.service';
import { isUserSession, UserSession } from './types/user-session';
import { UserSession, isUserSession } from './types/user-session';

@ApiTags('app')
@UseGuards(SessionAuthGuard)
@Controller()
export class AppController {
Expand All @@ -24,7 +26,6 @@ export class AppController {

@Get()
getHello(): string {
// this.logger.log('get hello world');
return this.appService.getHello();
}

Expand Down
12 changes: 8 additions & 4 deletions server/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@ import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth/auth.module';
import { ProblemModule } from './problem/problem.module';
import { RoomUserModule } from './room-user/room-user.module';
import { RoomModule } from './room/room.module';
import { SocketModule } from './socket/socket.module';
import { UserModule } from './user/user.module';
import { ShortLoggerService } from './short-logger/short-logger.service';
import { SocketModule } from './socket/socket.module';
import { SubmissionModule } from './submission/submission.module';
import { UserModule } from './user/user.module';

@Module({
imports: [
Expand All @@ -30,7 +31,7 @@ import { SubmissionModule } from './submission/submission.module';
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
entities: [__dirname + '/**/*.entity.*'],
logging: false,
logging: true,
synchronize: true, // production시 false로 변경
namingStrategy: new SnakeNamingStrategy(),
}),
Expand All @@ -40,8 +41,11 @@ import { SubmissionModule } from './submission/submission.module';
RoomModule,
ProblemModule,
SubmissionModule,
RoomUserModule,
],
controllers: [AppController],
providers: [AppService, Logger, ShortLoggerService],
})
export class AppModule {}
export class AppModule {
constructor() {}
}
20 changes: 12 additions & 8 deletions server/src/auth/auth.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Body,
Controller,
Get,
Logger,
Expand All @@ -7,9 +8,12 @@ import {
Res,
UseGuards,
} from '@nestjs/common';
import { GithubAuthGuard, MockAuthGuard, SessionAuthGuard } from './auth.guard';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { Request, Response } from 'express';
import { GithubAuthGuard, MockAuthGuard, SessionAuthGuard } from './auth.guard';
import { LoginDto } from './login.dto';

@ApiTags('auth')
@Controller('auth')
export class AuthController {
private readonly logger = new Logger(AuthController.name);
Expand All @@ -18,22 +22,22 @@ export class AuthController {

@Get('github')
@UseGuards(GithubAuthGuard)
async login() {
this.logger.debug('login...');
}
async login() {}

@Get('github/callback')
@UseGuards(GithubAuthGuard)
async authCallback(@Req() req: Request, @Res() res: Response) {
this.logger.debug('authCallback...');
res.redirect(`${process.env.CLIENT_URL}/home`);
}

@Post('mock')
@UseGuards(MockAuthGuard)
async mockLogin() {
this.logger.debug('MockAuthGuard passed!');
// res.redirect(`${process.env.CLIENT_URL}/home`);
@ApiOperation({
summary: 'Mock Login',
description: 'Supports Mock Login for development purpose.',
})
async mockLogin(@Body() loginDto: LoginDto) {
this.logger.debug('MockAuthGuard passed! loginDto: ', loginDto);
return 'mock user login successful!';
}

Expand Down
6 changes: 6 additions & 0 deletions server/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ export class AuthService {
provider: username,
providerId: password,
};

if (!username.startsWith('mock')) {
this.logger.error('username must start with "mock"');
throw new BadRequestException('잘못된 로그인 요청입니다!');
}

const user =
await this.userService.findUserByProviderInfo(mockProviderInfo);

Expand Down
18 changes: 18 additions & 0 deletions server/src/auth/login.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';

export class LoginDto {
@ApiProperty({
example: 'mockuser',
required: true,
})
@IsString()
username!: string;

@ApiProperty({
example: 'mockuser',
required: true,
})
@IsString()
password!: string;
}
2 changes: 2 additions & 0 deletions server/src/common/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export const isNil = (value: any): value is null | undefined =>
value === null || value === undefined;
16 changes: 9 additions & 7 deletions server/src/main.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
import { ValidationPipe } from '@nestjs/common';
import { HttpAdapterHost, NestFactory } from '@nestjs/core';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { AppModule } from './app.module';
import * as session from 'express-session';
import * as passport from 'passport';
import RedisStore from 'connect-redis';
import * as cookieParser from 'cookie-parser';
import { ValidationPipe } from '@nestjs/common';
import * as session from 'express-session';
import Redis from 'ioredis';
import * as morgan from 'morgan';
import { ShortLoggerService } from './short-logger/short-logger.service';
import * as passport from 'passport';
import { AppModule } from './app.module';
import { ExceptionsFilter } from './exceptions/exceptions.filter';
import { ShortLoggerService } from './short-logger/short-logger.service';
import { SocketIOAdapter } from './socket/socket.adapter';
import Redis from 'ioredis';
import RedisStore from 'connect-redis';

Error.stackTraceLimit = Infinity;

async function bootstrap() {
const app = await NestFactory.create(AppModule, {
Expand Down
5 changes: 5 additions & 0 deletions server/src/room-user/dto/ranking-response.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export interface RankingResponseDto {
username: string;
numberOfProblemsSolved: number;
mostRecentCorrectSubmissionTime: string;
}
19 changes: 16 additions & 3 deletions server/src/room-user/room-user.service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import RoomUser from './room-user.entity';
import { Repository } from 'typeorm';
import User from '../entities/user.entity';
import { RoomUserInput } from '../types/room-user-input';
import { Repository } from 'typeorm';
import RoomUser from './room-user.entity';

@Injectable()
export class RoomUserService {
private readonly logger = new Logger(RoomUserService.name);

constructor(
@InjectRepository(RoomUser)
private readonly roomUserRepository: Repository<RoomUser>,
Expand All @@ -31,4 +33,15 @@ export class RoomUserService {
where: { room: { code: roomCode } },
});
}

async findUsersByRoomCode(code: string) {
const qb = this.roomUserRepository
.createQueryBuilder('roomUser')
.innerJoin('roomUser.room', 'room', 'room.code = :code', { code })
.innerJoinAndSelect('roomUser.user', 'user');

const roomUsers = await qb.getMany();

return roomUsers.map((roomUser) => roomUser.user);
}
}
21 changes: 18 additions & 3 deletions server/src/room/room.controller.ts
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
import {
Body,
Controller,
Get,
HttpCode,
HttpStatus,
Logger,
Param,
Post,
Req,
UseGuards,
} from '@nestjs/common';
import { RoomService } from './room.service';
import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { Request } from 'express';
import User from '../entities/user.entity';
import { SessionAuthGuard } from '../auth/auth.guard';
import User from '../entities/user.entity';
import { RoomUserService } from '../room-user/room-user.service';
import { RoomService } from './room.service';

@Controller('room')
@ApiTags('room')
@UseGuards(SessionAuthGuard)
export class RoomController {
private readonly logger = new Logger(RoomController.name);

constructor(private readonly roomService: RoomService) {}
constructor(
private readonly roomService: RoomService,
private readonly roomUserService: RoomUserService,
) {}

@ApiResponse({
status: 400,
Expand Down Expand Up @@ -54,4 +60,13 @@ export class RoomController {
this.logger.debug(`user ${user.username} exiting room...`);
return await this.roomService.exitRoom(user);
}

@ApiOperation({
summary: '방에 참가한 유저들 조회',
})
@Get('/:code/users')
@HttpCode(HttpStatus.OK)
async getRoomUsers(@Param('code') code: string) {
return await this.roomUserService.findUsersByRoomCode(code);
}
}
4 changes: 3 additions & 1 deletion server/src/room/room.module.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { Module } from '@nestjs/common';
import { forwardRef, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import Room from '../entities/room.entity';
import { RoomUserModule } from '../room-user/room-user.module';
import { UserModule } from '../user/user.module';
import { RoomController } from './room.controller';
import { RoomService } from './room.service';
import { SocketModule } from '../socket/socket.module';
import { SubmissionModule } from '../submission/submission.module';

@Module({
imports: [
UserModule,
RoomUserModule,
TypeOrmModule.forFeature([Room]),
SocketModule,
forwardRef(() => SubmissionModule),
],
controllers: [RoomController],
providers: [RoomService],
Expand Down
11 changes: 8 additions & 3 deletions server/src/room/room.service.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
import {
BadRequestException,
Inject,
Injectable,
InternalServerErrorException,
Logger,
forwardRef,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import * as crypto from 'crypto';
import { SubmissionService } from 'src/submission/submission.service';
import { Repository } from 'typeorm';
import Room from '../entities/room.entity';
import User from '../entities/user.entity';
import { RoomUserService } from '../room-user/room-user.service';
import { UserService } from '../user/user.service';
import { Repository } from 'typeorm';
import RoomUser from '../room-user/room-user.entity';
import { RoomUserService } from '../room-user/room-user.service';
import { SocketService } from '../socket/socket.service';
import { UserService } from '../user/user.service';

@Injectable()
export class RoomService {
Expand All @@ -26,6 +29,8 @@ export class RoomService {
@InjectRepository(Room)
private readonly roomRepository: Repository<Room>,
private readonly socketService: SocketService,
@Inject(forwardRef(() => SubmissionService))
private readonly submissionService: SubmissionService,
) {}

/**
Expand Down
13 changes: 12 additions & 1 deletion server/src/submission/submission.controller.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Body, Controller, Get, Post, Query } from '@nestjs/common';
import { Body, Controller, Get, Logger, Post, Query } from '@nestjs/common';
import { ApiOperation } from '@nestjs/swagger';
import { RankingResponseDto } from 'src/room-user/dto/ranking-response.dto';
import { RoomSubmissionDto } from './dto/roomSubmission.dto';
import { SubmissionDto } from './dto/submission.dto';
import { SubmissionService } from './submission.service';

@Controller('submission')
export class SubmissionController {
private readonly logger = new Logger(SubmissionController.name);

constructor(private readonly submissionService: SubmissionService) {}

@ApiOperation({
Expand All @@ -25,4 +28,12 @@ export class SubmissionController {
async getRoomSubmission(@Query() roomSubmissionDto: RoomSubmissionDto) {
return await this.submissionService.getRoomSubmission(roomSubmissionDto);
}
@Get('ranking')
async getRanking(
@Query() roomSubmissionDto: RoomSubmissionDto,
): Promise<RankingResponseDto[]> {
return this.submissionService.getUsersRankingByRoomCode(
roomSubmissionDto.roomCode,
);
}
}
5 changes: 3 additions & 2 deletions server/src/submission/submission.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Module } from '@nestjs/common';
import { forwardRef, Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import Submission from '../entities/submission.entity';
import { ProblemModule } from '../problem/problem.module';
Expand All @@ -13,12 +13,13 @@ import { RoomModule } from '../room/room.module';
imports: [
UserModule,
ProblemModule,
RoomModule,
forwardRef(() => RoomModule),
RoomUserModule,
TypeOrmModule.forFeature([Submission]),
SocketModule,
],
controllers: [SubmissionController],
providers: [SubmissionService],
exports: [SubmissionService],
})
export class SubmissionModule {}
Empty file.
Loading

0 comments on commit 3694f3e

Please sign in to comment.