Skip to content

Commit

Permalink
Merge pull request #209 from wafflestudio/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
Hank-Choi authored Jan 18, 2024
2 parents 4de7558 + 8e6b5d5 commit 205adf5
Show file tree
Hide file tree
Showing 30 changed files with 940 additions and 139 deletions.
3 changes: 2 additions & 1 deletion api/src/main/kotlin/filter/ErrorWebFilter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -58,13 +58,14 @@ class ErrorWebFilter(
private fun makeErrorBody(
exception: Snu4tException,
): ErrorBody {
return ErrorBody(exception.error.errorCode, exception.errorMessage, exception.displayMessage)
return ErrorBody(exception.error.errorCode, exception.errorMessage, exception.displayMessage, exception.ext)
}
}

private data class ErrorBody(
val errcode: Long,
val message: String,
val displayMessage: String,
// TODO: 구버전 대응용 ext 필드. 추후 삭제
val ext: Map<String, String> = mapOf(),
)
4 changes: 2 additions & 2 deletions api/src/main/kotlin/handler/TimetableHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -97,9 +97,9 @@ class TimetableHandler(
suspend fun modifyTimetableTheme(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
val theme = req.awaitBody<TimetableModifyThemeRequestDto>().theme
val body = req.awaitBody<TimetableModifyThemeRequestDto>()

timetableService.modifyTimetableTheme(userId, timetableId, theme).let(::TimetableLegacyDto)
timetableService.modifyTimetableTheme(userId, timetableId, body.theme, body.themeId).let(::TimetableLegacyDto)
}

suspend fun setPrimary(req: ServerRequest): ServerResponse = handle(req) {
Expand Down
23 changes: 14 additions & 9 deletions api/src/main/kotlin/handler/TimetableLectureHandler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@ package com.wafflestudio.snu4t.handler

import com.fasterxml.jackson.annotation.JsonProperty
import com.wafflestudio.snu4t.middleware.SnuttRestApiDefaultMiddleware
import com.wafflestudio.snu4t.timetables.dto.TimetableLegacyDto
import com.wafflestudio.snu4t.timetables.dto.request.CustomTimetableLectureAddLegacyRequestDto
import com.wafflestudio.snu4t.timetables.dto.request.TimetableLectureModifyLegacyRequestDto
import com.wafflestudio.snu4t.timetables.service.TimetableLectureService
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.awaitBody
import org.springframework.web.reactive.function.server.awaitBodyOrNull

@Component
class TimetableLectureHandler(
Expand All @@ -21,55 +23,58 @@ class TimetableLectureHandler(
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
val customTimetable = req.awaitBody<CustomTimetableLectureAddLegacyRequestDto>()
val isForced = customTimetable.isForced
val isForced = req.parseQueryParam<Boolean>("isForced") ?: customTimetable.isForced

timetableLectureService.addCustomTimetableLecture(
userId = userId,
timetableId = timetableId,
timetableLectureRequest = customTimetable,
isForced = isForced,
)
).let(::TimetableLegacyDto)
}

suspend fun addLecture(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
val lectureId = req.pathVariable("lectureId")
val isForced = req.awaitBody<ForcedReq>().isForced
val isForced = req.parseQueryParam<Boolean>("isForced") ?: req.awaitBodyOrNull<ForcedReq>()?.isForced ?: false

timetableLectureService.addLecture(
userId = userId,
timetableId = timetableId,
lectureId = lectureId,
isForced = isForced,
)
).let(::TimetableLegacyDto)
}

suspend fun resetTimetableLecture(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
val timetableLectureId = req.pathVariable("timetableLectureId")
val isForced = req.parseQueryParam<Boolean>("isForced") ?: req.awaitBodyOrNull<ForcedReq>()?.isForced ?: false

timetableLectureService.resetTimetableLecture(
userId = userId,
timetableId = timetableId,
timetableLectureId = timetableLectureId,
)
isForced,
).let(::TimetableLegacyDto)
}

suspend fun modifyTimetableLecture(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
val timetableLectureId = req.pathVariable("timetableLectureId")
val modifyRequestDto = req.awaitBody<TimetableLectureModifyLegacyRequestDto>()
val isForced = modifyRequestDto.isForced
val isForced = req.parseQueryParam<Boolean>("isForced") ?: modifyRequestDto.isForced

timetableLectureService.modifyTimetableLecture(
userId = userId,
timetableId = timetableId,
timetableLectureId = timetableLectureId,
modifyTimetableLectureRequestDto = modifyRequestDto,
isForced = isForced,
)
).let(::TimetableLegacyDto)
}

suspend fun deleteTimetableLecture(req: ServerRequest): ServerResponse = handle(req) {
Expand All @@ -81,11 +86,11 @@ class TimetableLectureHandler(
userId = userId,
timetableId = timetableId,
timetableLectureId = timetableLectureId,
)
).let(::TimetableLegacyDto)
}

data class ForcedReq(
@JsonProperty("is_forced")
val isForced: Boolean
val isForced: Boolean?
)
}
74 changes: 74 additions & 0 deletions api/src/main/kotlin/handler/TimetableThemeHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package com.wafflestudio.snu4t.handler

import com.wafflestudio.snu4t.common.enum.BasicThemeType
import com.wafflestudio.snu4t.common.exception.InvalidPathParameterException
import com.wafflestudio.snu4t.middleware.SnuttRestApiDefaultMiddleware
import com.wafflestudio.snu4t.timetables.dto.TimetableThemeDto
import com.wafflestudio.snu4t.timetables.dto.request.TimetableThemeAddRequestDto
import com.wafflestudio.snu4t.timetables.dto.request.TimetableThemeModifyRequestDto
import com.wafflestudio.snu4t.timetables.service.TimetableThemeService
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.awaitBody

@Component
class TimetableThemeHandler(
private val timetableThemeService: TimetableThemeService,
snuttRestApiDefaultMiddleware: SnuttRestApiDefaultMiddleware,
) : ServiceHandler(snuttRestApiDefaultMiddleware) {
suspend fun getThemes(req: ServerRequest) = handle(req) {
val userId = req.userId

timetableThemeService.getThemes(userId).map(::TimetableThemeDto)
}

suspend fun addTheme(req: ServerRequest) = handle(req) {
val userId = req.userId
val body = req.awaitBody<TimetableThemeAddRequestDto>()

timetableThemeService.addTheme(userId, body.name, body.colors).let(::TimetableThemeDto)
}

suspend fun modifyTheme(req: ServerRequest) = handle(req) {
val userId = req.userId
val themeId = req.pathVariable("themeId")
val body = req.awaitBody<TimetableThemeModifyRequestDto>()

timetableThemeService.modifyTheme(userId, themeId, body.name, body.colors).let(::TimetableThemeDto)
}

suspend fun deleteTheme(req: ServerRequest) = handle(req) {
val userId = req.userId
val themeId = req.pathVariable("themeId")

timetableThemeService.deleteTheme(userId, themeId)
}

suspend fun copyTheme(req: ServerRequest) = handle(req) {
val userId = req.userId
val themeId = req.pathVariable("themeId")

timetableThemeService.copyTheme(userId, themeId).let(::TimetableThemeDto)
}

suspend fun setDefault(req: ServerRequest) = handle(req) {
val userId = req.userId
val themeId = req.pathVariable("themeId")

timetableThemeService.setDefault(userId, themeId).let(::TimetableThemeDto)
}

suspend fun setBasicThemeTypeDefault(req: ServerRequest) = handle(req) {
val userId = req.userId
val basicThemeType = req.pathVariable("basicThemeTypeValue").toIntOrNull()?.let { BasicThemeType.from(it) } ?: throw InvalidPathParameterException("basicThemeTypeValue")

timetableThemeService.setDefault(userId, basicThemeType = basicThemeType).let(::TimetableThemeDto)
}

suspend fun unsetDefault(req: ServerRequest) = handle(req) {
val userId = req.userId
val themeId = req.pathVariable("themeId")

timetableThemeService.unsetDefault(userId, themeId).let(::TimetableThemeDto)
}
}
18 changes: 18 additions & 0 deletions api/src/main/kotlin/router/MainRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import com.wafflestudio.snu4t.handler.LectureSearchHandler
import com.wafflestudio.snu4t.handler.NotificationHandler
import com.wafflestudio.snu4t.handler.TimetableHandler
import com.wafflestudio.snu4t.handler.TimetableLectureHandler
import com.wafflestudio.snu4t.handler.TimetableThemeHandler
import com.wafflestudio.snu4t.handler.UserHandler
import com.wafflestudio.snu4t.handler.VacancyNotifcationHandler
import com.wafflestudio.snu4t.router.docs.AdminDocs
Expand All @@ -20,6 +21,7 @@ import com.wafflestudio.snu4t.router.docs.ConfigDocs
import com.wafflestudio.snu4t.router.docs.FriendDocs
import com.wafflestudio.snu4t.router.docs.LectureSearchDocs
import com.wafflestudio.snu4t.router.docs.NotificationDocs
import com.wafflestudio.snu4t.router.docs.ThemeDocs
import com.wafflestudio.snu4t.router.docs.TimetableDocs
import com.wafflestudio.snu4t.router.docs.UserDocs
import com.wafflestudio.snu4t.router.docs.VacancyNotificationDocs
Expand All @@ -39,6 +41,7 @@ class MainRouter(
private val vacancyNotificationHandler: VacancyNotifcationHandler,
private val timeTableHandler: TimetableHandler,
private val timeTableLectureHandler: TimetableLectureHandler,
private val timetableThemeHandler: TimetableThemeHandler,
private val bookmarkHandler: BookmarkHandler,
private val lectureSearchHandler: LectureSearchHandler,
private val friendHandler: FriendHandler,
Expand Down Expand Up @@ -174,6 +177,21 @@ class MainRouter(
}
}

@Bean
@ThemeDocs
fun timetableThemeRoute() = v1CoRouter {
"/themes".nest {
GET("", timetableThemeHandler::getThemes)
POST("", timetableThemeHandler::addTheme)
PATCH("{themeId}", timetableThemeHandler::modifyTheme)
DELETE("{themeId}", timetableThemeHandler::deleteTheme)
POST("{themeId}/copy", timetableThemeHandler::copyTheme)
POST("{themeId}/default", timetableThemeHandler::setDefault)
POST("basic/{basicThemeTypeValue}/default", timetableThemeHandler::setBasicThemeTypeDefault)
DELETE("{themeId}/default", timetableThemeHandler::unsetDefault)
}
}

private fun v1CoRouter(r: CoRouterFunctionDsl.() -> Unit) = coRouter {
path("/v1").or("").nest(r)
}
Expand Down
91 changes: 91 additions & 0 deletions api/src/main/kotlin/router/docs/ThemeDocs.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package com.wafflestudio.snu4t.router.docs

import com.wafflestudio.snu4t.timetables.dto.TimetableThemeDto
import com.wafflestudio.snu4t.timetables.dto.request.TimetableThemeAddRequestDto
import io.swagger.v3.oas.annotations.Operation
import io.swagger.v3.oas.annotations.Parameter
import io.swagger.v3.oas.annotations.enums.ParameterIn
import io.swagger.v3.oas.annotations.media.ArraySchema
import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.parameters.RequestBody
import io.swagger.v3.oas.annotations.responses.ApiResponse
import org.springdoc.core.annotations.RouterOperation
import org.springdoc.core.annotations.RouterOperations
import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.RequestMethod

@RouterOperations(
RouterOperation(
path = "/v1/themes", method = [RequestMethod.GET], produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
operationId = "getThemes",
parameters = [Parameter(`in` = ParameterIn.QUERY, name = "state", required = true)],
responses = [ApiResponse(responseCode = "200", content = [Content(array = ArraySchema(schema = Schema(implementation = TimetableThemeDto::class)))])],
),
),
RouterOperation(
path = "/v1/themes", method = [RequestMethod.POST], produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
operationId = "addTheme",
requestBody = RequestBody(
content = [Content(schema = Schema(implementation = TimetableThemeAddRequestDto::class))],
required = true,
),
responses = [ApiResponse(responseCode = "200", content = [Content(schema = Schema(implementation = TimetableThemeDto::class))])],
),
),
RouterOperation(
path = "/v1/themes/{themeId}", method = [RequestMethod.PATCH], produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
operationId = "modifyTheme",
parameters = [Parameter(`in` = ParameterIn.PATH, name = "themeId", required = true)],
requestBody = RequestBody(
content = [Content(schema = Schema(implementation = TimetableThemeAddRequestDto::class))],
required = true,
),
responses = [ApiResponse(responseCode = "200", content = [Content(schema = Schema(implementation = TimetableThemeDto::class))])],
),
),
RouterOperation(
path = "/v1/themes/{themeId}", method = [RequestMethod.DELETE], produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
operationId = "deleteTheme",
parameters = [Parameter(`in` = ParameterIn.PATH, name = "themeId", required = true)],
responses = [ApiResponse(responseCode = "200", content = [Content(schema = Schema())])],
),
),
RouterOperation(
path = "/v1/themes/{themeId}/copy", method = [RequestMethod.POST], produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
operationId = "copyTheme",
parameters = [Parameter(`in` = ParameterIn.PATH, name = "themeId", required = true)],
responses = [ApiResponse(responseCode = "200", content = [Content(schema = Schema(implementation = TimetableThemeDto::class))])],
),
),
RouterOperation(
path = "/v1/themes/{themeId}/default", method = [RequestMethod.POST], produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
operationId = "setDefault",
parameters = [Parameter(`in` = ParameterIn.PATH, name = "themeId", required = true)],
responses = [ApiResponse(responseCode = "200", content = [Content(schema = Schema(implementation = TimetableThemeDto::class))])],
),
),
RouterOperation(
path = "/v1/themes/basic/{basicThemeTypeValue}/default", method = [RequestMethod.POST], produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
operationId = "setBasicThemeTypeDefault",
parameters = [Parameter(`in` = ParameterIn.PATH, name = "basicThemeTypeValue", required = true)],
responses = [ApiResponse(responseCode = "200", content = [Content(schema = Schema(implementation = TimetableThemeDto::class))])],
),
),
RouterOperation(
path = "/v1/themes/{themeId}/default", method = [RequestMethod.DELETE], produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
operationId = "unsetDefault",
parameters = [Parameter(`in` = ParameterIn.PATH, name = "themeId", required = true)],
responses = [ApiResponse(responseCode = "200", content = [Content(schema = Schema(implementation = TimetableThemeDto::class))])],
),
),
)
annotation class ThemeDocs
Loading

0 comments on commit 205adf5

Please sign in to comment.