Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#60 Added InvalidArgument and NotFound exceptions #61

Merged
merged 5 commits into from
Apr 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import ru.vityaman.lms.botalka.api.http.message.toModel
import ru.vityaman.lms.botalka.api.http.server.UserDraftMessage
import ru.vityaman.lms.botalka.api.http.server.UserMessage
import ru.vityaman.lms.botalka.api.http.server.apis.UserApi
import ru.vityaman.lms.botalka.domain.exception.orNotFound
import ru.vityaman.lms.botalka.domain.model.User
import ru.vityaman.lms.botalka.logic.UserService

Expand All @@ -18,6 +19,7 @@ class UserHttpApi(
override suspend fun userIdGet(id: Int): ResponseEntity<UserMessage> {
val userId = User.Id(id)
val user = userService.getById(userId)
.orNotFound("User with id ${userId.number} not found")
return ResponseEntity.ok(user.toMessage())
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package ru.vityaman.lms.botalka.api.http.error

import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import ru.vityaman.lms.botalka.api.http.server.GeneralErrorMessage
import ru.vityaman.lms.botalka.domain.exception.DomainException
import ru.vityaman.lms.botalka.domain.exception.InvalidValueException
import ru.vityaman.lms.botalka.domain.exception.NotFoundException

val DomainException.httpCode: HttpStatus
get() = when (this) {
is InvalidValueException -> HttpStatus.NOT_FOUND
is NotFoundException -> HttpStatus.NOT_FOUND
else -> TODO()
}

val HttpStatus.reason: String
get() = this.reasonPhrase

fun DomainException.toResponseEntity() =
ResponseEntity
.status(this.httpCode)
.body(
GeneralErrorMessage(
code = this.httpCode.value(),
status = this.httpCode.reasonPhrase,
message = this.message!!,
),
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ru.vityaman.lms.botalka.api.http.error

import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import ru.vityaman.lms.botalka.domain.exception.DomainException

@RestControllerAdvice
class DomainExceptionMapping {
@ExceptionHandler(DomainException::class)
fun handle(exception: DomainException) =
exception.toResponseEntity()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package ru.vityaman.lms.botalka.api.http.message

import ru.vityaman.lms.botalka.api.http.server.WorkspaceCommentMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceEventKindMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceEventMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceFeedbackMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceSubmissionMessage
import ru.vityaman.lms.botalka.domain.model.Workspace

private val Workspace.Event.kind
get() =
when (this) {
is Workspace.Comment -> WorkspaceEventKindMessage.COMMENT
is Workspace.Feedback -> WorkspaceEventKindMessage.FEEDBACK
is Workspace.Submission -> WorkspaceEventKindMessage.SUBMISSION
}

fun Workspace.Comment.toMessage() =
WorkspaceCommentMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
text = this.text,
creationMoment = this.creationMoment,
)

fun Workspace.Feedback.toMessage() =
WorkspaceFeedbackMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
comment = this.comment,
score = this.score?.value?.toInt(),
creationMoment = this.creationMoment,
)

fun Workspace.Submission.toMessage() =
WorkspaceSubmissionMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
note = this.note,
creationMoment = this.creationMoment,
)

fun Workspace.Event.toMessage(): WorkspaceEventMessage =
when (this) {
is Workspace.Comment -> this.toMessage()
is Workspace.Feedback -> this.toMessage()
is Workspace.Submission -> this.toMessage()
}
Original file line number Diff line number Diff line change
@@ -1,14 +1,9 @@
package ru.vityaman.lms.botalka.api.http.message

import ru.vityaman.lms.botalka.api.http.server.WorkspaceCommentDraftMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceCommentMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceEventDraftMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceEventKindMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceEventMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceFeedbackDraftMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceFeedbackMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceSubmissionDraftMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceSubmissionMessage
import ru.vityaman.lms.botalka.domain.model.Homework
import ru.vityaman.lms.botalka.domain.model.Teacher
import ru.vityaman.lms.botalka.domain.model.User
Expand Down Expand Up @@ -56,45 +51,3 @@ fun WorkspaceFeedbackDraftMessage.toModel(producer: User.Id) =
comment = this.comment,
score = this.score?.let { Homework.Score(it.toShort()) },
)

private val Workspace.Event.kind
get() =
when (this) {
is Workspace.Comment -> WorkspaceEventKindMessage.COMMENT
is Workspace.Feedback -> WorkspaceEventKindMessage.FEEDBACK
is Workspace.Submission -> WorkspaceEventKindMessage.SUBMISSION
}

fun Workspace.Event.toMessage(): WorkspaceEventMessage =
when (this) {
is Workspace.Comment -> {
WorkspaceCommentMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
text = this.text,
creationMoment = this.creationMoment,
)
}

is Workspace.Feedback -> {
WorkspaceFeedbackMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
comment = this.comment,
score = this.score?.value?.toInt(),
creationMoment = this.creationMoment,
)
}

is Workspace.Submission -> {
WorkspaceSubmissionMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
note = this.note,
creationMoment = this.creationMoment,
)
}
}
12 changes: 0 additions & 12 deletions botalka/src/main/kotlin/ru/vityaman/lms/botalka/commons/Require.kt

This file was deleted.

15 changes: 15 additions & 0 deletions botalka/src/main/kotlin/ru/vityaman/lms/botalka/domain/Require.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package ru.vityaman.lms.botalka.domain

import ru.vityaman.lms.botalka.domain.exception.InvalidValueException

fun expect(isValid: Boolean, message: StringBuilder.() -> Unit) {
if (!isValid) {
throw InvalidValueException(buildString(message))
}
}

fun expectId(number: Int) {
expect(0 < number) {
append("Unique id must be a positive, got $number")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ru.vityaman.lms.botalka.domain.exception

class InvalidValueException(string: String) :
DomainException(string)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ru.vityaman.lms.botalka.domain.exception

class NotFoundException(message: String) :
DomainException(message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package ru.vityaman.lms.botalka.domain.exception

fun <T> T?.orNotFound(message: String): T =
this ?: throw NotFoundException(message)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package ru.vityaman.lms.botalka.domain.model

import ru.vityaman.lms.botalka.commons.abbreviated
import ru.vityaman.lms.botalka.commons.expect
import ru.vityaman.lms.botalka.commons.expectId
import ru.vityaman.lms.botalka.domain.expect
import ru.vityaman.lms.botalka.domain.expectId
import java.time.OffsetDateTime

data class Homework(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package ru.vityaman.lms.botalka.domain.model

import ru.vityaman.lms.botalka.commons.expectId
import ru.vityaman.lms.botalka.domain.expectId

data class PromotionRequest(
val id: Id,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package ru.vityaman.lms.botalka.domain.model

import ru.vityaman.lms.botalka.commons.abbreviated
import ru.vityaman.lms.botalka.commons.expect
import ru.vityaman.lms.botalka.commons.expectId
import ru.vityaman.lms.botalka.domain.expect
import ru.vityaman.lms.botalka.domain.expectId

data class User(
val id: Id,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package ru.vityaman.lms.botalka.domain.model

import ru.vityaman.lms.botalka.commons.expectId
import ru.vityaman.lms.botalka.domain.expectId
import java.time.OffsetDateTime

data class Workspace(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package ru.vityaman.lms.botalka.logic
import ru.vityaman.lms.botalka.domain.model.User

interface UserService {
suspend fun getById(id: User.Id): User
suspend fun getById(id: User.Id): User?
suspend fun create(user: User.Draft): User
suspend fun promote(id: User.Id, role: User.Role)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package ru.vityaman.lms.botalka.logic.basic
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Service
import ru.vityaman.lms.botalka.domain.exception.PromotionRequestResolvedException
import ru.vityaman.lms.botalka.domain.exception.orNotFound
import ru.vityaman.lms.botalka.domain.model.PromotionRequest
import ru.vityaman.lms.botalka.logic.PromotionService
import ru.vityaman.lms.botalka.logic.UserService
Expand All @@ -19,18 +20,22 @@ class BasicPromotionService(
storage.create(promotion)

override suspend fun approve(id: PromotionRequest.Id) {
val request = storage.getById(id)
if (request.isResolved) {
throw PromotionRequestResolvedException(request.id)
}
val request = getUnresolvedRequestById(id)
storage.updateStatus(request.id, PromotionRequest.Status.APPROVED)
userService.promote(request.user, request.role)
}

override suspend fun reject(id: PromotionRequest.Id) {
if (storage.getById(id).isResolved) {
throw PromotionRequestResolvedException(id)
}
getUnresolvedRequestById(id)
storage.updateStatus(id, PromotionRequest.Status.REJECTED)
}

private suspend fun getUnresolvedRequestById(id: PromotionRequest.Id) =
storage.getById(id)
.orNotFound("Promotion request with id ${id.number} not found")
.also {
if (it.isResolved) {
throw PromotionRequestResolvedException(it.id)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import ru.vityaman.lms.botalka.storage.UserStorage
class BasicUserService(
@Autowired private val storage: UserStorage,
) : UserService {
override suspend fun getById(id: User.Id): User =
override suspend fun getById(id: User.Id): User? =
storage.getById(id)

override suspend fun create(user: User.Draft): User =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ interface PromotionStorage {
status: PromotionRequest.Status,
)

suspend fun getById(id: PromotionRequest.Id): PromotionRequest
suspend fun getById(id: PromotionRequest.Id): PromotionRequest?
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import ru.vityaman.lms.botalka.domain.model.Teacher
import ru.vityaman.lms.botalka.domain.model.User

interface UserStorage {
suspend fun getById(id: User.Id): User
suspend fun getById(id: User.Id): User?
suspend fun create(user: User.Draft): User
suspend fun create(teacher: Teacher)
suspend fun create(student: Student)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ru.vityaman.lms.botalka.storage.jooq

import kotlinx.coroutines.reactor.awaitSingle
import kotlinx.coroutines.reactor.awaitSingleOrNull
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Repository
import reactor.kotlin.core.publisher.toMono
Expand Down Expand Up @@ -42,11 +43,11 @@ class JooqPromotionStorage(
.map { }
.awaitSingle()

override suspend fun getById(id: PromotionRequest.Id): PromotionRequest =
override suspend fun getById(id: PromotionRequest.Id): PromotionRequest? =
database.execute
.selectFrom(PROMOTION_REQUEST)
.where(PROMOTION_REQUEST.ID.equal(id.number))
.toMono()
.map { it.toModel() }
.awaitSingle()
.awaitSingleOrNull()
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,14 @@ import ru.vityaman.lms.botalka.storage.jooq.tables.references.USER
class JooqUserStorage(
@Autowired private val database: JooqDatabase,
) : UserStorage {
override suspend fun getById(id: User.Id): User =
override suspend fun getById(id: User.Id): User? =
database.execute
.selectFrom(USER)
.where(USER.ID.equal(id.number))
.toMono()
.map { it.toModel() }
.awaitSingle()
.copy(
.awaitSingleOrNull()
?.copy(
roles = setOfNotNull(
teacher(id)?.let { User.Role.TEACHER },
student(id)?.let { User.Role.STUDENT },
Expand Down
Loading
Loading