Skip to content

Commit

Permalink
#48 Homework Submit/Feedback Loop smoking version (#54)
Browse files Browse the repository at this point in the history
  • Loading branch information
vityaman authored Apr 13, 2024
1 parent 76cc4d8 commit f5faae7
Show file tree
Hide file tree
Showing 34 changed files with 1,004 additions and 64 deletions.
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

0 comments on commit f5faae7

Please sign in to comment.