Skip to content

Commit

Permalink
timetable 관련 코드 작성 (시간표 내 강의 관련 제외) (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
Hank-Choi authored Aug 16, 2023
1 parent 279b440 commit cc17563
Show file tree
Hide file tree
Showing 13 changed files with 251 additions and 70 deletions.
4 changes: 2 additions & 2 deletions api/src/main/kotlin/handler/ServiceHandler.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.wafflestudio.snu4t.handler

import com.wafflestudio.snu4t.common.exception.InvalidParameterException
import com.wafflestudio.snu4t.common.exception.InvalidQueryParameterException
import com.wafflestudio.snu4t.common.exception.MissingRequiredParameterException
import com.wafflestudio.snu4t.middleware.Middleware
import com.wafflestudio.snu4t.middleware.plus
Expand Down Expand Up @@ -30,7 +30,7 @@ abstract class ServiceHandler(val handlerMiddleware: Middleware = Middleware.NoO

fun <T> ServerRequest.parseQueryParam(name: String, convert: (String) -> T?): T? =
this.queryParamOrNull(name)?.runCatching { convert(this)!! }
?.getOrElse { throw InvalidParameterException(name) }
?.getOrElse { throw InvalidQueryParameterException(name) }

inline fun <reified T> ServerRequest.parseRequiredQueryParam(name: String): T =
parseQueryParam<T>(name)
Expand Down
124 changes: 118 additions & 6 deletions api/src/main/kotlin/handler/TimetableHandler.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package com.wafflestudio.snu4t.handler

import com.wafflestudio.snu4t.common.enum.Semester
import com.wafflestudio.snu4t.common.exception.InvalidPathParameterException
import com.wafflestudio.snu4t.middleware.SnuttRestApiDefaultMiddleware
import com.wafflestudio.snu4t.timetables.dto.TimetableDto
import com.wafflestudio.snu4t.timetables.dto.request.TimetableAddRequestDto
import com.wafflestudio.snu4t.timetables.dto.request.TimetableModifyRequestDto
import com.wafflestudio.snu4t.timetables.dto.request.TimetableModifyThemeRequestDto
import com.wafflestudio.snu4t.timetables.service.TimetableService
import kotlinx.coroutines.flow.toList
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 timetables.dto.TimetableBriefDto

@Component
class TimetableHandler(
Expand All @@ -13,19 +22,122 @@ class TimetableHandler(
) : ServiceHandler(
handlerMiddleware = snuttRestApiDefaultMiddleware
) {
suspend fun getBriefs(req: ServerRequest): ServerResponse = handle(req) {
suspend fun getTimetableBriefs(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId

timeTableService.getBriefs(userId = userId)
timeTableService.getTimetables(userId = userId).map(::TimetableBriefDto)
}

suspend fun getLink(req: ServerRequest): ServerResponse = handle(req) {
val timetableId = req.pathVariable("id")
timeTableService.getLink(timetableId)
suspend fun getMostRecentlyUpdatedTimetables(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId

timeTableService.getMostRecentlyUpdatedTimetable(userId).let(::TimetableDto)
}

suspend fun getTimetablesBySemester(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val year = req.pathVariable("year").toInt()
val semester =
Semester.getOfValue(req.pathVariable("semester").toInt()) ?: throw InvalidPathParameterException("semester")

timeTableService.getTimetablesBySemester(userId, year, semester).toList().map(::TimetableDto)
}

suspend fun addTimetable(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val sourceTimetableId = req.parseQueryParam<String>("source")
val body = req.awaitBody<TimetableAddRequestDto>()

if (sourceTimetableId == null) {
timeTableService.addTimetable(userId, body)
} else {
timeTableService.copyTimetable(userId, sourceTimetableId)
}
timeTableService.getTimetables(userId = userId).map(::TimetableBriefDto)
}

suspend fun getTimetable(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")

timeTableService.getTimetable(userId, timetableId).let(::TimetableDto)
}

suspend fun modifyTimetable(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
val body = req.awaitBody<TimetableModifyRequestDto>()

timeTableService.modifyTimetableTitle(userId, timetableId, body.title)
timeTableService.getTimetables(userId = userId).map(::TimetableBriefDto)
}

suspend fun deleteTimetable(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")

timeTableService.deleteTimetable(userId, timetableId)
timeTableService.getTimetables(userId = userId).map(::TimetableBriefDto)
}

suspend fun getTimetableLink(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
timeTableService.getTimetableLink(userId, timetableId)
}

suspend fun copyTimetable(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")

timeTableService.copyTimetable(userId, timetableId)
timeTableService.getTimetables(userId = userId).map(::TimetableBriefDto)
}

suspend fun modifyTimetableTheme(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
val theme = req.awaitBody<TimetableModifyThemeRequestDto>().theme

timeTableService.modifyTimetableTheme(userId, timetableId, theme).let(::TimetableDto)
}

suspend fun addCustomLecture(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
TODO("Not yet implemented")
}

suspend fun addLecture(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
val lectureId = req.pathVariable("lectureId")
TODO("Not yet implemented")
}

suspend fun resetTimetableLecture(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
val timetableLectureId = req.pathVariable("timetableLectureId")
TODO("Not yet implemented")
}

suspend fun modifyTimetableLecture(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
val timetableLectureId = req.pathVariable("timetableLectureId")
TODO("Not yet implemented")
}

suspend fun deleteTimetableLecture(req: ServerRequest): ServerResponse = handle(req) {
val userId = req.userId
val timetableId = req.pathVariable("timetableId")
val lectureId = req.pathVariable("timetableLectureId")
TODO("Not yet implemented")
}

suspend fun setPrimary(req: ServerRequest): ServerResponse = handle(req) {
val timetableId = req.pathVariable("id")
val timetableId = req.pathVariable("timetableId")
timeTableService.setPrimary(req.userId, timetableId)
}
}
24 changes: 19 additions & 5 deletions api/src/main/kotlin/router/MainRouter.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,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.TableDocs
import com.wafflestudio.snu4t.router.docs.TimetableDocs
import com.wafflestudio.snu4t.router.docs.UserDocs
import com.wafflestudio.snu4t.router.docs.VacancyNotificationDocs
import org.springframework.context.annotation.Bean
Expand Down Expand Up @@ -74,12 +74,26 @@ class MainRouter(
}

@Bean
@TableDocs
@TimetableDocs
fun tableRoute() = v1CoRouter {
"/tables".nest {
GET("", timeTableHandler::getBriefs)
GET("/{id}/links", timeTableHandler::getLink)
POST("/{id}/primary", timeTableHandler::setPrimary)
GET("", timeTableHandler::getTimetableBriefs)
GET("/recent", timeTableHandler::getMostRecentlyUpdatedTimetables)
GET("/{year}/{semester}", timeTableHandler::getTimetablesBySemester)
POST("", timeTableHandler::addTimetable)
GET("/{timetableId}", timeTableHandler::getTimetable)
PUT("/{timetableId}", timeTableHandler::modifyTimetable)
DELETE("/{timetableId}", timeTableHandler::deleteTimetable)
POST("/{timetableId}/copy", timeTableHandler::copyTimetable)
PUT("/{timetableId}/theme", timeTableHandler::modifyTimetableTheme)
POST("/{timetableId}/primary", timeTableHandler::setPrimary)
"{timetableId}/lecture".nest {
POST("", timeTableHandler::addCustomLecture)
POST("/{lectureId}", timeTableHandler::addLecture)
PUT("/{timetableLectureId}/reset", timeTableHandler::resetTimetableLecture)
PUT("/{timetableLectureId}", timeTableHandler::modifyTimetableLecture)
DELETE("/{timetableLectureId}", timeTableHandler::deleteTimetableLecture)
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package com.wafflestudio.snu4t.router.docs

import com.wafflestudio.snu4t.common.dynamiclink.dto.DynamicLinkResponse
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.Content
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.responses.ApiResponse
Expand All @@ -21,14 +18,6 @@ import timetables.dto.TimetableBriefDto
responses = [ApiResponse(responseCode = "200", content = [Content(schema = Schema(implementation = TimetableBriefDto::class))])]
),
),
RouterOperation(
path = "/v1/tables/{id}/links", method = [RequestMethod.GET], produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
operationId = "getLink",
parameters = [Parameter(`in` = ParameterIn.PATH, name = "id", required = true)],
responses = [ApiResponse(responseCode = "200", content = [Content(schema = Schema(implementation = DynamicLinkResponse::class))])]
),
),
RouterOperation(
path = "/v1/tables/{id}/primary", method = [RequestMethod.POST], produces = [MediaType.APPLICATION_JSON_VALUE],
operation = Operation(
Expand All @@ -37,4 +26,4 @@ import timetables.dto.TimetableBriefDto
),
),
)
annotation class TableDocs
annotation class TimetableDocs
1 change: 1 addition & 0 deletions core/src/main/kotlin/common/exception/ErrorType.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ enum class ErrorType(
INVALID_LOCAL_ID(HttpStatus.FORBIDDEN, 0x3000, "localId가 유효하지 않습니다.", "아이디는 4~32자의 영문자와 숫자로 이루어져야 합니다."),
INVALID_PASSWORD(HttpStatus.FORBIDDEN, 0x3001, "password가 유효하지 않습니다.", "비밀번호는 6~20자로 영문자와 숫자를 모두 포함해야 합니다."),
DUPLICATE_LOCAL_ID(HttpStatus.FORBIDDEN, 0x3002, "localId가 중복되었습니다.", "이미 사용 중인 아이디입니다."),
DUPLICATE_TIMETABLE_TITLE(HttpStatus.FORBIDDEN, 0x3003, "timetable title이 중복되었습니다.", "이미 사용중인 시간표 이름입니다."),
INVALID_EMAIL(HttpStatus.FORBIDDEN, 0x300F, "email이 유효하지 않습니다.", "이메일 형식이 올바르지 않습니다."),

LECTURE_NOT_FOUND(HttpStatus.NOT_FOUND, 0x4003, "lecture가 없습니다.", "해당 강의는 존재하지 않습니다."),
Expand Down
6 changes: 5 additions & 1 deletion core/src/main/kotlin/common/exception/Snu4tException.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ object UserNotFoundException : Snu4tException(ErrorType.USER_NOT_FOUND)
class MissingRequiredParameterException(fieldName: String) :
Snu4tException(ErrorType.MISSING_PARAMETER, "필수값이 누락되었습니다. ($fieldName)")

class InvalidParameterException(fieldName: String) :
class InvalidPathParameterException(fieldName: String) :
Snu4tException(ErrorType.INVALID_PARAMETER, "잘못된 값입니다. (path parameter: $fieldName)")

class InvalidQueryParameterException(fieldName: String) :
Snu4tException(ErrorType.INVALID_PARAMETER, "잘못된 값입니다. (query parameter: $fieldName)")

class InvalidBodyFieldValueException(fieldName: String) :
Expand All @@ -38,6 +41,7 @@ object NoUserFcmKeyException : Snu4tException(ErrorType.NO_USER_FCM_KEY)
object InvalidRegistrationForPreviousSemesterCourseException :
Snu4tException(ErrorType.INVALID_REGISTRATION_FOR_PREVIOUS_SEMESTER_COURSE)

object DuplicateTimetableTitleException : Snu4tException(ErrorType.DUPLICATE_TIMETABLE_TITLE)
object TimetableNotFoundException : Snu4tException(ErrorType.TIMETABLE_NOT_FOUND)
object ConfigNotFoundException : Snu4tException(ErrorType.CONFIG_NOT_FOUND)
object FriendNotFoundException : Snu4tException(ErrorType.FRIEND_NOT_FOUND)
Expand Down
Empty file.
4 changes: 2 additions & 2 deletions core/src/main/kotlin/timetables/data/Timetable.kt
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ data class Timetable(
@JsonProperty("lecture_list")
var lectures: List<TimetableLecture> = emptyList(),
var title: String,
val theme: TimetableTheme,
var theme: TimetableTheme,
@Field("is_primary")
val isPrimary: Boolean? = null,
var isPrimary: Boolean? = null,
@Field("updated_at")
@JsonProperty("updated_at")
var updatedAt: Instant = Instant.now(),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.wafflestudio.snu4t.timetables.dto.request

import com.wafflestudio.snu4t.common.enum.Semester

data class TimetableAddRequestDto(
val year: Int,
val semester: Semester,
val title: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package com.wafflestudio.snu4t.timetables.dto.request

data class TimetableModifyRequestDto(
val title: String,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.wafflestudio.snu4t.timetables.dto.request

import com.wafflestudio.snu4t.common.enum.TimetableTheme

data class TimetableModifyThemeRequestDto(
val theme: TimetableTheme,
)
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ import org.springframework.data.repository.kotlin.CoroutineCrudRepository

interface TimetableRepository : CoroutineCrudRepository<Timetable, String>, TimetableCustomRepository {
fun findAllByUserId(userId: String): Flow<Timetable>
fun findAllByUserIdAndYearAndSemester(userId: String, year: Int, semester: Semester): Flow<Timetable>
suspend fun findByUserIdAndId(userId: String, id: String): Timetable?
suspend fun findByUserIdOrderByUpdatedAtDesc(userId: String): Timetable?
suspend fun findByUserIdAndYearAndSemesterAndTitle(userId: String, year: Int, semester: Semester, title: String): Timetable?
fun findByUserIdAndYearAndSemester(userId: String, year: Int, semester: Semester): Flow<Timetable>
suspend fun findByUserIdAndYearAndSemesterAndIsPrimaryTrue(userId: String, year: Int, semester: Semester): Timetable?
suspend fun existsByUserIdAndYearAndSemesterAndTitle(userId: String, year: Int, semester: Semester, title: String): Boolean
Expand Down
Loading

0 comments on commit cc17563

Please sign in to comment.