Skip to content
Merged
250 changes: 235 additions & 15 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# Node.js 기본
node_modules/
pnpm-debug.log*

# 환경 변수 및 인증 정보
.env
.env.local
Expand All @@ -17,22 +13,246 @@ dist/
*.swp
*.swo

# OS별 시스템 파일
.DS_Store
Thumbs.db

# 로그 파일
logs/*
*.log
*.gz
*.out

# 빌드 및 캐시 파일
.cache/
build/
temp/
tmp/
# Created by https://www.toptal.com/developers/gitignore/api/macos,windows,visualstudiocode,node,git
# Edit at https://www.toptal.com/developers/gitignore?templates=macos,windows,visualstudiocode,node,git

### Git ###
# Created by git for backups. To disable backups in Git:
# $ git config --global mergetool.keepBackup false
*.orig

# Created by git when using merge tools for conflicts
*.BACKUP.*
*.BASE.*
*.LOCAL.*
*.REMOTE.*
*_BACKUP_*.txt
*_BASE_*.txt
*_LOCAL_*.txt
*_REMOTE_*.txt

### macOS ###
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### macOS Patch ###
# iCloud generated files
*.icloud

### Node ###
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*

# 테스트 출력
coverage/
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage
*.lcov

# nyc test coverage
.nyc_output

# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Snowpack dependency directory (https://snowpack.dev/)
web_modules/

# TypeScript cache
*.tsbuildinfo

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional stylelint cache
.stylelintcache

# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local

# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache

# Next.js build output
.next
out

# Nuxt.js build / generate output
.nuxt
dist

# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public

# vuepress build output
.vuepress/dist

# vuepress v2.x temp and cache directory
.temp

# Docusaurus cache and generated files
.docusaurus

# Serverless directories
.serverless/

# FuseBox cache
.fusebox/

# DynamoDB Local files
.dynamodb/

# TernJS port file
.tern-port

# Stores VSCode versions used for testing VSCode extensions
.vscode-test

# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

### Node Patch ###
# Serverless Webpack directories
.webpack/

# Optional stylelint cache

# SvelteKit build / generate output
.svelte-kit

### VisualStudioCode ###
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
!.vscode/*.code-snippets

# Local History for Visual Studio Code
.history/

# Built Visual Studio Code Extensions
*.vsix

### VisualStudioCode Patch ###
# Ignore all local history of files
.history
.ionide

### Windows ###
# Windows thumbnail cache files
Thumbs.db
Thumbs.db:encryptable
ehthumbs.db
ehthumbs_vista.db

# Dump file
*.stackdump

# Folder config file
[Dd]esktop.ini

# Recycle Bin used on file shares
$RECYCLE.BIN/

# Windows Installer files
*.cab
*.msi
*.msix
*.msm
*.msp

# Windows shortcuts
*.lnk

# End of https://www.toptal.com/developers/gitignore/api/macos,windows,visualstudiocode,node,git
2 changes: 1 addition & 1 deletion src/controllers/leaderboard.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
GetPostLeaderboardQuery,
UserLeaderboardResponseDto,
PostLeaderboardResponseDto,
} from '@/types/index';
} from '@/types';

export class LeaderboardController {
constructor(private leaderboardService: LeaderboardService) {}
Expand Down
21 changes: 6 additions & 15 deletions src/controllers/noti.controller.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,19 @@
import { NextFunction, Request, RequestHandler, Response } from 'express';
import logger from '@/configs/logger.config';
import { NotiService } from "@/services/noti.service";
import { NotiPostsResponseDto } from "@/types/dto/responses/notiResponse.type";
import { NotiService } from '@/services/noti.service';
import { NotiPostsResponseDto } from '@/types/dto/responses/notiResponse.type';

export class NotiController {
constructor(private notiService: NotiService) { }
constructor(private notiService: NotiService) {}

getAllNotiPosts: RequestHandler = async (
req: Request,
res: Response<NotiPostsResponseDto>,
next: NextFunction,
) => {
getAllNotiPosts: RequestHandler = async (req: Request, res: Response<NotiPostsResponseDto>, next: NextFunction) => {
try {
const result = await this.notiService.getAllNotiPosts();
const response = new NotiPostsResponseDto(
true,
'전체 noti post 조회에 성공하였습니다.',
{ posts: result },
null,
);
const response = new NotiPostsResponseDto(true, '전체 noti post 조회에 성공하였습니다.', { posts: result }, null);
res.status(200).json(response);
} catch (error) {
logger.error('전체 조회 실패:', error);
next(error);
}
};
}
}
2 changes: 1 addition & 1 deletion src/controllers/post.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
} from '@/types';

export class PostController {
constructor(private postService: PostService) { }
constructor(private postService: PostService) {}

getAllPosts: RequestHandler = async (
req: Request<object, object, object, GetAllPostsQuery>,
Expand Down
33 changes: 33 additions & 0 deletions src/controllers/totalStats.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { NextFunction, Request, RequestHandler, Response } from 'express';
import logger from '@/configs/logger.config';
import { BadRequestError } from '@/exception';
import { GetTotalStatsQuery, TotalStatsResponseDto } from '@/types';
import { TotalStatsService } from '@/services/totalStats.service';

export class TotalStatsController {
constructor(private totalStatsService: TotalStatsService) {}

getTotalStats: RequestHandler = async (
req: Request<object, object, object, GetTotalStatsQuery>,
res: Response<TotalStatsResponseDto>,
next: NextFunction,
) => {
try {
const { id } = req.user;
const { period, type } = req.query;

// 미들웨어에서 GetTotalStatsQueryDto 에 의해 걸리는데 런타임과 IDE 에서 구분을 못함, 이를 위해 추가
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분이 정확히 어떤 것을 의미하는지 조금 이해하기 어렵네요..
정확히 무슨 이유로 추가해주신걸까요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

기준님도 아시다시피 type, interface (typscript) 문법들은 run-time 에서는 없어지잖아요~
그렇기때문에 벨리데이션은 무조건 DTO 로 해야합니다. validateRequestDto(GetTotalStatsQueryDto, 'query')

하지만 컨트롤러의 Request 객체는 class type 선언을 쓰기엔 복잡하죠(제네릭부터 선언해야할 것이 엄청 많음). 그래서 interface 로 사용하죠! (그래서 내용이 같은거, express 가 ts 랑 사실 그렇게 궁합이 맞냐라는 질문에 역시 세모인 이유)

그래서 IDE와 린팅은 이를 인터페이스 타입이라 인지하고 옵셔널 값으로 인지해버려요. (더욱이 require 로 해버리면 제네릭을 써야하는 상황) 그래서 이 라인이 필요합니다!

if (!type) throw new BadRequestError('type 파라미터가 필요합니다.');

const stats = await this.totalStatsService.getTotalStats(id, period, type);
const message = this.totalStatsService.getSuccessMessage(type);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 부분 서비스단에서 type 매개변수도 기본값 설정해주면 따로 체크 안해도 괜찮을 것 같은데 어떻게 생각하시나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#33 (comment)

와 같은 맥락입니다! optional 빼면 여기 Type 체크를 제네릭부터 다시 시작하거나 강제 casting 을 해야 해서요!
미들웨어 벨리데이션에서 이미 검증하기에 컨트롤러 도착할땐 무조건 type 이 이미 존재하긴 합니다! IDE 를 위한 라인..!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 그 부분 이해했습니다!
저는 period도 동일하게 DTO의 생성자에서 기본값 설정이 된 채로 들어오는데, 서비스단에서 기본값 설정을 해두셨길래 type도 똑같이 해두면 되지 않을까 싶었습니다.

export class TotalStatsService {
  async getTotalStats(
    userId: number,
    period: TotalStatsPeriod = 7, // 기존에도 있었던 부분
    type: TotalStatsType = 'view',
  ): Promise<TotalStatsItem[]> {}
}

이렇게요! 그럼 상위에서도 TS에러가 뜨지 않아서요. (확인해보니 리더보드도 이런 식으로 우회되었네요.)
근데 이런 식으로 쓰려면 getSuccessMessage 와 같은 메서드에서도 기본값을 정해두어야 해서, 애매하게 보이긴 하네요.

현우님께서 편하신 방향으로 🙏

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오케이! 이왕 default 값 넣어버린거, 서비스 arguments 에 모두 default 값 넣는 걸로 update 할게요! 좋은 지적 너무 감사해요!


const response = new TotalStatsResponseDto(true, message, stats, null);

res.status(200).json(response);
} catch (error) {
logger.error('전체 통계 조회 실패:', error);
next(error);
}
};
}
Loading
Loading