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

#48 Homework Submit/Feedback Loop smoking version #54

Merged
merged 13 commits into from
Apr 13, 2024
4 changes: 3 additions & 1 deletion .github/workflows/gradle.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,6 @@ jobs:
uses: gradle/actions/setup-gradle@v3

- name: Build Gateway
run: (./gradlew :botalka:build)
run: |-
./gradlew :botalka:jooqCodegen
./gradlew :botalka:build
18 changes: 13 additions & 5 deletions botalka/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ plugins {
id("lms.conventions.jooq")
id("lms.conventions.spring")
id("lms.conventions.spring-oapi")
id("lms.conventions.kotlin-oapi")
}

val basePackage = "$group.lms.botalka"
Expand All @@ -25,19 +26,26 @@ jooq {
}
}

val oapiPackageName = "$basePackage.api.http"
val oapiServerPackageName = "$basePackage.api.http.server"
val oapiClientPackageName = "$basePackage.api.http.client"

tasks.withType<OpenAPIGenerateTask> {
packageName = oapiPackageName
modelPackage = oapiPackageName
tasks.named("generateServer", OpenAPIGenerateTask::class) {
packageName = oapiServerPackageName
modelPackage = oapiServerPackageName
}

tasks.named("generateClient", OpenAPIGenerateTask::class) {
packageName = oapiClientPackageName
modelPackage = oapiClientPackageName
}

koverReport {
filters {
excludes {
classes(
"$basePackage.BotalkaApplicationKt",
"$oapiPackageName.apis.**",
"$oapiServerPackageName.**",
"$oapiClientPackageName.**",
"$jooqPackageName.**",
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,59 @@
package ru.vityaman.lms.botalka.api.http.endpoint

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.map
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RestController
import ru.vityaman.lms.botalka.api.http.HomeworkDraftMessage
import ru.vityaman.lms.botalka.api.http.HomeworkMessage
import ru.vityaman.lms.botalka.api.http.apis.HomeworkApi
import ru.vityaman.lms.botalka.api.http.message.toMessage
import ru.vityaman.lms.botalka.api.http.message.toModel
import ru.vityaman.lms.botalka.api.http.server.HomeworkDraftMessage
import ru.vityaman.lms.botalka.api.http.server.HomeworkMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceEventDraftMessage
import ru.vityaman.lms.botalka.api.http.server.WorkspaceEventMessage
import ru.vityaman.lms.botalka.api.http.server.apis.HomeworkApi
import ru.vityaman.lms.botalka.domain.model.Homework
import ru.vityaman.lms.botalka.domain.model.Student
import ru.vityaman.lms.botalka.domain.model.User
import ru.vityaman.lms.botalka.domain.model.Workspace
import ru.vityaman.lms.botalka.logic.HomeworkService
import ru.vityaman.lms.botalka.logic.WorkspaceService

@RestController
class HomeworkHttpApi(
@Autowired private val service: HomeworkService,
@Autowired private val homework: HomeworkService,
@Autowired private val workspace: WorkspaceService,
) : HomeworkApi {
override suspend fun homeworkPost(
homeworkDraftMessage: HomeworkDraftMessage,
): ResponseEntity<HomeworkMessage> {
val draft = homeworkDraftMessage.toModel()
val homework = service.create(draft)
val homework = homework.create(draft)
return ResponseEntity.ok(homework.toMessage())
}

override suspend fun postEvent(
homeworkId: Int,
studentId: Int,
producerId: Int,
workspaceEventDraftMessage: WorkspaceEventDraftMessage,
): ResponseEntity<WorkspaceEventMessage> {
val homework = Homework.Id(homeworkId)
val student = Student(User.Id(studentId))
val producer = User.Id(producerId)
val draft = workspaceEventDraftMessage.toModel(producer)
val event = workspace.produce(Workspace.Id(homework, student), draft)
return ResponseEntity.ok(event.toMessage())
}

override fun getEvent(
homeworkId: Int,
studentId: Int,
): ResponseEntity<Flow<WorkspaceEventMessage>> {
val homework = Homework.Id(homeworkId)
val student = Student(User.Id(studentId))
return workspace.events(Workspace.Id(homework, student))
.map { it.toMessage() }
.let { ResponseEntity.ok(it) }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package ru.vityaman.lms.botalka.api.http.endpoint

import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RestController
import ru.vityaman.lms.botalka.api.http.apis.MonitoringApi
import ru.vityaman.lms.botalka.api.http.server.apis.MonitoringApi

@RestController
class MonitoringHttpApi : MonitoringApi {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RestController
import ru.vityaman.lms.botalka.api.http.PromotionRequestDraftMessage
import ru.vityaman.lms.botalka.api.http.PromotionRequestMessage
import ru.vityaman.lms.botalka.api.http.PromotionRequestPatchMessage
import ru.vityaman.lms.botalka.api.http.apis.PromotionApi
import ru.vityaman.lms.botalka.api.http.error.InvalidPromotionRequestStatus
import ru.vityaman.lms.botalka.api.http.message.toMessage
import ru.vityaman.lms.botalka.api.http.message.toModel
import ru.vityaman.lms.botalka.api.http.server.PromotionRequestDraftMessage
import ru.vityaman.lms.botalka.api.http.server.PromotionRequestMessage
import ru.vityaman.lms.botalka.api.http.server.PromotionRequestPatchMessage
import ru.vityaman.lms.botalka.api.http.server.apis.PromotionApi
import ru.vityaman.lms.botalka.domain.model.PromotionRequest
import ru.vityaman.lms.botalka.domain.model.User
import ru.vityaman.lms.botalka.logic.PromotionService
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package ru.vityaman.lms.botalka.api.http.endpoint
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.RestController
import ru.vityaman.lms.botalka.api.http.UserDraftMessage
import ru.vityaman.lms.botalka.api.http.UserMessage
import ru.vityaman.lms.botalka.api.http.apis.UserApi
import ru.vityaman.lms.botalka.api.http.message.toMessage
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.model.User
import ru.vityaman.lms.botalka.logic.UserService

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

import ru.vityaman.lms.botalka.api.http.HomeworkDraftMessage
import ru.vityaman.lms.botalka.api.http.HomeworkMessage
import ru.vityaman.lms.botalka.api.http.server.HomeworkDraftMessage
import ru.vityaman.lms.botalka.api.http.server.HomeworkMessage
import ru.vityaman.lms.botalka.domain.model.Homework

fun Homework.toMessage(): HomeworkMessage =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package ru.vityaman.lms.botalka.api.http.message

import ru.vityaman.lms.botalka.api.http.PromotionRequestMessage
import ru.vityaman.lms.botalka.api.http.PromotionRequestStatusMessage
import ru.vityaman.lms.botalka.api.http.server.PromotionRequestMessage
import ru.vityaman.lms.botalka.api.http.server.PromotionRequestStatusMessage
import ru.vityaman.lms.botalka.commons.BiMap
import ru.vityaman.lms.botalka.commons.BiMap.Companion.invoke
import ru.vityaman.lms.botalka.domain.model.PromotionRequest
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package ru.vityaman.lms.botalka.api.http.message

import ru.vityaman.lms.botalka.api.http.UserDraftMessage
import ru.vityaman.lms.botalka.api.http.UserMessage
import ru.vityaman.lms.botalka.api.http.UserRoleMessage
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.UserRoleMessage
import ru.vityaman.lms.botalka.commons.BiMap
import ru.vityaman.lms.botalka.commons.BiMap.Companion.invoke
import ru.vityaman.lms.botalka.domain.model.User
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
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
import ru.vityaman.lms.botalka.domain.model.Workspace

fun WorkspaceEventDraftMessage.toModel(producer: User.Id) =
when (this) {
is WorkspaceCommentDraftMessage -> {
this.toModel(producer)
}

is WorkspaceSubmissionDraftMessage -> {
this.toModel(producer)
}

is WorkspaceFeedbackDraftMessage -> {
this.toModel(producer)
}

else -> {
throw NotImplementedError(
buildString {
append("WorkspaceEvent type '${this@toModel.kind}' ")
append("is not yet supported")
},
)
}
}

fun WorkspaceCommentDraftMessage.toModel(producer: User.Id) =
Workspace.Comment.Draft(
producer = producer,
text = this.text,
)

fun WorkspaceSubmissionDraftMessage.toModel(producer: User.Id) =
Workspace.Submission.Draft(
producer = producer,
note = this.note,
)

fun WorkspaceFeedbackDraftMessage.toModel(producer: User.Id) =
Workspace.Feedback.Draft(
teacher = Teacher(producer),
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,
creationMoment = this.creationMoment,
)
}

is Workspace.Submission -> {
WorkspaceSubmissionMessage(
kind = this.kind,
id = this.id.number,
producerId = this.producer.number,
note = this.note,
creationMoment = this.creationMoment,
)
}
}
37 changes: 37 additions & 0 deletions botalka/src/main/kotlin/ru/vityaman/lms/botalka/commons/Merge.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package ru.vityaman.lms.botalka.commons

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.channelFlow
import kotlinx.coroutines.flow.produceIn

fun <T, U : Comparable<U>> mergeOrdered(
lhs: Flow<T>,
rhs: Flow<T>,
key: (T) -> U,
): Flow<T> = channelFlow {
val lhsChan = lhs.produceIn(this)
val rhsChan = rhs.produceIn(this)

var left = lhsChan.receiveCatching().getOrNull()
var right = rhsChan.receiveCatching().getOrNull()

while (left != null && right != null) {
if (key(left) < key(right)) {
send(left)
left = lhsChan.receiveCatching().getOrNull()
} else {
send(right)
right = rhsChan.receiveCatching().getOrNull()
}
}

while (left != null) {
send(left)
left = lhsChan.receiveCatching().getOrNull()
}

while (right != null) {
send(right)
right = rhsChan.receiveCatching().getOrNull()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package ru.vityaman.lms.botalka.domain.model

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

data class Workspace(
val id: Id,
val events: List<Event>,
) {
data class Id(val homework: Homework.Id, val student: Student)

sealed class Event {
abstract val id: Id
abstract val producer: User.Id
abstract val creationMoment: OffsetDateTime

@JvmInline
value class Id(val number: Int) {
init {
expectId(number)
}
}

sealed class Draft
}

data class Comment(
override val id: Id,
override val producer: User.Id,
val text: String,
override val creationMoment: OffsetDateTime,
) : Event() {
data class Draft(
val producer: User.Id,
val text: String,
) : Event.Draft()
}

data class Submission(
override val id: Id,
override val producer: User.Id,
val note: String,
override val creationMoment: OffsetDateTime,
) : Event() {
data class Draft(
val producer: User.Id,
val note: String,
) : Event.Draft()
}

data class Feedback(
override val id: Id,
val teacher: Teacher,
val comment: String,
val score: Homework.Score?,
override val creationMoment: OffsetDateTime,
) : Event() {
data class Draft(
val teacher: Teacher,
val comment: String,
val score: Homework.Score?,
) : Event.Draft()

override val producer: User.Id
get() = teacher.id
}
}
Loading
Loading