Skip to content

Commit a97a17e

Browse files
authored
Merge pull request #308 from sparcs-kaist/chore/swagger-language-header
Chore/swagger language header
2 parents 44fbc2b + fef78bc commit a97a17e

File tree

17 files changed

+126
-77
lines changed

17 files changed

+126
-77
lines changed

apps/server/docs/openapi-generator.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,21 @@ async function main() {
237237

238238
if (!deco) return
239239
const dName = deco.getName().toLowerCase() // query, body, param
240+
241+
// === GetLanguage decorator detection ===
242+
// NOTE: We check by decorator name directly ("GetLanguage").
243+
// Also, ignore the normal process below and just inject header param.
244+
if (deco.getName() === 'GetLanguage') {
245+
// Inject accept-language header parameter if not already pushed
246+
parameters.push({
247+
name: 'accept-language',
248+
in: 'header',
249+
required: false,
250+
schema: { type: 'string', enum: ['en', 'ko'] },
251+
description: 'en/ko (기본: ko). 언어 헤더 파라미터',
252+
})
253+
return
254+
}
240255
if (!['query', 'param', 'body'].includes(dName)) {
241256
return
242257
}

apps/server/docs/swagger.json

Lines changed: 33 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -918,7 +918,7 @@
918918
"content": {
919919
"application/json": {
920920
"schema": {
921-
"$ref": "#/components/schemas/import(\"/Users/larrykwon/sparcs/nest/otl-nest/apps/server/src/modules/notification/domain/notification\").NotificationRequest"
921+
"$ref": "#/components/schemas/import(\"/Users/sciberbee/Documents/otl/otlplus-server/apps/server/src/modules/notification/domain/notification\").NotificationRequest"
922922
}
923923
}
924924
}
@@ -936,7 +936,7 @@
936936
"content": {
937937
"application/json": {
938938
"schema": {
939-
"$ref": "#/components/schemas/import(\"/Users/larrykwon/sparcs/nest/otl-nest/apps/server/src/modules/notification/domain/notification\").FCMNotificationRequest"
939+
"$ref": "#/components/schemas/import(\"/Users/sciberbee/Documents/otl/otlplus-server/apps/server/src/modules/notification/domain/notification\").FCMNotificationRequest"
940940
}
941941
}
942942
}
@@ -963,7 +963,7 @@
963963
"content": {
964964
"application/json": {
965965
"schema": {
966-
"$ref": "#/components/schemas/import(\"/Users/larrykwon/sparcs/nest/otl-nest/apps/server/src/modules/notification/domain/notification\").UserNotification"
966+
"$ref": "#/components/schemas/import(\"/Users/sciberbee/Documents/otl/otlplus-server/apps/server/src/modules/notification/domain/notification\").UserNotification"
967967
}
968968
}
969969
}
@@ -992,7 +992,7 @@
992992
"schema": {
993993
"type": "array",
994994
"items": {
995-
"$ref": "#/components/schemas/import(\"/Users/larrykwon/sparcs/nest/otl-nest/apps/server/src/modules/agreement/domain/UserAgreement\").Agreement"
995+
"$ref": "#/components/schemas/import(\"/Users/sciberbee/Documents/otl/otlplus-server/apps/server/src/modules/agreement/domain/UserAgreement\").Agreement"
996996
}
997997
}
998998
}
@@ -1010,7 +1010,7 @@
10101010
"content": {
10111011
"application/json": {
10121012
"schema": {
1013-
"$ref": "#/components/schemas/import(\"/Users/larrykwon/sparcs/nest/otl-nest/apps/server/src/modules/notification/domain/notification\").Notification"
1013+
"$ref": "#/components/schemas/import(\"/Users/sciberbee/Documents/otl/otlplus-server/apps/server/src/modules/notification/domain/notification\").Notification"
10141014
}
10151015
}
10161016
}
@@ -1035,7 +1035,7 @@
10351035
"content": {
10361036
"application/json": {
10371037
"schema": {
1038-
"$ref": "#/components/schemas/import(\"/Users/larrykwon/sparcs/nest/otl-nest/apps/server/src/modules/notification/domain/notification\").Notification"
1038+
"$ref": "#/components/schemas/import(\"/Users/sciberbee/Documents/otl/otlplus-server/apps/server/src/modules/notification/domain/notification\").Notification"
10391039
}
10401040
}
10411041
}
@@ -1087,7 +1087,7 @@
10871087
"schema": {
10881088
"type": "array",
10891089
"items": {
1090-
"$ref": "#/components/schemas/import(\"/Users/larrykwon/sparcs/nest/otl-nest/apps/server/src/modules/notification/domain/notification\").Notification"
1090+
"$ref": "#/components/schemas/import(\"/Users/sciberbee/Documents/otl/otlplus-server/apps/server/src/modules/notification/domain/notification\").Notification"
10911091
}
10921092
}
10931093
}
@@ -1110,7 +1110,7 @@
11101110
"type": "null"
11111111
},
11121112
{
1113-
"$ref": "#/components/schemas/import(\"/Users/larrykwon/sparcs/nest/otl-nest/apps/server/src/modules/notification/domain/notification\").Notification"
1113+
"$ref": "#/components/schemas/import(\"/Users/sciberbee/Documents/otl/otlplus-server/apps/server/src/modules/notification/domain/notification\").Notification"
11141114
}
11151115
]
11161116
}
@@ -2631,6 +2631,16 @@
26312631
"schema": {
26322632
"$ref": "#/components/schemas/IReviewV2.QueryDto"
26332633
}
2634+
},
2635+
{
2636+
"name": "accept-language",
2637+
"in": "header",
2638+
"required": false,
2639+
"schema": {
2640+
"type": "string",
2641+
"enum": ["en", "ko"]
2642+
},
2643+
"description": "en/ko (기본: ko). 언어 헤더 파라미터"
26342644
}
26352645
]
26362646
},
@@ -2933,6 +2943,16 @@
29332943
"schema": {
29342944
"type": "string"
29352945
}
2946+
},
2947+
{
2948+
"name": "accept-language",
2949+
"in": "header",
2950+
"required": false,
2951+
"schema": {
2952+
"type": "string",
2953+
"enum": ["en", "ko"]
2954+
},
2955+
"description": "en/ko (기본: ko). 언어 헤더 파라미터"
29362956
}
29372957
]
29382958
}
@@ -6550,6 +6570,9 @@
65506570
"direction": {
65516571
"type": "string",
65526572
"enum": ["ltr", "rtl"]
6573+
},
6574+
"lang": {
6575+
"type": "string"
65536576
}
65546577
},
65556578
"required": [
@@ -6576,7 +6599,8 @@
65766599
"textBaseline",
65776600
"textAlign",
65786601
"canvas",
6579-
"direction"
6602+
"direction",
6603+
"lang"
65806604
],
65816605
"additionalProperties": false
65826606
},
Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,25 @@
11
import { createParamDecorator, ExecutionContext } from '@nestjs/common'
22

3-
export const GetLanguage = createParamDecorator((_data, ctx: ExecutionContext): string => {
3+
export const GetLanguage = createParamDecorator((_data, ctx: ExecutionContext): Language => {
44
const req = ctx.switchToHttp().getRequest()
55
const acceptLanguageHeader = req.headers['accept-language']
66

77
if (typeof acceptLanguageHeader !== 'string' || !acceptLanguageHeader) {
8-
return 'kr'
8+
return 'ko'
99
}
1010

1111
const languages = acceptLanguageHeader.split(',').map((langPart) => langPart.trim().split(';')[0].toLowerCase())
1212

13-
if (languages.some((lang) => lang.startsWith('ko'))) {
14-
return 'kr'
13+
for (const lang of languages) {
14+
if (lang.startsWith('ko')) {
15+
return 'ko'
16+
}
17+
if (lang.startsWith('en')) {
18+
return 'en'
19+
}
1520
}
1621

17-
return 'en'
22+
return 'ko'
1823
})
24+
25+
export type Language = 'en' | 'ko'

apps/server/src/common/interfaces/ICourseV2.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,9 @@ export type CourseType = 'ALL' | 'BR' | 'BE' | 'MR' | 'ME' | 'MGC' | 'HSE' | 'GR
1414
export namespace ICourseV2 {
1515
export interface Basic {
1616
id: number
17-
name: string // kr : title / en : title_en
17+
name: string // ko : title / en : title_en
1818
code: string // new_code
19-
type: string // kr, en 구분
19+
type: string // ko, en 구분
2020
department: IDepartmentV2.Basic
2121
professors: IProfessorV2.Basic[]
2222
summary: string

apps/server/src/common/serializer/v2/review.v2.serializer.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { EReview } from '@otl/prisma-client/entities'
66
export const toJsonReviewV2 = (
77
review: EReview.Extended,
88
user?: session_userprofile | null,
9-
language: string = 'kr',
9+
language: string = 'ko',
1010
): IReviewV2.Basic => {
1111
let isLiked = false
1212
if (user && review.review_reviewvote) {

apps/server/src/common/serializer/v2/timetable.serializer.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { Language } from '@otl/server-nest/common/decorators/get-language.decorator'
12
import { ITimetableV2 } from '@otl/server-nest/common/interfaces/v2'
23
import { toJsonClasstime } from '@otl/server-nest/common/serializer/classtime.serializer'
34
import { toJsonExamtime } from '@otl/server-nest/common/serializer/examtime.serializer'
@@ -16,7 +17,7 @@ export const toJsonTimetableV2 = (timetable: ETimetable.Basic): ITimetableV2.Res
1617

1718
export const toJsonTimetableV2WithLectures = (
1819
timetable: ETimetable.Details,
19-
language: string,
20+
language: Language,
2021
): ITimetableV2.GetResDto => ({
2122
id: timetable.id,
2223
name: timetable.name ?? '',

apps/server/src/modules/courses/v2/course.v2.controller.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager'
22
import {
33
BadRequestException, Controller, ExecutionContext, Get, Param, Query, UseInterceptors,
44
} from '@nestjs/common'
5-
import { GetLanguage } from '@otl/server-nest/common/decorators/get-language.decorator'
5+
import { GetLanguage, Language } from '@otl/server-nest/common/decorators/get-language.decorator'
66
import { GetUser } from '@otl/server-nest/common/decorators/get-user.decorator'
77
import { Public } from '@otl/server-nest/common/decorators/skip-auth.decorator'
88
import { ICourseV2 } from '@otl/server-nest/common/interfaces'
@@ -42,7 +42,7 @@ export class CourseV2Controller {
4242
async getCourses(
4343
@Query() query: ICourseV2.Query,
4444
@GetUser() user: session_userprofile,
45-
@GetLanguage() language: 'kr' | 'en',
45+
@GetLanguage() language: Language,
4646
): Promise<ICourseV2.Basic[]> {
4747
const courses = await this.coursesService.getCourses(query, user, language)
4848
return courses
@@ -54,7 +54,7 @@ export class CourseV2Controller {
5454
@Public()
5555
async getCoursesPublic(
5656
@Query() query: ICourseV2.Query,
57-
@GetLanguage() language: 'kr' | 'en',
57+
@GetLanguage() language: Language,
5858
): Promise<ICourseV2.Basic[]> {
5959
const courses = await this.coursesService.getCourses(query, null, language)
6060
return courses
@@ -64,7 +64,7 @@ export class CourseV2Controller {
6464
async getCourseById(
6565
@Param('id') id: number,
6666
@GetUser() user: session_userprofile,
67-
@GetLanguage() language: 'kr' | 'en',
67+
@GetLanguage() language: Language,
6868
): Promise<ICourseV2.Detail> {
6969
// 숫자 형식이 아닌 경우
7070
if (Number.isNaN(id)) throw new BadRequestException('Invalid course id')
@@ -85,7 +85,7 @@ export class CourseV2Controller {
8585
@CacheTTL(CourseV2Controller.cacheTTLFactory)
8686
@UseInterceptors(CacheInterceptor)
8787
@Public()
88-
async getCourseByIdPublic(@Param('id') id: number, @GetLanguage() language: 'kr' | 'en'): Promise<ICourseV2.Detail> {
88+
async getCourseByIdPublic(@Param('id') id: number, @GetLanguage() language: Language): Promise<ICourseV2.Detail> {
8989
// 숫자 형식이 아닌 경우
9090
if (Number.isNaN(id)) throw new BadRequestException('Invalid course id')
9191
try {

apps/server/src/modules/courses/v2/courses.v2.service.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import { ECourseV2 } from '@otl/prisma-client/entities'
66
import { CourseRepositoryV2 } from '@otl/prisma-client/repositories'
77
import { ProfessorRepositoryV2 } from '@otl/prisma-client/repositories/professor.repository.v2'
88

9-
type language = 'kr' | 'en'
9+
type language = 'ko' | 'en'
1010

1111
type courseHistory = {
1212
year: number

apps/server/src/modules/lectures/v2/lectures.v2.controller.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager'
22
import {
33
Controller, ExecutionContext, Get, Query, UseInterceptors,
44
} from '@nestjs/common'
5-
import { GetLanguage } from '@otl/server-nest/common/decorators/get-language.decorator'
5+
import { GetLanguage, Language } from '@otl/server-nest/common/decorators/get-language.decorator'
66
import { GetUser } from '@otl/server-nest/common/decorators/get-user.decorator'
77
import { Public } from '@otl/server-nest/common/decorators/skip-auth.decorator'
88
import { ILectureV2 } from '@otl/server-nest/common/interfaces'
@@ -40,7 +40,7 @@ export class LecturesControllerV2 {
4040
async getLectures(
4141
@Query() query: ILectureV2.getQuery,
4242
@GetUser() user: session_userprofile,
43-
@GetLanguage() language: 'kr' | 'en',
43+
@GetLanguage() language: Language,
4444
): Promise<ILectureV2.courseWrapped> {
4545
return await this.LectureService.getLectureByFilter(query, user, language)
4646
}
@@ -51,7 +51,7 @@ export class LecturesControllerV2 {
5151
@Get('/public')
5252
async getLecturesPublic(
5353
@Query() query: ILectureV2.getQuery,
54-
@GetLanguage() language: 'kr' | 'en',
54+
@GetLanguage() language: Language,
5555
): Promise<ILectureV2.courseWrapped> {
5656
return await this.LectureService.getLectureByFilter(query, null, language)
5757
}

apps/server/src/modules/lectures/v2/lectures.v2.service.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { Injectable } from '@nestjs/common'
2+
import { Language } from '@otl/server-nest/common/decorators/get-language.decorator'
23
import { ILectureV2 } from '@otl/server-nest/common/interfaces'
34
import { session_userprofile } from '@prisma/client'
45

@@ -14,7 +15,7 @@ export class LecturesServiceV2 {
1415
public async getLectureByFilter(
1516
query: ILectureV2.getQuery,
1617
user: session_userprofile | null,
17-
lang: 'kr' | 'en',
18+
language: Language,
1819
): Promise<ILectureV2.courseWrapped> {
1920
// Helpers
2021
const toMinutes = (v: unknown): number => {
@@ -35,7 +36,7 @@ export class LecturesServiceV2 {
3536
const {
3637
keyword, type, department, level, year, semester, day, begin, end, order, limit, offset,
3738
} = query
38-
const choose = (kr?: string | null, en?: string | null) => (lang === 'en' ? (en ?? '').trim() || (kr ?? '') : (kr ?? '').trim() || (en ?? ''))
39+
const choose = (ko?: string | null, en?: string | null) => (language === 'en' ? (en ?? '').trim() || (ko ?? '') : (ko ?? '').trim() || (en ?? ''))
3940
const lectures = await this.lectureRepository.filterByRequest(
4041
keyword,
4142
type as unknown as string[] | undefined,

0 commit comments

Comments
 (0)