From 3bd624ef9373694eb5030afe5c5e49b42c7acd2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Fri, 5 Apr 2024 12:47:22 +0200 Subject: [PATCH 01/29] Initial domain concept --- .../sourcegrade/lab/hub/db/DomainEntity.kt | 14 ++++++++++ .../lab/hub/db/MutableRepository.kt | 11 ++++++++ .../org/sourcegrade/lab/hub/db/Repository.kt | 10 +++++++ .../sourcegrade/lab/hub/db/UserRepository.kt | 12 ++++++++ .../org/sourcegrade/lab/hub/db/UserTable.kt | 19 +++++++++++++ .../sourcegrade/lab/hub/domain/Assignment.kt | 13 +++++++++ .../org/sourcegrade/lab/hub/domain/Course.kt | 14 ++++++++++ .../sourcegrade/lab/hub/domain/GradingRun.kt | 28 +++++++++++++++++++ .../org/sourcegrade/lab/hub/domain/Role.kt | 10 +++++++ .../sourcegrade/lab/hub/domain/Semester.kt | 12 ++++++++ .../sourcegrade/lab/hub/domain/Submission.kt | 14 ++++++++++ .../org/sourcegrade/lab/hub/domain/User.kt | 11 ++++++++ .../lab/hub/graphql/UserEndpoint.kt | 5 ++++ 13 files changed, 173 insertions(+) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DomainEntity.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/MutableRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Repository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Semester.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserEndpoint.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DomainEntity.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DomainEntity.kt new file mode 100644 index 0000000..cadb68d --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DomainEntity.kt @@ -0,0 +1,14 @@ +package org.sourcegrade.lab.hub.db + +import java.util.UUID + +interface DomainEntity { + val id: UUID +} + +interface DomainFacet { + suspend fun getOriginal(): E +} + + +interface Creates diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/MutableRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/MutableRepository.kt new file mode 100644 index 0000000..3e44a1b --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/MutableRepository.kt @@ -0,0 +1,11 @@ +package org.sourcegrade.lab.hub.db + +interface MutableRepository> : Repository { + suspend fun create(item: C): E + suspend fun put(item: C): PutResult + + data class PutResult( + val entity: E, + val created: Boolean, + ) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Repository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Repository.kt new file mode 100644 index 0000000..d646edf --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Repository.kt @@ -0,0 +1,10 @@ +package org.sourcegrade.lab.hub.db + +import java.util.UUID + +interface Repository { + suspend fun getById(id: UUID): E? + suspend fun exists(id: UUID): Boolean + suspend fun countAll(): Long + suspend fun deleteById(id: UUID): Boolean +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserRepository.kt new file mode 100644 index 0000000..00afddd --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserRepository.kt @@ -0,0 +1,12 @@ +package org.sourcegrade.lab.hub.db + +import org.sourcegrade.lab.hub.models.User + +class UserRepository { + suspend fun getByUsername(username: String): User? { + TODO() + } + suspend fun getByEmail(email: String): User? { + TODO() + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt new file mode 100644 index 0000000..13c9b61 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt @@ -0,0 +1,19 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.UUIDEntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.UUIDTable +import java.util.UUID + +internal object UserTable : UUIDTable("sgl_users") { + val username = varchar("username", 255).uniqueIndex() + val email = varchar("email", 255).uniqueIndex().nullable() +} + +internal class UserEntity(id: EntityID) : UUIDEntity(id) { + var username: String by UserTable.username + var email: String? by UserTable.email + + companion object : UUIDEntityClass(UserTable) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt new file mode 100644 index 0000000..e26d2ff --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -0,0 +1,13 @@ +package org.sourcegrade.lab.hub.domain + +import org.sourcegrade.lab.hub.db.DomainEntity +import java.time.ZonedDateTime +import java.util.UUID + +data class Assignment( + override val id: UUID, + val name: String, + val description: String, + val course: Course, + val submissionDeadline: ZonedDateTime, +) : DomainEntity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt new file mode 100644 index 0000000..c1d60d0 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt @@ -0,0 +1,14 @@ +package org.sourcegrade.lab.hub.domain + +import org.sourcegrade.lab.hub.db.DomainEntity +import java.util.UUID + +data class Course( + override val id: UUID, + val name: String, + val description: String, + val semester: Semester, + val assignments: List, +) : DomainEntity { + +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt new file mode 100644 index 0000000..87ba592 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt @@ -0,0 +1,28 @@ +package org.sourcegrade.lab.hub.domain + +import org.sourcegrade.lab.hub.db.DomainEntity +import java.util.UUID + +data class GradingRun( + override val id: UUID, + val maxPoints: Int, + val minPoints: Int, + val rubric: ByteArray, +) : DomainEntity { + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + other as GradingRun + if (id != other.id) return false + if (maxPoints != other.maxPoints) return false + if (minPoints != other.minPoints) return false + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + maxPoints + result = 31 * result + minPoints + return result + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt new file mode 100644 index 0000000..3d49a44 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt @@ -0,0 +1,10 @@ +package org.sourcegrade.lab.hub.domain + +import org.sourcegrade.lab.hub.db.DomainEntity +import java.util.UUID + +data class Role( + override val id: UUID, + val scope: String, + val permissions: List, +) : DomainEntity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Semester.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Semester.kt new file mode 100644 index 0000000..7c85544 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Semester.kt @@ -0,0 +1,12 @@ +package org.sourcegrade.lab.hub.domain + +import org.sourcegrade.lab.hub.db.DomainEntity +import java.time.ZonedDateTime +import java.util.UUID + +data class Semester( + override val id: UUID, + val name: String, + val start: ZonedDateTime, + val end: ZonedDateTime, +) : DomainEntity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt new file mode 100644 index 0000000..4a4f838 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt @@ -0,0 +1,14 @@ +package org.sourcegrade.lab.hub.domain + +import org.sourcegrade.lab.hub.db.DomainEntity +import java.time.ZonedDateTime +import java.util.UUID + +data class Submission( + override val id: UUID, + val assignment: Assignment, + val submitter: User, + val uploaded: ZonedDateTime, + val gradingRuns: List, + val lastGradingRun: GradingRun, +) : DomainEntity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt new file mode 100644 index 0000000..17540ab --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -0,0 +1,11 @@ +package org.sourcegrade.lab.hub.domain + +import org.sourcegrade.lab.hub.db.DomainEntity +import java.util.UUID + +data class User( + override val id: UUID, + val username: String, + val email: String? = null, + val roles: List, +) : DomainEntity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserEndpoint.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserEndpoint.kt new file mode 100644 index 0000000..3be9ee7 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserEndpoint.kt @@ -0,0 +1,5 @@ +package org.sourcegrade.lab.hub.graphql + +class UserQuery { + +} From 7cb2251432ed66221650e758be81bfd30de20588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Fri, 5 Apr 2024 21:59:20 +0200 Subject: [PATCH 02/29] Semester -> Term and add owner to Course --- .../main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt | 7 +++---- .../sourcegrade/lab/hub/domain/{Semester.kt => Term.kt} | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) rename hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/{Semester.kt => Term.kt} (92%) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt index c1d60d0..60c2ea4 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt @@ -7,8 +7,7 @@ data class Course( override val id: UUID, val name: String, val description: String, - val semester: Semester, + val term: Term, + val owner: User, val assignments: List, -) : DomainEntity { - -} +) : DomainEntity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Semester.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt similarity index 92% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Semester.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt index 7c85544..1432471 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Semester.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt @@ -4,7 +4,7 @@ import org.sourcegrade.lab.hub.db.DomainEntity import java.time.ZonedDateTime import java.util.UUID -data class Semester( +data class Term( override val id: UUID, val name: String, val start: ZonedDateTime, From 9ccf8cbb2d634d34df3bf2f3ced810753e38c3da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Fri, 5 Apr 2024 22:51:00 +0200 Subject: [PATCH 03/29] Add SubmissionGroup(Category) and refactor a bit --- .../org/sourcegrade/lab/hub/domain/Assignment.kt | 2 +- .../kotlin/org/sourcegrade/lab/hub/domain/Course.kt | 2 +- .../sourcegrade/lab/hub/{db => domain}/DomainEntity.kt | 2 +- .../org/sourcegrade/lab/hub/domain/GradingRun.kt | 1 - .../lab/hub/{db => domain}/MutableRepository.kt | 2 +- .../sourcegrade/lab/hub/{db => domain}/Repository.kt | 2 +- .../main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt | 1 - .../org/sourcegrade/lab/hub/domain/Submission.kt | 2 +- .../org/sourcegrade/lab/hub/domain/SubmissionGroup.kt | 9 +++++++++ .../lab/hub/domain/SubmissionGroupCategory.kt | 10 ++++++++++ .../main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt | 1 - .../main/kotlin/org/sourcegrade/lab/hub/domain/User.kt | 1 - 12 files changed, 25 insertions(+), 10 deletions(-) rename hub/src/main/kotlin/org/sourcegrade/lab/hub/{db => domain}/DomainEntity.kt (82%) rename hub/src/main/kotlin/org/sourcegrade/lab/hub/{db => domain}/MutableRepository.kt (87%) rename hub/src/main/kotlin/org/sourcegrade/lab/hub/{db => domain}/Repository.kt (85%) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt index e26d2ff..5c50e09 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -1,6 +1,5 @@ package org.sourcegrade.lab.hub.domain -import org.sourcegrade.lab.hub.db.DomainEntity import java.time.ZonedDateTime import java.util.UUID @@ -10,4 +9,5 @@ data class Assignment( val description: String, val course: Course, val submissionDeadline: ZonedDateTime, + val submissionGroupCategory: SubmissionGroupCategory, ) : DomainEntity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt index 60c2ea4..25bc151 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt @@ -1,6 +1,5 @@ package org.sourcegrade.lab.hub.domain -import org.sourcegrade.lab.hub.db.DomainEntity import java.util.UUID data class Course( @@ -9,5 +8,6 @@ data class Course( val description: String, val term: Term, val owner: User, + val submissionGroupCategories: List, val assignments: List, ) : DomainEntity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DomainEntity.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt similarity index 82% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DomainEntity.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt index cadb68d..d5cb04c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DomainEntity.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt @@ -1,4 +1,4 @@ -package org.sourcegrade.lab.hub.db +package org.sourcegrade.lab.hub.domain import java.util.UUID diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt index 87ba592..5a049eb 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt @@ -1,6 +1,5 @@ package org.sourcegrade.lab.hub.domain -import org.sourcegrade.lab.hub.db.DomainEntity import java.util.UUID data class GradingRun( diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/MutableRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt similarity index 87% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/MutableRepository.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt index 3e44a1b..2cf4351 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/MutableRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt @@ -1,4 +1,4 @@ -package org.sourcegrade.lab.hub.db +package org.sourcegrade.lab.hub.domain interface MutableRepository> : Repository { suspend fun create(item: C): E diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Repository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt similarity index 85% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Repository.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt index d646edf..a46f6c5 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Repository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt @@ -1,4 +1,4 @@ -package org.sourcegrade.lab.hub.db +package org.sourcegrade.lab.hub.domain import java.util.UUID diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt index 3d49a44..a3afa67 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt @@ -1,6 +1,5 @@ package org.sourcegrade.lab.hub.domain -import org.sourcegrade.lab.hub.db.DomainEntity import java.util.UUID data class Role( diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt index 4a4f838..7af1adc 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt @@ -1,6 +1,5 @@ package org.sourcegrade.lab.hub.domain -import org.sourcegrade.lab.hub.db.DomainEntity import java.time.ZonedDateTime import java.util.UUID @@ -8,6 +7,7 @@ data class Submission( override val id: UUID, val assignment: Assignment, val submitter: User, + val group: SubmissionGroup, val uploaded: ZonedDateTime, val gradingRuns: List, val lastGradingRun: GradingRun, diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt new file mode 100644 index 0000000..ad1c832 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt @@ -0,0 +1,9 @@ +package org.sourcegrade.lab.hub.domain + +import java.util.UUID + +data class SubmissionGroup( + override val id: UUID, + val users: List, + val category: SubmissionGroupCategory, +) : DomainEntity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt new file mode 100644 index 0000000..32362b6 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt @@ -0,0 +1,10 @@ +package org.sourcegrade.lab.hub.domain + +import java.util.UUID + +data class SubmissionGroupCategory( + override val id: UUID, + val name: String, + val minSize: Int, + val maxSize: Int, +) : DomainEntity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt index 1432471..b277e26 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt @@ -1,6 +1,5 @@ package org.sourcegrade.lab.hub.domain -import org.sourcegrade.lab.hub.db.DomainEntity import java.time.ZonedDateTime import java.util.UUID diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index 17540ab..e8b2254 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -1,6 +1,5 @@ package org.sourcegrade.lab.hub.domain -import org.sourcegrade.lab.hub.db.DomainEntity import java.util.UUID data class User( From 98f3827fb4281afd0527d812aae87ed570b09036 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Mon, 8 Apr 2024 13:03:32 +0200 Subject: [PATCH 04/29] Put SubmissionGroupCategory into SubmissionGroup as nested class --- .../org/sourcegrade/lab/hub/domain/Assignment.kt | 2 +- .../sourcegrade/lab/hub/domain/SubmissionGroup.kt | 12 ++++++++++-- .../lab/hub/domain/SubmissionGroupCategory.kt | 10 ---------- 3 files changed, 11 insertions(+), 13 deletions(-) delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt index 5c50e09..18fafb2 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -9,5 +9,5 @@ data class Assignment( val description: String, val course: Course, val submissionDeadline: ZonedDateTime, - val submissionGroupCategory: SubmissionGroupCategory, + val submissionGroupCategory: SubmissionGroup.Category, ) : DomainEntity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt index ad1c832..e27f3f1 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt @@ -5,5 +5,13 @@ import java.util.UUID data class SubmissionGroup( override val id: UUID, val users: List, - val category: SubmissionGroupCategory, -) : DomainEntity + val category: Category, +) : DomainEntity { + + data class Category( + override val id: UUID, + val name: String, + val minSize: Int, + val maxSize: Int, + ) : DomainEntity +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt deleted file mode 100644 index 32362b6..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.sourcegrade.lab.hub.domain - -import java.util.UUID - -data class SubmissionGroupCategory( - override val id: UUID, - val name: String, - val minSize: Int, - val maxSize: Int, -) : DomainEntity From 21ddaf6f241b07b18a1e444781b2ec7e999a2c74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Mon, 8 Apr 2024 13:04:13 +0200 Subject: [PATCH 05/29] Format --- .../kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt | 1 - .../kotlin/org/sourcegrade/lab/hub/graphql/UserEndpoint.kt | 5 ----- 2 files changed, 6 deletions(-) delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserEndpoint.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt index d5cb04c..0dd9385 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt @@ -10,5 +10,4 @@ interface DomainFacet { suspend fun getOriginal(): E } - interface Creates diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserEndpoint.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserEndpoint.kt deleted file mode 100644 index 3be9ee7..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserEndpoint.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.sourcegrade.lab.hub.graphql - -class UserQuery { - -} From 673e6c10061e6407de9869b403f3ea4207973034 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Tue, 16 Apr 2024 16:19:15 +0200 Subject: [PATCH 06/29] Add tables --- .../sourcegrade/lab/hub/db/AssignmentTable.kt | 27 +++++++++++ .../org/sourcegrade/lab/hub/db/CourseTable.kt | 25 ++++++++++ .../lab/hub/db/RolePermissionTable.kt | 8 ++++ .../org/sourcegrade/lab/hub/db/RoleTable.kt | 14 ++++++ .../sourcegrade/lab/hub/db/SubmissionGroup.kt | 5 ++ .../hub/db/SubmissionGroupCategoryTable.kt | 10 ++++ .../org/sourcegrade/lab/hub/db/UserTable.kt | 12 ++++- .../org/sourcegrade/lab/hub/domain/Course.kt | 2 +- .../sourcegrade/lab/hub/domain/Repository.kt | 2 + .../org/sourcegrade/lab/hub/domain/User.kt | 10 ++-- .../lab/hub/domain/UserRepository.kt | 8 ++++ .../lab/hub/domain/UserRepositoryImpl.kt | 46 +++++++++++++++++++ .../sourcegrade/lab/hub/graphql/Queries.kt | 8 ++++ .../lab/hub/graphql/RepositoryQuery.kt | 8 ++++ 14 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/AssignmentTable.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseTable.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionTable.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RoleTable.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroup.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategoryTable.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepositoryImpl.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Queries.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RepositoryQuery.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/AssignmentTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/AssignmentTable.kt new file mode 100644 index 0000000..3724222 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/AssignmentTable.kt @@ -0,0 +1,27 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.AssignmentEntity.Companion.referrersOn +import org.sourcegrade.lab.hub.db.RolePermissionTable.references +import org.sourcegrade.lab.hub.domain.Course +import java.util.UUID + +internal object AssignmentTable : UUIDTable("sgl_assignments") { + val courseId = reference("course_id", CourseTable.id) + val name = varchar("name", 255).uniqueIndex() + val description = varchar("description", 16 * 1024) + val submissionDeadLine = timestamp("submissionDeadline") // TODO: time zone info + val submissionGroupCategory = reference("submission_group_category", SubmissionGroupCategoryTable.id) +} + +internal class AssignmentEntity(id: EntityID) : UUIDEntity(id) { + val courseId by AssignmentTable.courseId references CourseTable.id + val name by AssignmentTable.name + val description by AssignmentTable.description + + companion object : EntityClass(AssignmentTable) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseTable.kt new file mode 100644 index 0000000..277657e --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseTable.kt @@ -0,0 +1,25 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.SizedIterable +import org.sourcegrade.lab.hub.db.AssignmentEntity.Companion.referrersOn +import org.sourcegrade.lab.hub.db.RolePermissionTable.references +import java.util.UUID + +internal object CourseTable : UUIDTable("sgl_courses") { + val name = varchar("name", 255) + val description = varchar("description", 255) + val term = varchar("term", 255) + val ownerId = reference("owner_id", UserTable.id) +} + +internal class CourseEntity(id: EntityID) : UUIDEntity(id) { + val name by CourseTable.name + val description by CourseTable.description + val term by CourseTable.term + + val owner: EntityID by CourseTable.ownerId references UserTable.id + val assignments: SizedIterable by AssignmentEntity referrersOn AssignmentTable.courseId +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionTable.kt new file mode 100644 index 0000000..b3ca8e9 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionTable.kt @@ -0,0 +1,8 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.sql.Table + +internal object RolePermissionTable : Table("sgl_role_permissions") { + val roleId = reference("role_id", RoleTable.id) + val permission = varchar("permission", 1024) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RoleTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RoleTable.kt new file mode 100644 index 0000000..1980c18 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RoleTable.kt @@ -0,0 +1,14 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.UUIDTable +import java.util.UUID + +internal object RoleTable : UUIDTable("sgl_roles") { + val scope = varchar("scope", 255) +} + +internal class RoleEntity(id: EntityID) : UUIDEntity(id) { + val scope: String by RoleTable.scope +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroup.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroup.kt new file mode 100644 index 0000000..1ff2430 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroup.kt @@ -0,0 +1,5 @@ +package org.sourcegrade.lab.hub.db + +internal object SubmissionGroup { + +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategoryTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategoryTable.kt new file mode 100644 index 0000000..e8d65cd --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategoryTable.kt @@ -0,0 +1,10 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.id.UUIDTable + +internal object SubmissionGroupCategoryTable : UUIDTable("sgl_submission_group_categories") { + val courseId = reference("course_id", CourseTable.id) + val name = varchar("name", 255).uniqueIndex() + val minSize = integer("min_size") + val maxSize = integer("max_size") +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt index 13c9b61..0a1a502 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt @@ -4,16 +4,24 @@ import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.UUIDEntityClass import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.ResultRow +import org.sourcegrade.lab.hub.domain.User import java.util.UUID internal object UserTable : UUIDTable("sgl_users") { val username = varchar("username", 255).uniqueIndex() - val email = varchar("email", 255).uniqueIndex().nullable() + val email = varchar("email", 255).uniqueIndex() } internal class UserEntity(id: EntityID) : UUIDEntity(id) { var username: String by UserTable.username - var email: String? by UserTable.email + var email: String by UserTable.email companion object : UUIDEntityClass(UserTable) } + +internal fun ResultRow.toUser() = User( + id = this[UserTable.id].value, + username = this[UserTable.username], + email = this[UserTable.email], +) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt index 25bc151..50e5c49 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt @@ -8,6 +8,6 @@ data class Course( val description: String, val term: Term, val owner: User, - val submissionGroupCategories: List, + val submissionGroupCategories: List, val assignments: List, ) : DomainEntity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt index a46f6c5..76c0591 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt @@ -1,8 +1,10 @@ package org.sourcegrade.lab.hub.domain import java.util.UUID +import kotlin.reflect.KClass interface Repository { + val entityType: KClass suspend fun getById(id: UUID): E? suspend fun exists(id: UUID): Boolean suspend fun countAll(): Long diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index e8b2254..1ff79ce 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -5,6 +5,10 @@ import java.util.UUID data class User( override val id: UUID, val username: String, - val email: String? = null, - val roles: List, -) : DomainEntity + val email: String, +) : DomainEntity { + data class CreateDto( + val username: String, + val email: String, + ) : Creates +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepository.kt new file mode 100644 index 0000000..6b844da --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepository.kt @@ -0,0 +1,8 @@ +package org.sourcegrade.lab.hub.domain + +import kotlin.reflect.KClass + +interface UserRepository : MutableRepository { + override val entityType: KClass + get() = User::class +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepositoryImpl.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepositoryImpl.kt new file mode 100644 index 0000000..6e13916 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepositoryImpl.kt @@ -0,0 +1,46 @@ +package org.sourcegrade.lab.hub.domain + +import org.apache.logging.log4j.Logger +import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.sourcegrade.lab.hub.db.UserTable +import java.util.UUID + +class UserRepositoryImpl( + private val logger: Logger +) : UserRepository { + + override suspend fun create(item: User.CreateDto): User = newSuspendedTransaction { + val result = UserTable.insert { + it[username] = item.username + it[email] = item.email + }.resultedValues + + val user = checkNotNull(result) { "Failed to create User ${item.username}" } + .single().toUser() + + logger.info("Created new User ${user.id} with data $item") + + user + } + + override suspend fun put(item: User.CreateDto): MutableRepository.PutResult { + TODO("Not yet implemented") + } + + override suspend fun getById(id: UUID): User? { + TODO("Not yet implemented") + } + + override suspend fun exists(id: UUID): Boolean { + TODO("Not yet implemented") + } + + override suspend fun countAll(): Long { + TODO("Not yet implemented") + } + + override suspend fun deleteById(id: UUID): Boolean { + TODO("Not yet implemented") + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Queries.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Queries.kt new file mode 100644 index 0000000..b6299ba --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Queries.kt @@ -0,0 +1,8 @@ +package org.sourcegrade.lab.hub.graphql + +import com.expediagroup.graphql.server.operations.Query + +object Queries { + val queries: List = + listOf() +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RepositoryQuery.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RepositoryQuery.kt new file mode 100644 index 0000000..577bc5b --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RepositoryQuery.kt @@ -0,0 +1,8 @@ +package org.sourcegrade.lab.hub.graphql + +import org.sourcegrade.lab.hub.domain.DomainEntity +import org.sourcegrade.lab.hub.domain.Repository + +class RepositoryQuery(val repository: Repository) { + +} From f855c068ff8d264b50da6317b01a8346c23fab5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Tue, 16 Apr 2024 23:50:24 +0200 Subject: [PATCH 07/29] Unsolved issue with exposed --- .../sourcegrade/lab/hub/db/AssignmentTable.kt | 17 +-------- .../org/sourcegrade/lab/hub/db/CourseTable.kt | 17 +-------- .../sourcegrade/lab/hub/db/GradingRunTable.kt | 9 +++++ .../lab/hub/db/RolePermissionTable.kt | 2 +- .../org/sourcegrade/lab/hub/db/RoleTable.kt | 8 +---- .../sourcegrade/lab/hub/db/SubmissionGroup.kt | 5 --- .../hub/db/SubmissionGroupCategoryTable.kt | 2 +- .../hub/db/SubmissionGroupMembershipTable.kt | 8 +++++ .../lab/hub/db/SubmissionGroupTable.kt | 8 +++++ .../sourcegrade/lab/hub/db/SubmissionTable.kt | 9 +++++ .../org/sourcegrade/lab/hub/db/TermTable.kt | 11 ++++++ .../org/sourcegrade/lab/hub/db/UserTable.kt | 17 --------- .../sourcegrade/lab/hub/domain/Assignment.kt | 32 ++++++++++++----- .../org/sourcegrade/lab/hub/domain/Course.kt | 36 ++++++++++++++----- .../lab/hub/domain/DomainEntity.kt | 13 ------- .../sourcegrade/lab/hub/domain/GradingRun.kt | 33 +++++++---------- .../lab/hub/domain/MutableRepository.kt | 11 ------ .../sourcegrade/lab/hub/domain/Repository.kt | 12 ------- .../org/sourcegrade/lab/hub/domain/Role.kt | 22 +++++++++--- .../sourcegrade/lab/hub/domain/Submission.kt | 31 ++++++++++------ .../lab/hub/domain/SubmissionGroup.kt | 19 +++++----- .../lab/hub/domain/SubmissionGroupCategory.kt | 16 +++++++++ .../org/sourcegrade/lab/hub/domain/Term.kt | 25 +++++++++---- .../org/sourcegrade/lab/hub/domain/User.kt | 29 ++++++++++----- .../lab/hub/graphql/RepositoryQuery.kt | 1 - 25 files changed, 212 insertions(+), 181 deletions(-) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRunTable.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroup.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembershipTable.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupTable.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionTable.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/TermTable.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/AssignmentTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/AssignmentTable.kt index 3724222..0cc3d9b 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/AssignmentTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/AssignmentTable.kt @@ -1,27 +1,12 @@ package org.sourcegrade.lab.hub.db -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -import org.sourcegrade.lab.hub.db.AssignmentEntity.Companion.referrersOn -import org.sourcegrade.lab.hub.db.RolePermissionTable.references -import org.sourcegrade.lab.hub.domain.Course -import java.util.UUID internal object AssignmentTable : UUIDTable("sgl_assignments") { - val courseId = reference("course_id", CourseTable.id) + val courseId = reference("course_id", CourseTable) val name = varchar("name", 255).uniqueIndex() val description = varchar("description", 16 * 1024) val submissionDeadLine = timestamp("submissionDeadline") // TODO: time zone info val submissionGroupCategory = reference("submission_group_category", SubmissionGroupCategoryTable.id) } - -internal class AssignmentEntity(id: EntityID) : UUIDEntity(id) { - val courseId by AssignmentTable.courseId references CourseTable.id - val name by AssignmentTable.name - val description by AssignmentTable.description - - companion object : EntityClass(AssignmentTable) -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseTable.kt index 277657e..9b414c3 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseTable.kt @@ -1,25 +1,10 @@ package org.sourcegrade.lab.hub.db -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable -import org.jetbrains.exposed.sql.SizedIterable -import org.sourcegrade.lab.hub.db.AssignmentEntity.Companion.referrersOn -import org.sourcegrade.lab.hub.db.RolePermissionTable.references -import java.util.UUID internal object CourseTable : UUIDTable("sgl_courses") { val name = varchar("name", 255) val description = varchar("description", 255) val term = varchar("term", 255) - val ownerId = reference("owner_id", UserTable.id) -} - -internal class CourseEntity(id: EntityID) : UUIDEntity(id) { - val name by CourseTable.name - val description by CourseTable.description - val term by CourseTable.term - - val owner: EntityID by CourseTable.ownerId references UserTable.id - val assignments: SizedIterable by AssignmentEntity referrersOn AssignmentTable.courseId + val ownerId = reference("owner_id", UserTable) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRunTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRunTable.kt new file mode 100644 index 0000000..fd62e35 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRunTable.kt @@ -0,0 +1,9 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.id.UUIDTable + +internal object GradingRunTable : UUIDTable("sgl_grading_runs") { + val maxPoints = integer("max_points") + val minPoints = integer("min_points") + // TODO: rubric = ... +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionTable.kt index b3ca8e9..96a6b77 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionTable.kt @@ -3,6 +3,6 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.sql.Table internal object RolePermissionTable : Table("sgl_role_permissions") { - val roleId = reference("role_id", RoleTable.id) + val roleId = reference("role_id", RoleTable) val permission = varchar("permission", 1024) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RoleTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RoleTable.kt index 1980c18..6984066 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RoleTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RoleTable.kt @@ -1,14 +1,8 @@ package org.sourcegrade.lab.hub.db -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable -import java.util.UUID internal object RoleTable : UUIDTable("sgl_roles") { + val name = varchar("name", 255) val scope = varchar("scope", 255) } - -internal class RoleEntity(id: EntityID) : UUIDEntity(id) { - val scope: String by RoleTable.scope -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroup.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroup.kt deleted file mode 100644 index 1ff2430..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroup.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.sourcegrade.lab.hub.db - -internal object SubmissionGroup { - -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategoryTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategoryTable.kt index e8d65cd..80bbbe6 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategoryTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategoryTable.kt @@ -3,8 +3,8 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable internal object SubmissionGroupCategoryTable : UUIDTable("sgl_submission_group_categories") { - val courseId = reference("course_id", CourseTable.id) val name = varchar("name", 255).uniqueIndex() + val courseId = reference("course_id", CourseTable) val minSize = integer("min_size") val maxSize = integer("max_size") } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembershipTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembershipTable.kt new file mode 100644 index 0000000..09e9c32 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembershipTable.kt @@ -0,0 +1,8 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.sql.Table + +internal object SubmissionGroupMembershipTable : Table("sgl_submission_group_memberships") { + val userId = reference("user_id", UserTable).uniqueIndex() + val submissionGroupId = reference("submission_group_id", SubmissionGroupTable) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupTable.kt new file mode 100644 index 0000000..6f5c839 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupTable.kt @@ -0,0 +1,8 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.id.UUIDTable + +internal object SubmissionGroupTable : UUIDTable("sgl_submission_groups") { + val name = varchar("name", 255).uniqueIndex() + val categoryId = reference("category_id", SubmissionGroupCategoryTable) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionTable.kt new file mode 100644 index 0000000..a3a1937 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionTable.kt @@ -0,0 +1,9 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.id.UUIDTable + +internal object SubmissionTable : UUIDTable("sgl_submissions") { + val assignmentId = reference("assignment_id", AssignmentTable) + val submitterId = reference("submitter_id", UserTable) + val groupId = reference("group_id", SubmissionGroupTable) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/TermTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/TermTable.kt new file mode 100644 index 0000000..f2ec230 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/TermTable.kt @@ -0,0 +1,11 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import java.util.UUID + +object TermTable : UUIDTable("sgl_terms") { + val name = varchar("name", 255).uniqueIndex() + val start = timestamp("start") + val end = timestamp("end") +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt index 0a1a502..6ca20bf 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt @@ -1,27 +1,10 @@ package org.sourcegrade.lab.hub.db -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.UUIDEntityClass -import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.ResultRow import org.sourcegrade.lab.hub.domain.User -import java.util.UUID internal object UserTable : UUIDTable("sgl_users") { val username = varchar("username", 255).uniqueIndex() val email = varchar("email", 255).uniqueIndex() } - -internal class UserEntity(id: EntityID) : UUIDEntity(id) { - var username: String by UserTable.username - var email: String by UserTable.email - - companion object : UUIDEntityClass(UserTable) -} - -internal fun ResultRow.toUser() = User( - id = this[UserTable.id].value, - username = this[UserTable.username], - email = this[UserTable.email], -) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt index 18fafb2..4096abc 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -1,13 +1,27 @@ package org.sourcegrade.lab.hub.domain -import java.time.ZonedDateTime +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.sourcegrade.lab.hub.db.AssignmentTable +import org.sourcegrade.lab.hub.db.CourseTable +import org.sourcegrade.lab.hub.db.RolePermissionTable.references import java.util.UUID -data class Assignment( - override val id: UUID, - val name: String, - val description: String, - val course: Course, - val submissionDeadline: ZonedDateTime, - val submissionGroupCategory: SubmissionGroup.Category, -) : DomainEntity +//data class Assignment( +// override val id: UUID, +// val name: String, +// val description: String, +// val course: Course, +// val submissionDeadline: ZonedDateTime, +// val submissionGroupCategory: SubmissionGroup.Category, +//) : DomainEntity + +class Assignment(id: EntityID) : UUIDEntity(id) { + val courseId: EntityID by AssignmentTable.courseId references CourseTable.id + val course: Course by Course referencedOn AssignmentTable.courseId + val name: String by AssignmentTable.name + val description: String by AssignmentTable.description + + companion object : EntityClass(AssignmentTable) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt index 50e5c49..e7dbb64 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt @@ -1,13 +1,31 @@ package org.sourcegrade.lab.hub.domain +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.SizedIterable +import org.sourcegrade.lab.hub.db.AssignmentTable +import org.sourcegrade.lab.hub.db.CourseTable +import org.sourcegrade.lab.hub.db.RolePermissionTable.references +import org.sourcegrade.lab.hub.db.UserTable import java.util.UUID -data class Course( - override val id: UUID, - val name: String, - val description: String, - val term: Term, - val owner: User, - val submissionGroupCategories: List, - val assignments: List, -) : DomainEntity +//data class Course( +// override val id: UUID, +// val name: String, +// val description: String, +// val term: Term, +// val owner: User, +// val submissionGroupCategories: List, +// val assignments: List, +//) : DomainEntity + +class Course(id: EntityID) : UUIDEntity(id) { + val name: String by CourseTable.name + val description: String by CourseTable.description + val term: String by CourseTable.term + val owner: User by User referencedOn CourseTable.ownerId + val assignments: SizedIterable by Assignment referrersOn AssignmentTable.courseId + + companion object : EntityClass(CourseTable) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt deleted file mode 100644 index 0dd9385..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt +++ /dev/null @@ -1,13 +0,0 @@ -package org.sourcegrade.lab.hub.domain - -import java.util.UUID - -interface DomainEntity { - val id: UUID -} - -interface DomainFacet { - suspend fun getOriginal(): E -} - -interface Creates diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt index 5a049eb..7715bb6 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt @@ -1,27 +1,18 @@ package org.sourcegrade.lab.hub.domain +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.sourcegrade.lab.hub.db.GradingRunTable import java.util.UUID -data class GradingRun( - override val id: UUID, - val maxPoints: Int, - val minPoints: Int, - val rubric: ByteArray, -) : DomainEntity { - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - other as GradingRun - if (id != other.id) return false - if (maxPoints != other.maxPoints) return false - if (minPoints != other.minPoints) return false - return true - } +//data class GradingRun( +// override val id: UUID, +// val maxPoints: Int, +// val minPoints: Int, +// val rubric: ByteArray, +//) : DomainEntity - override fun hashCode(): Int { - var result = id.hashCode() - result = 31 * result + maxPoints - result = 31 * result + minPoints - return result - } +class GradingRun(id: EntityID) : UUIDEntity(id) { + val maxPoints: Int by GradingRunTable.maxPoints + val minPoints: Int by GradingRunTable.minPoints } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt deleted file mode 100644 index 2cf4351..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.sourcegrade.lab.hub.domain - -interface MutableRepository> : Repository { - suspend fun create(item: C): E - suspend fun put(item: C): PutResult - - data class PutResult( - val entity: E, - val created: Boolean, - ) -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt deleted file mode 100644 index 76c0591..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.sourcegrade.lab.hub.domain - -import java.util.UUID -import kotlin.reflect.KClass - -interface Repository { - val entityType: KClass - suspend fun getById(id: UUID): E? - suspend fun exists(id: UUID): Boolean - suspend fun countAll(): Long - suspend fun deleteById(id: UUID): Boolean -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt index a3afa67..c48ff30 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt @@ -1,9 +1,21 @@ package org.sourcegrade.lab.hub.domain +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.SizedIterable +import org.sourcegrade.lab.hub.db.CourseTable.references +import org.sourcegrade.lab.hub.db.RolePermissionTable +import org.sourcegrade.lab.hub.db.RoleTable import java.util.UUID -data class Role( - override val id: UUID, - val scope: String, - val permissions: List, -) : DomainEntity +//data class Role( +// override val id: UUID, +// val scope: String, +// val permissions: List, +//) : DomainEntity + +internal class Role(id: EntityID) : UUIDEntity(id) { + val name: String by RoleTable.name + val scope: String by RoleTable.scope +// val permissions: SizedIterable by ...? https://github.com/JetBrains/Exposed/issues/928 +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt index 7af1adc..e63be2e 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt @@ -1,14 +1,25 @@ package org.sourcegrade.lab.hub.domain -import java.time.ZonedDateTime +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.sourcegrade.lab.hub.db.SubmissionTable import java.util.UUID -data class Submission( - override val id: UUID, - val assignment: Assignment, - val submitter: User, - val group: SubmissionGroup, - val uploaded: ZonedDateTime, - val gradingRuns: List, - val lastGradingRun: GradingRun, -) : DomainEntity +//data class Submission( +// override val id: UUID, +// val assignment: Assignment, +// val submitter: User, +// val group: SubmissionGroup, +// val uploaded: ZonedDateTime, +// val gradingRuns: List, +// val lastGradingRun: GradingRun, +//) : DomainEntity + +class Submission(id: EntityID) : UUIDEntity(id) { + val assignment by Assignment referencedOn SubmissionTable.assignmentId + val submitter by User referencedOn SubmissionTable.submitterId + val group by SubmissionGroup referencedOn SubmissionTable.groupId + + companion object : EntityClass(SubmissionTable) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt index e27f3f1..1e70a0c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt @@ -1,17 +1,14 @@ package org.sourcegrade.lab.hub.domain +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.sourcegrade.lab.hub.db.SubmissionGroupTable import java.util.UUID -data class SubmissionGroup( - override val id: UUID, - val users: List, - val category: Category, -) : DomainEntity { +class SubmissionGroup(id: EntityID) : UUIDEntity(id) { + val name by SubmissionGroupTable.name + val category by SubmissionGroupCategory referencedOn SubmissionGroupTable.categoryId - data class Category( - override val id: UUID, - val name: String, - val minSize: Int, - val maxSize: Int, - ) : DomainEntity + companion object : EntityClass(SubmissionGroupTable) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt new file mode 100644 index 0000000..cc03448 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt @@ -0,0 +1,16 @@ +package org.sourcegrade.lab.hub.domain + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.sourcegrade.lab.hub.db.SubmissionGroupCategoryTable +import java.util.UUID + +class SubmissionGroupCategory(id: EntityID) : UUIDEntity(id) { + val name by SubmissionGroupCategoryTable.name + val course by Course referencedOn SubmissionGroupCategoryTable.courseId + val minSize by SubmissionGroupCategoryTable.minSize + val maxSize by SubmissionGroupCategoryTable.maxSize + + companion object : EntityClass(SubmissionGroupCategoryTable) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt index b277e26..1d73264 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt @@ -1,11 +1,22 @@ package org.sourcegrade.lab.hub.domain -import java.time.ZonedDateTime +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.sourcegrade.lab.hub.db.TermTable import java.util.UUID -data class Term( - override val id: UUID, - val name: String, - val start: ZonedDateTime, - val end: ZonedDateTime, -) : DomainEntity +//data class Term( +// override val id: UUID, +// val name: String, +// val start: ZonedDateTime, +// val end: ZonedDateTime, +//) : DomainEntity + +class Term(id: EntityID) : UUIDEntity(id) { + val name by TermTable.name + val start by TermTable.start + val end by TermTable.end + + companion object : EntityClass(TermTable) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index 1ff79ce..814bd81 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -1,14 +1,25 @@ package org.sourcegrade.lab.hub.domain +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.sourcegrade.lab.hub.db.UserTable import java.util.UUID -data class User( - override val id: UUID, - val username: String, - val email: String, -) : DomainEntity { - data class CreateDto( - val username: String, - val email: String, - ) : Creates +//data class User( +// override val id: UUID, +// val username: String, +// val email: String, +//) : DomainEntity { +// data class CreateDto( +// val username: String, +// val email: String, +// ) : Creates +//} + +class User(id: EntityID) : UUIDEntity(id) { + val username by UserTable.username + val email by UserTable.email + + companion object : EntityClass(UserTable) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RepositoryQuery.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RepositoryQuery.kt index 577bc5b..0736a10 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RepositoryQuery.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RepositoryQuery.kt @@ -1,7 +1,6 @@ package org.sourcegrade.lab.hub.graphql import org.sourcegrade.lab.hub.domain.DomainEntity -import org.sourcegrade.lab.hub.domain.Repository class RepositoryQuery(val repository: Repository) { From 91c3872daf90bfdad161474aaecaa964555fc22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Wed, 17 Apr 2024 17:07:08 +0200 Subject: [PATCH 08/29] Progess --- .../db/{AssignmentTable.kt => Assignments.kt} | 6 ++-- .../lab/hub/db/{CourseTable.kt => Courses.kt} | 4 +-- .../db/{GradingRunTable.kt => GradingRuns.kt} | 3 +- .../lab/hub/db/RolePermissionBindings.kt | 13 ++++++++ .../lab/hub/db/RolePermissionTable.kt | 8 ----- .../lab/hub/db/{RoleTable.kt => Roles.kt} | 2 +- ...yTable.kt => SubmissionGroupCategories.kt} | 4 +-- .../lab/hub/db/SubmissionGroupMembers.kt | 8 +++++ .../hub/db/SubmissionGroupMembershipTable.kt | 8 ----- ...ssionGroupTable.kt => SubmissionGroups.kt} | 4 +-- .../sourcegrade/lab/hub/db/SubmissionTable.kt | 9 ------ .../org/sourcegrade/lab/hub/db/Submissions.kt | 11 +++++++ .../lab/hub/db/{TermTable.kt => Terms.kt} | 3 +- .../sourcegrade/lab/hub/db/UserRepository.kt | 12 -------- .../lab/hub/db/{UserTable.kt => Users.kt} | 4 +-- .../sourcegrade/lab/hub/domain/Assignment.kt | 16 +++++----- .../org/sourcegrade/lab/hub/domain/Course.kt | 18 +++++------ .../sourcegrade/lab/hub/domain/GradingRun.kt | 11 +++++-- .../org/sourcegrade/lab/hub/domain/Role.kt | 30 +++++++++++-------- .../lab/hub/domain/RolePermissionBinding.kt | 15 ++++++++++ .../sourcegrade/lab/hub/domain/Submission.kt | 13 ++++---- .../lab/hub/domain/SubmissionGroup.kt | 10 ++++--- .../lab/hub/domain/SubmissionGroupCategory.kt | 12 ++++---- .../org/sourcegrade/lab/hub/domain/Term.kt | 10 +++---- .../org/sourcegrade/lab/hub/domain/User.kt | 8 ++--- .../lab/hub/domain/UserRepositoryImpl.kt | 4 +-- 26 files changed, 134 insertions(+), 112 deletions(-) rename hub/src/main/kotlin/org/sourcegrade/lab/hub/db/{AssignmentTable.kt => Assignments.kt} (71%) rename hub/src/main/kotlin/org/sourcegrade/lab/hub/db/{CourseTable.kt => Courses.kt} (65%) rename hub/src/main/kotlin/org/sourcegrade/lab/hub/db/{GradingRunTable.kt => GradingRuns.kt} (60%) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionBindings.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionTable.kt rename hub/src/main/kotlin/org/sourcegrade/lab/hub/db/{RoleTable.kt => Roles.kt} (75%) rename hub/src/main/kotlin/org/sourcegrade/lab/hub/db/{SubmissionGroupCategoryTable.kt => SubmissionGroupCategories.kt} (58%) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembers.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembershipTable.kt rename hub/src/main/kotlin/org/sourcegrade/lab/hub/db/{SubmissionGroupTable.kt => SubmissionGroups.kt} (69%) delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionTable.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt rename hub/src/main/kotlin/org/sourcegrade/lab/hub/db/{TermTable.kt => Terms.kt} (79%) delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserRepository.kt rename hub/src/main/kotlin/org/sourcegrade/lab/hub/db/{UserTable.kt => Users.kt} (58%) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/RolePermissionBinding.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/AssignmentTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt similarity index 71% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/AssignmentTable.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt index 0cc3d9b..fd566ce 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/AssignmentTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt @@ -3,10 +3,10 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -internal object AssignmentTable : UUIDTable("sgl_assignments") { - val courseId = reference("course_id", CourseTable) +internal object Assignments : UUIDTable("sgl_assignments") { + val courseId = reference("course_id", Courses) val name = varchar("name", 255).uniqueIndex() val description = varchar("description", 16 * 1024) val submissionDeadLine = timestamp("submissionDeadline") // TODO: time zone info - val submissionGroupCategory = reference("submission_group_category", SubmissionGroupCategoryTable.id) + val submissionGroupCategory = reference("submission_group_category", SubmissionGroupCategories.id) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt similarity index 65% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseTable.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt index 9b414c3..aace4e6 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt @@ -2,9 +2,9 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable -internal object CourseTable : UUIDTable("sgl_courses") { +internal object Courses : UUIDTable("sgl_courses") { val name = varchar("name", 255) val description = varchar("description", 255) val term = varchar("term", 255) - val ownerId = reference("owner_id", UserTable) + val ownerId = reference("owner_id", Users) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRunTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt similarity index 60% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRunTable.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt index fd62e35..d6dc6ab 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRunTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt @@ -2,7 +2,8 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable -internal object GradingRunTable : UUIDTable("sgl_grading_runs") { +internal object GradingRuns : UUIDTable("sgl_grading_runs") { + val submissionId = reference("submission_id", Submissions) val maxPoints = integer("max_points") val minPoints = integer("min_points") // TODO: rubric = ... diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionBindings.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionBindings.kt new file mode 100644 index 0000000..30e87e2 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionBindings.kt @@ -0,0 +1,13 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.id.UUIDTable + +internal object RolePermissionBindings : UUIDTable("sgl_role_permission_bindings") { + val roleId = reference("role_id", Roles) + val permission = varchar("permission", 255) + val value = bool("value") + + init { + uniqueIndex(roleId, permission) + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionTable.kt deleted file mode 100644 index 96a6b77..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionTable.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.sourcegrade.lab.hub.db - -import org.jetbrains.exposed.sql.Table - -internal object RolePermissionTable : Table("sgl_role_permissions") { - val roleId = reference("role_id", RoleTable) - val permission = varchar("permission", 1024) -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RoleTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Roles.kt similarity index 75% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RoleTable.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Roles.kt index 6984066..4f0af47 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RoleTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Roles.kt @@ -2,7 +2,7 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable -internal object RoleTable : UUIDTable("sgl_roles") { +internal object Roles : UUIDTable("sgl_roles") { val name = varchar("name", 255) val scope = varchar("scope", 255) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategoryTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt similarity index 58% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategoryTable.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt index 80bbbe6..1e62b52 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategoryTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt @@ -2,9 +2,9 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable -internal object SubmissionGroupCategoryTable : UUIDTable("sgl_submission_group_categories") { +internal object SubmissionGroupCategories : UUIDTable("sgl_submission_group_categories") { val name = varchar("name", 255).uniqueIndex() - val courseId = reference("course_id", CourseTable) + val courseId = reference("course_id", Courses) val minSize = integer("min_size") val maxSize = integer("max_size") } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembers.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembers.kt new file mode 100644 index 0000000..d487821 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembers.kt @@ -0,0 +1,8 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.sql.Table + +internal object SubmissionGroupMembers : Table("sgl_submission_group_members") { + val userId = reference("user_id", Users).uniqueIndex() + val submissionGroupId = reference("submission_group_id", SubmissionGroups) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembershipTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembershipTable.kt deleted file mode 100644 index 09e9c32..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembershipTable.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.sourcegrade.lab.hub.db - -import org.jetbrains.exposed.sql.Table - -internal object SubmissionGroupMembershipTable : Table("sgl_submission_group_memberships") { - val userId = reference("user_id", UserTable).uniqueIndex() - val submissionGroupId = reference("submission_group_id", SubmissionGroupTable) -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt similarity index 69% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupTable.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt index 6f5c839..c0dd1e2 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt @@ -2,7 +2,7 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable -internal object SubmissionGroupTable : UUIDTable("sgl_submission_groups") { +internal object SubmissionGroups : UUIDTable("sgl_submission_groups") { val name = varchar("name", 255).uniqueIndex() - val categoryId = reference("category_id", SubmissionGroupCategoryTable) + val categoryId = reference("category_id", SubmissionGroupCategories) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionTable.kt deleted file mode 100644 index a3a1937..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionTable.kt +++ /dev/null @@ -1,9 +0,0 @@ -package org.sourcegrade.lab.hub.db - -import org.jetbrains.exposed.dao.id.UUIDTable - -internal object SubmissionTable : UUIDTable("sgl_submissions") { - val assignmentId = reference("assignment_id", AssignmentTable) - val submitterId = reference("submitter_id", UserTable) - val groupId = reference("group_id", SubmissionGroupTable) -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt new file mode 100644 index 0000000..4fddb40 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt @@ -0,0 +1,11 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp + +internal object Submissions : UUIDTable("sgl_submissions") { + val assignmentId = reference("assignment_id", Assignments) + val submitterId = reference("submitter_id", Users) + val groupId = reference("group_id", SubmissionGroups) + val uploaded = timestamp("uploaded") +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/TermTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt similarity index 79% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/TermTable.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt index f2ec230..d368ca9 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/TermTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt @@ -2,9 +2,8 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -import java.util.UUID -object TermTable : UUIDTable("sgl_terms") { +object Terms : UUIDTable("sgl_terms") { val name = varchar("name", 255).uniqueIndex() val start = timestamp("start") val end = timestamp("end") diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserRepository.kt deleted file mode 100644 index 00afddd..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserRepository.kt +++ /dev/null @@ -1,12 +0,0 @@ -package org.sourcegrade.lab.hub.db - -import org.sourcegrade.lab.hub.models.User - -class UserRepository { - suspend fun getByUsername(username: String): User? { - TODO() - } - suspend fun getByEmail(email: String): User? { - TODO() - } -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt similarity index 58% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt index 6ca20bf..6689266 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UserTable.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt @@ -1,10 +1,8 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable -import org.jetbrains.exposed.sql.ResultRow -import org.sourcegrade.lab.hub.domain.User -internal object UserTable : UUIDTable("sgl_users") { +internal object Users : UUIDTable("sgl_users") { val username = varchar("username", 255).uniqueIndex() val email = varchar("email", 255).uniqueIndex() } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt index 4096abc..d280df6 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -3,9 +3,9 @@ package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.AssignmentTable -import org.sourcegrade.lab.hub.db.CourseTable -import org.sourcegrade.lab.hub.db.RolePermissionTable.references +import org.sourcegrade.lab.hub.db.Assignments +import org.sourcegrade.lab.hub.db.Courses +import org.sourcegrade.lab.hub.db.RolePermissionBindings.references import java.util.UUID //data class Assignment( @@ -18,10 +18,10 @@ import java.util.UUID //) : DomainEntity class Assignment(id: EntityID) : UUIDEntity(id) { - val courseId: EntityID by AssignmentTable.courseId references CourseTable.id - val course: Course by Course referencedOn AssignmentTable.courseId - val name: String by AssignmentTable.name - val description: String by AssignmentTable.description + val courseId: EntityID by Assignments.courseId references Courses.id + val course: Course by Course referencedOn Assignments.courseId + val name: String by Assignments.name + val description: String by Assignments.description - companion object : EntityClass(AssignmentTable) + companion object : EntityClass(Assignments) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt index e7dbb64..9607a4e 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt @@ -4,10 +4,8 @@ import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SizedIterable -import org.sourcegrade.lab.hub.db.AssignmentTable -import org.sourcegrade.lab.hub.db.CourseTable -import org.sourcegrade.lab.hub.db.RolePermissionTable.references -import org.sourcegrade.lab.hub.db.UserTable +import org.sourcegrade.lab.hub.db.Assignments +import org.sourcegrade.lab.hub.db.Courses import java.util.UUID //data class Course( @@ -21,11 +19,11 @@ import java.util.UUID //) : DomainEntity class Course(id: EntityID) : UUIDEntity(id) { - val name: String by CourseTable.name - val description: String by CourseTable.description - val term: String by CourseTable.term - val owner: User by User referencedOn CourseTable.ownerId - val assignments: SizedIterable by Assignment referrersOn AssignmentTable.courseId + val name: String by Courses.name + val description: String by Courses.description + val term: String by Courses.term + val owner: User by User referencedOn Courses.ownerId + val assignments: SizedIterable by Assignment referrersOn Assignments.courseId - companion object : EntityClass(CourseTable) + companion object : EntityClass(Courses) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt index 7715bb6..dd69338 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt @@ -1,8 +1,9 @@ package org.sourcegrade.lab.hub.domain +import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.GradingRunTable +import org.sourcegrade.lab.hub.db.GradingRuns import java.util.UUID //data class GradingRun( @@ -13,6 +14,10 @@ import java.util.UUID //) : DomainEntity class GradingRun(id: EntityID) : UUIDEntity(id) { - val maxPoints: Int by GradingRunTable.maxPoints - val minPoints: Int by GradingRunTable.minPoints + val submission: Submission by Submission referencedOn GradingRuns.submissionId + val maxPoints: Int by GradingRuns.maxPoints + val minPoints: Int by GradingRuns.minPoints + // TODO: Rubric + + companion object : EntityClass(GradingRuns) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt index c48ff30..ffae72c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt @@ -1,21 +1,27 @@ package org.sourcegrade.lab.hub.domain +import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SizedIterable -import org.sourcegrade.lab.hub.db.CourseTable.references -import org.sourcegrade.lab.hub.db.RolePermissionTable -import org.sourcegrade.lab.hub.db.RoleTable +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.sourcegrade.lab.hub.db.RolePermissionBindings +import org.sourcegrade.lab.hub.db.Roles import java.util.UUID -//data class Role( -// override val id: UUID, -// val scope: String, -// val permissions: List, -//) : DomainEntity +class Role(id: EntityID) : UUIDEntity(id) { + val name: String by Roles.name + val scope: String by Roles.scope + val permissionBindings: SizedIterable by RolePermissionBinding referrersOn RolePermissionBindings.roleId -internal class Role(id: EntityID) : UUIDEntity(id) { - val name: String by RoleTable.name - val scope: String by RoleTable.scope -// val permissions: SizedIterable by ...? https://github.com/JetBrains/Exposed/issues/928 + companion object : EntityClass(Roles) +} + +suspend fun Role.hasPermission(permission: String): Boolean? = newSuspendedTransaction { + // TODO: hierarchical permissions + RolePermissionBindings.select(RolePermissionBindings.value) + .where { RolePermissionBindings.roleId eq this@hasPermission.id } + .where { RolePermissionBindings.permission eq permission } + .firstOrNull() + ?.let { it[RolePermissionBindings.value] } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/RolePermissionBinding.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/RolePermissionBinding.kt new file mode 100644 index 0000000..c1556dd --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/RolePermissionBinding.kt @@ -0,0 +1,15 @@ +package org.sourcegrade.lab.hub.domain + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.sourcegrade.lab.hub.db.RolePermissionBindings +import java.util.UUID + +class RolePermissionBinding(id: EntityID) : UUIDEntity(id) { + val roleId by RolePermissionBindings.roleId + val permission by RolePermissionBindings.permission + val value by RolePermissionBindings.value + + companion object : EntityClass(RolePermissionBindings) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt index e63be2e..de6ab38 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt @@ -3,7 +3,8 @@ package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.SubmissionTable +import org.sourcegrade.lab.hub.db.GradingRuns +import org.sourcegrade.lab.hub.db.Submissions import java.util.UUID //data class Submission( @@ -17,9 +18,11 @@ import java.util.UUID //) : DomainEntity class Submission(id: EntityID) : UUIDEntity(id) { - val assignment by Assignment referencedOn SubmissionTable.assignmentId - val submitter by User referencedOn SubmissionTable.submitterId - val group by SubmissionGroup referencedOn SubmissionTable.groupId + val assignment by Assignment referencedOn Submissions.assignmentId + val submitter by User referencedOn Submissions.submitterId + val group by SubmissionGroup referencedOn Submissions.groupId + val uploaded by Submissions.uploaded + val gradingRuns by GradingRun referrersOn GradingRuns.submissionId - companion object : EntityClass(SubmissionTable) + companion object : EntityClass(Submissions) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt index 1e70a0c..acfb732 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt @@ -3,12 +3,14 @@ package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.SubmissionGroupTable +import org.sourcegrade.lab.hub.db.SubmissionGroupMembers +import org.sourcegrade.lab.hub.db.SubmissionGroups import java.util.UUID class SubmissionGroup(id: EntityID) : UUIDEntity(id) { - val name by SubmissionGroupTable.name - val category by SubmissionGroupCategory referencedOn SubmissionGroupTable.categoryId + val name by SubmissionGroups.name + val category by SubmissionGroupCategory referencedOn SubmissionGroups.categoryId + val members by User via SubmissionGroupMembers - companion object : EntityClass(SubmissionGroupTable) + companion object : EntityClass(SubmissionGroups) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt index cc03448..edfb183 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt @@ -3,14 +3,14 @@ package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.SubmissionGroupCategoryTable +import org.sourcegrade.lab.hub.db.SubmissionGroupCategories import java.util.UUID class SubmissionGroupCategory(id: EntityID) : UUIDEntity(id) { - val name by SubmissionGroupCategoryTable.name - val course by Course referencedOn SubmissionGroupCategoryTable.courseId - val minSize by SubmissionGroupCategoryTable.minSize - val maxSize by SubmissionGroupCategoryTable.maxSize + val name by SubmissionGroupCategories.name + val course by Course referencedOn SubmissionGroupCategories.courseId + val minSize by SubmissionGroupCategories.minSize + val maxSize by SubmissionGroupCategories.maxSize - companion object : EntityClass(SubmissionGroupCategoryTable) + companion object : EntityClass(SubmissionGroupCategories) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt index 1d73264..2f436cd 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt @@ -3,7 +3,7 @@ package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.TermTable +import org.sourcegrade.lab.hub.db.Terms import java.util.UUID //data class Term( @@ -14,9 +14,9 @@ import java.util.UUID //) : DomainEntity class Term(id: EntityID) : UUIDEntity(id) { - val name by TermTable.name - val start by TermTable.start - val end by TermTable.end + val name by Terms.name + val start by Terms.start + val end by Terms.end - companion object : EntityClass(TermTable) + companion object : EntityClass(Terms) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index 814bd81..0c928d7 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -3,7 +3,7 @@ package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.UserTable +import org.sourcegrade.lab.hub.db.Users import java.util.UUID //data class User( @@ -18,8 +18,8 @@ import java.util.UUID //} class User(id: EntityID) : UUIDEntity(id) { - val username by UserTable.username - val email by UserTable.email + val username by Users.username + val email by Users.email - companion object : EntityClass(UserTable) + companion object : EntityClass(Users) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepositoryImpl.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepositoryImpl.kt index 6e13916..8399801 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepositoryImpl.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepositoryImpl.kt @@ -3,7 +3,7 @@ package org.sourcegrade.lab.hub.domain import org.apache.logging.log4j.Logger import org.jetbrains.exposed.sql.insert import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.db.UserTable +import org.sourcegrade.lab.hub.db.Users import java.util.UUID class UserRepositoryImpl( @@ -11,7 +11,7 @@ class UserRepositoryImpl( ) : UserRepository { override suspend fun create(item: User.CreateDto): User = newSuspendedTransaction { - val result = UserTable.insert { + val result = Users.insert { it[username] = item.username it[email] = item.email }.resultedValues From 85594bc42cac2d999e9accc6b2958ee4b085f086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Wed, 24 Apr 2024 10:53:42 +0200 Subject: [PATCH 09/29] Rubric stuff --- .../org/sourcegrade/lab/hub/db/Criteria.kt | 11 ++++++++++ .../sourcegrade/lab/hub/db/GradedCriteria.kt | 12 +++++++++++ .../sourcegrade/lab/hub/db/GradedRubrics.kt | 10 +++++++++ .../org/sourcegrade/lab/hub/db/GradingRuns.kt | 7 ++++--- .../org/sourcegrade/lab/hub/db/Rubrics.kt | 8 +++++++ .../sourcegrade/lab/hub/domain/Criterion.kt | 16 ++++++++++++++ .../lab/hub/domain/GradedCriterion.kt | 18 ++++++++++++++++ .../lab/hub/domain/GradedRubric.kt | 21 +++++++++++++++++++ .../sourcegrade/lab/hub/domain/GradingRun.kt | 8 ++++--- .../org/sourcegrade/lab/hub/domain/Rubric.kt | 14 +++++++++++++ model/src/main/protobuf/criterion.proto | 2 +- model/src/main/protobuf/number_range.proto | 11 ---------- model/src/main/protobuf/point_range.proto | 14 +++++-------- model/src/main/protobuf/rubric.proto | 3 +-- 14 files changed, 126 insertions(+), 29 deletions(-) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Criteria.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedCriteria.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedRubrics.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedCriterion.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt delete mode 100644 model/src/main/protobuf/number_range.proto diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Criteria.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Criteria.kt new file mode 100644 index 0000000..47c0089 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Criteria.kt @@ -0,0 +1,11 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.id.UUIDTable + +internal object Criteria : UUIDTable("sgl_criteria") { + val parentRubricId = reference("parent_rubric_id", Rubrics) + val parentCriterionId = reference("parent_criterion_id", Criteria).nullable() + val minPoints = integer("minPoints") + val maxPoints = integer("maxPoints") + val description = text("description") +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedCriteria.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedCriteria.kt new file mode 100644 index 0000000..07d5faa --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedCriteria.kt @@ -0,0 +1,12 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.id.UUIDTable + +object GradedCriteria : UUIDTable("sgl_graded_criteria") { + val parentGradedRubricId = reference("parent_graded_rubric_id", GradedRubrics) + val parentGradedCriterionId = reference("parent_graded_criterion_id", GradedCriteria).nullable() + val criterionId = reference("criterion_id", Criteria) + val achievedMinPoints = integer("achieved_min_points") + val achievedMaxPoints = integer("achieved_max_points") + val message = text("message") +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedRubrics.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedRubrics.kt new file mode 100644 index 0000000..bdfdcaa --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedRubrics.kt @@ -0,0 +1,10 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.id.UUIDTable + +internal object GradedRubrics : UUIDTable("sgl_graded_rubrics") { + val rubricId = reference("rubric_id", Rubrics) + val name = varchar("name", 255) + val achievedMinPoints = integer("achieved_min_points") + val achievedMaxPoints = integer("achieved_max_points") +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt index d6dc6ab..966384e 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt @@ -4,7 +4,8 @@ import org.jetbrains.exposed.dao.id.UUIDTable internal object GradingRuns : UUIDTable("sgl_grading_runs") { val submissionId = reference("submission_id", Submissions) - val maxPoints = integer("max_points") - val minPoints = integer("min_points") - // TODO: rubric = ... + val rubricId = reference("rubric_id", Rubrics) + val gradedRubricId = reference("graded_rubric_id", GradedRubrics) + val minPoints = integer("minPoints") + val maxPoints = integer("maxPoints") } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt new file mode 100644 index 0000000..8df3567 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt @@ -0,0 +1,8 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.id.UUIDTable + +internal object Rubrics : UUIDTable("sgl_rubrics") { + val maxPoints = integer("max_points") + val minPoints = integer("min_points") +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt new file mode 100644 index 0000000..c3074f0 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt @@ -0,0 +1,16 @@ +package org.sourcegrade.lab.hub.domain + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.SizedIterable +import org.sourcegrade.lab.hub.db.Criteria +import java.util.UUID + +class Criterion(id: EntityID) : UUIDEntity(id) { + val parentRubric: Rubric by Rubric referencedOn Criteria.parentRubricId + val parentCriterion: Criterion? by Criterion optionalReferencedOn Criteria.parentCriterionId + val childCriteria: SizedIterable by Criterion optionalReferrersOn Criteria.parentCriterionId + + companion object : EntityClass(Criteria) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedCriterion.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedCriterion.kt new file mode 100644 index 0000000..f17b19b --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedCriterion.kt @@ -0,0 +1,18 @@ +package org.sourcegrade.lab.hub.domain + +import org.jetbrains.exposed.dao.Entity +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.sourcegrade.lab.hub.db.GradedCriteria +import java.util.UUID + +class GradedCriterion(id: EntityID) : Entity(id) { + val parentGradedRubric: GradedRubric by GradedRubric referencedOn GradedCriteria.parentGradedRubricId + val parentGradedCriterion: GradedCriterion? by GradedCriterion optionalReferencedOn GradedCriteria.parentGradedCriterionId + val criterion: Criterion by Criterion referencedOn GradedCriteria.criterionId + val achievedMinPoints: Int by GradedCriteria.achievedMinPoints + val achievedMaxPoints: Int by GradedCriteria.achievedMaxPoints + val message: String by GradedCriteria.message + + companion object : EntityClass(GradedCriteria) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt new file mode 100644 index 0000000..5e5774d --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt @@ -0,0 +1,21 @@ +package org.sourcegrade.lab.hub.domain + +import org.jetbrains.exposed.dao.Entity +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.and +import org.sourcegrade.lab.hub.db.GradedCriteria +import org.sourcegrade.lab.hub.db.GradedRubrics +import java.util.UUID + +class GradedRubric(id: EntityID) : Entity(id) { + val rubric: Rubric by Rubric referencedOn GradedRubrics.rubricId + val name: String by GradedRubrics.name + val achievedMinPoints: Int by GradedRubrics.achievedMinPoints + val achievedMaxPoints: Int by GradedRubrics.achievedMaxPoints + val criteria: SizedIterable + get() = GradedCriterion.find { GradedCriteria.parentGradedRubricId eq id and GradedCriteria.parentGradedCriterionId.isNull() } + + companion object : EntityClass(GradedRubrics) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt index dd69338..69e2d6b 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt @@ -3,6 +3,7 @@ package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID +import org.sourcegrade.lab.hub.db.GradedRubrics import org.sourcegrade.lab.hub.db.GradingRuns import java.util.UUID @@ -15,9 +16,10 @@ import java.util.UUID class GradingRun(id: EntityID) : UUIDEntity(id) { val submission: Submission by Submission referencedOn GradingRuns.submissionId - val maxPoints: Int by GradingRuns.maxPoints - val minPoints: Int by GradingRuns.minPoints - // TODO: Rubric +// val achievedMinPoints: Int by GradingRuns.achievedMinPoints +// val achievedMaxPoints: Int by GradingRuns.achievedMaxPoints + val rubric: Rubric by Rubric referencedOn GradingRuns.rubricId + val gradedRubric: GradedRubric by GradedRubric referencedOn GradingRuns.gradedRubricId companion object : EntityClass(GradingRuns) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt new file mode 100644 index 0000000..5cee5a3 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt @@ -0,0 +1,14 @@ +package org.sourcegrade.lab.hub.domain + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.SizedIterable +import org.sourcegrade.lab.hub.db.Criteria +import org.sourcegrade.lab.hub.db.Rubrics +import java.util.UUID + +class Rubric(id: EntityID) : UUIDEntity(id) { + val childCriteria: SizedIterable by Criterion referrersOn Criteria.parentRubricId + companion object : EntityClass(Rubrics) +} diff --git a/model/src/main/protobuf/criterion.proto b/model/src/main/protobuf/criterion.proto index 4b94f46..24dc282 100644 --- a/model/src/main/protobuf/criterion.proto +++ b/model/src/main/protobuf/criterion.proto @@ -17,7 +17,7 @@ message Criterion { // The description of the criterion. ShowcaseString description = 3; // The possible amount of points that can be achieved. - NumberRange possible_points = 4; + PointRange possible_points = 4; // The points that have been achieved. PointRange achieved_points = 5; // The message displayed if the criterion is not fulfilled. diff --git a/model/src/main/protobuf/number_range.proto b/model/src/main/protobuf/number_range.proto deleted file mode 100644 index fd21583..0000000 --- a/model/src/main/protobuf/number_range.proto +++ /dev/null @@ -1,11 +0,0 @@ -syntax = "proto3"; - -package org.sourcegrade.lab.model.rubric; - -// Defines a range of numbers. The range is inclusive. -message NumberRange { - // Minimum value of the range (inclusive) - int32 min = 1; - // Maximum value of the range (inclusive) - int32 max = 2; -} diff --git a/model/src/main/protobuf/point_range.proto b/model/src/main/protobuf/point_range.proto index 92cef76..1bc7a9b 100644 --- a/model/src/main/protobuf/point_range.proto +++ b/model/src/main/protobuf/point_range.proto @@ -2,14 +2,10 @@ syntax = "proto3"; package org.sourcegrade.lab.model.rubric; -import "number_range.proto"; - -// Defines a range of points. The range is inclusive. +// Defines a range of numbers. The range is inclusive. message PointRange { - oneof value { - // Single point value - int32 single = 1; - // Range of points - NumberRange range = 2; - } + // Minimum value of the range (inclusive) + int32 min = 1; + // Maximum value of the range (inclusive) + int32 max = 2; } diff --git a/model/src/main/protobuf/rubric.proto b/model/src/main/protobuf/rubric.proto index 5f44452..3e1d39a 100644 --- a/model/src/main/protobuf/rubric.proto +++ b/model/src/main/protobuf/rubric.proto @@ -5,7 +5,6 @@ package org.sourcegrade.lab.model.rubric; import "point_range.proto"; import "criterion.proto"; import "showcase_string.proto"; -import "number_range.proto"; // Represents a rubric containing criteria. message Rubric { @@ -16,7 +15,7 @@ message Rubric { // The description of the rubric. ShowcaseString description = 3; // The possible amount of points that can be achieved. - NumberRange possible_points = 4; + PointRange possible_points = 4; // The points that have been achieved. PointRange achieved_points = 5; // The criteria of the rubric. From 252d93b72186a6d1234b7862ba6d0370e4d28794 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Wed, 24 Apr 2024 12:43:50 +0200 Subject: [PATCH 10/29] Fix GradingRun --- .../org/sourcegrade/lab/hub/db/GradingRuns.kt | 8 +++++--- .../sourcegrade/lab/hub/domain/GradingRun.kt | 18 +++++++----------- 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt index 966384e..cfb67cd 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt @@ -4,8 +4,10 @@ import org.jetbrains.exposed.dao.id.UUIDTable internal object GradingRuns : UUIDTable("sgl_grading_runs") { val submissionId = reference("submission_id", Submissions) - val rubricId = reference("rubric_id", Rubrics) val gradedRubricId = reference("graded_rubric_id", GradedRubrics) - val minPoints = integer("minPoints") - val maxPoints = integer("maxPoints") + val rubricId = reference("rubric_id", Rubrics) + val minPoints = integer("min_points") + val maxPoints = integer("max_points") + val achievedMinPoints = integer("achieved_min_points") + val achievedMaxPoints = integer("achieved_max_points") } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt index 69e2d6b..804649e 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt @@ -3,23 +3,19 @@ package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.GradedRubrics import org.sourcegrade.lab.hub.db.GradingRuns import java.util.UUID -//data class GradingRun( -// override val id: UUID, -// val maxPoints: Int, -// val minPoints: Int, -// val rubric: ByteArray, -//) : DomainEntity - class GradingRun(id: EntityID) : UUIDEntity(id) { val submission: Submission by Submission referencedOn GradingRuns.submissionId -// val achievedMinPoints: Int by GradingRuns.achievedMinPoints -// val achievedMaxPoints: Int by GradingRuns.achievedMaxPoints - val rubric: Rubric by Rubric referencedOn GradingRuns.rubricId val gradedRubric: GradedRubric by GradedRubric referencedOn GradingRuns.gradedRubricId + // duplicated data from GradedRubric to minimize joins for frequent queries + val rubric: Rubric by Rubric referencedOn GradingRuns.rubricId + val minPoints: Int by GradingRuns.minPoints + val maxPoints: Int by GradingRuns.maxPoints + val achievedMinPoints: Int by GradingRuns.achievedMinPoints + val achievedMaxPoints: Int by GradingRuns.achievedMaxPoints + companion object : EntityClass(GradingRuns) } From 7268c683b55d410ed6656a161db2f389e03ddc36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Wed, 24 Apr 2024 14:17:42 +0200 Subject: [PATCH 11/29] More stuff --- build.gradle.kts | 9 +++++++++ .../org/sourcegrade/lab/hub/db/Assignments.kt | 3 ++- .../kotlin/org/sourcegrade/lab/hub/db/Courses.kt | 2 ++ .../org/sourcegrade/lab/hub/db/GradingRuns.kt | 2 ++ .../lab/hub/db/RolePermissionBindings.kt | 2 ++ .../kotlin/org/sourcegrade/lab/hub/db/Roles.kt | 2 ++ .../kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt | 2 ++ .../lab/hub/db/SubmissionGroupCategories.kt | 2 ++ .../lab/hub/db/SubmissionGroupMembers.kt | 5 ++++- .../org/sourcegrade/lab/hub/db/SubmissionGroups.kt | 2 ++ .../org/sourcegrade/lab/hub/db/Submissions.kt | 1 + .../kotlin/org/sourcegrade/lab/hub/db/Users.kt | 2 ++ .../org/sourcegrade/lab/hub/domain/Assignment.kt | 12 +++--------- .../org/sourcegrade/lab/hub/domain/Course.kt | 14 +++----------- .../org/sourcegrade/lab/hub/domain/Criterion.kt | 3 +++ .../org/sourcegrade/lab/hub/domain/GradedRubric.kt | 3 ++- .../org/sourcegrade/lab/hub/domain/Rubric.kt | 8 +++++++- .../org/sourcegrade/lab/hub/domain/Submission.kt | 11 +---------- model/src/main/protobuf/criterion.proto | 1 - 19 files changed, 51 insertions(+), 35 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 245ffa2..b156969 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,6 +30,15 @@ allprojects { targetCompatibility = "17" } } + + kotlin { + compilerOptions { + freeCompilerArgs = listOf( + "-opt-in=kotlin.RequiresOptIn", + "-Xcontext-receivers", + ) + } + } } tasks { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt index fd566ce..60f2cdf 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt @@ -4,9 +4,10 @@ import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp internal object Assignments : UUIDTable("sgl_assignments") { + val createdUtc = timestamp("createdUtc") val courseId = reference("course_id", Courses) val name = varchar("name", 255).uniqueIndex() val description = varchar("description", 16 * 1024) - val submissionDeadLine = timestamp("submissionDeadline") // TODO: time zone info + val submissionDeadline = timestamp("submissionDeadline") // TODO: time zone info val submissionGroupCategory = reference("submission_group_category", SubmissionGroupCategories.id) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt index aace4e6..1e1db4c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt @@ -1,8 +1,10 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp internal object Courses : UUIDTable("sgl_courses") { + val createdUtc = timestamp("createdUtc") val name = varchar("name", 255) val description = varchar("description", 255) val term = varchar("term", 255) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt index cfb67cd..84c7d2f 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt @@ -1,8 +1,10 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp internal object GradingRuns : UUIDTable("sgl_grading_runs") { + val createdUtc = timestamp("createdUtc") val submissionId = reference("submission_id", Submissions) val gradedRubricId = reference("graded_rubric_id", GradedRubrics) val rubricId = reference("rubric_id", Rubrics) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionBindings.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionBindings.kt index 30e87e2..51e5635 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionBindings.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionBindings.kt @@ -1,8 +1,10 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp internal object RolePermissionBindings : UUIDTable("sgl_role_permission_bindings") { + val createdUtc = timestamp("createdUtc") val roleId = reference("role_id", Roles) val permission = varchar("permission", 255) val value = bool("value") diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Roles.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Roles.kt index 4f0af47..89f68fb 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Roles.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Roles.kt @@ -1,8 +1,10 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp internal object Roles : UUIDTable("sgl_roles") { + val createdUtc = timestamp("createdUtc") val name = varchar("name", 255) val scope = varchar("scope", 255) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt index 8df3567..92abbbd 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt @@ -1,8 +1,10 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp internal object Rubrics : UUIDTable("sgl_rubrics") { + val createdUtc = timestamp("createdUtc") val maxPoints = integer("max_points") val minPoints = integer("min_points") } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt index 1e62b52..a26d848 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt @@ -1,8 +1,10 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp internal object SubmissionGroupCategories : UUIDTable("sgl_submission_group_categories") { + val createdUtc = timestamp("createdUtc") val name = varchar("name", 255).uniqueIndex() val courseId = reference("course_id", Courses) val minSize = integer("min_size") diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembers.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembers.kt index d487821..dadf60d 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembers.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembers.kt @@ -1,8 +1,11 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp internal object SubmissionGroupMembers : Table("sgl_submission_group_members") { - val userId = reference("user_id", Users).uniqueIndex() + val createdUtc = timestamp("createdUtc") + val endedUtc = timestamp("endedUtc").nullable() + val userId = reference("user_id", Users) val submissionGroupId = reference("submission_group_id", SubmissionGroups) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt index c0dd1e2..331bb72 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt @@ -1,8 +1,10 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp internal object SubmissionGroups : UUIDTable("sgl_submission_groups") { + val createdUtc = timestamp("createdUtc") val name = varchar("name", 255).uniqueIndex() val categoryId = reference("category_id", SubmissionGroupCategories) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt index 4fddb40..c24c18c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt @@ -4,6 +4,7 @@ import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp internal object Submissions : UUIDTable("sgl_submissions") { + val createdUtc = timestamp("createdUtc") val assignmentId = reference("assignment_id", Assignments) val submitterId = reference("submitter_id", Users) val groupId = reference("group_id", SubmissionGroups) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt index 6689266..5561064 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt @@ -1,8 +1,10 @@ package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp internal object Users : UUIDTable("sgl_users") { + val createdUtc = timestamp("createdUtc") val username = varchar("username", 255).uniqueIndex() val email = varchar("email", 255).uniqueIndex() } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt index d280df6..f68984a 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -1,5 +1,6 @@ package org.sourcegrade.lab.hub.domain +import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID @@ -8,20 +9,13 @@ import org.sourcegrade.lab.hub.db.Courses import org.sourcegrade.lab.hub.db.RolePermissionBindings.references import java.util.UUID -//data class Assignment( -// override val id: UUID, -// val name: String, -// val description: String, -// val course: Course, -// val submissionDeadline: ZonedDateTime, -// val submissionGroupCategory: SubmissionGroup.Category, -//) : DomainEntity - class Assignment(id: EntityID) : UUIDEntity(id) { val courseId: EntityID by Assignments.courseId references Courses.id val course: Course by Course referencedOn Assignments.courseId val name: String by Assignments.name val description: String by Assignments.description + val submissionDeadline: Instant by Assignments.submissionDeadline + val submissionGroupCategory: SubmissionGroupCategory by SubmissionGroupCategory referencedOn Assignments.submissionGroupCategory companion object : EntityClass(Assignments) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt index 9607a4e..7307ba3 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt @@ -6,23 +6,15 @@ import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SizedIterable import org.sourcegrade.lab.hub.db.Assignments import org.sourcegrade.lab.hub.db.Courses +import org.sourcegrade.lab.hub.db.SubmissionGroupCategories import java.util.UUID -//data class Course( -// override val id: UUID, -// val name: String, -// val description: String, -// val term: Term, -// val owner: User, -// val submissionGroupCategories: List, -// val assignments: List, -//) : DomainEntity - class Course(id: EntityID) : UUIDEntity(id) { val name: String by Courses.name val description: String by Courses.description val term: String by Courses.term - val owner: User by User referencedOn Courses.ownerId + val owner: User by User referencedOn Courses.ownerId // TODO: Multiple owners + val submissionGroupCategories: SizedIterable by SubmissionGroupCategory referrersOn SubmissionGroupCategories.courseId val assignments: SizedIterable by Assignment referrersOn Assignments.courseId companion object : EntityClass(Courses) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt index c3074f0..417f02b 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt @@ -8,6 +8,9 @@ import org.sourcegrade.lab.hub.db.Criteria import java.util.UUID class Criterion(id: EntityID) : UUIDEntity(id) { + val minPoints: Int by Criteria.minPoints + val maxPoints: Int by Criteria.maxPoints + val description: String by Criteria.description val parentRubric: Rubric by Rubric referencedOn Criteria.parentRubricId val parentCriterion: Criterion? by Criterion optionalReferencedOn Criteria.parentCriterionId val childCriteria: SizedIterable by Criterion optionalReferrersOn Criteria.parentCriterionId diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt index 5e5774d..9727146 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt @@ -14,7 +14,8 @@ class GradedRubric(id: EntityID) : Entity(id) { val name: String by GradedRubrics.name val achievedMinPoints: Int by GradedRubrics.achievedMinPoints val achievedMaxPoints: Int by GradedRubrics.achievedMaxPoints - val criteria: SizedIterable + val allChildCriteria: SizedIterable by GradedCriterion referrersOn GradedCriteria.parentGradedRubricId + val childCriteria: SizedIterable get() = GradedCriterion.find { GradedCriteria.parentGradedRubricId eq id and GradedCriteria.parentGradedCriterionId.isNull() } companion object : EntityClass(GradedRubrics) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt index 5cee5a3..4f66293 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt @@ -4,11 +4,17 @@ import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.and import org.sourcegrade.lab.hub.db.Criteria import org.sourcegrade.lab.hub.db.Rubrics import java.util.UUID class Rubric(id: EntityID) : UUIDEntity(id) { - val childCriteria: SizedIterable by Criterion referrersOn Criteria.parentRubricId + val minPoints: Int by Rubrics.minPoints + val maxPoints: Int by Rubrics.maxPoints + val allChildCriteria: SizedIterable by Criterion referrersOn Criteria.parentRubricId + val childCriteria: SizedIterable + get() = Criterion.find { Criteria.parentRubricId eq id and Criteria.parentCriterionId.isNull() } + companion object : EntityClass(Rubrics) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt index de6ab38..e25a0ca 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt @@ -7,22 +7,13 @@ import org.sourcegrade.lab.hub.db.GradingRuns import org.sourcegrade.lab.hub.db.Submissions import java.util.UUID -//data class Submission( -// override val id: UUID, -// val assignment: Assignment, -// val submitter: User, -// val group: SubmissionGroup, -// val uploaded: ZonedDateTime, -// val gradingRuns: List, -// val lastGradingRun: GradingRun, -//) : DomainEntity - class Submission(id: EntityID) : UUIDEntity(id) { val assignment by Assignment referencedOn Submissions.assignmentId val submitter by User referencedOn Submissions.submitterId val group by SubmissionGroup referencedOn Submissions.groupId val uploaded by Submissions.uploaded val gradingRuns by GradingRun referrersOn GradingRuns.submissionId + // TODO: lastGradingRun companion object : EntityClass(Submissions) } diff --git a/model/src/main/protobuf/criterion.proto b/model/src/main/protobuf/criterion.proto index 24dc282..5f51a21 100644 --- a/model/src/main/protobuf/criterion.proto +++ b/model/src/main/protobuf/criterion.proto @@ -2,7 +2,6 @@ syntax = "proto3"; package org.sourcegrade.lab.model.rubric; -import "number_range.proto"; import "point_range.proto"; import "test_run.proto"; import "criterion_accumulator.proto"; From 22dfe8acb0f858d2bb019633191de957905381bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Wed, 24 Apr 2024 14:43:11 +0200 Subject: [PATCH 12/29] Small cleanup --- build.gradle.kts | 9 ---- .../org/sourcegrade/lab/hub/domain/User.kt | 11 ----- .../lab/hub/domain/UserRepository.kt | 8 ---- .../lab/hub/domain/UserRepositoryImpl.kt | 46 ------------------- .../lab/hub/graphql/RepositoryQuery.kt | 7 --- 5 files changed, 81 deletions(-) delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepository.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepositoryImpl.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RepositoryQuery.kt diff --git a/build.gradle.kts b/build.gradle.kts index b156969..245ffa2 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -30,15 +30,6 @@ allprojects { targetCompatibility = "17" } } - - kotlin { - compilerOptions { - freeCompilerArgs = listOf( - "-opt-in=kotlin.RequiresOptIn", - "-Xcontext-receivers", - ) - } - } } tasks { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index 0c928d7..9a0bef1 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -6,17 +6,6 @@ import org.jetbrains.exposed.dao.id.EntityID import org.sourcegrade.lab.hub.db.Users import java.util.UUID -//data class User( -// override val id: UUID, -// val username: String, -// val email: String, -//) : DomainEntity { -// data class CreateDto( -// val username: String, -// val email: String, -// ) : Creates -//} - class User(id: EntityID) : UUIDEntity(id) { val username by Users.username val email by Users.email diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepository.kt deleted file mode 100644 index 6b844da..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepository.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.sourcegrade.lab.hub.domain - -import kotlin.reflect.KClass - -interface UserRepository : MutableRepository { - override val entityType: KClass - get() = User::class -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepositoryImpl.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepositoryImpl.kt deleted file mode 100644 index 8399801..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserRepositoryImpl.kt +++ /dev/null @@ -1,46 +0,0 @@ -package org.sourcegrade.lab.hub.domain - -import org.apache.logging.log4j.Logger -import org.jetbrains.exposed.sql.insert -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.db.Users -import java.util.UUID - -class UserRepositoryImpl( - private val logger: Logger -) : UserRepository { - - override suspend fun create(item: User.CreateDto): User = newSuspendedTransaction { - val result = Users.insert { - it[username] = item.username - it[email] = item.email - }.resultedValues - - val user = checkNotNull(result) { "Failed to create User ${item.username}" } - .single().toUser() - - logger.info("Created new User ${user.id} with data $item") - - user - } - - override suspend fun put(item: User.CreateDto): MutableRepository.PutResult { - TODO("Not yet implemented") - } - - override suspend fun getById(id: UUID): User? { - TODO("Not yet implemented") - } - - override suspend fun exists(id: UUID): Boolean { - TODO("Not yet implemented") - } - - override suspend fun countAll(): Long { - TODO("Not yet implemented") - } - - override suspend fun deleteById(id: UUID): Boolean { - TODO("Not yet implemented") - } -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RepositoryQuery.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RepositoryQuery.kt deleted file mode 100644 index 0736a10..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RepositoryQuery.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.sourcegrade.lab.hub.graphql - -import org.sourcegrade.lab.hub.domain.DomainEntity - -class RepositoryQuery(val repository: Repository) { - -} From 2088d7a32296ddae3971b10528581805e7a7911a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Wed, 24 Apr 2024 14:56:15 +0200 Subject: [PATCH 13/29] Remove term comment --- hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt index 2f436cd..d92dbbd 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt @@ -6,13 +6,6 @@ import org.jetbrains.exposed.dao.id.EntityID import org.sourcegrade.lab.hub.db.Terms import java.util.UUID -//data class Term( -// override val id: UUID, -// val name: String, -// val start: ZonedDateTime, -// val end: ZonedDateTime, -//) : DomainEntity - class Term(id: EntityID) : UUIDEntity(id) { val name by Terms.name val start by Terms.start From 7fdfe877c54ce829a694ce85fa7329ac5b768048 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Wed, 24 Apr 2024 15:05:48 +0200 Subject: [PATCH 14/29] Clean up --- .../org/sourcegrade/lab/hub/db/Assignments.kt | 2 +- .../sourcegrade/lab/hub/domain/GradingRun.kt | 2 ++ .../lab/hub/domain/RolePermissionBinding.kt | 6 +++--- .../sourcegrade/lab/hub/domain/Submission.kt | 19 +++++++++++++------ .../lab/hub/domain/SubmissionGroup.kt | 7 ++++--- .../lab/hub/domain/SubmissionGroupCategory.kt | 8 ++++---- .../org/sourcegrade/lab/hub/domain/Term.kt | 7 ++++--- .../org/sourcegrade/lab/hub/domain/User.kt | 4 ++-- 8 files changed, 33 insertions(+), 22 deletions(-) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt index 60f2cdf..aee11b4 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt @@ -8,6 +8,6 @@ internal object Assignments : UUIDTable("sgl_assignments") { val courseId = reference("course_id", Courses) val name = varchar("name", 255).uniqueIndex() val description = varchar("description", 16 * 1024) - val submissionDeadline = timestamp("submissionDeadline") // TODO: time zone info + val submissionDeadline = timestamp("submissionDeadline") val submissionGroupCategory = reference("submission_group_category", SubmissionGroupCategories.id) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt index 804649e..7b1f290 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt @@ -1,5 +1,6 @@ package org.sourcegrade.lab.hub.domain +import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID @@ -7,6 +8,7 @@ import org.sourcegrade.lab.hub.db.GradingRuns import java.util.UUID class GradingRun(id: EntityID) : UUIDEntity(id) { + val createdUtc: Instant by GradingRuns.createdUtc val submission: Submission by Submission referencedOn GradingRuns.submissionId val gradedRubric: GradedRubric by GradedRubric referencedOn GradingRuns.gradedRubricId diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/RolePermissionBinding.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/RolePermissionBinding.kt index c1556dd..fc80916 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/RolePermissionBinding.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/RolePermissionBinding.kt @@ -7,9 +7,9 @@ import org.sourcegrade.lab.hub.db.RolePermissionBindings import java.util.UUID class RolePermissionBinding(id: EntityID) : UUIDEntity(id) { - val roleId by RolePermissionBindings.roleId - val permission by RolePermissionBindings.permission - val value by RolePermissionBindings.value + val role: Role by Role referencedOn RolePermissionBindings.roleId + val permission: String by RolePermissionBindings.permission + val value: Boolean by RolePermissionBindings.value companion object : EntityClass(RolePermissionBindings) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt index e25a0ca..f09fd57 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt @@ -1,19 +1,26 @@ package org.sourcegrade.lab.hub.domain +import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.SortOrder import org.sourcegrade.lab.hub.db.GradingRuns import org.sourcegrade.lab.hub.db.Submissions import java.util.UUID class Submission(id: EntityID) : UUIDEntity(id) { - val assignment by Assignment referencedOn Submissions.assignmentId - val submitter by User referencedOn Submissions.submitterId - val group by SubmissionGroup referencedOn Submissions.groupId - val uploaded by Submissions.uploaded - val gradingRuns by GradingRun referrersOn GradingRuns.submissionId - // TODO: lastGradingRun + val createdUtc: Instant by Submissions.createdUtc + val assignment: Assignment by Assignment referencedOn Submissions.assignmentId + val submitter: User by User referencedOn Submissions.submitterId + val group: SubmissionGroup by SubmissionGroup referencedOn Submissions.groupId + val uploaded: Instant by Submissions.uploaded + val gradingRuns: SizedIterable by GradingRun referrersOn GradingRuns.submissionId + val lastGradingRun: GradingRun? + get() = GradingRun.find { GradingRuns.submissionId eq id } + .orderBy(GradingRuns.createdUtc to SortOrder.DESC) + .firstOrNull() companion object : EntityClass(Submissions) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt index acfb732..d4a6ab4 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt @@ -3,14 +3,15 @@ package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.SizedIterable import org.sourcegrade.lab.hub.db.SubmissionGroupMembers import org.sourcegrade.lab.hub.db.SubmissionGroups import java.util.UUID class SubmissionGroup(id: EntityID) : UUIDEntity(id) { - val name by SubmissionGroups.name - val category by SubmissionGroupCategory referencedOn SubmissionGroups.categoryId - val members by User via SubmissionGroupMembers + val name: String by SubmissionGroups.name + val category: SubmissionGroupCategory by SubmissionGroupCategory referencedOn SubmissionGroups.categoryId + val members: SizedIterable by User via SubmissionGroupMembers companion object : EntityClass(SubmissionGroups) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt index edfb183..68d3f1b 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt @@ -7,10 +7,10 @@ import org.sourcegrade.lab.hub.db.SubmissionGroupCategories import java.util.UUID class SubmissionGroupCategory(id: EntityID) : UUIDEntity(id) { - val name by SubmissionGroupCategories.name - val course by Course referencedOn SubmissionGroupCategories.courseId - val minSize by SubmissionGroupCategories.minSize - val maxSize by SubmissionGroupCategories.maxSize + val name: String by SubmissionGroupCategories.name + val course: Course by Course referencedOn SubmissionGroupCategories.courseId + val minSize: Int by SubmissionGroupCategories.minSize + val maxSize: Int by SubmissionGroupCategories.maxSize companion object : EntityClass(SubmissionGroupCategories) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt index d92dbbd..2ec3705 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt @@ -1,5 +1,6 @@ package org.sourcegrade.lab.hub.domain +import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID @@ -7,9 +8,9 @@ import org.sourcegrade.lab.hub.db.Terms import java.util.UUID class Term(id: EntityID) : UUIDEntity(id) { - val name by Terms.name - val start by Terms.start - val end by Terms.end + val name: String by Terms.name + val start: Instant by Terms.start + val end: Instant by Terms.end companion object : EntityClass(Terms) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index 9a0bef1..af7c22c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -7,8 +7,8 @@ import org.sourcegrade.lab.hub.db.Users import java.util.UUID class User(id: EntityID) : UUIDEntity(id) { - val username by Users.username - val email by Users.email + val username: String by Users.username + val email: String by Users.email companion object : EntityClass(Users) } From 831fcefcf8c9a341972f14427120af2749185400 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Wed, 24 Apr 2024 15:08:51 +0200 Subject: [PATCH 15/29] Yeet --- .../org/sourcegrade/lab/hub/domain/User.kt | 4 +- .../sourcegrade/lab/hub/graphql/Queries.kt | 8 -- .../lab/hub/http/AuthenticationModule.kt | 11 +- .../lab/hub/models/CourseMembers.kt | 42 ------ .../org/sourcegrade/lab/hub/models/Courses.kt | 65 --------- .../lab/hub/models/DummyDataPopulator.kt | 102 -------------- .../org/sourcegrade/lab/hub/models/Models.kt | 23 ---- .../org/sourcegrade/lab/hub/models/User.kt | 52 ------- .../sourcegrade/lab/hub/queries/BasicQuery.kt | 41 ------ .../lab/hub/queries/CoursEndpoints.kt | 117 ---------------- .../lab/hub/queries/HelloWorldQuery.kt | 7 - .../lab/hub/queries/UserEndpoints.kt | 127 ------------------ 12 files changed, 8 insertions(+), 591 deletions(-) delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Queries.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/models/CourseMembers.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/models/Courses.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/models/DummyDataPopulator.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/models/Models.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/models/User.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/BasicQuery.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/CoursEndpoints.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/HelloWorldQuery.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/UserEndpoints.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index af7c22c..4efd8ae 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -7,8 +7,8 @@ import org.sourcegrade.lab.hub.db.Users import java.util.UUID class User(id: EntityID) : UUIDEntity(id) { - val username: String by Users.username - val email: String by Users.email + var username: String by Users.username + var email: String by Users.email companion object : EntityClass(Users) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Queries.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Queries.kt deleted file mode 100644 index b6299ba..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Queries.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.sourcegrade.lab.hub.graphql - -import com.expediagroup.graphql.server.operations.Query - -object Queries { - val queries: List = - listOf() -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt index 393c5e6..c25cada 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt @@ -40,9 +40,10 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.models.User -import org.sourcegrade.lab.hub.models.Users +import org.sourcegrade.lab.hub.db.Users +import org.sourcegrade.lab.hub.domain.User import java.io.File +import java.util.UUID import kotlin.collections.set import kotlin.time.Duration.Companion.hours import io.ktor.client.plugins.contentnegotiation.ContentNegotiation as ClientContentNegotiation @@ -154,7 +155,7 @@ fun Application.authenticationModule() { val session = UserSession( - user.id.value, + user.id.value.toString(), checkNotNull(principal.state) { "No state" }, principal.accessToken, userInfo.email, @@ -176,7 +177,7 @@ fun Application.authenticationModule() { } } get("current-user") { - withUser { call.respond(it.toDTO()) } + withUser { call.respond(it) } // TODO: Conversion to DTO } } } @@ -194,7 +195,7 @@ suspend fun PipelineContext.withUserSession(block: su suspend fun PipelineContext.withUser(block: suspend (User) -> Unit) { withUserSession { session -> - val user = newSuspendedTransaction { User.findById(session.userId) } + val user = newSuspendedTransaction { User.findById(UUID.fromString(session.userId)) } checkNotNull(user) { "Could not find user ${session.email} in DB" } block(user) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/CourseMembers.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/CourseMembers.kt deleted file mode 100644 index ee03f9b..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/CourseMembers.kt +++ /dev/null @@ -1,42 +0,0 @@ -package org.sourcegrade.lab.hub.models - -import kotlinx.serialization.Serializable -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.ResultRow - -object CourseMembers : Models("course_members") { - val course = reference("course_id", Courses) - val user = reference("user_id", Users) - override val primaryKey = PrimaryKey(course, user, name = "pk_${tableName}_course_user") -} - -@Serializable -class CourseMemberDTO( - val id: String, - val course: String, - val user: String, -) - -class CourseMember(id: EntityID) : Model(id) { - companion object : EntityClass(CourseMembers) - - var course by Course referencedOn CourseMembers.course - var user by User referencedOn CourseMembers.user - - override fun toDTO(): CourseMemberDTO { - return CourseMemberDTO( - this.id.value, - this.course.id.value, - this.user.id.value, - ) - } -} - -fun ResultRow.toCourseMemberDTO(): CourseMemberDTO { - return CourseMemberDTO( - this[CourseMembers.id].value, - this[CourseMembers.course].value, - this[CourseMembers.user].value, - ) -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/Courses.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/Courses.kt deleted file mode 100644 index 5c92faa..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/Courses.kt +++ /dev/null @@ -1,65 +0,0 @@ -package org.sourcegrade.lab.hub.models - -import kotlinx.serialization.Serializable -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.ResultRow -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction - -enum class SemesterType { - WS, - SS, -} - -object Courses : Models("courses") { - val name = varchar("name", 255) - val description = varchar("description", 255) - val semesterType = enumerationByName("semesterType", 2, SemesterType::class) - val semesterStartYear = integer("semesterStartYear") -} - -class Course(id: EntityID) : Model(id) { - companion object : EntityClass(Courses) - - var name by Courses.name - var description by Courses.description - var semesterType by Courses.semesterType - var semesterStartYear by Courses.semesterStartYear - var members by User via CourseMembers - - override fun toDTO(): CourseDTO { - return CourseDTO( - this.id.value, - this.name, - this.description, - this.semesterType, - this.semesterStartYear, - ) - } -} - -@Serializable -class CourseDTO( - val id: String, - val name: String, - val description: String, - val semesterType: SemesterType, - val semesterStartYear: Int, -) { - suspend fun members(): List { - return newSuspendedTransaction { - Course.findById(this@CourseDTO.id)?.members?.map { it.toDTO() } - ?: throw IllegalArgumentException("No Course with id $id found") - } - } -} - -fun ResultRow.toCourseDTO(): CourseDTO { - return CourseDTO( - this[Courses.id].value, - this[Courses.name], - this[Courses.description], - this[Courses.semesterType], - this[Courses.semesterStartYear], - ) -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/DummyDataPopulator.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/DummyDataPopulator.kt deleted file mode 100644 index 26e6c2a..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/DummyDataPopulator.kt +++ /dev/null @@ -1,102 +0,0 @@ -package org.sourcegrade.lab.hub.models - -import org.jetbrains.exposed.sql.Database -import org.jetbrains.exposed.sql.SchemaUtils -import org.jetbrains.exposed.sql.StdOutSqlLogger -import org.jetbrains.exposed.sql.addLogger -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction - -suspend fun main(args: Array) { - // read HOCON config file - val config = com.typesafe.config.ConfigFactory.load() - val dbConfig = config.getConfig("ktor.db") - Database.connect( - url = dbConfig.getString("url"), - driver = "org.postgresql.Driver", - user = dbConfig.getString("user"), - password = dbConfig.getString("password"), - ) - newSuspendedTransaction { - addLogger(StdOutSqlLogger) - - // delete and re-create tables - val wantedTables = listOf(Users, Courses, CourseMembers) - - val tables = SchemaUtils.listTables() - if (tables.isNotEmpty()) { - SchemaUtils.drop(*wantedTables.toTypedArray()) - } - SchemaUtils.create(*wantedTables.toTypedArray()) - - val dummyUsers = - listOf( - User.new { - username = "John Doe" - email = "test@example.com" -// password = "test" - }, - User.new { - username = "John Smith" - email = "john.smith@aol.com" -// password = "toast" - }, - User.new { - username = "Bernd Scheuert" - email = "b.scheuert@gmail.com" -// password = "meinnameistbernd" - }, - ) - - val dummyCourses = - listOf( - Course.new { - name = "Funktionale und objektorientierte Programmierkonzepte" - description = - "In diesem Kurs lernen Sie die Grundlagen der funktionalen und objektorientierten Programmierung kennen." - semesterType = SemesterType.WS - semesterStartYear = 2023 - }, - Course.new { - name = "Software Engineering" - description = "In diesem Kurs lernen Sie die Grundlagen des Software Engineerings kennen." - semesterType = SemesterType.SS - semesterStartYear = 2023 - }, - Course.new { - name = "Mathematik I für Informatiker" - description = - "In diesem Kurs lernen Sie die Grundlagen der Mathematik kennen. Dazu gehören unter anderem die Grundlagen der Analysis und der Linearen Algebra." - semesterType = SemesterType.WS - semesterStartYear = 2023 - }, - ) - - val dummyCourseMembers = - listOf( - CourseMember.new { - course = dummyCourses[0] - user = dummyUsers[0] - }, - CourseMember.new { - course = dummyCourses[0] - user = dummyUsers[1] - }, - CourseMember.new { - course = dummyCourses[1] - user = dummyUsers[1] - }, - CourseMember.new { - course = dummyCourses[1] - user = dummyUsers[2] - }, - CourseMember.new { - course = dummyCourses[2] - user = dummyUsers[0] - }, - CourseMember.new { - course = dummyCourses[2] - user = dummyUsers[2] - }, - ) - } -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/Models.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/Models.kt deleted file mode 100644 index 90cd0f7..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/Models.kt +++ /dev/null @@ -1,23 +0,0 @@ -package org.sourcegrade.lab.hub.models - -import org.jetbrains.exposed.dao.Entity -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.dao.id.IdTable -import org.jetbrains.exposed.sql.Column -import java.util.UUID - -open class Models : IdTable { - constructor() : super() - constructor(name: String) : super(name) - - final override val id: Column> = - varchar("id", 36) - .clientDefault { UUID.randomUUID().toString() }.entityId() - override val primaryKey = PrimaryKey(id, name = "pk_${tableName}_id") -} - -abstract class Model(id: EntityID) : Entity(id) { - abstract fun toDTO(): T -} - -// TODO: add https://github.com/JetBrains/Exposed/issues/497#issuecomment-520266191 diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/User.kt deleted file mode 100644 index d12d6f9..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/models/User.kt +++ /dev/null @@ -1,52 +0,0 @@ -package org.sourcegrade.lab.hub.models - -import kotlinx.serialization.Serializable -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.ResultRow -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction - -object Users : Models("users") { - val email = varchar("email", 255).uniqueIndex() - val username = varchar("username", 255).uniqueIndex() -} - -@Serializable -class UserDTO( - val id: String, - val username: String, - val email: String, -) { - suspend fun courses(): List { - return newSuspendedTransaction { - User.findById(this@UserDTO.id)?.courses?.map { it.toDTO() } - ?: throw IllegalArgumentException("No User with id $id found") - } - } -} - -class User(id: EntityID) : Model(id) { - companion object : EntityClass(Users) - - var username by Users.username - - // var password by Users.password - var email by Users.email - var courses by Course via CourseMembers - - override fun toDTO(): UserDTO { - return UserDTO( - this.id.value, - this.username, - this.email, - ) - } -} - -fun ResultRow.toUserDTO(): UserDTO { - return UserDTO( - this[Users.id].value, - this[Users.username], - this[Users.email], - ) -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/BasicQuery.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/BasicQuery.kt deleted file mode 100644 index 46fb668..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/BasicQuery.kt +++ /dev/null @@ -1,41 +0,0 @@ -package org.sourcegrade.lab.hub.queries - -import graphql.schema.DataFetchingEnvironment -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.models.Model - -class BasicQuery(private val entityClass: EntityClass>) { - suspend fun helloWorld(): String { - return "Hello World" - } -} - -// generics not supported by gql :( -abstract class BasicMutation(private val entityClass: EntityClass>) { - protected fun requireId(environment: DataFetchingEnvironment): String { - return environment.executionStepInfo.parent?.arguments?.get("id") as? String - ?: throw IllegalArgumentException("id is required") - } - - suspend fun fetch(environment: DataFetchingEnvironment): T { - val id = requireId(environment) - return newSuspendedTransaction { - entityClass.findById(id)?.toDTO() ?: throw IllegalArgumentException("No user with id $id found") - } - } - - suspend fun delete(environment: DataFetchingEnvironment): T { - val id = requireId(environment) - return newSuspendedTransaction { - entityClass.findById(id)?.apply { - delete() - }?.toDTO() ?: throw IllegalArgumentException("No user with id $id found") - } - } - - abstract suspend fun create( - environment: DataFetchingEnvironment, - input: T, - ): T -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/CoursEndpoints.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/CoursEndpoints.kt deleted file mode 100644 index 4dabaef..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/CoursEndpoints.kt +++ /dev/null @@ -1,117 +0,0 @@ -package org.sourcegrade.lab.hub.queries - -import com.expediagroup.graphql.generator.annotations.GraphQLDescription -import com.expediagroup.graphql.generator.annotations.GraphQLName -import com.expediagroup.graphql.server.operations.Mutation -import com.expediagroup.graphql.server.operations.Query -import graphql.schema.DataFetchingEnvironment -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.models.Course -import org.sourcegrade.lab.hub.models.CourseDTO -import org.sourcegrade.lab.hub.models.SemesterType -import org.sourcegrade.lab.hub.models.UserDTO - -@GraphQLDescription("Query collection for Courses") -class CourseQuery { - private fun requireId(environment: DataFetchingEnvironment): String { - return environment.executionStepInfo.parent?.arguments?.get("id") as? String - ?: throw IllegalArgumentException("id is required") - } - - @GraphQLDescription("Get a list of all Courses") - suspend fun fetchAll(): List { - return newSuspendedTransaction { - Course.all().map { it.toDTO() } - } - } - - @GraphQLDescription("Get a single Course by id") - suspend fun fetch(environment: DataFetchingEnvironment): CourseDTO { - val id = requireId(environment) - return newSuspendedTransaction { - Course.findById(id)?.toDTO() ?: throw IllegalArgumentException("No Course with id $id found") - } - } - - @GraphQLDescription("Get the members of a Course by id") - suspend fun members(environment: DataFetchingEnvironment): List { - val id = requireId(environment) - return newSuspendedTransaction { - Course.findById(id)?.members?.toList()?.map { it.toDTO() } ?: throw IllegalArgumentException("No Course with id $id found") - } - } -} - -class CourseQueries : Query { - suspend fun course( - environment: DataFetchingEnvironment, - id: String? = null, - ): CourseQuery { - return CourseQuery() - } -} - -@GraphQLDescription("Mutation collection for Courses") -class CourseMutation { - private fun requireId(environment: DataFetchingEnvironment): String { - return environment.executionStepInfo.parent?.arguments?.get("id") as? String - ?: throw IllegalArgumentException("id is required") - } - - data class CreateCourseInput(val name: String, val description: String, val semesterType: SemesterType, val semesterStartYear: Int) - - @GraphQLDescription("Create a new Course") - suspend fun create( - environment: DataFetchingEnvironment, - input: CreateCourseInput, - ): CourseDTO { - return newSuspendedTransaction { - Course.new { - this.name = input.name - this.description = input.description - this.semesterType = input.semesterType - this.semesterStartYear = input.semesterStartYear - }.toDTO() - } - } - - @GraphQLDescription("Delete a Course by id") - suspend fun delete(environment: DataFetchingEnvironment): CourseDTO { - val id = requireId(environment) - return newSuspendedTransaction { - Course.findById(id)?.apply { - delete() - }?.toDTO() ?: throw IllegalArgumentException("No Course with id $id found") - } - } - - @GraphQLDescription("Get a single Course by id") - suspend fun fetch(environment: DataFetchingEnvironment): CourseDTO { - val id = requireId(environment) - return newSuspendedTransaction { - Course.findById(id)?.toDTO() ?: throw IllegalArgumentException("No Course with id $id found") - } - } - - @GraphQLDescription("Update a Course's Name") - suspend fun updateName( - environment: DataFetchingEnvironment, - @GraphQLName("name") newName: String, - ): String { - val id = requireId(environment) - return newSuspendedTransaction { - Course.findById(id)?.apply { - name = newName - }?.toDTO() ?: throw IllegalArgumentException("No Course with id $id found") - }.name - } -} - -class CourseMutations : Mutation { - suspend fun course( - environment: DataFetchingEnvironment, - id: String? = null, - ): CourseMutation { - return CourseMutation() - } -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/HelloWorldQuery.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/HelloWorldQuery.kt deleted file mode 100644 index e78a596..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/HelloWorldQuery.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.sourcegrade.lab.hub.queries - -import com.expediagroup.graphql.server.operations.Query - -class HelloWorldQuery : Query { - fun helloWorld(name: String? = null) = "Hello, ${name ?: "World"}!" -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/UserEndpoints.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/UserEndpoints.kt deleted file mode 100644 index fd5295f..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/queries/UserEndpoints.kt +++ /dev/null @@ -1,127 +0,0 @@ -package org.sourcegrade.lab.hub.queries - -import com.expediagroup.graphql.generator.annotations.GraphQLDescription -import com.expediagroup.graphql.generator.annotations.GraphQLName -import com.expediagroup.graphql.server.operations.Mutation -import com.expediagroup.graphql.server.operations.Query -import graphql.schema.DataFetchingEnvironment -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.models.CourseDTO -import org.sourcegrade.lab.hub.models.User -import org.sourcegrade.lab.hub.models.UserDTO - -@GraphQLDescription("Query collection for users") -class UserQuery { - private fun requireId(environment: DataFetchingEnvironment): String { - return environment.executionStepInfo.parent?.arguments?.get("id") as? String - ?: throw IllegalArgumentException("id is required") - } - - @GraphQLDescription("Get a list of all users") - suspend fun fetchAll(): List { - return newSuspendedTransaction { - User.all().map { it.toDTO() } - } - } - - @GraphQLDescription("Get a single user by id") - suspend fun fetch(environment: DataFetchingEnvironment): UserDTO { - val id = requireId(environment) - return newSuspendedTransaction { - User.findById(id)?.toDTO() ?: throw IllegalArgumentException("No user with id $id found") - } - } - - @GraphQLDescription("Get the courses of a user by id") - suspend fun courses(environment: DataFetchingEnvironment): List { - val id = requireId(environment) - return newSuspendedTransaction { - User.findById(id)?.courses?.toList()?.map { it.toDTO() } ?: throw IllegalArgumentException("No user with id $id found") - } - } -} - -class UserQueries : Query { - suspend fun user( - environment: DataFetchingEnvironment, - id: String? = null, - ): UserQuery { - return UserQuery() - } -} - -@GraphQLDescription("Mutation collection for users") -class UserMutation { - private fun requireId(environment: DataFetchingEnvironment): String { - return environment.executionStepInfo.parent?.arguments?.get("id") as? String - ?: throw IllegalArgumentException("id is required") - } - - data class CreateUserInput(val email: String, val username: String, val password: String? = null) - - @GraphQLDescription("Create a new user") - suspend fun create( - environment: DataFetchingEnvironment, - input: CreateUserInput, - ): UserDTO { - return newSuspendedTransaction { - User.new { - this.email = input.email - this.username = input.username - }.toDTO() - } - } - - @GraphQLDescription("Delete a user by id") - suspend fun delete(environment: DataFetchingEnvironment): UserDTO { - val id = requireId(environment) - return newSuspendedTransaction { - User.findById(id)?.apply { - delete() - }?.toDTO() ?: throw IllegalArgumentException("No user with id $id found") - } - } - - @GraphQLDescription("Get a single user by id") - suspend fun fetch(environment: DataFetchingEnvironment): UserDTO { - val id = requireId(environment) - return newSuspendedTransaction { - User.findById(id)?.toDTO() ?: throw IllegalArgumentException("No user with id $id found") - } - } - - @GraphQLDescription("Update a user's email") - suspend fun updateEmail( - environment: DataFetchingEnvironment, - @GraphQLName("email") newEmail: String, - ): String { - val id = requireId(environment) - return newSuspendedTransaction { - User.findById(id)?.apply { - email = newEmail - }?.toDTO() ?: throw IllegalArgumentException("No user with id $id found") - }.email - } - - @GraphQLDescription("Update a user's username") - suspend fun updateUsername( - environment: DataFetchingEnvironment, - @GraphQLName("username") newUsername: String, - ): String { - val id = requireId(environment) - return newSuspendedTransaction { - User.findById(id)?.apply { - username = newUsername - }?.toDTO() ?: throw IllegalArgumentException("No user with id $id found") - }.username - } -} - -class UserMutations : Mutation { - suspend fun user( - environment: DataFetchingEnvironment, - id: String? = null, - ): UserMutation { - return UserMutation() - } -} From ad07463488f653bad3cf879e08fc4b549dd1fde8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Wed, 24 Apr 2024 15:15:15 +0200 Subject: [PATCH 16/29] Fix style --- .../kotlin/org/sourcegrade/lab/hub/Module.kt | 38 ++++++++----------- 1 file changed, 16 insertions(+), 22 deletions(-) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt index 7c71cf9..c8f16b5 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt @@ -1,6 +1,5 @@ package org.sourcegrade.lab.hub -import com.expediagroup.graphql.server.ktor.GraphQL import com.expediagroup.graphql.server.ktor.graphQLGetRoute import com.expediagroup.graphql.server.ktor.graphQLPostRoute import com.expediagroup.graphql.server.ktor.graphQLSDLRoute @@ -18,11 +17,6 @@ import io.ktor.server.routing.Routing import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.DatabaseConfig import org.sourcegrade.lab.hub.http.authenticationModule -import org.sourcegrade.lab.hub.queries.CourseMutations -import org.sourcegrade.lab.hub.queries.CourseQueries -import org.sourcegrade.lab.hub.queries.HelloWorldQuery -import org.sourcegrade.lab.hub.queries.UserMutations -import org.sourcegrade.lab.hub.queries.UserQueries fun Application.module() { val environment = environment @@ -54,22 +48,22 @@ fun Application.module() { anyHost() } - install(GraphQL) { - schema { - packages = listOf("org.sourcegrade.lab.hub") - queries = - listOf( - HelloWorldQuery(), - UserQueries(), - CourseQueries(), - ) - mutations = - listOf( - UserMutations(), - CourseMutations(), - ) - } - } +// install(GraphQL) { +// schema { +// packages = listOf("org.sourcegrade.lab.hub") +// queries = +// listOf( +// HelloWorldQuery(), +// UserQueries(), +// CourseQueries(), +// ) +// mutations = +// listOf( +// UserMutations(), +// CourseMutations(), +// ) +// } +// } install(Routing) { graphQLGetRoute() From 8205adcd899d931e5389fd706acd8ea18f03cb4c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Wed, 24 Apr 2024 22:32:20 +0200 Subject: [PATCH 17/29] Extract domain interfaces --- .../org/sourcegrade/lab/hub/db/Assignments.kt | 24 ++++++++- .../lab/hub/db/CourseMemberships.kt | 29 +++++++++++ .../org/sourcegrade/lab/hub/db/Courses.kt | 24 +++++++++ .../org/sourcegrade/lab/hub/db/Criteria.kt | 22 +++++++++ .../sourcegrade/lab/hub/db/GradedCriteria.kt | 21 ++++++++ .../sourcegrade/lab/hub/db/GradedRubrics.kt | 24 +++++++++ .../org/sourcegrade/lab/hub/db/GradingRuns.kt | 27 ++++++++++ .../lab/hub/db/ReferenceMutator.kt | 20 ++++++++ .../lab/hub/db/RolePermissionBindings.kt | 15 ------ .../org/sourcegrade/lab/hub/db/Roles.kt | 10 ---- .../org/sourcegrade/lab/hub/db/Rubrics.kt | 26 ++++++++++ .../lab/hub/db/SubmissionGroupCategories.kt | 17 +++++++ .../lab/hub/db/SubmissionGroupMembers.kt | 11 ----- .../lab/hub/db/SubmissionGroupMemberships.kt | 24 +++++++++ .../lab/hub/db/SubmissionGroups.kt | 20 ++++++++ .../org/sourcegrade/lab/hub/db/Submissions.kt | 24 +++++++++ .../org/sourcegrade/lab/hub/db/Terms.kt | 15 +++++- .../org/sourcegrade/lab/hub/db/Users.kt | 49 +++++++++++++++++++ .../sourcegrade/lab/hub/domain/Assignment.kt | 27 +++++----- .../org/sourcegrade/lab/hub/domain/Course.kt | 21 +++----- .../sourcegrade/lab/hub/domain/Criterion.kt | 21 +++----- .../lab/hub/domain/DomainEntity.kt | 9 ++++ .../lab/hub/domain/GradedCriterion.kt | 22 +++------ .../lab/hub/domain/GradedRubric.kt | 22 +++------ .../sourcegrade/lab/hub/domain/GradingRun.kt | 32 +++++------- .../org/sourcegrade/lab/hub/domain/Role.kt | 27 ---------- .../lab/hub/domain/RolePermissionBinding.kt | 15 ------ .../org/sourcegrade/lab/hub/domain/Rubric.kt | 20 +++----- .../sourcegrade/lab/hub/domain/Submission.kt | 25 +++------- .../lab/hub/domain/SubmissionGroup.kt | 16 ++---- .../lab/hub/domain/SubmissionGroupCategory.kt | 18 ++----- .../org/sourcegrade/lab/hub/domain/Term.kt | 15 ++---- .../sourcegrade/lab/hub/domain/TermScoped.kt | 5 ++ .../org/sourcegrade/lab/hub/domain/User.kt | 18 +++---- .../lab/hub/domain/UserMembership.kt | 17 +++++++ 35 files changed, 481 insertions(+), 251 deletions(-) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutator.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionBindings.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Roles.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembers.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/RolePermissionBinding.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/TermScoped.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt index aee11b4..f413adf 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt @@ -1,13 +1,35 @@ package org.sourcegrade.lab.hub.db +import kotlinx.datetime.Instant +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.domain.Assignment +import org.sourcegrade.lab.hub.domain.Course +import java.util.UUID internal object Assignments : UUIDTable("sgl_assignments") { val createdUtc = timestamp("createdUtc") val courseId = reference("course_id", Courses) + val submissionGroupCategory = reference("submission_group_category", SubmissionGroupCategories.id) + val name = varchar("name", 255).uniqueIndex() val description = varchar("description", 16 * 1024) val submissionDeadline = timestamp("submissionDeadline") - val submissionGroupCategory = reference("submission_group_category", SubmissionGroupCategories.id) +} + +internal class DBAssignment(id: EntityID) : UUIDEntity(id), Assignment { + override val uuid: UUID = id.value + override val createdUtc: Instant by Assignments.createdUtc + override val course: Course by DBCourse referencedOn Assignments.courseId + override val submissionGroupCategory: DBSubmissionGroupCategory by DBSubmissionGroupCategory referencedOn Assignments.submissionGroupCategory + + override var name: String by Assignments.name + override var description: String by Assignments.description + override var submissionDeadlineUtc: Instant by Assignments.submissionDeadline + override var submissionGroupCategoryId: UUID by SubmissionGroupCategories.referenceMutator(Assignments.submissionGroupCategory) + + companion object : EntityClass(Assignments) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt new file mode 100644 index 0000000..044b8d5 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt @@ -0,0 +1,29 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.domain.Course +import org.sourcegrade.lab.hub.domain.UserMembership +import java.util.UUID + +internal object CourseMemberships : UUIDTable("sgl_course_membership") { + val createdUtc = timestamp("createdUtc") + val userId = reference("user_id", Users) + val startUtc = timestamp("startUtc") + val endUtc = timestamp("endUtc").nullable() + val courseId = reference("course_id", Courses) +} + +internal class DBCourseMembership(id: EntityID) : UUIDEntity(id), UserMembership { + override val uuid: UUID = id.value + override val createdUtc by CourseMemberships.createdUtc + override val startUtc by CourseMemberships.startUtc + override val endUtc by CourseMemberships.endUtc + override val user: DBUser by DBUser referencedOn CourseMemberships.userId + override val target: DBCourse by DBCourse referencedOn CourseMemberships.courseId + + companion object : EntityClass(CourseMemberships) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt index 1e1db4c..67370cc 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt @@ -1,7 +1,16 @@ package org.sourcegrade.lab.hub.db +import kotlinx.datetime.Instant +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.domain.Assignment +import org.sourcegrade.lab.hub.domain.Course +import org.sourcegrade.lab.hub.domain.Term +import java.util.UUID internal object Courses : UUIDTable("sgl_courses") { val createdUtc = timestamp("createdUtc") @@ -10,3 +19,18 @@ internal object Courses : UUIDTable("sgl_courses") { val term = varchar("term", 255) val ownerId = reference("owner_id", Users) } + +internal class DBCourse(id: EntityID) : UUIDEntity(id), Course { + override val uuid: UUID = id.value + override val createdUtc: Instant by Courses.createdUtc + override val term: Term by Term referencedOn Courses.term + override val submissionGroupCategories: SizedIterable by DBSubmissionGroupCategory referrersOn SubmissionGroupCategories.courseId + override val assignments: SizedIterable by DBAssignment referrersOn Assignments.courseId + override val owner: DBUser by DBUser referencedOn Courses.ownerId // TODO: Multiple owners + + override var name: String by Courses.name + override var description: String by Courses.description + override var ownerId: UUID by Users.referenceMutator(Courses.ownerId) + + companion object : EntityClass(Courses) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Criteria.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Criteria.kt index 47c0089..8c30e00 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Criteria.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Criteria.kt @@ -1,6 +1,14 @@ package org.sourcegrade.lab.hub.db +import kotlinx.datetime.Instant +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.SizedIterable +import org.sourcegrade.lab.hub.domain.Criterion +import org.sourcegrade.lab.hub.domain.Rubric +import java.util.UUID internal object Criteria : UUIDTable("sgl_criteria") { val parentRubricId = reference("parent_rubric_id", Rubrics) @@ -9,3 +17,17 @@ internal object Criteria : UUIDTable("sgl_criteria") { val maxPoints = integer("maxPoints") val description = text("description") } + +class DBCriterion(id: EntityID) : UUIDEntity(id), Criterion { + override val uuid: UUID = id.value + override val createdUtc: Instant + get() = parentRubric.createdUtc + override val minPoints: Int by Criteria.minPoints + override val maxPoints: Int by Criteria.maxPoints + override val description: String by Criteria.description + override val parentRubric: Rubric by DBRubric referencedOn Criteria.parentRubricId + override val parentCriterion: DBCriterion? by DBCriterion optionalReferencedOn Criteria.parentCriterionId + override val childCriteria: SizedIterable by DBCriterion optionalReferrersOn Criteria.parentCriterionId + + companion object : EntityClass(Criteria) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedCriteria.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedCriteria.kt index 07d5faa..c5f2261 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedCriteria.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedCriteria.kt @@ -1,6 +1,13 @@ package org.sourcegrade.lab.hub.db +import kotlinx.datetime.Instant +import org.jetbrains.exposed.dao.Entity +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.sourcegrade.lab.hub.domain.GradedCriterion +import org.sourcegrade.lab.hub.domain.GradedRubric +import java.util.UUID object GradedCriteria : UUIDTable("sgl_graded_criteria") { val parentGradedRubricId = reference("parent_graded_rubric_id", GradedRubrics) @@ -10,3 +17,17 @@ object GradedCriteria : UUIDTable("sgl_graded_criteria") { val achievedMaxPoints = integer("achieved_max_points") val message = text("message") } + +class DBGradedCriterion(id: EntityID) : Entity(id), GradedCriterion { + override val uuid: UUID = id.value + override val createdUtc: Instant + get() = parentGradedRubric.createdUtc + override val parentGradedRubric: GradedRubric by DBGradedRubric referencedOn GradedCriteria.parentGradedRubricId + override val parentGradedCriterion: DBGradedCriterion? by DBGradedCriterion optionalReferencedOn GradedCriteria.parentGradedCriterionId + override val criterion: DBCriterion by DBCriterion referencedOn GradedCriteria.criterionId + override val achievedMinPoints: Int by GradedCriteria.achievedMinPoints + override val achievedMaxPoints: Int by GradedCriteria.achievedMaxPoints + override val message: String by GradedCriteria.message + + companion object : EntityClass(GradedCriteria) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedRubrics.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedRubrics.kt index bdfdcaa..f2fc15b 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedRubrics.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedRubrics.kt @@ -1,10 +1,34 @@ package org.sourcegrade.lab.hub.db +import kotlinx.datetime.Instant +import org.jetbrains.exposed.dao.Entity +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.domain.GradedRubric +import java.util.UUID internal object GradedRubrics : UUIDTable("sgl_graded_rubrics") { + val createdUtc = timestamp("created_utc") val rubricId = reference("rubric_id", Rubrics) val name = varchar("name", 255) val achievedMinPoints = integer("achieved_min_points") val achievedMaxPoints = integer("achieved_max_points") } + +class DBGradedRubric(id: EntityID) : Entity(id), GradedRubric { + override val uuid: UUID = id.value + override val createdUtc: Instant by GradedRubrics.createdUtc + override val rubric: DBRubric by DBRubric referencedOn GradedRubrics.rubricId + override val name: String by GradedRubrics.name + override val achievedMinPoints: Int by GradedRubrics.achievedMinPoints + override val achievedMaxPoints: Int by GradedRubrics.achievedMaxPoints + override val allChildCriteria: SizedIterable by DBGradedCriterion referrersOn GradedCriteria.parentGradedRubricId + override val childCriteria: SizedIterable + get() = DBGradedCriterion.find { (GradedCriteria.parentGradedRubricId eq id) and GradedCriteria.parentGradedCriterionId.isNull() } + + companion object : EntityClass(GradedRubrics) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt index 84c7d2f..af1e4c7 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt @@ -1,15 +1,42 @@ package org.sourcegrade.lab.hub.db +import kotlinx.datetime.Instant +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.kotlin.datetime.duration import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.domain.GradingRun +import java.util.UUID +import kotlin.time.Duration internal object GradingRuns : UUIDTable("sgl_grading_runs") { val createdUtc = timestamp("createdUtc") val submissionId = reference("submission_id", Submissions) val gradedRubricId = reference("graded_rubric_id", GradedRubrics) + val runtime = duration("runtime") + + // duplicated data from GradedRubric to minimize joins for frequent queries val rubricId = reference("rubric_id", Rubrics) val minPoints = integer("min_points") val maxPoints = integer("max_points") val achievedMinPoints = integer("achieved_min_points") val achievedMaxPoints = integer("achieved_max_points") } + +internal class DBGradingRun(id: EntityID) : UUIDEntity(id), GradingRun { + override val uuid: UUID = id.value + override val createdUtc: Instant by GradingRuns.createdUtc + override val submission: DBSubmission by DBSubmission referencedOn GradingRuns.submissionId + override val gradedRubric: DBGradedRubric by DBGradedRubric referencedOn GradingRuns.gradedRubricId + override val runtime: Duration by GradingRuns.runtime + + override val rubric: DBRubric by DBRubric referencedOn GradingRuns.rubricId + override val minPoints: Int by GradingRuns.minPoints + override val maxPoints: Int by GradingRuns.maxPoints + override val achievedMinPoints: Int by GradingRuns.achievedMinPoints + override val achievedMaxPoints: Int by GradingRuns.achievedMaxPoints + + companion object : EntityClass(GradingRuns) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutator.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutator.kt new file mode 100644 index 0000000..314d738 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutator.kt @@ -0,0 +1,20 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.Entity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.IdTable +import org.jetbrains.exposed.sql.Column +import kotlin.reflect.KProperty + +fun > IdTable.referenceMutator(column: Column>): ReferenceMutator = ReferenceMutator(this, column) + +class ReferenceMutator>( + private val table: IdTable, + private val column: Column>, +) { + operator fun getValue(self: Entity, property: KProperty<*>): ID = + with(self) { column.getValue(self, property).value } + + operator fun setValue(self: Entity, property: KProperty<*>, value: ID) = + with(self) { column.setValue(self, property, EntityID(value, table)) } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionBindings.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionBindings.kt deleted file mode 100644 index 51e5635..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RolePermissionBindings.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.sourcegrade.lab.hub.db - -import org.jetbrains.exposed.dao.id.UUIDTable -import org.jetbrains.exposed.sql.kotlin.datetime.timestamp - -internal object RolePermissionBindings : UUIDTable("sgl_role_permission_bindings") { - val createdUtc = timestamp("createdUtc") - val roleId = reference("role_id", Roles) - val permission = varchar("permission", 255) - val value = bool("value") - - init { - uniqueIndex(roleId, permission) - } -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Roles.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Roles.kt deleted file mode 100644 index 89f68fb..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Roles.kt +++ /dev/null @@ -1,10 +0,0 @@ -package org.sourcegrade.lab.hub.db - -import org.jetbrains.exposed.dao.id.UUIDTable -import org.jetbrains.exposed.sql.kotlin.datetime.timestamp - -internal object Roles : UUIDTable("sgl_roles") { - val createdUtc = timestamp("createdUtc") - val name = varchar("name", 255) - val scope = varchar("scope", 255) -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt index 92abbbd..54106ed 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt @@ -1,10 +1,36 @@ package org.sourcegrade.lab.hub.db +import kotlinx.datetime.Instant +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.domain.Assignment +import org.sourcegrade.lab.hub.domain.Criterion +import org.sourcegrade.lab.hub.domain.Rubric +import java.util.UUID internal object Rubrics : UUIDTable("sgl_rubrics") { val createdUtc = timestamp("createdUtc") + val name = varchar("name", 255) + val assignmentId = reference("assignment_id", Assignments) val maxPoints = integer("max_points") val minPoints = integer("min_points") } + +class DBRubric(id: EntityID) : UUIDEntity(id), Rubric { + override val uuid: UUID = id.value + override val createdUtc: Instant by Rubrics.createdUtc + override var name: String by Rubrics.name + override val minPoints: Int by Rubrics.minPoints + override val maxPoints: Int by Rubrics.maxPoints + override val assignment: Assignment by DBAssignment referencedOn Rubrics.assignmentId + override val allChildCriteria: SizedIterable by DBCriterion referrersOn Criteria.parentRubricId + override val childCriteria: SizedIterable + get() = DBCriterion.find { (Criteria.parentRubricId eq id) and Criteria.parentCriterionId.isNull() } + + companion object : EntityClass(Rubrics) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt index a26d848..1236a36 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt @@ -1,7 +1,13 @@ package org.sourcegrade.lab.hub.db +import kotlinx.datetime.Instant +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.domain.SubmissionGroupCategory +import java.util.UUID internal object SubmissionGroupCategories : UUIDTable("sgl_submission_group_categories") { val createdUtc = timestamp("createdUtc") @@ -10,3 +16,14 @@ internal object SubmissionGroupCategories : UUIDTable("sgl_submission_group_cate val minSize = integer("min_size") val maxSize = integer("max_size") } + +internal class DBSubmissionGroupCategory(id: EntityID) : UUIDEntity(id), SubmissionGroupCategory { + override val uuid: UUID = id.value + override val createdUtc: Instant by SubmissionGroupCategories.createdUtc + override val course: DBCourse by DBCourse referencedOn SubmissionGroupCategories.courseId + override var name: String by SubmissionGroupCategories.name + override var minSize: Int by SubmissionGroupCategories.minSize + override var maxSize: Int by SubmissionGroupCategories.maxSize + + companion object : EntityClass(SubmissionGroupCategories) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembers.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembers.kt deleted file mode 100644 index dadf60d..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMembers.kt +++ /dev/null @@ -1,11 +0,0 @@ -package org.sourcegrade.lab.hub.db - -import org.jetbrains.exposed.sql.Table -import org.jetbrains.exposed.sql.kotlin.datetime.timestamp - -internal object SubmissionGroupMembers : Table("sgl_submission_group_members") { - val createdUtc = timestamp("createdUtc") - val endedUtc = timestamp("endedUtc").nullable() - val userId = reference("user_id", Users) - val submissionGroupId = reference("submission_group_id", SubmissionGroups) -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt new file mode 100644 index 0000000..bcabfb6 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt @@ -0,0 +1,24 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import java.util.UUID + +internal object SubmissionGroupMemberships : Table("sgl_submission_group_membership") { + val createdUtc = timestamp("createdUtc") + val startUtc = timestamp("startUtc") + val endUtc = timestamp("endUtc").nullable() + val userId = reference("user_id", Users) + val submissionGroupId = reference("submission_group_id", SubmissionGroups) +} + +internal class DBSubmissionGroupMembership(id: EntityID) : UUIDEntity(id), SubmissionGroupMembership { + override val uuid: UUID = id.value + override val createdUtc by SubmissionGroupMemberships.createdUtc + override val startUtc by SubmissionGroupMemberships.startUtc + override val endUtc by SubmissionGroupMemberships.endUtc + override val user: DBUser by DBUser referencedOn SubmissionGroupMemberships.userId + override val submissionGroup: DBSubmissionGroup by DBSubmissionGroup referencedOn SubmissionGroupMemberships.submissionGroupId +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt index 331bb72..528b371 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt @@ -1,10 +1,30 @@ package org.sourcegrade.lab.hub.db +import kotlinx.datetime.Instant +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.domain.SubmissionGroup +import org.sourcegrade.lab.hub.domain.Term +import java.util.UUID internal object SubmissionGroups : UUIDTable("sgl_submission_groups") { val createdUtc = timestamp("createdUtc") val name = varchar("name", 255).uniqueIndex() val categoryId = reference("category_id", SubmissionGroupCategories) } + +internal class DBSubmissionGroup(id: EntityID) : UUIDEntity(id), SubmissionGroup { + override val uuid: UUID = id.value + override val createdUtc: Instant by SubmissionGroups.createdUtc + override val term: Term + get() = category.course.term + override val name: String by SubmissionGroups.name + override val category: DBSubmissionGroupCategory by DBSubmissionGroupCategory referencedOn SubmissionGroups.categoryId + override val members: SizedIterable by DBUser via SubmissionGroupMemberships + + companion object : EntityClass(SubmissionGroups) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt index c24c18c..9d097c6 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt @@ -1,7 +1,15 @@ package org.sourcegrade.lab.hub.db +import kotlinx.datetime.Instant +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.domain.Submission +import java.util.UUID internal object Submissions : UUIDTable("sgl_submissions") { val createdUtc = timestamp("createdUtc") @@ -10,3 +18,19 @@ internal object Submissions : UUIDTable("sgl_submissions") { val groupId = reference("group_id", SubmissionGroups) val uploaded = timestamp("uploaded") } + +internal class DBSubmission(id: EntityID) : UUIDEntity(id), Submission { + override val uuid: UUID = id.value + override val createdUtc: Instant by Submissions.createdUtc + override val assignment: DBAssignment by DBAssignment referencedOn Submissions.assignmentId + override val submitter: DBUser by DBUser referencedOn Submissions.submitterId + override val group: DBSubmissionGroup by DBSubmissionGroup referencedOn Submissions.groupId + override val uploaded: Instant by Submissions.uploaded + override val gradingRuns: SizedIterable by DBGradingRun referrersOn GradingRuns.submissionId + override val lastGradingRun: DBGradingRun? + get() = DBGradingRun.find { GradingRuns.submissionId eq id } + .orderBy(GradingRuns.createdUtc to SortOrder.DESC) + .firstOrNull() + + companion object : EntityClass(Submissions) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt index d368ca9..b4196c2 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt @@ -1,10 +1,23 @@ package org.sourcegrade.lab.hub.db +import kotlinx.datetime.Instant +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import java.util.UUID -object Terms : UUIDTable("sgl_terms") { +internal object Terms : UUIDTable("sgl_terms") { val name = varchar("name", 255).uniqueIndex() val start = timestamp("start") val end = timestamp("end") } + +internal class Term(id: EntityID) : UUIDEntity(id) { + val name: String by Terms.name + val start: Instant by Terms.start + val end: Instant by Terms.end + + companion object : EntityClass(Terms) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt index 5561064..8ac58c2 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt @@ -1,10 +1,59 @@ package org.sourcegrade.lab.hub.db +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.jetbrains.exposed.sql.mapLazy +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.sourcegrade.lab.hub.domain.Course +import org.sourcegrade.lab.hub.domain.Submission +import org.sourcegrade.lab.hub.domain.SubmissionGroup +import org.sourcegrade.lab.hub.domain.User +import org.sourcegrade.lab.hub.domain.UserMembership +import java.util.UUID internal object Users : UUIDTable("sgl_users") { val createdUtc = timestamp("createdUtc") val username = varchar("username", 255).uniqueIndex() + val displayname = varchar("displayname", 255) val email = varchar("email", 255).uniqueIndex() } + +internal class DBUser(id: EntityID) : UUIDEntity(id), User { + override val uuid: UUID = id.value + override val createdUtc: Instant by Users.createdUtc + override var username: String by Users.username + override var displayname: String by Users.displayname + override var email: String by Users.email + + override val courseMemberships: UserMembership.Accessor = object : UserMembership.Accessor { + override suspend fun all(): SizedIterable> = newSuspendedTransaction { + DBCourseMembership.find { CourseMemberships.userId eq id } + } + + override suspend fun current(): SizedIterable> = newSuspendedTransaction { + DBCourseMembership.find { CourseMemberships.userId eq id and CourseMemberships.endUtc.isNull() } + } + + override suspend fun active(): SizedIterable> = newSuspendedTransaction { + CourseMemberships.innerJoin(Courses).innerJoin(Terms).selectAll() + .where { (CourseMemberships.userId eq id) and CourseMemberships.endUtc.isNull() } + .where { (Terms.start lessEq Clock.System.now()) and (Terms.end greaterEq Clock.System.now()) } + .mapLazy { DBCourseMembership.wrapRow(it) } + } + } + + override val submissionGroupsMemberships: UserMembership.Accessor + get() = TODO("Not yet implemented") + override val submissions: SizedIterable + get() = TODO("Not yet implemented") + + companion object : EntityClass(Users) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt index f68984a..bc3fa01 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -1,21 +1,20 @@ package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.Assignments -import org.sourcegrade.lab.hub.db.Courses -import org.sourcegrade.lab.hub.db.RolePermissionBindings.references +import org.jetbrains.exposed.sql.SizedIterable import java.util.UUID -class Assignment(id: EntityID) : UUIDEntity(id) { - val courseId: EntityID by Assignments.courseId references Courses.id - val course: Course by Course referencedOn Assignments.courseId - val name: String by Assignments.name - val description: String by Assignments.description - val submissionDeadline: Instant by Assignments.submissionDeadline - val submissionGroupCategory: SubmissionGroupCategory by SubmissionGroupCategory referencedOn Assignments.submissionGroupCategory +interface Assignment : DomainEntity { + val course: Course + val submissionGroupCategory: SubmissionGroupCategory - companion object : EntityClass(Assignments) + var name: String + var description: String + var submissionDeadlineUtc: Instant + var submissionGroupCategoryId: UUID + + interface UserView { + val assignment: Assignment + suspend fun submissions(): SizedIterable + } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt index 7307ba3..f3b581e 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt @@ -1,21 +1,14 @@ package org.sourcegrade.lab.hub.domain -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SizedIterable -import org.sourcegrade.lab.hub.db.Assignments -import org.sourcegrade.lab.hub.db.Courses -import org.sourcegrade.lab.hub.db.SubmissionGroupCategories import java.util.UUID -class Course(id: EntityID) : UUIDEntity(id) { - val name: String by Courses.name - val description: String by Courses.description - val term: String by Courses.term - val owner: User by User referencedOn Courses.ownerId // TODO: Multiple owners - val submissionGroupCategories: SizedIterable by SubmissionGroupCategory referrersOn SubmissionGroupCategories.courseId - val assignments: SizedIterable by Assignment referrersOn Assignments.courseId +interface Course : TermScoped { + val submissionGroupCategories: SizedIterable + val assignments: SizedIterable + val owner: User - companion object : EntityClass(Courses) + var name: String + var description: String + var ownerId: UUID } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt index 417f02b..4fd8882 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt @@ -1,19 +1,12 @@ package org.sourcegrade.lab.hub.domain -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SizedIterable -import org.sourcegrade.lab.hub.db.Criteria -import java.util.UUID -class Criterion(id: EntityID) : UUIDEntity(id) { - val minPoints: Int by Criteria.minPoints - val maxPoints: Int by Criteria.maxPoints - val description: String by Criteria.description - val parentRubric: Rubric by Rubric referencedOn Criteria.parentRubricId - val parentCriterion: Criterion? by Criterion optionalReferencedOn Criteria.parentCriterionId - val childCriteria: SizedIterable by Criterion optionalReferrersOn Criteria.parentCriterionId - - companion object : EntityClass(Criteria) +interface Criterion : DomainEntity { + val description: String + val minPoints: Int + val maxPoints: Int + val parentRubric: Rubric + val parentCriterion: Criterion? + val childCriteria: SizedIterable } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt new file mode 100644 index 0000000..8027461 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt @@ -0,0 +1,9 @@ +package org.sourcegrade.lab.hub.domain + +import kotlinx.datetime.Instant +import java.util.UUID + +interface DomainEntity { + val uuid: UUID + val createdUtc: Instant +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedCriterion.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedCriterion.kt index f17b19b..abc0bed 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedCriterion.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedCriterion.kt @@ -1,18 +1,10 @@ package org.sourcegrade.lab.hub.domain -import org.jetbrains.exposed.dao.Entity -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.GradedCriteria -import java.util.UUID - -class GradedCriterion(id: EntityID) : Entity(id) { - val parentGradedRubric: GradedRubric by GradedRubric referencedOn GradedCriteria.parentGradedRubricId - val parentGradedCriterion: GradedCriterion? by GradedCriterion optionalReferencedOn GradedCriteria.parentGradedCriterionId - val criterion: Criterion by Criterion referencedOn GradedCriteria.criterionId - val achievedMinPoints: Int by GradedCriteria.achievedMinPoints - val achievedMaxPoints: Int by GradedCriteria.achievedMaxPoints - val message: String by GradedCriteria.message - - companion object : EntityClass(GradedCriteria) +interface GradedCriterion : DomainEntity { + val parentGradedRubric: GradedRubric + val parentGradedCriterion: GradedCriterion? + val criterion: Criterion + val achievedMinPoints: Int + val achievedMaxPoints: Int + val message: String } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt index 9727146..991d9cb 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt @@ -1,22 +1,12 @@ package org.sourcegrade.lab.hub.domain -import org.jetbrains.exposed.dao.Entity -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SizedIterable -import org.jetbrains.exposed.sql.and -import org.sourcegrade.lab.hub.db.GradedCriteria -import org.sourcegrade.lab.hub.db.GradedRubrics -import java.util.UUID -class GradedRubric(id: EntityID) : Entity(id) { - val rubric: Rubric by Rubric referencedOn GradedRubrics.rubricId - val name: String by GradedRubrics.name - val achievedMinPoints: Int by GradedRubrics.achievedMinPoints - val achievedMaxPoints: Int by GradedRubrics.achievedMaxPoints - val allChildCriteria: SizedIterable by GradedCriterion referrersOn GradedCriteria.parentGradedRubricId +interface GradedRubric : DomainEntity { + val rubric: Rubric + val name: String + val achievedMinPoints: Int + val achievedMaxPoints: Int + val allChildCriteria: SizedIterable val childCriteria: SizedIterable - get() = GradedCriterion.find { GradedCriteria.parentGradedRubricId eq id and GradedCriteria.parentGradedCriterionId.isNull() } - - companion object : EntityClass(GradedRubrics) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt index 7b1f290..51fe636 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt @@ -1,23 +1,15 @@ package org.sourcegrade.lab.hub.domain -import kotlinx.datetime.Instant -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.GradingRuns -import java.util.UUID - -class GradingRun(id: EntityID) : UUIDEntity(id) { - val createdUtc: Instant by GradingRuns.createdUtc - val submission: Submission by Submission referencedOn GradingRuns.submissionId - val gradedRubric: GradedRubric by GradedRubric referencedOn GradingRuns.gradedRubricId - - // duplicated data from GradedRubric to minimize joins for frequent queries - val rubric: Rubric by Rubric referencedOn GradingRuns.rubricId - val minPoints: Int by GradingRuns.minPoints - val maxPoints: Int by GradingRuns.maxPoints - val achievedMinPoints: Int by GradingRuns.achievedMinPoints - val achievedMaxPoints: Int by GradingRuns.achievedMaxPoints - - companion object : EntityClass(GradingRuns) +import kotlin.time.Duration + +interface GradingRun : DomainEntity { + val submission: Submission + val gradedRubric: GradedRubric + val runtime: Duration + + val rubric: Rubric + val minPoints: Int + val maxPoints: Int + val achievedMinPoints: Int + val achievedMaxPoints: Int } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt deleted file mode 100644 index ffae72c..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Role.kt +++ /dev/null @@ -1,27 +0,0 @@ -package org.sourcegrade.lab.hub.domain - -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.SizedIterable -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.db.RolePermissionBindings -import org.sourcegrade.lab.hub.db.Roles -import java.util.UUID - -class Role(id: EntityID) : UUIDEntity(id) { - val name: String by Roles.name - val scope: String by Roles.scope - val permissionBindings: SizedIterable by RolePermissionBinding referrersOn RolePermissionBindings.roleId - - companion object : EntityClass(Roles) -} - -suspend fun Role.hasPermission(permission: String): Boolean? = newSuspendedTransaction { - // TODO: hierarchical permissions - RolePermissionBindings.select(RolePermissionBindings.value) - .where { RolePermissionBindings.roleId eq this@hasPermission.id } - .where { RolePermissionBindings.permission eq permission } - .firstOrNull() - ?.let { it[RolePermissionBindings.value] } -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/RolePermissionBinding.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/RolePermissionBinding.kt deleted file mode 100644 index fc80916..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/RolePermissionBinding.kt +++ /dev/null @@ -1,15 +0,0 @@ -package org.sourcegrade.lab.hub.domain - -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.RolePermissionBindings -import java.util.UUID - -class RolePermissionBinding(id: EntityID) : UUIDEntity(id) { - val role: Role by Role referencedOn RolePermissionBindings.roleId - val permission: String by RolePermissionBindings.permission - val value: Boolean by RolePermissionBindings.value - - companion object : EntityClass(RolePermissionBindings) -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt index 4f66293..d3f03da 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt @@ -1,20 +1,12 @@ package org.sourcegrade.lab.hub.domain -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SizedIterable -import org.jetbrains.exposed.sql.and -import org.sourcegrade.lab.hub.db.Criteria -import org.sourcegrade.lab.hub.db.Rubrics -import java.util.UUID -class Rubric(id: EntityID) : UUIDEntity(id) { - val minPoints: Int by Rubrics.minPoints - val maxPoints: Int by Rubrics.maxPoints - val allChildCriteria: SizedIterable by Criterion referrersOn Criteria.parentRubricId +interface Rubric : DomainEntity { + val name: String + val minPoints: Int + val maxPoints: Int + val assignment: Assignment + val allChildCriteria: SizedIterable val childCriteria: SizedIterable - get() = Criterion.find { Criteria.parentRubricId eq id and Criteria.parentCriterionId.isNull() } - - companion object : EntityClass(Rubrics) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt index f09fd57..004b5ea 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt @@ -1,26 +1,13 @@ package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SizedIterable -import org.jetbrains.exposed.sql.SortOrder -import org.sourcegrade.lab.hub.db.GradingRuns -import org.sourcegrade.lab.hub.db.Submissions -import java.util.UUID -class Submission(id: EntityID) : UUIDEntity(id) { - val createdUtc: Instant by Submissions.createdUtc - val assignment: Assignment by Assignment referencedOn Submissions.assignmentId - val submitter: User by User referencedOn Submissions.submitterId - val group: SubmissionGroup by SubmissionGroup referencedOn Submissions.groupId - val uploaded: Instant by Submissions.uploaded - val gradingRuns: SizedIterable by GradingRun referrersOn GradingRuns.submissionId +interface Submission : DomainEntity { + val assignment: Assignment + val submitter: User + val group: SubmissionGroup + val uploaded: Instant + val gradingRuns: SizedIterable val lastGradingRun: GradingRun? - get() = GradingRun.find { GradingRuns.submissionId eq id } - .orderBy(GradingRuns.createdUtc to SortOrder.DESC) - .firstOrNull() - - companion object : EntityClass(Submissions) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt index d4a6ab4..1ed2870 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt @@ -1,17 +1,9 @@ package org.sourcegrade.lab.hub.domain -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.sql.SizedIterable -import org.sourcegrade.lab.hub.db.SubmissionGroupMembers -import org.sourcegrade.lab.hub.db.SubmissionGroups -import java.util.UUID -class SubmissionGroup(id: EntityID) : UUIDEntity(id) { - val name: String by SubmissionGroups.name - val category: SubmissionGroupCategory by SubmissionGroupCategory referencedOn SubmissionGroups.categoryId - val members: SizedIterable by User via SubmissionGroupMembers - - companion object : EntityClass(SubmissionGroups) +interface SubmissionGroup : TermScoped { + val name: String + val category: SubmissionGroupCategory + val members: SizedIterable } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt index 68d3f1b..ee6c51c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt @@ -1,16 +1,8 @@ package org.sourcegrade.lab.hub.domain -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.SubmissionGroupCategories -import java.util.UUID - -class SubmissionGroupCategory(id: EntityID) : UUIDEntity(id) { - val name: String by SubmissionGroupCategories.name - val course: Course by Course referencedOn SubmissionGroupCategories.courseId - val minSize: Int by SubmissionGroupCategories.minSize - val maxSize: Int by SubmissionGroupCategories.maxSize - - companion object : EntityClass(SubmissionGroupCategories) +interface SubmissionGroupCategory : DomainEntity { + val course: Course + var name: String + var minSize: Int + var maxSize: Int } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt index 2ec3705..e756b9c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt @@ -1,16 +1,9 @@ package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.Terms -import java.util.UUID -class Term(id: EntityID) : UUIDEntity(id) { - val name: String by Terms.name - val start: Instant by Terms.start - val end: Instant by Terms.end - - companion object : EntityClass(Terms) +interface Term : DomainEntity { + val name: String + val start: Instant + val end: Instant? } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/TermScoped.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/TermScoped.kt new file mode 100644 index 0000000..137b4d1 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/TermScoped.kt @@ -0,0 +1,5 @@ +package org.sourcegrade.lab.hub.domain + +interface TermScoped : DomainEntity { + val term: Term +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index 4efd8ae..861e310 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -1,14 +1,14 @@ package org.sourcegrade.lab.hub.domain -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID -import org.sourcegrade.lab.hub.db.Users -import java.util.UUID +import org.jetbrains.exposed.sql.SizedIterable -class User(id: EntityID) : UUIDEntity(id) { - var username: String by Users.username - var email: String by Users.email +interface User : DomainEntity { + var username: String + var displayname: String + var email: String - companion object : EntityClass(Users) + val courseMemberships: UserMembership.Accessor + val submissionGroupsMemberships: UserMembership.Accessor + suspend fun assignments(): SizedIterable + suspend fun submissions(): SizedIterable } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt new file mode 100644 index 0000000..07c5e0b --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt @@ -0,0 +1,17 @@ +package org.sourcegrade.lab.hub.domain + +import kotlinx.datetime.Instant +import org.jetbrains.exposed.sql.SizedIterable + +interface UserMembership : DomainEntity { + val startUtc: Instant + val endUtc: Instant? + val user: User + val target: T + + interface Accessor { + suspend fun all(): SizedIterable> + suspend fun current(): SizedIterable> + suspend fun active(): SizedIterable> + } +} From 03517affa6b95bc4cc7bdb54ec9a8f807fa9f2fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Thu, 25 Apr 2024 00:29:01 +0200 Subject: [PATCH 18/29] Work on user sql --- .../org/sourcegrade/lab/hub/db/Courses.kt | 4 +- .../lab/hub/db/SubmissionGroupMemberships.kt | 13 ++- .../org/sourcegrade/lab/hub/db/Terms.kt | 21 +++-- .../org/sourcegrade/lab/hub/db/Users.kt | 80 ++++++++++++++----- .../sourcegrade/lab/hub/domain/Assignment.kt | 6 -- .../lab/hub/domain/AssignmentParticipation.kt | 18 +++++ .../sourcegrade/lab/hub/domain/Submission.kt | 6 ++ .../org/sourcegrade/lab/hub/domain/Term.kt | 8 ++ .../org/sourcegrade/lab/hub/domain/User.kt | 34 +++++++- .../lab/hub/domain/UserMembership.kt | 10 +-- 10 files changed, 154 insertions(+), 46 deletions(-) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/AssignmentParticipation.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt index 67370cc..29172c0 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt @@ -16,14 +16,14 @@ internal object Courses : UUIDTable("sgl_courses") { val createdUtc = timestamp("createdUtc") val name = varchar("name", 255) val description = varchar("description", 255) - val term = varchar("term", 255) + val term = reference("term_id", Terms) val ownerId = reference("owner_id", Users) } internal class DBCourse(id: EntityID) : UUIDEntity(id), Course { override val uuid: UUID = id.value override val createdUtc: Instant by Courses.createdUtc - override val term: Term by Term referencedOn Courses.term + override val term: Term by DBTerm referencedOn Courses.term override val submissionGroupCategories: SizedIterable by DBSubmissionGroupCategory referrersOn SubmissionGroupCategories.courseId override val assignments: SizedIterable by DBAssignment referrersOn Assignments.courseId override val owner: DBUser by DBUser referencedOn Courses.ownerId // TODO: Multiple owners diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt index bcabfb6..7eeefe9 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt @@ -1,12 +1,15 @@ package org.sourcegrade.lab.hub.db +import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.domain.SubmissionGroup +import org.sourcegrade.lab.hub.domain.UserMembership import java.util.UUID -internal object SubmissionGroupMemberships : Table("sgl_submission_group_membership") { +internal object SubmissionGroupMemberships : UUIDTable("sgl_submission_group_membership") { val createdUtc = timestamp("createdUtc") val startUtc = timestamp("startUtc") val endUtc = timestamp("endUtc").nullable() @@ -14,11 +17,13 @@ internal object SubmissionGroupMemberships : Table("sgl_submission_group_members val submissionGroupId = reference("submission_group_id", SubmissionGroups) } -internal class DBSubmissionGroupMembership(id: EntityID) : UUIDEntity(id), SubmissionGroupMembership { +internal class DBSubmissionGroupMembership(id: EntityID) : UUIDEntity(id), UserMembership { override val uuid: UUID = id.value override val createdUtc by SubmissionGroupMemberships.createdUtc override val startUtc by SubmissionGroupMemberships.startUtc override val endUtc by SubmissionGroupMemberships.endUtc override val user: DBUser by DBUser referencedOn SubmissionGroupMemberships.userId - override val submissionGroup: DBSubmissionGroup by DBSubmissionGroup referencedOn SubmissionGroupMemberships.submissionGroupId + override val target: DBSubmissionGroup by DBSubmissionGroup referencedOn SubmissionGroupMemberships.submissionGroupId + + companion object : EntityClass(SubmissionGroupMemberships) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt index b4196c2..98e333d 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt @@ -5,19 +5,30 @@ import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.domain.Term import java.util.UUID internal object Terms : UUIDTable("sgl_terms") { val name = varchar("name", 255).uniqueIndex() + val createdUtc = timestamp("createdUtc") val start = timestamp("start") val end = timestamp("end") } -internal class Term(id: EntityID) : UUIDEntity(id) { - val name: String by Terms.name - val start: Instant by Terms.start - val end: Instant by Terms.end +internal class DBTerm(id: EntityID) : UUIDEntity(id), Term { + override val uuid: UUID = id.value + override val createdUtc: Instant by Terms.createdUtc + override val name: String by Terms.name + override val start: Instant by Terms.start + override val end: Instant by Terms.end - companion object : EntityClass(Terms) + companion object : EntityClass(Terms) } + +internal fun DBTerm.Companion.getCurrentOrNull(): DBTerm? = + DBTerm.all().orderBy(Terms.start to SortOrder.DESC).firstOrNull() + +internal fun DBTerm.Companion.getCurrent(): DBTerm = + checkNotNull(getCurrentOrNull()) { "No current term found" } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt index 8ac58c2..bfe9c76 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt @@ -1,20 +1,23 @@ package org.sourcegrade.lab.hub.db -import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.Query import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.innerJoin import org.jetbrains.exposed.sql.kotlin.datetime.timestamp import org.jetbrains.exposed.sql.mapLazy import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.sourcegrade.lab.hub.domain.Assignment import org.sourcegrade.lab.hub.domain.Course import org.sourcegrade.lab.hub.domain.Submission import org.sourcegrade.lab.hub.domain.SubmissionGroup +import org.sourcegrade.lab.hub.domain.Term import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserMembership import java.util.UUID @@ -33,27 +36,64 @@ internal class DBUser(id: EntityID) : UUIDEntity(id), User { override var displayname: String by Users.displayname override var email: String by Users.email - override val courseMemberships: UserMembership.Accessor = object : UserMembership.Accessor { - override suspend fun all(): SizedIterable> = newSuspendedTransaction { - DBCourseMembership.find { CourseMemberships.userId eq id } - } - - override suspend fun current(): SizedIterable> = newSuspendedTransaction { - DBCourseMembership.find { CourseMemberships.userId eq id and CourseMemberships.endUtc.isNull() } - } - - override suspend fun active(): SizedIterable> = newSuspendedTransaction { - CourseMemberships.innerJoin(Courses).innerJoin(Terms).selectAll() - .where { (CourseMemberships.userId eq id) and CourseMemberships.endUtc.isNull() } - .where { (Terms.start lessEq Clock.System.now()) and (Terms.end greaterEq Clock.System.now()) } - .mapLazy { DBCourseMembership.wrapRow(it) } - } + private fun Query.termPredicate(term: Term.Matcher, now: Instant): Query = when (term) { + is Term.Matcher.All -> this + is Term.Matcher.Current -> where { (Terms.start lessEq now) and (Terms.end greaterEq now) } + is Term.Matcher.ByName -> where { Terms.name.eq(term.name) } + is Term.Matcher.ById -> where { Terms.id.eq(term.id) } } - override val submissionGroupsMemberships: UserMembership.Accessor - get() = TODO("Not yet implemented") - override val submissions: SizedIterable - get() = TODO("Not yet implemented") + private fun Query.membershipStatusPredicate(status: UserMembership.Status, now: Instant): Query = when (status) { + UserMembership.Status.ALL -> this + UserMembership.Status.FUTURE -> where { CourseMemberships.startUtc greater now } + UserMembership.Status.PAST -> where { CourseMemberships.endUtc less now } + UserMembership.Status.CURRENT -> where { CourseMemberships.endUtc.isNull() } + } + + override suspend fun courseMemberships( + status: UserMembership.Status, + term: Term.Matcher, + now: Instant, + ): SizedIterable> = newSuspendedTransaction { + CourseMemberships.innerJoin(Courses).innerJoin(Terms).selectAll() + .where { (CourseMemberships.userId eq uuid) } + .termPredicate(term, now) + .membershipStatusPredicate(status, now) + .mapLazy { DBCourseMembership.wrapRow(it) } + } + + override suspend fun submissionGroupMemberships( + status: UserMembership.Status, + term: Term.Matcher, + now: Instant, + ): SizedIterable> = newSuspendedTransaction { + SubmissionGroupMemberships.innerJoin(SubmissionGroups).innerJoin(Terms).selectAll() + .where { (CourseMemberships.userId eq uuid) } + .termPredicate(term, now) + .membershipStatusPredicate(status, now) + .mapLazy { DBSubmissionGroupMembership.wrapRow(it) } + } + + override suspend fun assignments( + term: Term.Matcher, + now: Instant, + ): SizedIterable = newSuspendedTransaction { + Assignments.innerJoin(Courses).innerJoin(Terms).innerJoin(CourseMemberships).selectAll() + .where { CourseMemberships.userId eq uuid } + .termPredicate(term, now) + .membershipStatusPredicate(UserMembership.Status.CURRENT, now) + .mapLazy { DBAssignment.wrapRow(it) } + } + + override suspend fun submissions( + status: Submission.Status, // TODO: Use parameter + term: Term.Matcher, + now: Instant, + ): SizedIterable = newSuspendedTransaction { + Submissions.innerJoin(SubmissionGroupMemberships) { SubmissionGroupMemberships.userId eq uuid }.selectAll() + .termPredicate(term, now) + .mapLazy { DBSubmission.wrapRow(it) } + } companion object : EntityClass(Users) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt index bc3fa01..160c87f 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -1,7 +1,6 @@ package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant -import org.jetbrains.exposed.sql.SizedIterable import java.util.UUID interface Assignment : DomainEntity { @@ -12,9 +11,4 @@ interface Assignment : DomainEntity { var description: String var submissionDeadlineUtc: Instant var submissionGroupCategoryId: UUID - - interface UserView { - val assignment: Assignment - suspend fun submissions(): SizedIterable - } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/AssignmentParticipation.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/AssignmentParticipation.kt new file mode 100644 index 0000000..d1d1935 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/AssignmentParticipation.kt @@ -0,0 +1,18 @@ +package org.sourcegrade.lab.hub.domain + +import org.jetbrains.exposed.sql.SizedIterable + +interface AssignmentParticipation : DomainEntity { + val assignment: Assignment + val user: User + suspend fun submissions(): SizedIterable + + enum class Status { + ALL, + FUTURE, + OPEN, + NOT_SUBMITTED, + SUBMITTED, + EXPIRED, + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt index 004b5ea..3a2762f 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt @@ -10,4 +10,10 @@ interface Submission : DomainEntity { val uploaded: Instant val gradingRuns: SizedIterable val lastGradingRun: GradingRun? + + enum class Status { + ALL, + PENDING_GRADE, + GRADED, + } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt index e756b9c..75aa775 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt @@ -1,9 +1,17 @@ package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant +import java.util.UUID interface Term : DomainEntity { val name: String val start: Instant val end: Instant? + + sealed interface Matcher { + data object All : Matcher + data object Current : Matcher + data class ByName(val name: String) : Matcher + data class ById(val id: UUID) : Matcher + } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index 861e310..3b43f50 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -1,5 +1,7 @@ package org.sourcegrade.lab.hub.domain +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant import org.jetbrains.exposed.sql.SizedIterable interface User : DomainEntity { @@ -7,8 +9,32 @@ interface User : DomainEntity { var displayname: String var email: String - val courseMemberships: UserMembership.Accessor - val submissionGroupsMemberships: UserMembership.Accessor - suspend fun assignments(): SizedIterable - suspend fun submissions(): SizedIterable + suspend fun courseMemberships( + status: UserMembership.Status = UserMembership.Status.CURRENT, + term: Term.Matcher = Term.Matcher.Current, + now: Instant = Clock.System.now(), + ): SizedIterable> + + suspend fun submissionGroupMemberships( + status: UserMembership.Status = UserMembership.Status.CURRENT, + term: Term.Matcher = Term.Matcher.Current, + now: Instant = Clock.System.now(), + ): SizedIterable> + +// suspend fun assignmentParticipations( +// status: AssignmentParticipation.Status = AssignmentParticipation.Status.OPEN, +// term: Term.Matcher = Term.Matcher.Current, +// now: Instant = Clock.System.now(), +// ): SizedIterable + + suspend fun assignments( + term: Term.Matcher = Term.Matcher.Current, + now: Instant = Clock.System.now(), + ): SizedIterable + + suspend fun submissions( + status: Submission.Status = Submission.Status.ALL, + term: Term.Matcher = Term.Matcher.Current, + now: Instant = Clock.System.now(), + ): SizedIterable } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt index 07c5e0b..20d83c8 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt @@ -1,7 +1,6 @@ package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant -import org.jetbrains.exposed.sql.SizedIterable interface UserMembership : DomainEntity { val startUtc: Instant @@ -9,9 +8,10 @@ interface UserMembership : DomainEntity { val user: User val target: T - interface Accessor { - suspend fun all(): SizedIterable> - suspend fun current(): SizedIterable> - suspend fun active(): SizedIterable> + enum class Status { + ALL, + FUTURE, + CURRENT, + PAST, } } From f8cdcc868bd17692dc47a7ab6c3ca75de4b13021 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Thu, 25 Apr 2024 19:48:35 +0200 Subject: [PATCH 19/29] Add repositories --- .../kotlin/org/sourcegrade/lab/hub/Module.kt | 22 ++++++------- .../org/sourcegrade/lab/hub/db/Assignments.kt | 33 ++++++++++++++++--- .../lab/hub/db/ReferenceMutator.kt | 20 ----------- .../lab/hub/db/ReferenceMutatorDelegate.kt | 28 ++++++++++++++++ .../lab/hub/db/UUIDEntityClassRepository.kt | 28 ++++++++++++++++ .../sourcegrade/lab/hub/domain/Assignment.kt | 11 ++++++- .../lab/hub/domain/DomainEntity.kt | 6 ++++ .../lab/hub/domain/MutableRepository.kt | 26 +++++++++++++++ .../sourcegrade/lab/hub/domain/Repository.kt | 28 ++++++++++++++++ .../sourcegrade/lab/hub/domain/Submission.kt | 6 ++++ .../org/sourcegrade/lab/hub/domain/User.kt | 2 ++ .../lab/hub/domain/UserMembership.kt | 7 ++++ .../hub/domain/repo/AssignmentRepository.kt | 13 ++++++++ .../hub/domain/repo/SubmissionRepository.kt | 14 ++++++++ .../domain/repo/UserMembershipRepository.kt | 21 ++++++++++++ .../lab/hub/graphql/UserQueries.kt | 20 +++++++++++ 16 files changed, 249 insertions(+), 36 deletions(-) delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutator.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutatorDelegate.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserQueries.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt index c8f16b5..a1290d5 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt @@ -1,5 +1,6 @@ package org.sourcegrade.lab.hub +import com.expediagroup.graphql.server.ktor.GraphQL import com.expediagroup.graphql.server.ktor.graphQLGetRoute import com.expediagroup.graphql.server.ktor.graphQLPostRoute import com.expediagroup.graphql.server.ktor.graphQLSDLRoute @@ -16,6 +17,7 @@ import io.ktor.server.request.path import io.ktor.server.routing.Routing import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.DatabaseConfig +import org.sourcegrade.lab.hub.graphql.UserQueries import org.sourcegrade.lab.hub.http.authenticationModule fun Application.module() { @@ -48,22 +50,20 @@ fun Application.module() { anyHost() } -// install(GraphQL) { -// schema { -// packages = listOf("org.sourcegrade.lab.hub") -// queries = -// listOf( -// HelloWorldQuery(), -// UserQueries(), -// CourseQueries(), -// ) + install(GraphQL) { + schema { + packages = listOf("org.sourcegrade.lab.hub") + queries = + listOf( + UserQueries(), + ) // mutations = // listOf( // UserMutations(), // CourseMutations(), // ) -// } -// } + } + } install(Routing) { graphQLGetRoute() diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt index f413adf..88ce925 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt @@ -5,9 +5,13 @@ import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.sourcegrade.lab.hub.domain.Assignment -import org.sourcegrade.lab.hub.domain.Course +import org.sourcegrade.lab.hub.domain.MutableRepository +import org.sourcegrade.lab.hub.domain.Repository +import org.sourcegrade.lab.hub.domain.repo.MutableAssignmentRepository import java.util.UUID internal object Assignments : UUIDTable("sgl_assignments") { @@ -23,13 +27,34 @@ internal object Assignments : UUIDTable("sgl_assignments") { internal class DBAssignment(id: EntityID) : UUIDEntity(id), Assignment { override val uuid: UUID = id.value override val createdUtc: Instant by Assignments.createdUtc - override val course: Course by DBCourse referencedOn Assignments.courseId - override val submissionGroupCategory: DBSubmissionGroupCategory by DBSubmissionGroupCategory referencedOn Assignments.submissionGroupCategory + + override var submissionGroupCategory: DBSubmissionGroupCategory by DBSubmissionGroupCategory referencedOn Assignments.submissionGroupCategory + override var course: DBCourse by DBCourse referencedOn Assignments.courseId override var name: String by Assignments.name override var description: String by Assignments.description override var submissionDeadlineUtc: Instant by Assignments.submissionDeadline - override var submissionGroupCategoryId: UUID by SubmissionGroupCategories.referenceMutator(Assignments.submissionGroupCategory) + + override suspend fun setSubmissionGroupCategoryId(id: UUID): Boolean = ::course.mutateReference(id) companion object : EntityClass(Assignments) } + +internal class DBAssignmentRepository : MutableAssignmentRepository, Repository by UUIDEntityClassRepository(DBAssignment) { + override suspend fun findByCourse(courseId: UUID): SizedIterable = + newSuspendedTransaction { DBAssignment.find { Assignments.courseId eq courseId } } + + override suspend fun create(item: Assignment.CreateDto): Assignment = newSuspendedTransaction { + DBAssignment.new { + course = DBCourse.findByIdNotNull(item.courseId) + submissionGroupCategory = DBSubmissionGroupCategory.findByIdNotNull(item.submissionGroupCategoryId) + name = item.name + description = item.description + submissionDeadlineUtc = item.submissionDeadlineUtc + } + } + + override suspend fun put(item: Assignment.CreateDto): MutableRepository.PutResult { + TODO("Not yet implemented") + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutator.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutator.kt deleted file mode 100644 index 314d738..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutator.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.sourcegrade.lab.hub.db - -import org.jetbrains.exposed.dao.Entity -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.dao.id.IdTable -import org.jetbrains.exposed.sql.Column -import kotlin.reflect.KProperty - -fun > IdTable.referenceMutator(column: Column>): ReferenceMutator = ReferenceMutator(this, column) - -class ReferenceMutator>( - private val table: IdTable, - private val column: Column>, -) { - operator fun getValue(self: Entity, property: KProperty<*>): ID = - with(self) { column.getValue(self, property).value } - - operator fun setValue(self: Entity, property: KProperty<*>, value: ID) = - with(self) { column.setValue(self, property, EntityID(value, table)) } -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutatorDelegate.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutatorDelegate.kt new file mode 100644 index 0000000..903fd95 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutatorDelegate.kt @@ -0,0 +1,28 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.Entity +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import kotlin.reflect.KMutableProperty0 +import kotlin.reflect.full.companionObjectInstance + +internal suspend inline fun , reified E : Entity> KMutableProperty0.mutateReference(id: ID): Boolean { + @Suppress("UNCHECKED_CAST") + val entityClass: EntityClass = + checkNotNull(E::class.companionObjectInstance as? EntityClass) { + "Companion EntityClass for ${E::class} not found" + } + + return newSuspendedTransaction { + val entity = entityClass.findById(id) + if (entity == null) { + false + } else { + set(entity) + true + } + } +} + +internal inline fun , reified E : Entity> EntityClass.findByIdNotNull(id: ID): E = + findById(id) ?: error("${E::class.simpleName} $id not found") diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt new file mode 100644 index 0000000..66f7f0d --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt @@ -0,0 +1,28 @@ +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.Entity +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.sourcegrade.lab.hub.domain.DomainEntity +import org.sourcegrade.lab.hub.domain.Repository +import java.util.UUID + +internal class UUIDEntityClassRepository(private val entityClass: EntityClass) : Repository + where E : Entity, E : DomainEntity { + + override suspend fun findById(id: UUID): E? = + newSuspendedTransaction { entityClass.findById(id) } + + override suspend fun exists(id: UUID): Boolean = + newSuspendedTransaction { entityClass.findById(id) != null } + + override suspend fun countAll(): Long = + newSuspendedTransaction { entityClass.all().count() } + + override suspend fun deleteById(id: UUID): Boolean = + newSuspendedTransaction { + entityClass.findById(id) + ?.let { it.delete(); true } + ?: false + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt index 160c87f..29410de 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -10,5 +10,14 @@ interface Assignment : DomainEntity { var name: String var description: String var submissionDeadlineUtc: Instant - var submissionGroupCategoryId: UUID + + suspend fun setSubmissionGroupCategoryId(id: UUID): Boolean + + data class CreateDto( + val courseId: UUID, + val submissionGroupCategoryId: UUID, + val name: String, + val description: String, + val submissionDeadlineUtc: Instant, + ) : Creates } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt index 8027461..5d98c2c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt @@ -7,3 +7,9 @@ interface DomainEntity { val uuid: UUID val createdUtc: Instant } + +interface Creates + +interface IdempotentCreates : Creates { + val uuid: UUID +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt new file mode 100644 index 0000000..c962607 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt @@ -0,0 +1,26 @@ +/* + * Anvil - AnvilPowered.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.domain + +interface MutableRepository> : Repository { + suspend fun create(item: C): E + suspend fun put(item: C): PutResult + + data class PutResult(val entity: E, val created: Boolean) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt new file mode 100644 index 0000000..90fefee --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt @@ -0,0 +1,28 @@ +/* + * Anvil - AnvilPowered.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.domain + +import java.util.UUID + +interface Repository { + suspend fun findById(id: UUID): E? // TODO: Eager loading + suspend fun exists(id: UUID): Boolean + suspend fun countAll(): Long + suspend fun deleteById(id: UUID): Boolean +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt index 3a2762f..985b838 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt @@ -2,6 +2,7 @@ package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant import org.jetbrains.exposed.sql.SizedIterable +import java.util.UUID interface Submission : DomainEntity { val assignment: Assignment @@ -16,4 +17,9 @@ interface Submission : DomainEntity { PENDING_GRADE, GRADED, } + + data class CreateDto( + val assignmentId: UUID, + val bytes: ByteArray, + ) : Creates } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index 3b43f50..e3c77cb 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -9,6 +9,8 @@ interface User : DomainEntity { var displayname: String var email: String + // TODO: Split into UserActions + // TODO: Get from UserMembershipRepository suspend fun courseMemberships( status: UserMembership.Status = UserMembership.Status.CURRENT, term: Term.Matcher = Term.Matcher.Current, diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt index 20d83c8..b3cb140 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt @@ -1,6 +1,7 @@ package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant +import java.util.UUID interface UserMembership : DomainEntity { val startUtc: Instant @@ -8,6 +9,12 @@ interface UserMembership : DomainEntity { val user: User val target: T + data class CreateDto( + val userId: UUID, + val targetId: UUID, + val startUtc: Instant? = null, // or else now + ) : Creates> + enum class Status { ALL, FUTURE, diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt new file mode 100644 index 0000000..ceec9b1 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt @@ -0,0 +1,13 @@ +package org.sourcegrade.lab.hub.domain.repo + +import org.jetbrains.exposed.sql.SizedIterable +import org.sourcegrade.lab.hub.domain.Assignment +import org.sourcegrade.lab.hub.domain.MutableRepository +import org.sourcegrade.lab.hub.domain.Repository +import java.util.UUID + +interface AssignmentRepository : Repository { + suspend fun findByCourse(courseId: UUID): SizedIterable +} + +interface MutableAssignmentRepository : AssignmentRepository, MutableRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt new file mode 100644 index 0000000..3df5ce1 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt @@ -0,0 +1,14 @@ +package org.sourcegrade.lab.hub.domain.repo + +import org.jetbrains.exposed.sql.SizedIterable +import org.sourcegrade.lab.hub.domain.MutableRepository +import org.sourcegrade.lab.hub.domain.Repository +import org.sourcegrade.lab.hub.domain.Submission +import java.util.UUID + +interface SubmissionRepository : Repository { + suspend fun findByAssignment(assignmentId: UUID): SizedIterable + suspend fun findByUserAndAssignment(userId: UUID, assignmentId: UUID): SizedIterable +} + +interface MutableSubmissionRepository : SubmissionRepository, MutableRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt new file mode 100644 index 0000000..ef9146f --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt @@ -0,0 +1,21 @@ +package org.sourcegrade.lab.hub.domain.repo + +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import org.jetbrains.exposed.sql.SizedIterable +import org.sourcegrade.lab.hub.domain.MutableRepository +import org.sourcegrade.lab.hub.domain.Repository +import org.sourcegrade.lab.hub.domain.Term +import org.sourcegrade.lab.hub.domain.TermScoped +import org.sourcegrade.lab.hub.domain.UserMembership + +interface UserMembershipRepository : Repository> { + suspend fun find( + status: UserMembership.Status = UserMembership.Status.CURRENT, + term: Term.Matcher = Term.Matcher.Current, + now: Instant = Clock.System.now(), + ): SizedIterable +} + +interface MutableUserMembershipRepository : UserMembershipRepository, + MutableRepository, UserMembership.CreateDto> diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserQueries.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserQueries.kt new file mode 100644 index 0000000..1a1f267 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserQueries.kt @@ -0,0 +1,20 @@ +package org.sourcegrade.lab.hub.graphql + +import com.expediagroup.graphql.generator.annotations.GraphQLDescription +import com.expediagroup.graphql.server.operations.Query +import graphql.schema.DataFetchingEnvironment +import org.sourcegrade.lab.hub.domain.User + +class UserQueries : Query { + suspend fun user( + environment: DataFetchingEnvironment, + id: String? = null + ): UserQuery = UserQuery() +} + +@GraphQLDescription("Query user collection") +class UserQuery { + suspend fun fetch(environment: DataFetchingEnvironment): User { + + } +} From 9c3a6a7acf1cfb892ced83e9d881a22e18580c1f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Wed, 19 Jun 2024 10:00:02 +0200 Subject: [PATCH 20/29] Get graphql to work --- build.gradle.kts | 20 +-- docker-compose.dev.yml | 2 +- gradle/libs.versions.toml | 2 +- hub/build.gradle.kts | 15 +++ .../kotlin/org/sourcegrade/lab/hub/Main.kt | 27 ++++- .../kotlin/org/sourcegrade/lab/hub/Module.kt | 109 ++++++++++++++--- .../kotlin/org/sourcegrade/lab/hub/Routing.kt | 18 +++ .../org/sourcegrade/lab/hub/db/Assignments.kt | 29 ++++- .../lab/hub/db/CourseMemberships.kt | 24 +++- .../org/sourcegrade/lab/hub/db/Courses.kt | 25 +++- .../org/sourcegrade/lab/hub/db/Criteria.kt | 20 +++ .../org/sourcegrade/lab/hub/db/DBModule.kt | 38 ++++++ .../sourcegrade/lab/hub/db/GradedCriteria.kt | 20 +++ .../sourcegrade/lab/hub/db/GradedRubrics.kt | 23 +++- .../org/sourcegrade/lab/hub/db/GradingRuns.kt | 24 +++- .../lab/hub/db/ReferenceMutatorDelegate.kt | 18 +++ .../org/sourcegrade/lab/hub/db/Rubrics.kt | 24 +++- .../lab/hub/db/SubmissionGroupCategories.kt | 24 +++- .../lab/hub/db/SubmissionGroupMemberships.kt | 23 +++- .../lab/hub/db/SubmissionGroups.kt | 24 +++- .../org/sourcegrade/lab/hub/db/Submissions.kt | 24 +++- .../org/sourcegrade/lab/hub/db/Terms.kt | 20 +++ .../lab/hub/db/UUIDEntityClassRepository.kt | 18 +++ .../org/sourcegrade/lab/hub/db/Users.kt | 88 ++++++++++++-- .../sourcegrade/lab/hub/domain/Assignment.kt | 18 +++ .../lab/hub/domain/AssignmentParticipation.kt | 18 +++ .../org/sourcegrade/lab/hub/domain/Course.kt | 20 ++- .../sourcegrade/lab/hub/domain/Criterion.kt | 18 +++ .../lab/hub/domain/DomainEntity.kt | 18 +++ .../lab/hub/domain/GradedCriterion.kt | 18 +++ .../lab/hub/domain/GradedRubric.kt | 18 +++ .../sourcegrade/lab/hub/domain/GradingRun.kt | 18 +++ .../lab/hub/domain/MutableRepository.kt | 7 +- .../sourcegrade/lab/hub/domain/Repository.kt | 6 +- .../org/sourcegrade/lab/hub/domain/Rubric.kt | 18 +++ .../sourcegrade/lab/hub/domain/Submission.kt | 20 ++- .../lab/hub/domain/SubmissionGroup.kt | 18 +++ .../lab/hub/domain/SubmissionGroupCategory.kt | 18 +++ .../org/sourcegrade/lab/hub/domain/Term.kt | 44 ++++++- .../sourcegrade/lab/hub/domain/TermScoped.kt | 18 +++ .../org/sourcegrade/lab/hub/domain/User.kt | 70 ++++++++++- .../lab/hub/domain/UserMembership.kt | 20 ++- .../hub/domain/repo/AssignmentRepository.kt | 18 +++ .../hub/domain/repo/SubmissionRepository.kt | 18 +++ .../domain/repo/UserMembershipRepository.kt | 20 ++- .../lab/hub/domain/repo/UserRepository.kt | 34 ++++++ .../lab/hub/graphql/GraphQLRepository.kt | 41 +++++++ .../sourcegrade/lab/hub/graphql/Scalars.kt | 114 ++++++++++++++++++ .../org/sourcegrade/lab/hub/graphql/User.kt | 59 +++++++++ .../lab/hub/graphql/UserQueries.kt | 20 --- .../lab/hub/http/AuthenticationModule.kt | 66 +++++----- 51 files changed, 1319 insertions(+), 115 deletions(-) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DBModule.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Scalars.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserQueries.kt diff --git a/build.gradle.kts b/build.gradle.kts index 245ffa2..335a350 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,3 +1,4 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { @@ -20,16 +21,19 @@ allprojects { ?.takeIf { version.toString().contains("SNAPSHOT") } ?.also { version = version.toString().replace("SNAPSHOT", "RC$it") } - tasks { - withType { - kotlinOptions.jvmTarget = "17" - } - withType { - options.encoding = "UTF-8" - sourceCompatibility = "17" - targetCompatibility = "17" + kotlin { + compilerOptions { + jvmTarget = JvmTarget.JVM_21 + freeCompilerArgs = listOf( + "-opt-in=kotlin.RequiresOptIn", + "-Xcontext-receivers", + ) } } + + java { + toolchain.languageVersion.set(JavaLanguageVersion.of(21)) + } } tasks { diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index c9ad5e8..64569e6 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -11,7 +11,7 @@ services: environment: - POSTGRES_USER=admin - POSTGRES_PASSWORD=admin - - POSTGRES_DB=lab + - POSTGRES_DB=sgl ports: - "5432:5432" diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index b28bd73..1200473 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,5 +50,5 @@ kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } kotlin-serialization = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin" } ktlint = "org.jlleitschuh.gradle.ktlint:11.6.1" protobuf = "com.google.protobuf:0.9.4" -shadow = { id = "com.github.johnrengelman.shadow", version = "8.1.1" } +shadow = { id = "io.github.goooler.shadow", version = "8.1.7" } ktor = "io.ktor.plugin:2.3.10" diff --git a/hub/build.gradle.kts b/hub/build.gradle.kts index 8e5537c..3b5c13b 100644 --- a/hub/build.gradle.kts +++ b/hub/build.gradle.kts @@ -6,6 +6,7 @@ plugins { dependencies { implementation(libs.graphql.server) + implementation(libs.koin) implementation(libs.ktor.server.netty) implementation(libs.ktor.server.auth) implementation(libs.ktor.server.contentnegotiation) @@ -33,3 +34,17 @@ dependencies { application { mainClass.set("org.sourcegrade.lab.hub.MainKt") } + +tasks { + named("runShadow") { + environment("SGL_DB_URL", "jdbc:postgresql://localhost:5432/sgl") + environment("SGL_DB_USER", "admin") + environment("SGL_DB_PASSWORD", "admin") + environment("SGL_DEPLOYMENT_URL", "http://localhost:8080") + environment("SGL_AUTH_URL", "http://localhost:8080/auth") + environment("SGL_AUTH_CLIENT_ID", "sgl") + environment("SGL_AUTH_CLIENT_SECRET", "sgl") + environment("SGL_AUTH_ACCESS_TOKEN_URL", "http://localhost:8080/auth/token") + environment("SGL_AUTH_SCOPES", "openid profile email") + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt index 4e3e4ca..4c509fb 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt @@ -1,5 +1,28 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub -import io.ktor.server.netty.EngineMain +import io.ktor.server.engine.embeddedServer +import io.ktor.server.netty.Netty -fun main(args: Array) = EngineMain.main(args) +fun main() { + embeddedServer(Netty) { + module() + }.start(wait = true) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt index a1290d5..e5b4284 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt @@ -1,45 +1,114 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub +import com.expediagroup.graphql.generator.hooks.SchemaGeneratorHooks import com.expediagroup.graphql.server.ktor.GraphQL import com.expediagroup.graphql.server.ktor.graphQLGetRoute import com.expediagroup.graphql.server.ktor.graphQLPostRoute import com.expediagroup.graphql.server.ktor.graphQLSDLRoute import com.expediagroup.graphql.server.ktor.graphiQLRoute +import graphql.schema.GraphQLType import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod -import io.ktor.http.Url import io.ktor.server.application.Application import io.ktor.server.application.install -import io.ktor.server.config.tryGetString import io.ktor.server.plugins.callloging.CallLogging import io.ktor.server.plugins.cors.routing.CORS import io.ktor.server.request.path import io.ktor.server.routing.Routing +import org.apache.logging.log4j.LogManager +import org.apache.logging.log4j.Logger import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.DatabaseConfig +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.koin.ktor.ext.inject +import org.koin.ktor.plugin.Koin +import org.sourcegrade.lab.hub.db.Assignments +import org.sourcegrade.lab.hub.db.CourseMemberships +import org.sourcegrade.lab.hub.db.Courses +import org.sourcegrade.lab.hub.db.Criteria +import org.sourcegrade.lab.hub.db.DBModule +import org.sourcegrade.lab.hub.db.GradedCriteria +import org.sourcegrade.lab.hub.db.GradedRubrics +import org.sourcegrade.lab.hub.db.GradingRuns +import org.sourcegrade.lab.hub.db.Rubrics +import org.sourcegrade.lab.hub.db.SubmissionGroupCategories +import org.sourcegrade.lab.hub.db.SubmissionGroupMemberships +import org.sourcegrade.lab.hub.db.Submissions +import org.sourcegrade.lab.hub.db.Terms +import org.sourcegrade.lab.hub.db.Users +import org.sourcegrade.lab.hub.graphql.Scalars +import org.sourcegrade.lab.hub.graphql.UserMutations import org.sourcegrade.lab.hub.graphql.UserQueries import org.sourcegrade.lab.hub.http.authenticationModule +import kotlin.reflect.KType fun Application.module() { - val environment = environment - val url = - Url( - environment.config.tryGetString("ktor.deployment.url") - ?: throw IllegalStateException("No deployment url set"), + install(Koin) { + modules( + module { + single { LogManager.getLogger("SGL Supervisor") } + singleOf(::UserQueries) + singleOf(::UserMutations) + }, + DBModule, ) - + } val databaseConfig = DatabaseConfig { keepLoadedReferencesOutOfTransaction = true } + val logger by inject() + + logger.info("Connecting to database...") Database.connect( - environment.config.tryGetString("ktor.db.url") ?: "", + url = getEnv("SGL_DB_URL"), driver = "org.postgresql.Driver", - user = environment.config.tryGetString("ktor.db.user") ?: "", - password = environment.config.tryGetString("ktor.db.password") ?: "", + user = getEnv("SGL_DB_USER"), + password = getEnv("SGL_DB_PASSWORD"), databaseConfig = databaseConfig, ) + logger.info("Finished connecting to database.") + logger.info("Creating tables...") + transaction { + SchemaUtils.createMissingTablesAndColumns( + Assignments, + CourseMemberships, + Courses, + Criteria, + GradedCriteria, + GradedRubrics, + GradingRuns, + Rubrics, + SubmissionGroupCategories, + SubmissionGroupMemberships, + Submissions, + Terms, + Users, + ) + } + logger.info("Finished creating tables.") install(CORS) { allowMethod(HttpMethod.Options) @@ -50,18 +119,22 @@ fun Application.module() { anyHost() } + + install(GraphQL) { schema { + hooks = object : SchemaGeneratorHooks { + override fun willGenerateGraphQLType(type: KType): GraphQLType? = Scalars.willGenerateGraphQLType(type) + } packages = listOf("org.sourcegrade.lab.hub") queries = listOf( - UserQueries(), + inject().value, + ) + mutations = + listOf( + inject().value, ) -// mutations = -// listOf( -// UserMutations(), -// CourseMutations(), -// ) } } @@ -79,3 +152,5 @@ fun Application.module() { filter { call -> call.request.path().startsWith("/") } } } + +internal fun getEnv(key: String): String = System.getenv(key) ?: throw IllegalStateException("$key not set in environment") diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Routing.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Routing.kt index 8d30a7a..bf583da 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Routing.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Routing.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub import io.ktor.client.request.get diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt index 88ce925..1cc6bc1 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt @@ -1,5 +1,25 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity @@ -8,6 +28,7 @@ import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.sourcegrade.lab.hub.db.Users.clientDefault import org.sourcegrade.lab.hub.domain.Assignment import org.sourcegrade.lab.hub.domain.MutableRepository import org.sourcegrade.lab.hub.domain.Repository @@ -15,7 +36,7 @@ import org.sourcegrade.lab.hub.domain.repo.MutableAssignmentRepository import java.util.UUID internal object Assignments : UUIDTable("sgl_assignments") { - val createdUtc = timestamp("createdUtc") + val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } val courseId = reference("course_id", Courses) val submissionGroupCategory = reference("submission_group_category", SubmissionGroupCategories.id) @@ -24,6 +45,7 @@ internal object Assignments : UUIDTable("sgl_assignments") { val submissionDeadline = timestamp("submissionDeadline") } +@GraphQLIgnore internal class DBAssignment(id: EntityID) : UUIDEntity(id), Assignment { override val uuid: UUID = id.value override val createdUtc: Instant by Assignments.createdUtc @@ -41,8 +63,9 @@ internal class DBAssignment(id: EntityID) : UUIDEntity(id), Assignment { } internal class DBAssignmentRepository : MutableAssignmentRepository, Repository by UUIDEntityClassRepository(DBAssignment) { - override suspend fun findByCourse(courseId: UUID): SizedIterable = - newSuspendedTransaction { DBAssignment.find { Assignments.courseId eq courseId } } + override suspend fun findByCourse(courseId: UUID): SizedIterable = newSuspendedTransaction { + DBAssignment.find { Assignments.courseId eq courseId } + } override suspend fun create(item: Assignment.CreateDto): Assignment = newSuspendedTransaction { DBAssignment.new { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt index 044b8d5..6a95eef 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt @@ -1,22 +1,44 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import kotlinx.datetime.Clock import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.Users.clientDefault import org.sourcegrade.lab.hub.domain.Course import org.sourcegrade.lab.hub.domain.UserMembership import java.util.UUID internal object CourseMemberships : UUIDTable("sgl_course_membership") { - val createdUtc = timestamp("createdUtc") + val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } val userId = reference("user_id", Users) val startUtc = timestamp("startUtc") val endUtc = timestamp("endUtc").nullable() val courseId = reference("course_id", Courses) } +@GraphQLIgnore internal class DBCourseMembership(id: EntityID) : UUIDEntity(id), UserMembership { override val uuid: UUID = id.value override val createdUtc by CourseMemberships.createdUtc diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt index 29172c0..105a53b 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt @@ -1,5 +1,25 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity @@ -7,19 +27,21 @@ import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.Users.clientDefault import org.sourcegrade.lab.hub.domain.Assignment import org.sourcegrade.lab.hub.domain.Course import org.sourcegrade.lab.hub.domain.Term import java.util.UUID internal object Courses : UUIDTable("sgl_courses") { - val createdUtc = timestamp("createdUtc") + val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } val name = varchar("name", 255) val description = varchar("description", 255) val term = reference("term_id", Terms) val ownerId = reference("owner_id", Users) } +@GraphQLIgnore internal class DBCourse(id: EntityID) : UUIDEntity(id), Course { override val uuid: UUID = id.value override val createdUtc: Instant by Courses.createdUtc @@ -30,7 +52,6 @@ internal class DBCourse(id: EntityID) : UUIDEntity(id), Course { override var name: String by Courses.name override var description: String by Courses.description - override var ownerId: UUID by Users.referenceMutator(Courses.ownerId) companion object : EntityClass(Courses) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Criteria.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Criteria.kt index 8c30e00..181c072 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Criteria.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Criteria.kt @@ -1,5 +1,24 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity @@ -18,6 +37,7 @@ internal object Criteria : UUIDTable("sgl_criteria") { val description = text("description") } +@GraphQLIgnore class DBCriterion(id: EntityID) : UUIDEntity(id), Criterion { override val uuid: UUID = id.value override val createdUtc: Instant diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DBModule.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DBModule.kt new file mode 100644 index 0000000..0b3076a --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DBModule.kt @@ -0,0 +1,38 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.db + +import org.koin.core.module.dsl.bind +import org.koin.core.module.dsl.singleOf +import org.koin.dsl.module +import org.sourcegrade.lab.hub.domain.repo.AssignmentRepository +import org.sourcegrade.lab.hub.domain.repo.MutableAssignmentRepository +import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository +import org.sourcegrade.lab.hub.domain.repo.UserRepository + +val DBModule = module { + singleOf(::DBAssignmentRepository) { + bind() + bind() + } + singleOf(::DBUserRepository) { + bind() + bind() + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedCriteria.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedCriteria.kt index c5f2261..2895fd4 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedCriteria.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedCriteria.kt @@ -1,5 +1,24 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.Entity import org.jetbrains.exposed.dao.EntityClass @@ -18,6 +37,7 @@ object GradedCriteria : UUIDTable("sgl_graded_criteria") { val message = text("message") } +@GraphQLIgnore class DBGradedCriterion(id: EntityID) : Entity(id), GradedCriterion { override val uuid: UUID = id.value override val createdUtc: Instant diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedRubrics.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedRubrics.kt index f2fc15b..902189e 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedRubrics.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradedRubrics.kt @@ -1,5 +1,25 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.Entity import org.jetbrains.exposed.dao.EntityClass @@ -12,13 +32,14 @@ import org.sourcegrade.lab.hub.domain.GradedRubric import java.util.UUID internal object GradedRubrics : UUIDTable("sgl_graded_rubrics") { - val createdUtc = timestamp("created_utc") + val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } val rubricId = reference("rubric_id", Rubrics) val name = varchar("name", 255) val achievedMinPoints = integer("achieved_min_points") val achievedMaxPoints = integer("achieved_max_points") } +@GraphQLIgnore class DBGradedRubric(id: EntityID) : Entity(id), GradedRubric { override val uuid: UUID = id.value override val createdUtc: Instant by GradedRubrics.createdUtc diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt index af1e4c7..2963cea 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt @@ -1,5 +1,25 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity @@ -7,12 +27,13 @@ import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.duration import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.Users.clientDefault import org.sourcegrade.lab.hub.domain.GradingRun import java.util.UUID import kotlin.time.Duration internal object GradingRuns : UUIDTable("sgl_grading_runs") { - val createdUtc = timestamp("createdUtc") + val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } val submissionId = reference("submission_id", Submissions) val gradedRubricId = reference("graded_rubric_id", GradedRubrics) val runtime = duration("runtime") @@ -25,6 +46,7 @@ internal object GradingRuns : UUIDTable("sgl_grading_runs") { val achievedMaxPoints = integer("achieved_max_points") } +@GraphQLIgnore internal class DBGradingRun(id: EntityID) : UUIDEntity(id), GradingRun { override val uuid: UUID = id.value override val createdUtc: Instant by GradingRuns.createdUtc diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutatorDelegate.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutatorDelegate.kt index 903fd95..8cc0fe6 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutatorDelegate.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutatorDelegate.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.Entity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt index 54106ed..019bfec 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt @@ -1,5 +1,25 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity @@ -8,19 +28,21 @@ import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.Users.clientDefault import org.sourcegrade.lab.hub.domain.Assignment import org.sourcegrade.lab.hub.domain.Criterion import org.sourcegrade.lab.hub.domain.Rubric import java.util.UUID internal object Rubrics : UUIDTable("sgl_rubrics") { - val createdUtc = timestamp("createdUtc") + val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } val name = varchar("name", 255) val assignmentId = reference("assignment_id", Assignments) val maxPoints = integer("max_points") val minPoints = integer("min_points") } +@GraphQLIgnore class DBRubric(id: EntityID) : UUIDEntity(id), Rubric { override val uuid: UUID = id.value override val createdUtc: Instant by Rubrics.createdUtc diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt index 1236a36..159bc64 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt @@ -1,22 +1,44 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.Users.clientDefault import org.sourcegrade.lab.hub.domain.SubmissionGroupCategory import java.util.UUID internal object SubmissionGroupCategories : UUIDTable("sgl_submission_group_categories") { - val createdUtc = timestamp("createdUtc") + val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } val name = varchar("name", 255).uniqueIndex() val courseId = reference("course_id", Courses) val minSize = integer("min_size") val maxSize = integer("max_size") } +@GraphQLIgnore internal class DBSubmissionGroupCategory(id: EntityID) : UUIDEntity(id), SubmissionGroupCategory { override val uuid: UUID = id.value override val createdUtc: Instant by SubmissionGroupCategories.createdUtc diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt index 7eeefe9..524e367 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt @@ -1,5 +1,25 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import kotlinx.datetime.Clock import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID @@ -10,13 +30,14 @@ import org.sourcegrade.lab.hub.domain.UserMembership import java.util.UUID internal object SubmissionGroupMemberships : UUIDTable("sgl_submission_group_membership") { - val createdUtc = timestamp("createdUtc") + val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } val startUtc = timestamp("startUtc") val endUtc = timestamp("endUtc").nullable() val userId = reference("user_id", Users) val submissionGroupId = reference("submission_group_id", SubmissionGroups) } +@GraphQLIgnore internal class DBSubmissionGroupMembership(id: EntityID) : UUIDEntity(id), UserMembership { override val uuid: UUID = id.value override val createdUtc by SubmissionGroupMemberships.createdUtc diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt index 528b371..5c32bfe 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt @@ -1,5 +1,25 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity @@ -7,16 +27,18 @@ import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.Users.clientDefault import org.sourcegrade.lab.hub.domain.SubmissionGroup import org.sourcegrade.lab.hub.domain.Term import java.util.UUID internal object SubmissionGroups : UUIDTable("sgl_submission_groups") { - val createdUtc = timestamp("createdUtc") + val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } val name = varchar("name", 255).uniqueIndex() val categoryId = reference("category_id", SubmissionGroupCategories) } +@GraphQLIgnore internal class DBSubmissionGroup(id: EntityID) : UUIDEntity(id), SubmissionGroup { override val uuid: UUID = id.value override val createdUtc: Instant by SubmissionGroups.createdUtc diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt index 9d097c6..354e07f 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt @@ -1,5 +1,25 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity @@ -8,17 +28,19 @@ import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.Users.clientDefault import org.sourcegrade.lab.hub.domain.Submission import java.util.UUID internal object Submissions : UUIDTable("sgl_submissions") { - val createdUtc = timestamp("createdUtc") + val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } val assignmentId = reference("assignment_id", Assignments) val submitterId = reference("submitter_id", Users) val groupId = reference("group_id", SubmissionGroups) val uploaded = timestamp("uploaded") } +@GraphQLIgnore internal class DBSubmission(id: EntityID) : UUIDEntity(id), Submission { override val uuid: UUID = id.value override val createdUtc: Instant by Submissions.createdUtc diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt index 98e333d..a6ffce7 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Terms.kt @@ -1,5 +1,24 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore import kotlinx.datetime.Instant import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity @@ -17,6 +36,7 @@ internal object Terms : UUIDTable("sgl_terms") { val end = timestamp("end") } +@GraphQLIgnore internal class DBTerm(id: EntityID) : UUIDEntity(id), Term { override val uuid: UUID = id.value override val createdUtc: Instant by Terms.createdUtc diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt index 66f7f0d..c9460c6 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db import org.jetbrains.exposed.dao.Entity diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt index bfe9c76..1197cac 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt @@ -1,6 +1,27 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.db +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import org.apache.logging.log4j.Logger import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID @@ -15,26 +36,30 @@ import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.sourcegrade.lab.hub.domain.Assignment import org.sourcegrade.lab.hub.domain.Course +import org.sourcegrade.lab.hub.domain.MutableRepository +import org.sourcegrade.lab.hub.domain.Repository import org.sourcegrade.lab.hub.domain.Submission import org.sourcegrade.lab.hub.domain.SubmissionGroup import org.sourcegrade.lab.hub.domain.Term import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserMembership +import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository import java.util.UUID internal object Users : UUIDTable("sgl_users") { - val createdUtc = timestamp("createdUtc") + val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } + val email = varchar("email", 255).uniqueIndex() val username = varchar("username", 255).uniqueIndex() val displayname = varchar("displayname", 255) - val email = varchar("email", 255).uniqueIndex() } +@GraphQLIgnore internal class DBUser(id: EntityID) : UUIDEntity(id), User { override val uuid: UUID = id.value override val createdUtc: Instant by Users.createdUtc + override var email: String by Users.email override var username: String by Users.username override var displayname: String by Users.displayname - override var email: String by Users.email private fun Query.termPredicate(term: Term.Matcher, now: Instant): Query = when (term) { is Term.Matcher.All -> this @@ -43,15 +68,15 @@ internal class DBUser(id: EntityID) : UUIDEntity(id), User { is Term.Matcher.ById -> where { Terms.id.eq(term.id) } } - private fun Query.membershipStatusPredicate(status: UserMembership.Status, now: Instant): Query = when (status) { - UserMembership.Status.ALL -> this - UserMembership.Status.FUTURE -> where { CourseMemberships.startUtc greater now } - UserMembership.Status.PAST -> where { CourseMemberships.endUtc less now } - UserMembership.Status.CURRENT -> where { CourseMemberships.endUtc.isNull() } + private fun Query.membershipStatusPredicate(status: UserMembership.UserMembershipStatus, now: Instant): Query = when (status) { + UserMembership.UserMembershipStatus.ALL -> this + UserMembership.UserMembershipStatus.FUTURE -> where { CourseMemberships.startUtc greater now } + UserMembership.UserMembershipStatus.PAST -> where { CourseMemberships.endUtc less now } + UserMembership.UserMembershipStatus.CURRENT -> where { CourseMemberships.endUtc.isNull() } } override suspend fun courseMemberships( - status: UserMembership.Status, + status: UserMembership.UserMembershipStatus, term: Term.Matcher, now: Instant, ): SizedIterable> = newSuspendedTransaction { @@ -63,7 +88,7 @@ internal class DBUser(id: EntityID) : UUIDEntity(id), User { } override suspend fun submissionGroupMemberships( - status: UserMembership.Status, + status: UserMembership.UserMembershipStatus, term: Term.Matcher, now: Instant, ): SizedIterable> = newSuspendedTransaction { @@ -81,12 +106,12 @@ internal class DBUser(id: EntityID) : UUIDEntity(id), User { Assignments.innerJoin(Courses).innerJoin(Terms).innerJoin(CourseMemberships).selectAll() .where { CourseMemberships.userId eq uuid } .termPredicate(term, now) - .membershipStatusPredicate(UserMembership.Status.CURRENT, now) + .membershipStatusPredicate(UserMembership.UserMembershipStatus.CURRENT, now) .mapLazy { DBAssignment.wrapRow(it) } } override suspend fun submissions( - status: Submission.Status, // TODO: Use parameter + status: Submission.SubmissionStatus, // TODO: Use parameter term: Term.Matcher, now: Instant, ): SizedIterable = newSuspendedTransaction { @@ -97,3 +122,42 @@ internal class DBUser(id: EntityID) : UUIDEntity(id), User { companion object : EntityClass(Users) } + +internal class DBUserRepository( + private val logger: Logger, +) : MutableUserRepository, Repository by UUIDEntityClassRepository(DBUser) { + override suspend fun findByUsername(username: String): User? = newSuspendedTransaction { + DBUser.find { Users.username eq username }.firstOrNull() + } + + override suspend fun findAllByUsername(partialUsername: String): SizedIterable = newSuspendedTransaction { + DBUser.find { Users.username like "%$partialUsername%" }.mapLazy { it.toSnapshot() } + } + + override suspend fun findByEmail(email: String): User? = newSuspendedTransaction { + DBUser.find { Users.email eq email }.firstOrNull() + } + + override suspend fun create(item: User.CreateDto): User = newSuspendedTransaction { + DBUser.new { + username = item.username + displayname = item.displayname + email = item.email + }.also { + logger.info("Created new user ${it.uuid} with data $item") + } + } + + override suspend fun put(item: User.CreateDto): MutableRepository.PutResult = newSuspendedTransaction { + val existingUser = findByUsername(item.username) + if (existingUser == null) { + MutableRepository.PutResult(create(item), created = true) + } else { + logger.info( + "Loaded existing user ${existingUser.username} (${existingUser.uuid}) with" + + "display name ${existingUser.displayname} and email ${existingUser.email}", + ) + MutableRepository.PutResult(existingUser, created = false) + } + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt index 29410de..aa8f707 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/AssignmentParticipation.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/AssignmentParticipation.kt index d1d1935..1c28942 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/AssignmentParticipation.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/AssignmentParticipation.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.sql.SizedIterable diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt index f3b581e..116116f 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt @@ -1,7 +1,24 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.sql.SizedIterable -import java.util.UUID interface Course : TermScoped { val submissionGroupCategories: SizedIterable @@ -10,5 +27,4 @@ interface Course : TermScoped { var name: String var description: String - var ownerId: UUID } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt index 4fd8882..b03b81b 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Criterion.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.sql.SizedIterable diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt index 5d98c2c..420a890 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedCriterion.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedCriterion.kt index abc0bed..b0581d0 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedCriterion.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedCriterion.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain interface GradedCriterion : DomainEntity { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt index 991d9cb..28d1862 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradedRubric.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.sql.SizedIterable diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt index 51fe636..e4785de 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/GradingRun.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain import kotlin.time.Duration diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt index c962607..842f12f 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt @@ -1,5 +1,5 @@ /* - * Anvil - AnvilPowered.org + * Lab - SourceGrade.org * Copyright (C) 2019-2024 Contributors * * This program is free software: you can redistribute it and/or modify @@ -18,9 +18,12 @@ package org.sourcegrade.lab.hub.domain +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore + +@GraphQLIgnore interface MutableRepository> : Repository { suspend fun create(item: C): E suspend fun put(item: C): PutResult - data class PutResult(val entity: E, val created: Boolean) + data class PutResult(val entity: E, val created: Boolean) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt index 90fefee..b638841 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt @@ -1,5 +1,5 @@ /* - * Anvil - AnvilPowered.org + * Lab - SourceGrade.org * Copyright (C) 2019-2024 Contributors * * This program is free software: you can redistribute it and/or modify @@ -18,11 +18,13 @@ package org.sourcegrade.lab.hub.domain +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore import java.util.UUID +@GraphQLIgnore interface Repository { suspend fun findById(id: UUID): E? // TODO: Eager loading + suspend fun deleteById(id: UUID): Boolean suspend fun exists(id: UUID): Boolean suspend fun countAll(): Long - suspend fun deleteById(id: UUID): Boolean } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt index d3f03da..3c95c37 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Rubric.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.sql.SizedIterable diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt index 985b838..ed87515 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant @@ -12,7 +30,7 @@ interface Submission : DomainEntity { val gradingRuns: SizedIterable val lastGradingRun: GradingRun? - enum class Status { + enum class SubmissionStatus { ALL, PENDING_GRADE, GRADED, diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt index 1ed2870..b540e60 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroup.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain import org.jetbrains.exposed.sql.SizedIterable diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt index ee6c51c..8a8f9f0 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain interface SubmissionGroupCategory : DomainEntity { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt index 75aa775..b597502 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant @@ -11,7 +29,31 @@ interface Term : DomainEntity { sealed interface Matcher { data object All : Matcher data object Current : Matcher - data class ByName(val name: String) : Matcher + data class ByName(val name: String) : Matcher { + init { + require(name.isNotBlank()) + } + } data class ById(val id: UUID) : Matcher + + + companion object { + fun fromString(value: String): Matcher = when (value) { + "All" -> All + "Current" -> Current + else -> { + regex.matchEntire(value)?.let { match -> + val type = match.groups["type"]?.value + val param = match.groups["param"]?.value!! + when (type) { + "ByName" -> ByName(param) + "ById" -> ById(UUID.fromString(param)) + else -> throw IllegalArgumentException("Invalid type: $type") + } + } ?: throw IllegalArgumentException("Invalid matcher: $value") + } + } + private val regex = "(?[a-zA-Z])\\((?.+\\))".toRegex() + } } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/TermScoped.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/TermScoped.kt index 137b4d1..172933f 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/TermScoped.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/TermScoped.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain interface TermScoped : DomainEntity { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index e3c77cb..5d53733 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -1,42 +1,104 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.jetbrains.exposed.sql.SizedIterable +import java.util.UUID interface User : DomainEntity { + var email: String var username: String var displayname: String - var email: String // TODO: Split into UserActions // TODO: Get from UserMembershipRepository + @GraphQLIgnore suspend fun courseMemberships( - status: UserMembership.Status = UserMembership.Status.CURRENT, + status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, term: Term.Matcher = Term.Matcher.Current, now: Instant = Clock.System.now(), ): SizedIterable> + suspend fun courseMemberships( + status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, + term: String = Term.Matcher.Current.toString(), + now: Instant = Clock.System.now(), + ): SizedIterable> = courseMemberships(status, Term.Matcher.fromString(term), now) + + @GraphQLIgnore suspend fun submissionGroupMemberships( - status: UserMembership.Status = UserMembership.Status.CURRENT, + status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, term: Term.Matcher = Term.Matcher.Current, now: Instant = Clock.System.now(), ): SizedIterable> + suspend fun submissionGroupMemberships( + status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, + term: String = Term.Matcher.Current.toString(), + now: Instant = Clock.System.now(), + ): SizedIterable> = submissionGroupMemberships(status, Term.Matcher.fromString(term), now) + // suspend fun assignmentParticipations( // status: AssignmentParticipation.Status = AssignmentParticipation.Status.OPEN, // term: Term.Matcher = Term.Matcher.Current, // now: Instant = Clock.System.now(), // ): SizedIterable + @GraphQLIgnore suspend fun assignments( term: Term.Matcher = Term.Matcher.Current, now: Instant = Clock.System.now(), ): SizedIterable + suspend fun assignments( + term: String = Term.Matcher.Current.toString(), + now: Instant = Clock.System.now(), + ): SizedIterable = assignments(Term.Matcher.fromString(term), now) + + @GraphQLIgnore suspend fun submissions( - status: Submission.Status = Submission.Status.ALL, + status: Submission.SubmissionStatus = Submission.SubmissionStatus.ALL, term: Term.Matcher = Term.Matcher.Current, now: Instant = Clock.System.now(), ): SizedIterable + + suspend fun submissions( + status: Submission.SubmissionStatus = Submission.SubmissionStatus.ALL, + term: String = Term.Matcher.Current.toString(), + now: Instant = Clock.System.now(), + ): SizedIterable = submissions(status, Term.Matcher.fromString(term), now) + + data class CreateDto( + val email: String, + val username: String, + val displayname: String = username, + ) : Creates + + data class Snapshot( + val uuid: UUID, + val email: String, + val username: String, + val displayname: String, + ) + + fun toSnapshot(): Snapshot = Snapshot(uuid, email, username, displayname) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt index b3cb140..3aaa33d 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant @@ -15,7 +33,7 @@ interface UserMembership : DomainEntity { val startUtc: Instant? = null, // or else now ) : Creates> - enum class Status { + enum class UserMembershipStatus { ALL, FUTURE, CURRENT, diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt index ceec9b1..969f2d1 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain.repo import org.jetbrains.exposed.sql.SizedIterable diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt index 3df5ce1..68c1de1 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain.repo import org.jetbrains.exposed.sql.SizedIterable diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt index ef9146f..e0a8eb7 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.domain.repo import kotlinx.datetime.Clock @@ -11,7 +29,7 @@ import org.sourcegrade.lab.hub.domain.UserMembership interface UserMembershipRepository : Repository> { suspend fun find( - status: UserMembership.Status = UserMembership.Status.CURRENT, + status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, term: Term.Matcher = Term.Matcher.Current, now: Instant = Clock.System.now(), ): SizedIterable diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt new file mode 100644 index 0000000..11cff43 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt @@ -0,0 +1,34 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.domain.repo + +import org.jetbrains.exposed.sql.SizedIterable +import org.sourcegrade.lab.hub.domain.MutableRepository +import org.sourcegrade.lab.hub.domain.Repository +import org.sourcegrade.lab.hub.domain.User + +interface UserRepository : Repository { + suspend fun findByUsername(username: String): User? + + suspend fun findAllByUsername(partialUsername: String): SizedIterable + + suspend fun findByEmail(email: String): User? +} + +interface MutableUserRepository : UserRepository, MutableRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLRepository.kt new file mode 100644 index 0000000..d7bc776 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLRepository.kt @@ -0,0 +1,41 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.graphql + +import graphql.schema.DataFetchingEnvironment +import org.sourcegrade.lab.hub.domain.DomainEntity +import org.sourcegrade.lab.hub.domain.Repository +import java.util.UUID + +// This doesn't work +fun Repository.toGraphQL(): GraphQLRepository = GraphQLRepositoryImpl(this) + +interface GraphQLRepository { + suspend fun findById(dfe: DataFetchingEnvironment): E? + suspend fun deleteById(dfe: DataFetchingEnvironment): Boolean + suspend fun exists(dfe: DataFetchingEnvironment): Boolean + suspend fun countAll(): Long +} + +private class GraphQLRepositoryImpl(private val repository: Repository) : GraphQLRepository { + override suspend fun findById(dfe: DataFetchingEnvironment): E? = repository.findById(UUID.fromString(dfe.getArgument("id"))) + override suspend fun deleteById(dfe: DataFetchingEnvironment): Boolean = repository.deleteById(UUID.fromString(dfe.getArgument("id"))) + override suspend fun exists(dfe: DataFetchingEnvironment): Boolean = repository.exists(UUID.fromString(dfe.getArgument("id"))) + override suspend fun countAll(): Long = repository.countAll() +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Scalars.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Scalars.kt new file mode 100644 index 0000000..cb9292d --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Scalars.kt @@ -0,0 +1,114 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.graphql + +import graphql.GraphQLContext +import graphql.execution.CoercedVariables +import graphql.language.StringValue +import graphql.language.Value +import graphql.schema.Coercing +import graphql.schema.GraphQLScalarType +import graphql.schema.GraphQLType +import kotlinx.datetime.Instant +import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.transactions.transaction +import java.util.Locale +import java.util.UUID +import kotlin.reflect.KType +import kotlin.time.Duration + +internal object Scalars { + + fun willGenerateGraphQLType(type: KType): GraphQLType? = + when (type.classifier) { + Instant::class -> instant + UUID::class -> uuid + Long::class -> long + Duration::class -> duration + SizedIterable::class -> sizedIterable + else -> null + } + + private val instant: GraphQLScalarType = GraphQLScalarType.newScalar() + .name("Instant") + .coercing( + object : Coercing { + override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): Instant = + Instant.parse(input as String) + + override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): String = + (dataFetcherResult as Instant).toString() + }, + ).build() + + private val uuid = GraphQLScalarType.newScalar() + .name("UUID") + .coercing( + object : Coercing { + override fun parseLiteral( + input: Value<*>, + variables: CoercedVariables, + graphQLContext: GraphQLContext, + locale: Locale, + ): UUID = UUID.fromString((input as StringValue).value) + + override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): UUID = + UUID.fromString(input.toString()) + + override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): String = + (dataFetcherResult as UUID).toString() + }, + ).build() + + private val long = GraphQLScalarType.newScalar() + .name("Long") + .coercing( + object : Coercing { + override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): Long = + input.toString().toLong() + + override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): String = + dataFetcherResult.toString() + }, + ).build() + + private val duration = GraphQLScalarType.newScalar() + .name("Duration") + .coercing( + object : Coercing { + override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): Duration = + Duration.parse(input as String) + + override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): String = + (dataFetcherResult as Duration).toString() + }, + ).build() + + private val sizedIterable = GraphQLScalarType.newScalar() + .name("SizedIterable") + .coercing( + object : Coercing, List<*>> { + override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): List<*> { + val result = transaction { (dataFetcherResult as SizedIterable<*>).toList() } + println("SizedIterable serialized to $result") + return result + } + }, + ).build() +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt new file mode 100644 index 0000000..2740d61 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt @@ -0,0 +1,59 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.graphql + +import com.expediagroup.graphql.generator.annotations.GraphQLDescription +import com.expediagroup.graphql.server.operations.Mutation +import com.expediagroup.graphql.server.operations.Query +import graphql.schema.DataFetchingEnvironment +import org.apache.logging.log4j.Logger +import org.sourcegrade.lab.hub.domain.User +import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository +import org.sourcegrade.lab.hub.domain.repo.UserRepository +import java.util.UUID + +class UserQueries(private val repository: UserRepository) : Query { + fun user(): UserQuery = UserQuery(repository) +} + +@GraphQLDescription("Query user collection") +class UserQuery(private val repository: UserRepository) : UserRepository by repository { + override suspend fun findById(id: UUID): User? = repository.findById(id) + override suspend fun deleteById(id: UUID): Boolean = repository.deleteById(id) + override suspend fun exists(id: UUID): Boolean = repository.exists(id) + override suspend fun countAll(): Long = repository.countAll() +} + +class UserMutations( + private val logger: Logger, + private val repository: MutableUserRepository, +) : Mutation { + fun user(): UserMutation = UserMutation(logger, repository) +} + +@GraphQLDescription("Mutation user collection") +class UserMutation( + private val logger: Logger, + private val repository: MutableUserRepository, +) { + suspend fun create(dfe: DataFetchingEnvironment, item: User.CreateDto): User.Snapshot { + logger.info("SelectionSet: ${dfe.selectionSet.fields.joinToString { it.name }}") + return repository.create(item).toSnapshot() + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserQueries.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserQueries.kt deleted file mode 100644 index 1a1f267..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserQueries.kt +++ /dev/null @@ -1,20 +0,0 @@ -package org.sourcegrade.lab.hub.graphql - -import com.expediagroup.graphql.generator.annotations.GraphQLDescription -import com.expediagroup.graphql.server.operations.Query -import graphql.schema.DataFetchingEnvironment -import org.sourcegrade.lab.hub.domain.User - -class UserQueries : Query { - suspend fun user( - environment: DataFetchingEnvironment, - id: String? = null - ): UserQuery = UserQuery() -} - -@GraphQLDescription("Query user collection") -class UserQuery { - suspend fun fetch(environment: DataFetchingEnvironment): User { - - } -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt index c25cada..bfd2e59 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt @@ -1,3 +1,21 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + package org.sourcegrade.lab.hub.http import io.ktor.client.HttpClient @@ -39,9 +57,11 @@ import io.ktor.util.pipeline.PipelineContext import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.db.Users +import org.koin.ktor.ext.inject import org.sourcegrade.lab.hub.domain.User +import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository +import org.sourcegrade.lab.hub.domain.repo.UserRepository +import org.sourcegrade.lab.hub.getEnv import java.io.File import java.util.UUID import kotlin.collections.set @@ -83,7 +103,7 @@ fun Application.authenticationModule() { } // oidc authentication oauth("Authentik") { - val url = ktorEnv.config.property("ktor.deployment.url").getString() + val url = getEnv("SGL_DEPLOYMENT_URL") urlProvider = { "$url$callback" } @@ -92,18 +112,12 @@ fun Application.authenticationModule() { val oauthSettings = OAuthServerSettings.OAuth2ServerSettings( name = "Authentik", - authorizeUrl = ktorEnv.config.property("ktor.oauth.authorizeUrl") - .getString(), - accessTokenUrl = ktorEnv.config.property("ktor.oauth.accessTokenUrl") - .getString(), + authorizeUrl = getEnv("SGL_AUTH_URL"), + accessTokenUrl = getEnv("SGL_AUTH_ACCESS_TOKEN_URL"), requestMethod = HttpMethod.Post, - clientId = ktorEnv.config.property("ktor.oauth.clientId") - .getString(), - clientSecret = ktorEnv.config.property("ktor.oauth.clientSecret") - .getString(), - defaultScopes = ktorEnv.config.tryGetString("ktor.oauth.scopes") - ?.split(" ") - ?: listOf("openid", "profile", "email"), + clientId = getEnv("SGL_AUTH_CLIENT_ID"), + clientSecret = getEnv("SGL_AUTH_CLIENT_SECRET"), + defaultScopes = getEnv("SGL_AUTH_SCOPES").split(" "), onStateCreated = { call, state -> // saves new state with redirect url value call.request.queryParameters["redirectUrl"]?.let { @@ -115,6 +129,7 @@ fun Application.authenticationModule() { } } routing { + val userRepository = inject().value route("/api/session") { install(ServerContentNegotiation) { json( @@ -141,21 +156,18 @@ fun Application.authenticationModule() { header("Authorization", "Bearer ${principal.accessToken}") }.body() + val createDto = User.CreateDto( + email = userInfo.email, + username = userInfo.preferredUsername, + ) + // find user in db - val user = - newSuspendedTransaction { - User.find { Users.email eq userInfo.email }.firstOrNull() - } ?: newSuspendedTransaction { - User.new { - username = userInfo.preferredUsername - email = userInfo.email - } - } + val user = userRepository.put(createDto).entity val session = UserSession( - user.id.value.toString(), + user.uuid.toString(), checkNotNull(principal.state) { "No state" }, principal.accessToken, userInfo.email, @@ -177,7 +189,7 @@ fun Application.authenticationModule() { } } get("current-user") { - withUser { call.respond(it) } // TODO: Conversion to DTO + withUser(userRepository) { call.respond(it) } // TODO: Conversion to DTO } } } @@ -193,9 +205,9 @@ suspend fun PipelineContext.withUserSession(block: su } } -suspend fun PipelineContext.withUser(block: suspend (User) -> Unit) { +suspend fun PipelineContext.withUser(userRepository: UserRepository, block: suspend (User) -> Unit) { withUserSession { session -> - val user = newSuspendedTransaction { User.findById(UUID.fromString(session.userId)) } + val user = userRepository.findById(UUID.fromString(session.userId)) checkNotNull(user) { "Could not find user ${session.email} in DB" } block(user) } From e29275684ea5017edfa9eb6c3c33a9a2a2903040 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Thu, 20 Jun 2024 17:00:55 +0200 Subject: [PATCH 21/29] Work on graph integration --- gradle/libs.versions.toml | 2 + hub/build.gradle.kts | 1 + .../kotlin/org/sourcegrade/lab/hub/Main.kt | 4 +- .../kotlin/org/sourcegrade/lab/hub/Module.kt | 6 +- .../lab/hub/db/CourseMemberships.kt | 3 +- .../org/sourcegrade/lab/hub/db/Courses.kt | 5 +- .../org/sourcegrade/lab/hub/db/DBModule.kt | 2 + .../lab/hub/db/EntityConversionContext.kt | 85 +++++++++ .../org/sourcegrade/lab/hub/db/GradingRuns.kt | 1 - .../sourcegrade/lab/hub/db/RelationOption.kt | 48 ++++++ .../org/sourcegrade/lab/hub/db/Rubrics.kt | 3 +- .../lab/hub/db/SubmissionGroupCategories.kt | 1 - .../lab/hub/db/SubmissionGroupMemberships.kt | 2 + .../lab/hub/db/SubmissionGroups.kt | 2 +- .../org/sourcegrade/lab/hub/db/Submissions.kt | 5 +- .../lab/hub/db/UUIDEntityClassRepository.kt | 27 +-- .../org/sourcegrade/lab/hub/db/Users.kt | 163 ------------------ .../lab/hub/db/assignment/AssignmentOps.kt | 26 +++ .../hub/db/assignment/AssignmentSnapshot.kt | 58 +++++++ .../hub/db/{ => assignment}/Assignments.kt | 42 ++++- .../sourcegrade/lab/hub/db/user/UserOps.kt | 106 ++++++++++++ .../lab/hub/db/user/UserSnapshot.kt | 76 ++++++++ .../org/sourcegrade/lab/hub/db/user/Users.kt | 119 +++++++++++++ .../sourcegrade/lab/hub/domain/Assignment.kt | 25 ++- .../lab/hub/domain/DomainEntity.kt | 3 + .../lab/hub/domain/DomainEntityCollection.kt | 90 ++++++++++ .../org/sourcegrade/lab/hub/domain/Term.kt | 20 ++- .../org/sourcegrade/lab/hub/domain/User.kt | 92 ++-------- .../hub/domain/repo/AssignmentRepository.kt | 6 +- .../hub/domain/repo/CollectionRepository.kt | 26 +++ .../domain/{ => repo}/MutableRepository.kt | 6 +- .../lab/hub/domain/{ => repo}/Repository.kt | 10 +- .../hub/domain/repo/SubmissionRepository.kt | 2 - .../domain/repo/UserMembershipRepository.kt | 2 - .../lab/hub/domain/repo/UserRepository.kt | 12 +- .../lab/hub/graphql/GraphQLRepository.kt | 41 ----- .../lab/hub/graphql/RelationOps.kt | 33 ++++ .../sourcegrade/lab/hub/graphql/Scalars.kt | 12 +- .../org/sourcegrade/lab/hub/graphql/User.kt | 58 ++++--- 39 files changed, 867 insertions(+), 358 deletions(-) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RelationOption.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/AssignmentOps.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/AssignmentSnapshot.kt rename hub/src/main/kotlin/org/sourcegrade/lab/hub/db/{ => assignment}/Assignments.kt (62%) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserOps.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserSnapshot.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt rename hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/{ => repo}/MutableRepository.kt (82%) rename hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/{ => repo}/Repository.kt (77%) delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RelationOps.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1200473..bdcbd2f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -44,6 +44,8 @@ protobuf-kotlin = "com.google.protobuf:protobuf-kotlin:4.26.1" bcrypt = "org.mindrot:jbcrypt:0.4" +shiro = "org.apache.shiro:shiro-core:2.0.1" + [plugins] kotlin-jvm = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } kotlin-kapt = { id = "org.jetbrains.kotlin.kapt", version.ref = "kotlin" } diff --git a/hub/build.gradle.kts b/hub/build.gradle.kts index 3b5c13b..be83a82 100644 --- a/hub/build.gradle.kts +++ b/hub/build.gradle.kts @@ -29,6 +29,7 @@ dependencies { implementation(libs.ktor.server.call.logging) implementation(libs.ktor.client.logging) implementation(libs.kotlin.reflect) + implementation(libs.shiro) } application { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt index 4c509fb..74800d0 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt @@ -22,7 +22,5 @@ import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty fun main() { - embeddedServer(Netty) { - module() - }.start(wait = true) + embeddedServer(Netty) { module() }.start(wait = true) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt index e5b4284..c21e5bb 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt @@ -43,7 +43,7 @@ import org.koin.core.module.dsl.singleOf import org.koin.dsl.module import org.koin.ktor.ext.inject import org.koin.ktor.plugin.Koin -import org.sourcegrade.lab.hub.db.Assignments +import org.sourcegrade.lab.hub.db.assignment.Assignments import org.sourcegrade.lab.hub.db.CourseMemberships import org.sourcegrade.lab.hub.db.Courses import org.sourcegrade.lab.hub.db.Criteria @@ -56,7 +56,7 @@ import org.sourcegrade.lab.hub.db.SubmissionGroupCategories import org.sourcegrade.lab.hub.db.SubmissionGroupMemberships import org.sourcegrade.lab.hub.db.Submissions import org.sourcegrade.lab.hub.db.Terms -import org.sourcegrade.lab.hub.db.Users +import org.sourcegrade.lab.hub.db.user.Users import org.sourcegrade.lab.hub.graphql.Scalars import org.sourcegrade.lab.hub.graphql.UserMutations import org.sourcegrade.lab.hub.graphql.UserQueries @@ -119,8 +119,6 @@ fun Application.module() { anyHost() } - - install(GraphQL) { schema { hooks = object : SchemaGeneratorHooks { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt index 6a95eef..918b916 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt @@ -25,7 +25,8 @@ import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -import org.sourcegrade.lab.hub.db.Users.clientDefault +import org.sourcegrade.lab.hub.db.user.DBUser +import org.sourcegrade.lab.hub.db.user.Users import org.sourcegrade.lab.hub.domain.Course import org.sourcegrade.lab.hub.domain.UserMembership import java.util.UUID diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt index 105a53b..40ed716 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt @@ -27,7 +27,10 @@ import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -import org.sourcegrade.lab.hub.db.Users.clientDefault +import org.sourcegrade.lab.hub.db.assignment.Assignments +import org.sourcegrade.lab.hub.db.assignment.DBAssignment +import org.sourcegrade.lab.hub.db.user.DBUser +import org.sourcegrade.lab.hub.db.user.Users import org.sourcegrade.lab.hub.domain.Assignment import org.sourcegrade.lab.hub.domain.Course import org.sourcegrade.lab.hub.domain.Term diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DBModule.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DBModule.kt index 0b3076a..eb36e64 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DBModule.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DBModule.kt @@ -21,6 +21,8 @@ package org.sourcegrade.lab.hub.db import org.koin.core.module.dsl.bind import org.koin.core.module.dsl.singleOf import org.koin.dsl.module +import org.sourcegrade.lab.hub.db.assignment.DBAssignmentRepository +import org.sourcegrade.lab.hub.db.user.DBUserRepository import org.sourcegrade.lab.hub.domain.repo.AssignmentRepository import org.sourcegrade.lab.hub.domain.repo.MutableAssignmentRepository import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt new file mode 100644 index 0000000..e8751f1 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt @@ -0,0 +1,85 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.db + +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.load +import org.jetbrains.exposed.dao.with +import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.mapLazy +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.sourcegrade.lab.hub.domain.DomainEntity +import org.sourcegrade.lab.hub.domain.Relation + +interface EntityConversionContext { + + fun createSnapshot(entity: N, relations: Set): E + + fun convertRelation(relation: Relation): Relation + + suspend fun entityConversion( + relations: List> = emptyList(), + statement: ConversionBody, + ): T +} + +typealias ConversionBody = suspend EntityConversion.() -> EntityConversion.BindResult + +class EntityConversionContextImpl( + private val snapshotFun: (N, Set) -> E, +) : EntityConversionContext { + + override fun createSnapshot(entity: N, relations: Set): E = snapshotFun(entity, relations) + + override fun convertRelation(relation: Relation): Relation { + @Suppress("UNCHECKED_CAST") + return (relation as Relation) + } + + override suspend fun entityConversion( + relations: List>, + statement: ConversionBody, + ): T { + val ec = EntityConversion(context = this, relations) + return newSuspendedTransaction { statement(ec).result } + } +} + +class EntityConversion( + private val context: EntityConversionContext, + relations: List>, +) { + + private val relationSet: Set = relations.map { it.name }.toSet() + private val relationArray: Array> = relations.map { context.convertRelation(it) }.toTypedArray() + private fun N.createSnapshot(): E = context.createSnapshot(this, relationSet) + + fun N.bind(): BindResult = + BindResult((if (relationArray.isNotEmpty()) load(*relationArray) else this).createSnapshot()) + + fun N?.bindNullable(): BindResult = + this?.bind() ?: BindResult(null) + + fun SizedIterable.bindIterable(): BindResult> = + BindResult((if (relationArray.isNotEmpty()) with(*relationArray) else this).mapLazy { it.createSnapshot() }) + + fun T.bindT(): BindResult = BindResult(this) + + class BindResult(val result: T) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt index 2963cea..3dffd3c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/GradingRuns.kt @@ -27,7 +27,6 @@ import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.duration import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -import org.sourcegrade.lab.hub.db.Users.clientDefault import org.sourcegrade.lab.hub.domain.GradingRun import java.util.UUID import kotlin.time.Duration diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RelationOption.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RelationOption.kt new file mode 100644 index 0000000..c02929c --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/RelationOption.kt @@ -0,0 +1,48 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.db + +import org.sourcegrade.lab.hub.domain.DomainEntity +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KClass +import kotlin.reflect.KProperty +import kotlin.reflect.KProperty0 + +internal sealed interface RelationOption : ReadOnlyProperty { + data class Loaded(val value: T) : RelationOption { + override fun getValue(thisRef: DomainEntity, property: KProperty<*>): T = value + } + + data object NotLoaded : RelationOption { + override fun getValue(thisRef: DomainEntity, property: KProperty<*>): Nothing { + throw RelationNotLoadedException(thisRef::class, property.name) + } + } + + companion object { + fun of(property: KProperty0, predicate: (String) -> Boolean): RelationOption = + if (predicate(property.name)) Loaded(property.get()) else NotLoaded + + context(Set) + fun of(property: KProperty0): RelationOption = of(property) { contains(property.name) } + } +} + +class RelationNotLoadedException internal constructor(entity: KClass, relation: String) : + RuntimeException("Relation $relation not loaded for entity ${entity.simpleName}") diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt index 019bfec..e031d6b 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Rubrics.kt @@ -28,7 +28,8 @@ import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -import org.sourcegrade.lab.hub.db.Users.clientDefault +import org.sourcegrade.lab.hub.db.assignment.Assignments +import org.sourcegrade.lab.hub.db.assignment.DBAssignment import org.sourcegrade.lab.hub.domain.Assignment import org.sourcegrade.lab.hub.domain.Criterion import org.sourcegrade.lab.hub.domain.Rubric diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt index 159bc64..960eb10 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt @@ -26,7 +26,6 @@ import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -import org.sourcegrade.lab.hub.db.Users.clientDefault import org.sourcegrade.lab.hub.domain.SubmissionGroupCategory import java.util.UUID diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt index 524e367..2e086ad 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupMemberships.kt @@ -25,6 +25,8 @@ import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.user.DBUser +import org.sourcegrade.lab.hub.db.user.Users import org.sourcegrade.lab.hub.domain.SubmissionGroup import org.sourcegrade.lab.hub.domain.UserMembership import java.util.UUID diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt index 5c32bfe..e60b10a 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroups.kt @@ -27,7 +27,7 @@ import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -import org.sourcegrade.lab.hub.db.Users.clientDefault +import org.sourcegrade.lab.hub.db.user.DBUser import org.sourcegrade.lab.hub.domain.SubmissionGroup import org.sourcegrade.lab.hub.domain.Term import java.util.UUID diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt index 354e07f..f35d9a5 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Submissions.kt @@ -28,7 +28,10 @@ import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.SortOrder import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -import org.sourcegrade.lab.hub.db.Users.clientDefault +import org.sourcegrade.lab.hub.db.assignment.Assignments +import org.sourcegrade.lab.hub.db.assignment.DBAssignment +import org.sourcegrade.lab.hub.db.user.DBUser +import org.sourcegrade.lab.hub.db.user.Users import org.sourcegrade.lab.hub.domain.Submission import java.util.UUID diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt index c9460c6..81bcaf6 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt @@ -18,24 +18,21 @@ package org.sourcegrade.lab.hub.db -import org.jetbrains.exposed.dao.Entity import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.sourcegrade.lab.hub.domain.DomainEntity -import org.sourcegrade.lab.hub.domain.Repository +import org.sourcegrade.lab.hub.domain.Relation +import org.sourcegrade.lab.hub.domain.repo.Repository import java.util.UUID -internal class UUIDEntityClassRepository(private val entityClass: EntityClass) : Repository - where E : Entity, E : DomainEntity { +internal class UUIDEntityClassRepository( + private val entityClass: EntityClass, + private val conversionContext: EntityConversionContext, +) : Repository, EntityConversionContext by conversionContext { - override suspend fun findById(id: UUID): E? = - newSuspendedTransaction { entityClass.findById(id) } - - override suspend fun exists(id: UUID): Boolean = - newSuspendedTransaction { entityClass.findById(id) != null } - - override suspend fun countAll(): Long = - newSuspendedTransaction { entityClass.all().count() } + override suspend fun findById(id: UUID, relations: List>): E? = + entityConversion(relations) { entityClass.findById(id).bindNullable() } override suspend fun deleteById(id: UUID): Boolean = newSuspendedTransaction { @@ -43,4 +40,10 @@ internal class UUIDEntityClassRepository(private val entityClass: EntityClass ?.let { it.delete(); true } ?: false } + + override suspend fun exists(id: UUID): Boolean = + newSuspendedTransaction { entityClass.findById(id) != null } + + override suspend fun countAll(): Long = + newSuspendedTransaction { entityClass.all().count() } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt deleted file mode 100644 index 1197cac..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Users.kt +++ /dev/null @@ -1,163 +0,0 @@ -/* - * Lab - SourceGrade.org - * Copyright (C) 2019-2024 Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.sourcegrade.lab.hub.db - -import com.expediagroup.graphql.generator.annotations.GraphQLIgnore -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -import org.apache.logging.log4j.Logger -import org.jetbrains.exposed.dao.EntityClass -import org.jetbrains.exposed.dao.UUIDEntity -import org.jetbrains.exposed.dao.id.EntityID -import org.jetbrains.exposed.dao.id.UUIDTable -import org.jetbrains.exposed.sql.Query -import org.jetbrains.exposed.sql.SizedIterable -import org.jetbrains.exposed.sql.and -import org.jetbrains.exposed.sql.innerJoin -import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -import org.jetbrains.exposed.sql.mapLazy -import org.jetbrains.exposed.sql.selectAll -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.domain.Assignment -import org.sourcegrade.lab.hub.domain.Course -import org.sourcegrade.lab.hub.domain.MutableRepository -import org.sourcegrade.lab.hub.domain.Repository -import org.sourcegrade.lab.hub.domain.Submission -import org.sourcegrade.lab.hub.domain.SubmissionGroup -import org.sourcegrade.lab.hub.domain.Term -import org.sourcegrade.lab.hub.domain.User -import org.sourcegrade.lab.hub.domain.UserMembership -import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository -import java.util.UUID - -internal object Users : UUIDTable("sgl_users") { - val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } - val email = varchar("email", 255).uniqueIndex() - val username = varchar("username", 255).uniqueIndex() - val displayname = varchar("displayname", 255) -} - -@GraphQLIgnore -internal class DBUser(id: EntityID) : UUIDEntity(id), User { - override val uuid: UUID = id.value - override val createdUtc: Instant by Users.createdUtc - override var email: String by Users.email - override var username: String by Users.username - override var displayname: String by Users.displayname - - private fun Query.termPredicate(term: Term.Matcher, now: Instant): Query = when (term) { - is Term.Matcher.All -> this - is Term.Matcher.Current -> where { (Terms.start lessEq now) and (Terms.end greaterEq now) } - is Term.Matcher.ByName -> where { Terms.name.eq(term.name) } - is Term.Matcher.ById -> where { Terms.id.eq(term.id) } - } - - private fun Query.membershipStatusPredicate(status: UserMembership.UserMembershipStatus, now: Instant): Query = when (status) { - UserMembership.UserMembershipStatus.ALL -> this - UserMembership.UserMembershipStatus.FUTURE -> where { CourseMemberships.startUtc greater now } - UserMembership.UserMembershipStatus.PAST -> where { CourseMemberships.endUtc less now } - UserMembership.UserMembershipStatus.CURRENT -> where { CourseMemberships.endUtc.isNull() } - } - - override suspend fun courseMemberships( - status: UserMembership.UserMembershipStatus, - term: Term.Matcher, - now: Instant, - ): SizedIterable> = newSuspendedTransaction { - CourseMemberships.innerJoin(Courses).innerJoin(Terms).selectAll() - .where { (CourseMemberships.userId eq uuid) } - .termPredicate(term, now) - .membershipStatusPredicate(status, now) - .mapLazy { DBCourseMembership.wrapRow(it) } - } - - override suspend fun submissionGroupMemberships( - status: UserMembership.UserMembershipStatus, - term: Term.Matcher, - now: Instant, - ): SizedIterable> = newSuspendedTransaction { - SubmissionGroupMemberships.innerJoin(SubmissionGroups).innerJoin(Terms).selectAll() - .where { (CourseMemberships.userId eq uuid) } - .termPredicate(term, now) - .membershipStatusPredicate(status, now) - .mapLazy { DBSubmissionGroupMembership.wrapRow(it) } - } - - override suspend fun assignments( - term: Term.Matcher, - now: Instant, - ): SizedIterable = newSuspendedTransaction { - Assignments.innerJoin(Courses).innerJoin(Terms).innerJoin(CourseMemberships).selectAll() - .where { CourseMemberships.userId eq uuid } - .termPredicate(term, now) - .membershipStatusPredicate(UserMembership.UserMembershipStatus.CURRENT, now) - .mapLazy { DBAssignment.wrapRow(it) } - } - - override suspend fun submissions( - status: Submission.SubmissionStatus, // TODO: Use parameter - term: Term.Matcher, - now: Instant, - ): SizedIterable = newSuspendedTransaction { - Submissions.innerJoin(SubmissionGroupMemberships) { SubmissionGroupMemberships.userId eq uuid }.selectAll() - .termPredicate(term, now) - .mapLazy { DBSubmission.wrapRow(it) } - } - - companion object : EntityClass(Users) -} - -internal class DBUserRepository( - private val logger: Logger, -) : MutableUserRepository, Repository by UUIDEntityClassRepository(DBUser) { - override suspend fun findByUsername(username: String): User? = newSuspendedTransaction { - DBUser.find { Users.username eq username }.firstOrNull() - } - - override suspend fun findAllByUsername(partialUsername: String): SizedIterable = newSuspendedTransaction { - DBUser.find { Users.username like "%$partialUsername%" }.mapLazy { it.toSnapshot() } - } - - override suspend fun findByEmail(email: String): User? = newSuspendedTransaction { - DBUser.find { Users.email eq email }.firstOrNull() - } - - override suspend fun create(item: User.CreateDto): User = newSuspendedTransaction { - DBUser.new { - username = item.username - displayname = item.displayname - email = item.email - }.also { - logger.info("Created new user ${it.uuid} with data $item") - } - } - - override suspend fun put(item: User.CreateDto): MutableRepository.PutResult = newSuspendedTransaction { - val existingUser = findByUsername(item.username) - if (existingUser == null) { - MutableRepository.PutResult(create(item), created = true) - } else { - logger.info( - "Loaded existing user ${existingUser.username} (${existingUser.uuid}) with" + - "display name ${existingUser.displayname} and email ${existingUser.email}", - ) - MutableRepository.PutResult(existingUser, created = false) - } - } -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/AssignmentOps.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/AssignmentOps.kt new file mode 100644 index 0000000..93614a3 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/AssignmentOps.kt @@ -0,0 +1,26 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.db.assignment + +import org.sourcegrade.lab.hub.domain.Assignment +import java.util.UUID + +suspend fun Assignment.setSubmissionGroupCategoryId(id: UUID): Boolean { + TODO("Not yet implemented") +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/AssignmentSnapshot.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/AssignmentSnapshot.kt new file mode 100644 index 0000000..27bba6a --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/AssignmentSnapshot.kt @@ -0,0 +1,58 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.db.assignment + +import kotlinx.datetime.Instant +import org.sourcegrade.lab.hub.db.RelationOption +import org.sourcegrade.lab.hub.domain.Assignment +import org.sourcegrade.lab.hub.domain.Course +import org.sourcegrade.lab.hub.domain.SubmissionGroupCategory +import java.util.UUID + +internal class AssignmentSnapshot( + uuidOption: RelationOption, + createdUtcOption: RelationOption, + courseOption: RelationOption, + submissionGroupCategoryOption: RelationOption, + nameOption: RelationOption, + descriptionOption: RelationOption, + submissionDeadlineUtcOption: RelationOption, +) : Assignment { + override val uuid: UUID by uuidOption + override val createdUtc: Instant by createdUtcOption + override val course: Course by courseOption + override val submissionGroupCategory: SubmissionGroupCategory by submissionGroupCategoryOption + override val name: String by nameOption + override val description: String by descriptionOption + override val submissionDeadlineUtc: Instant by submissionDeadlineUtcOption + + companion object { + fun of(assignment: Assignment, relations: Set): AssignmentSnapshot = with(relations) { + AssignmentSnapshot( + RelationOption.of(assignment::uuid), + RelationOption.of(assignment::createdUtc), + RelationOption.of(assignment::course), + RelationOption.of(assignment::submissionGroupCategory), + RelationOption.of(assignment::name), + RelationOption.of(assignment::description), + RelationOption.of(assignment::submissionDeadlineUtc), + ) + } + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt similarity index 62% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt index 1cc6bc1..e24ae47 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Assignments.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt @@ -16,11 +16,12 @@ * along with this program. If not, see . */ -package org.sourcegrade.lab.hub.db +package org.sourcegrade.lab.hub.db.assignment import com.expediagroup.graphql.generator.annotations.GraphQLIgnore import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import org.apache.logging.log4j.Logger import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID @@ -28,11 +29,23 @@ import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.db.Users.clientDefault +import org.sourcegrade.lab.hub.db.ConversionBody +import org.sourcegrade.lab.hub.db.Courses +import org.sourcegrade.lab.hub.db.DBCourse +import org.sourcegrade.lab.hub.db.DBSubmissionGroupCategory +import org.sourcegrade.lab.hub.db.EntityConversionContext +import org.sourcegrade.lab.hub.db.EntityConversionContextImpl +import org.sourcegrade.lab.hub.db.SubmissionGroupCategories +import org.sourcegrade.lab.hub.db.UUIDEntityClassRepository +import org.sourcegrade.lab.hub.db.findByIdNotNull import org.sourcegrade.lab.hub.domain.Assignment -import org.sourcegrade.lab.hub.domain.MutableRepository -import org.sourcegrade.lab.hub.domain.Repository +import org.sourcegrade.lab.hub.domain.AssignmentCollection +import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.MutableAssignment +import org.sourcegrade.lab.hub.domain.SizedIterableCollection import org.sourcegrade.lab.hub.domain.repo.MutableAssignmentRepository +import org.sourcegrade.lab.hub.domain.repo.MutableRepository +import org.sourcegrade.lab.hub.domain.repo.Repository import java.util.UUID internal object Assignments : UUIDTable("sgl_assignments") { @@ -46,7 +59,7 @@ internal object Assignments : UUIDTable("sgl_assignments") { } @GraphQLIgnore -internal class DBAssignment(id: EntityID) : UUIDEntity(id), Assignment { +internal class DBAssignment(id: EntityID) : UUIDEntity(id), MutableAssignment { override val uuid: UUID = id.value override val createdUtc: Instant by Assignments.createdUtc @@ -57,16 +70,23 @@ internal class DBAssignment(id: EntityID) : UUIDEntity(id), Assignment { override var description: String by Assignments.description override var submissionDeadlineUtc: Instant by Assignments.submissionDeadline - override suspend fun setSubmissionGroupCategoryId(id: UUID): Boolean = ::course.mutateReference(id) +// override suspend fun setSubmissionGroupCategoryId(id: UUID): Boolean = ::course.mutateReference(id) companion object : EntityClass(Assignments) } -internal class DBAssignmentRepository : MutableAssignmentRepository, Repository by UUIDEntityClassRepository(DBAssignment) { +private val conversionContext = EntityConversionContextImpl(AssignmentSnapshot::of) + +internal class DBAssignmentRepository( + private val logger: Logger, +) : MutableAssignmentRepository, Repository by UUIDEntityClassRepository(DBAssignment, conversionContext), + EntityConversionContext by conversionContext { override suspend fun findByCourse(courseId: UUID): SizedIterable = newSuspendedTransaction { DBAssignment.find { Assignments.courseId eq courseId } } + override suspend fun findAll(): AssignmentCollection = DBAssignmentCollection { DBAssignment.all().bindIterable() } + override suspend fun create(item: Assignment.CreateDto): Assignment = newSuspendedTransaction { DBAssignment.new { course = DBCourse.findByIdNotNull(item.courseId) @@ -81,3 +101,11 @@ internal class DBAssignmentRepository : MutableAssignmentRepository, Repository< TODO("Not yet implemented") } } + +internal class DBAssignmentCollection( + private val limit: Pair? = null, + private val orders: List = emptyList(), + private val body: ConversionBody>, +) : AssignmentCollection, + DomainEntityCollection + by SizedIterableCollection(Assignments, conversionContext, ::DBAssignmentCollection, limit, orders, body) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserOps.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserOps.kt new file mode 100644 index 0000000..e67290c --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserOps.kt @@ -0,0 +1,106 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.db.user + +import kotlinx.datetime.Instant +import org.jetbrains.exposed.sql.Query +import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.SortOrder +import org.jetbrains.exposed.sql.innerJoin +import org.jetbrains.exposed.sql.mapLazy +import org.jetbrains.exposed.sql.selectAll +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.sourcegrade.lab.hub.db.assignment.Assignments +import org.sourcegrade.lab.hub.db.CourseMemberships +import org.sourcegrade.lab.hub.db.Courses +import org.sourcegrade.lab.hub.db.assignment.DBAssignment +import org.sourcegrade.lab.hub.db.DBCourseMembership +import org.sourcegrade.lab.hub.db.DBSubmission +import org.sourcegrade.lab.hub.db.DBSubmissionGroupMembership +import org.sourcegrade.lab.hub.db.SubmissionGroupMemberships +import org.sourcegrade.lab.hub.db.SubmissionGroups +import org.sourcegrade.lab.hub.db.Submissions +import org.sourcegrade.lab.hub.db.Terms +import org.sourcegrade.lab.hub.domain.Assignment +import org.sourcegrade.lab.hub.domain.Course +import org.sourcegrade.lab.hub.domain.Submission +import org.sourcegrade.lab.hub.domain.SubmissionGroup +import org.sourcegrade.lab.hub.domain.Term +import org.sourcegrade.lab.hub.domain.User +import org.sourcegrade.lab.hub.domain.UserMembership +import org.sourcegrade.lab.hub.domain.termPredicate + +private fun Query.membershipStatusPredicate(status: UserMembership.UserMembershipStatus, now: Instant): Query = when (status) { + UserMembership.UserMembershipStatus.ALL -> this + UserMembership.UserMembershipStatus.FUTURE -> where { CourseMemberships.startUtc greater now } + UserMembership.UserMembershipStatus.PAST -> where { CourseMemberships.endUtc less now } + UserMembership.UserMembershipStatus.CURRENT -> where { CourseMemberships.endUtc.isNull() } +} + +suspend fun User.assignments( + term: Term.Matcher, + now: Instant, +): SizedIterable = newSuspendedTransaction { + Assignments.innerJoin(Courses).innerJoin(Terms).innerJoin(CourseMemberships).selectAll() + .where { CourseMemberships.userId eq uuid } + .termPredicate(term, now) + .membershipStatusPredicate(UserMembership.UserMembershipStatus.CURRENT, now) + .mapLazy { DBAssignment.wrapRow(it) } + .orderBy(Assignments.submissionDeadline to SortOrder.DESC) +} + +suspend fun User.courseMemberships( + status: UserMembership.UserMembershipStatus, + term: Term.Matcher, + now: Instant, +): SizedIterable> = newSuspendedTransaction { + CourseMemberships.innerJoin(Courses).innerJoin(Terms).selectAll() + .where { (CourseMemberships.userId eq uuid) } + .termPredicate(term, now) + .membershipStatusPredicate(status, now) + .mapLazy { DBCourseMembership.wrapRow(it) } +} + +suspend fun User.submissionGroupMemberships( + status: UserMembership.UserMembershipStatus, + term: Term.Matcher, + now: Instant, +): SizedIterable> = newSuspendedTransaction { + SubmissionGroupMemberships.innerJoin(SubmissionGroups).innerJoin(Terms).selectAll() + .where { (CourseMemberships.userId eq uuid) } + .termPredicate(term, now) + .membershipStatusPredicate(status, now) + .mapLazy { DBSubmissionGroupMembership.wrapRow(it) } +} + +suspend fun User.submissions( + status: Submission.SubmissionStatus, // TODO: Use parameter + term: Term.Matcher, + now: Instant, +): SizedIterable = newSuspendedTransaction { + Submissions.innerJoin(SubmissionGroupMemberships) { SubmissionGroupMemberships.userId eq uuid }.selectAll() + .termPredicate(term, now) + .mapLazy { DBSubmission.wrapRow(it) } +} + +// suspend fun assignmentParticipations( +// status: AssignmentParticipation.Status = AssignmentParticipation.Status.OPEN, +// term: Term.Matcher = Term.Matcher.Current, +// now: Instant = Clock.System.now(), +// ): SizedIterable diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserSnapshot.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserSnapshot.kt new file mode 100644 index 0000000..ba25f5e --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserSnapshot.kt @@ -0,0 +1,76 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.db.user + +import kotlinx.datetime.Instant +import org.sourcegrade.lab.hub.db.RelationOption +import org.sourcegrade.lab.hub.domain.User +import java.util.UUID + +internal class UserSnapshot( + uuidOption: RelationOption, + createdUtcOption: RelationOption, + emailOption: RelationOption, + usernameOption: RelationOption, + displaynameOption: RelationOption, +) : User { + + override val uuid: UUID by uuidOption + override val createdUtc: Instant by createdUtcOption + override val email: String by emailOption + override val username: String by usernameOption + override val displayname: String by displaynameOption + + // TODO: Split into UserActions + // TODO: Get from UserMembershipRepository +// suspend fun assignments( +// term: String = Term.Matcher.Current.toString(), +// now: Instant = Clock.System.now(), +// ): AssignmentCollection = TODO() +// +// suspend fun courseMemberships( +// status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, +// term: String = Term.Matcher.Current.toString(), +// now: Instant = Clock.System.now(), +// ): List> = TODO() +// +// suspend fun submissionGroupMemberships( +// status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, +// term: String = Term.Matcher.Current.toString(), +// now: Instant = Clock.System.now(), +// ): List> = TODO() +// +// suspend fun submissions( +// status: Submission.SubmissionStatus = Submission.SubmissionStatus.ALL, +// term: String = Term.Matcher.Current.toString(), +// now: Instant = Clock.System.now(), +// ): SizedIterable = submissions(status, Term.Matcher.fromString(term), now) + + companion object { + fun of(user: User, relations: Set): UserSnapshot = with(relations) { + UserSnapshot( + RelationOption.of(user::uuid), + RelationOption.of(user::createdUtc), + RelationOption.of(user::email), + RelationOption.of(user::username), + RelationOption.of(user::displayname), + ) + } + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt new file mode 100644 index 0000000..14234a6 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt @@ -0,0 +1,119 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.db.user + +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import org.apache.logging.log4j.Logger +import org.jetbrains.exposed.dao.EntityClass +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.dao.id.EntityID +import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.sourcegrade.lab.hub.db.ConversionBody +import org.sourcegrade.lab.hub.db.EntityConversionContext +import org.sourcegrade.lab.hub.db.EntityConversionContextImpl +import org.sourcegrade.lab.hub.db.UUIDEntityClassRepository +import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.MutableUser +import org.sourcegrade.lab.hub.domain.Relation +import org.sourcegrade.lab.hub.domain.SizedIterableCollection +import org.sourcegrade.lab.hub.domain.User +import org.sourcegrade.lab.hub.domain.UserCollection +import org.sourcegrade.lab.hub.domain.repo.MutableRepository +import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository +import org.sourcegrade.lab.hub.domain.repo.Repository +import java.util.UUID + +internal object Users : UUIDTable("sgl_users") { + val createdUtc = timestamp("createdUtc").clientDefault { Clock.System.now() } + val email = varchar("email", 255).uniqueIndex() + val username = varchar("username", 255).uniqueIndex() + val displayname = varchar("displayname", 255) +} + +@GraphQLIgnore +internal class DBUser(id: EntityID) : UUIDEntity(id), MutableUser { + override val uuid: UUID = id.value + override val createdUtc: Instant by Users.createdUtc + override var email: String by Users.email + override var username: String by Users.username + override var displayname: String by Users.displayname + + companion object : EntityClass(Users) +} + +private val conversionContext = EntityConversionContextImpl(UserSnapshot::of) + +internal class DBUserRepository( + private val logger: Logger, +) : MutableUserRepository, Repository by UUIDEntityClassRepository(DBUser, conversionContext), + EntityConversionContext by conversionContext { + + override suspend fun findByUsername(username: String, relations: List>): User? = + entityConversion(relations) { + DBUser.find { Users.username eq username }.firstOrNull().bindNullable() + } + + override suspend fun findAllByUsername(partialUsername: String, relations: List>): SizedIterable = + entityConversion(relations) { + DBUser.find { Users.username like "%$partialUsername%" }.bindIterable() + } + + override suspend fun findByEmail(email: String, relations: List>): User? = + entityConversion(relations) { + DBUser.find { Users.email eq email }.firstOrNull().bindNullable() + } + + override suspend fun findAll(): UserCollection = DBUserCollection { DBUser.all().bindIterable() } + + override suspend fun create(item: User.CreateDto): User = entityConversion(emptyList()) { + DBUser.new { + username = item.username + displayname = item.displayname + email = item.email + }.also { + logger.info("Created new user ${it.uuid} with data $item") + }.bind() + } + + override suspend fun put(item: User.CreateDto): MutableRepository.PutResult = newSuspendedTransaction { + val existingUser = findByUsername(item.username) + if (existingUser == null) { + MutableRepository.PutResult(create(item), created = true) + } else { + logger.info( + "Loaded existing user ${existingUser.username} (${existingUser.uuid}) with" + + "display name ${existingUser.displayname} and email ${existingUser.email}", + ) + MutableRepository.PutResult(existingUser, created = false) + } + } +} + +internal class DBUserCollection( + private val limit: Pair? = null, + private val orders: List = emptyList(), + private val body: ConversionBody>, +) : UserCollection, + DomainEntityCollection + by SizedIterableCollection(Users, conversionContext, ::DBUserCollection, limit, orders, body) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt index aa8f707..64ee65f 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -18,18 +18,18 @@ package org.sourcegrade.lab.hub.domain +import graphql.schema.DataFetchingEnvironment import kotlinx.datetime.Instant +import org.sourcegrade.lab.hub.graphql.extractRelations import java.util.UUID interface Assignment : DomainEntity { val course: Course val submissionGroupCategory: SubmissionGroupCategory - var name: String - var description: String - var submissionDeadlineUtc: Instant - - suspend fun setSubmissionGroupCategoryId(id: UUID): Boolean + val name: String + val description: String + val submissionDeadlineUtc: Instant data class CreateDto( val courseId: UUID, @@ -39,3 +39,18 @@ interface Assignment : DomainEntity { val submissionDeadlineUtc: Instant, ) : Creates } + +interface MutableAssignment : Assignment { + override var name: String + override var description: String + override var submissionDeadlineUtc: Instant +} + +interface AssignmentCollection : DomainEntityCollection { + override fun limit(num: Int, offset: Long): AssignmentCollection + override fun orderBy(orders: List): AssignmentCollection + override suspend fun count(): Long + override suspend fun empty(): Boolean + + suspend fun list(dfe: DataFetchingEnvironment): List = list(dfe.extractRelations()) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt index 420a890..b1e376c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt @@ -20,6 +20,7 @@ package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant import java.util.UUID +import kotlin.reflect.KProperty1 interface DomainEntity { val uuid: UUID @@ -31,3 +32,5 @@ interface Creates interface IdempotentCreates : Creates { val uuid: UUID } + +typealias Relation = KProperty1 diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt new file mode 100644 index 0000000..b038c19 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt @@ -0,0 +1,90 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.domain + +import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import org.jetbrains.exposed.dao.UUIDEntity +import org.jetbrains.exposed.sql.Expression +import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.Table +import org.sourcegrade.lab.hub.db.ConversionBody +import org.sourcegrade.lab.hub.db.EntityConversionContext +import org.jetbrains.exposed.sql.SortOrder as ExposedSortOrder + +@GraphQLIgnore +interface DomainEntityCollection> { + + fun limit(num: Int, offset: Long = 0): C + + fun page(page: Int, pageSize: Int): C = limit(pageSize, (page - 1) * pageSize.toLong()) + + fun orderBy(orders: List): C + + @GraphQLIgnore + suspend fun list(relations: List> = emptyList()): List + + suspend fun count(): Long + + suspend fun empty(): Boolean + + data class FieldOrdering(val field: String, val sortOrder: SortOrder = SortOrder.DESC) + + enum class SortOrder(val exposed: ExposedSortOrder) { + ASC(ExposedSortOrder.ASC), + DESC(ExposedSortOrder.DESC), + ASC_NULLS_FIRST(ExposedSortOrder.ASC_NULLS_FIRST), + DESC_NULLS_FIRST(ExposedSortOrder.DESC_NULLS_FIRST), + ASC_NULLS_LAST(ExposedSortOrder.ASC_NULLS_LAST), + DESC_NULLS_LAST(ExposedSortOrder.DESC_NULLS_LAST) + } +} + +internal class SizedIterableCollection>( + private val table: Table, + private val conversionContext: EntityConversionContext, + private val ctor: (Pair?, List, ConversionBody>) -> C, + private val limit: Pair?, + private val orders: List, + private val body: ConversionBody>, +) : DomainEntityCollection, EntityConversionContext by conversionContext { + override fun limit(num: Int, offset: Long): C = ctor(limit?.let { (n, o) -> minOf(num, n) to offset + o }, orders, body) + + // Note that this is not *technically* correct because nested orderings are not supported + // But its close enough, because it would only be noticeable in an order -> limit -> order scenario + override fun orderBy(orders: List): C = ctor(limit, orders, body) + + override suspend fun list(relations: List>): List = + entityConversion(relations, body) + .let { if (limit != null) it.limit(limit.first, limit.second) else it } + .let { if (orders.isNotEmpty()) it.orderBy(table, orders) else it } + .toList() + + override suspend fun count(): Long = entityConversion { body().result.count().bindT() } + + override suspend fun empty(): Boolean = entityConversion { body().result.empty().bindT() } +} + +private fun SizedIterable.orderBy( + table: Table, + orders: List, +): SizedIterable = orderBy( + *orders.map { order -> + table::class.members.find { it.name == order.field }?.call(table) as Expression<*> to order.sortOrder.exposed + }.toTypedArray(), +) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt index b597502..6addf94 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt @@ -19,6 +19,12 @@ package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant +import org.jetbrains.exposed.sql.Query +import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq +import org.jetbrains.exposed.sql.SqlExpressionBuilder.greaterEq +import org.jetbrains.exposed.sql.SqlExpressionBuilder.lessEq +import org.jetbrains.exposed.sql.and +import org.sourcegrade.lab.hub.db.Terms import java.util.UUID interface Term : DomainEntity { @@ -33,9 +39,13 @@ interface Term : DomainEntity { init { require(name.isNotBlank()) } + + override fun toString(): String = "ByName($name)" } - data class ById(val id: UUID) : Matcher + data class ById(val id: UUID) : Matcher { + override fun toString(): String = "ById($id)" + } companion object { fun fromString(value: String): Matcher = when (value) { @@ -53,7 +63,15 @@ interface Term : DomainEntity { } ?: throw IllegalArgumentException("Invalid matcher: $value") } } + private val regex = "(?[a-zA-Z])\\((?.+\\))".toRegex() } } } + +internal fun Query.termPredicate(term: Term.Matcher, now: Instant): Query = when (term) { + is Term.Matcher.All -> this + is Term.Matcher.Current -> where { (Terms.start lessEq now) and (Terms.end greaterEq now) } + is Term.Matcher.ByName -> where { Terms.name.eq(term.name) } + is Term.Matcher.ById -> where { Terms.id.eq(term.id) } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index 5d53733..c551c7c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -18,87 +18,33 @@ package org.sourcegrade.lab.hub.domain -import com.expediagroup.graphql.generator.annotations.GraphQLIgnore -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -import org.jetbrains.exposed.sql.SizedIterable -import java.util.UUID +import graphql.schema.DataFetchingEnvironment +import org.sourcegrade.lab.hub.graphql.extractRelations interface User : DomainEntity { - var email: String - var username: String - var displayname: String - - // TODO: Split into UserActions - // TODO: Get from UserMembershipRepository - @GraphQLIgnore - suspend fun courseMemberships( - status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, - term: Term.Matcher = Term.Matcher.Current, - now: Instant = Clock.System.now(), - ): SizedIterable> - - suspend fun courseMemberships( - status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, - term: String = Term.Matcher.Current.toString(), - now: Instant = Clock.System.now(), - ): SizedIterable> = courseMemberships(status, Term.Matcher.fromString(term), now) - - @GraphQLIgnore - suspend fun submissionGroupMemberships( - status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, - term: Term.Matcher = Term.Matcher.Current, - now: Instant = Clock.System.now(), - ): SizedIterable> - - suspend fun submissionGroupMemberships( - status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, - term: String = Term.Matcher.Current.toString(), - now: Instant = Clock.System.now(), - ): SizedIterable> = submissionGroupMemberships(status, Term.Matcher.fromString(term), now) - -// suspend fun assignmentParticipations( -// status: AssignmentParticipation.Status = AssignmentParticipation.Status.OPEN, -// term: Term.Matcher = Term.Matcher.Current, -// now: Instant = Clock.System.now(), -// ): SizedIterable - - @GraphQLIgnore - suspend fun assignments( - term: Term.Matcher = Term.Matcher.Current, - now: Instant = Clock.System.now(), - ): SizedIterable - - suspend fun assignments( - term: String = Term.Matcher.Current.toString(), - now: Instant = Clock.System.now(), - ): SizedIterable = assignments(Term.Matcher.fromString(term), now) - - @GraphQLIgnore - suspend fun submissions( - status: Submission.SubmissionStatus = Submission.SubmissionStatus.ALL, - term: Term.Matcher = Term.Matcher.Current, - now: Instant = Clock.System.now(), - ): SizedIterable - - suspend fun submissions( - status: Submission.SubmissionStatus = Submission.SubmissionStatus.ALL, - term: String = Term.Matcher.Current.toString(), - now: Instant = Clock.System.now(), - ): SizedIterable = submissions(status, Term.Matcher.fromString(term), now) + val email: String + val username: String + val displayname: String data class CreateDto( val email: String, val username: String, val displayname: String = username, ) : Creates +} - data class Snapshot( - val uuid: UUID, - val email: String, - val username: String, - val displayname: String, - ) +interface MutableUser : User { + override var email: String + override var username: String + override var displayname: String +} + +interface UserCollection : DomainEntityCollection { + override fun limit(num: Int, offset: Long): UserCollection + override fun page(page: Int, pageSize: Int): UserCollection + override fun orderBy(orders: List): UserCollection + override suspend fun count(): Long + override suspend fun empty(): Boolean - fun toSnapshot(): Snapshot = Snapshot(uuid, email, username, displayname) + suspend fun list(dfe: DataFetchingEnvironment): List = list(dfe.extractRelations()) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt index 969f2d1..b8dd082 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt @@ -20,11 +20,11 @@ package org.sourcegrade.lab.hub.domain.repo import org.jetbrains.exposed.sql.SizedIterable import org.sourcegrade.lab.hub.domain.Assignment -import org.sourcegrade.lab.hub.domain.MutableRepository -import org.sourcegrade.lab.hub.domain.Repository +import org.sourcegrade.lab.hub.domain.AssignmentCollection import java.util.UUID +import kotlin.reflect.KProperty1 -interface AssignmentRepository : Repository { +interface AssignmentRepository : CollectionRepository { suspend fun findByCourse(courseId: UUID): SizedIterable } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt new file mode 100644 index 0000000..a39401d --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt @@ -0,0 +1,26 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.domain.repo + +import org.sourcegrade.lab.hub.domain.DomainEntity +import org.sourcegrade.lab.hub.domain.DomainEntityCollection + +interface CollectionRepository> : Repository { + suspend fun findAll(): C +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt similarity index 82% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt index 842f12f..56f6eb0 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt @@ -16,12 +16,14 @@ * along with this program. If not, see . */ -package org.sourcegrade.lab.hub.domain +package org.sourcegrade.lab.hub.domain.repo import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import org.sourcegrade.lab.hub.domain.Creates +import org.sourcegrade.lab.hub.domain.DomainEntity @GraphQLIgnore -interface MutableRepository> : Repository { +interface MutableRepository> : Repository { suspend fun create(item: C): E suspend fun put(item: C): PutResult diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt similarity index 77% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt index b638841..9623388 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt @@ -16,14 +16,14 @@ * along with this program. If not, see . */ -package org.sourcegrade.lab.hub.domain +package org.sourcegrade.lab.hub.domain.repo -import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import org.sourcegrade.lab.hub.domain.DomainEntity +import org.sourcegrade.lab.hub.domain.Relation import java.util.UUID -@GraphQLIgnore -interface Repository { - suspend fun findById(id: UUID): E? // TODO: Eager loading +interface Repository { + suspend fun findById(id: UUID, relations: List> = emptyList()): E? suspend fun deleteById(id: UUID): Boolean suspend fun exists(id: UUID): Boolean suspend fun countAll(): Long diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt index 68c1de1..e06c43a 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt @@ -19,8 +19,6 @@ package org.sourcegrade.lab.hub.domain.repo import org.jetbrains.exposed.sql.SizedIterable -import org.sourcegrade.lab.hub.domain.MutableRepository -import org.sourcegrade.lab.hub.domain.Repository import org.sourcegrade.lab.hub.domain.Submission import java.util.UUID diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt index e0a8eb7..e5b0a88 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt @@ -21,8 +21,6 @@ package org.sourcegrade.lab.hub.domain.repo import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.jetbrains.exposed.sql.SizedIterable -import org.sourcegrade.lab.hub.domain.MutableRepository -import org.sourcegrade.lab.hub.domain.Repository import org.sourcegrade.lab.hub.domain.Term import org.sourcegrade.lab.hub.domain.TermScoped import org.sourcegrade.lab.hub.domain.UserMembership diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt index 11cff43..c6a979a 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt @@ -19,16 +19,16 @@ package org.sourcegrade.lab.hub.domain.repo import org.jetbrains.exposed.sql.SizedIterable -import org.sourcegrade.lab.hub.domain.MutableRepository -import org.sourcegrade.lab.hub.domain.Repository +import org.sourcegrade.lab.hub.domain.Relation import org.sourcegrade.lab.hub.domain.User +import org.sourcegrade.lab.hub.domain.UserCollection -interface UserRepository : Repository { - suspend fun findByUsername(username: String): User? +interface UserRepository : CollectionRepository { + suspend fun findByUsername(username: String, relations: List> = emptyList()): User? - suspend fun findAllByUsername(partialUsername: String): SizedIterable + suspend fun findAllByUsername(partialUsername: String, relations: List> = emptyList()): SizedIterable - suspend fun findByEmail(email: String): User? + suspend fun findByEmail(email: String, relations: List> = emptyList()): User? } interface MutableUserRepository : UserRepository, MutableRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLRepository.kt deleted file mode 100644 index d7bc776..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLRepository.kt +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Lab - SourceGrade.org - * Copyright (C) 2019-2024 Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.sourcegrade.lab.hub.graphql - -import graphql.schema.DataFetchingEnvironment -import org.sourcegrade.lab.hub.domain.DomainEntity -import org.sourcegrade.lab.hub.domain.Repository -import java.util.UUID - -// This doesn't work -fun Repository.toGraphQL(): GraphQLRepository = GraphQLRepositoryImpl(this) - -interface GraphQLRepository { - suspend fun findById(dfe: DataFetchingEnvironment): E? - suspend fun deleteById(dfe: DataFetchingEnvironment): Boolean - suspend fun exists(dfe: DataFetchingEnvironment): Boolean - suspend fun countAll(): Long -} - -private class GraphQLRepositoryImpl(private val repository: Repository) : GraphQLRepository { - override suspend fun findById(dfe: DataFetchingEnvironment): E? = repository.findById(UUID.fromString(dfe.getArgument("id"))) - override suspend fun deleteById(dfe: DataFetchingEnvironment): Boolean = repository.deleteById(UUID.fromString(dfe.getArgument("id"))) - override suspend fun exists(dfe: DataFetchingEnvironment): Boolean = repository.exists(UUID.fromString(dfe.getArgument("id"))) - override suspend fun countAll(): Long = repository.countAll() -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RelationOps.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RelationOps.kt new file mode 100644 index 0000000..8e558c5 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RelationOps.kt @@ -0,0 +1,33 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.graphql + +import graphql.schema.DataFetchingEnvironment +import org.sourcegrade.lab.hub.domain.DomainEntity +import org.sourcegrade.lab.hub.domain.Relation +import kotlin.reflect.KProperty1 + +internal inline fun DataFetchingEnvironment.extractRelations(): List> = + selectionSet.immediateFields.map { field -> coerceRelation(field.name) } + +internal inline fun coerceRelation(relation: String): Relation = + E::class.members.find { it.name == relation }?.let { + @Suppress("UNCHECKED_CAST") + it as KProperty1 + } ?: error("No relation $relation found on ${E::class.simpleName}, available relations are: ${E::class.members.map { it.name }}") diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Scalars.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Scalars.kt index cb9292d..36a3c2c 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Scalars.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/Scalars.kt @@ -20,6 +20,7 @@ package org.sourcegrade.lab.hub.graphql import graphql.GraphQLContext import graphql.execution.CoercedVariables +import graphql.language.IntValue import graphql.language.StringValue import graphql.language.Value import graphql.schema.Coercing @@ -80,8 +81,15 @@ internal object Scalars { .name("Long") .coercing( object : Coercing { + override fun parseLiteral( + input: Value<*>, + variables: CoercedVariables, + graphQLContext: GraphQLContext, + locale: Locale, + ): Long = (input as IntValue).value.toLong() + override fun parseValue(input: Any, graphQLContext: GraphQLContext, locale: Locale): Long = - input.toString().toLong() + (input as Int).toLong() override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): String = dataFetcherResult.toString() @@ -105,7 +113,7 @@ internal object Scalars { .coercing( object : Coercing, List<*>> { override fun serialize(dataFetcherResult: Any, graphQLContext: GraphQLContext, locale: Locale): List<*> { - val result = transaction { (dataFetcherResult as SizedIterable<*>).toList() } + val result = transaction { (dataFetcherResult as SizedIterable<*>).toList() } println("SizedIterable serialized to $result") return result } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt index 2740d61..bd36e95 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt @@ -23,37 +23,55 @@ import com.expediagroup.graphql.server.operations.Mutation import com.expediagroup.graphql.server.operations.Query import graphql.schema.DataFetchingEnvironment import org.apache.logging.log4j.Logger -import org.sourcegrade.lab.hub.domain.User +import org.sourcegrade.lab.hub.domain.UserCollection import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository import org.sourcegrade.lab.hub.domain.repo.UserRepository -import java.util.UUID -class UserQueries(private val repository: UserRepository) : Query { - fun user(): UserQuery = UserQuery(repository) +class UserQueries( + private val logger: Logger, + private val repository: UserRepository, +) : Query { + fun user(): UserQuery = UserQuery(logger, repository) } @GraphQLDescription("Query user collection") -class UserQuery(private val repository: UserRepository) : UserRepository by repository { - override suspend fun findById(id: UUID): User? = repository.findById(id) - override suspend fun deleteById(id: UUID): Boolean = repository.deleteById(id) - override suspend fun exists(id: UUID): Boolean = repository.exists(id) - override suspend fun countAll(): Long = repository.countAll() +class UserQuery( + private val logger: Logger, + private val repository: UserRepository, +) { + + suspend fun findAll(dfe: DataFetchingEnvironment): UserCollection { + val relations = dfe.selectionSet.fields.map { it.name } +// logger.info("Relations: $relations") + + + dfe.selectionSet.immediateFields + return repository.findAll() + } + +// suspend fun findById(id: UUID): User? = repository.findById(id) + + // suspend fun deleteById(id: UUID): Boolean = repository.deleteById(id) +// suspend fun exists(id: UUID): Boolean = repository.exists(id) +// suspend fun countAll(): Long = repository.countAll() + fun hello1(): String = "Hello, World!" } class UserMutations( private val logger: Logger, private val repository: MutableUserRepository, ) : Mutation { - fun user(): UserMutation = UserMutation(logger, repository) + fun hello2(): String = "Hello, World!" +// fun user(): UserMutation = UserMutation(logger, repository) } -@GraphQLDescription("Mutation user collection") -class UserMutation( - private val logger: Logger, - private val repository: MutableUserRepository, -) { - suspend fun create(dfe: DataFetchingEnvironment, item: User.CreateDto): User.Snapshot { - logger.info("SelectionSet: ${dfe.selectionSet.fields.joinToString { it.name }}") - return repository.create(item).toSnapshot() - } -} +//@GraphQLDescription("Mutation user collection") +//class UserMutation( +// private val logger: Logger, +// private val repository: MutableUserRepository, +//) { +//// suspend fun create(dfe: DataFetchingEnvironment, item: User.CreateDto): User.Snapshot { +//// logger.info("SelectionSet: ${dfe.selectionSet.fields.joinToString { it.name }}") +//// return repository.create(item) +//// } +//} From 2209c61808886b486e3cf9c9fb9ef0efc92450e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Fri, 21 Jun 2024 14:05:22 +0200 Subject: [PATCH 22/29] SizedIterableCollection limit and list --- .../lab/hub/domain/DomainEntityCollection.kt | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt index b038c19..672c9c0 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt @@ -63,17 +63,21 @@ internal class SizedIterableCollection, private val body: ConversionBody>, ) : DomainEntityCollection, EntityConversionContext by conversionContext { - override fun limit(num: Int, offset: Long): C = ctor(limit?.let { (n, o) -> minOf(num, n) to offset + o }, orders, body) + override fun limit(num: Int, offset: Long): C = + ctor(limit?.let { (n, o) -> minOf(num, n) to offset + o } ?: (num to offset), orders, body) // Note that this is not *technically* correct because nested orderings are not supported // But its close enough, because it would only be noticeable in an order -> limit -> order scenario - override fun orderBy(orders: List): C = ctor(limit, orders, body) + override fun orderBy(orders: List): C = + ctor(limit, orders, body) override suspend fun list(relations: List>): List = - entityConversion(relations, body) - .let { if (limit != null) it.limit(limit.first, limit.second) else it } - .let { if (orders.isNotEmpty()) it.orderBy(table, orders) else it } - .toList() + entityConversion(relations) { + body().result + .let { if (limit != null) it.limit(limit.first, limit.second) else it } + .let { if (orders.isNotEmpty()) it.orderBy(table, orders) else it } + .toList().bindT() + } override suspend fun count(): Long = entityConversion { body().result.count().bindT() } From ba7e372d161a8e50d52f62a46600c204933c93e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Sat, 22 Jun 2024 17:00:03 +0200 Subject: [PATCH 23/29] Continue work --- .../kotlin/org/sourcegrade/lab/hub/Module.kt | 2 +- .../lab/hub/db/CourseMemberships.kt | 2 + .../lab/hub/db/EntityConversionContext.kt | 22 ++++---- .../lab/hub/db/SubmissionGroupCategories.kt | 2 + .../lab/hub/db/UUIDEntityClassRepository.kt | 6 +++ .../lab/hub/db/assignment/Assignments.kt | 4 +- .../lab/hub/db/course/CourseSnapshot.kt | 52 +++++++++++++++++++ .../lab/hub/db/{ => course}/Courses.kt | 18 ++++++- .../sourcegrade/lab/hub/db/user/UserOps.kt | 2 +- .../org/sourcegrade/lab/hub/db/user/Users.kt | 13 ++--- .../org/sourcegrade/lab/hub/domain/Course.kt | 29 ++++++++++- .../lab/hub/domain/DomainEntity.kt | 1 + .../org/sourcegrade/lab/hub/domain/User.kt | 3 +- .../lab/hub/domain/repo/CourseRepository.kt | 38 ++++++++++++++ .../lab/hub/domain/repo/UserRepository.kt | 4 +- .../hub/graphql/GraphQLExecutionContext.kt | 39 ++++++++++++++ .../org/sourcegrade/lab/hub/graphql/User.kt | 24 ++++----- 17 files changed, 223 insertions(+), 38 deletions(-) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/CourseSnapshot.kt rename hub/src/main/kotlin/org/sourcegrade/lab/hub/db/{ => course}/Courses.kt (77%) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CourseRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLExecutionContext.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt index c21e5bb..0167c90 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt @@ -45,7 +45,7 @@ import org.koin.ktor.ext.inject import org.koin.ktor.plugin.Koin import org.sourcegrade.lab.hub.db.assignment.Assignments import org.sourcegrade.lab.hub.db.CourseMemberships -import org.sourcegrade.lab.hub.db.Courses +import org.sourcegrade.lab.hub.db.course.Courses import org.sourcegrade.lab.hub.db.Criteria import org.sourcegrade.lab.hub.db.DBModule import org.sourcegrade.lab.hub.db.GradedCriteria diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt index 918b916..89ce8da 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/CourseMemberships.kt @@ -25,6 +25,8 @@ import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.course.Courses +import org.sourcegrade.lab.hub.db.course.DBCourse import org.sourcegrade.lab.hub.db.user.DBUser import org.sourcegrade.lab.hub.db.user.Users import org.sourcegrade.lab.hub.domain.Course diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt index e8751f1..5aa441d 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt @@ -27,16 +27,13 @@ import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransacti import org.sourcegrade.lab.hub.domain.DomainEntity import org.sourcegrade.lab.hub.domain.Relation -interface EntityConversionContext { +interface EntityConversionContext { fun createSnapshot(entity: N, relations: Set): E fun convertRelation(relation: Relation): Relation - suspend fun entityConversion( - relations: List> = emptyList(), - statement: ConversionBody, - ): T + suspend fun entityConversion(relations: List> = emptyList(), statement: ConversionBody): T } typealias ConversionBody = suspend EntityConversion.() -> EntityConversion.BindResult @@ -61,7 +58,10 @@ class EntityConversionContextImpl( } } -class EntityConversion( +@DslMarker +annotation class EntityConversionDsl + +class EntityConversion( private val context: EntityConversionContext, relations: List>, ) { @@ -70,15 +70,19 @@ class EntityConversion( private val relationArray: Array> = relations.map { context.convertRelation(it) }.toTypedArray() private fun N.createSnapshot(): E = context.createSnapshot(this, relationSet) - fun N.bind(): BindResult = - BindResult((if (relationArray.isNotEmpty()) load(*relationArray) else this).createSnapshot()) - + @EntityConversionDsl fun N?.bindNullable(): BindResult = this?.bind() ?: BindResult(null) + @EntityConversionDsl + fun N.bind(): BindResult = + BindResult((if (relationArray.isNotEmpty()) load(*relationArray) else this).createSnapshot()) + + @EntityConversionDsl fun SizedIterable.bindIterable(): BindResult> = BindResult((if (relationArray.isNotEmpty()) with(*relationArray) else this).mapLazy { it.createSnapshot() }) + @EntityConversionDsl fun T.bindT(): BindResult = BindResult(this) class BindResult(val result: T) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt index 960eb10..c57255d 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt @@ -26,6 +26,8 @@ import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.course.Courses +import org.sourcegrade.lab.hub.db.course.DBCourse import org.sourcegrade.lab.hub.domain.SubmissionGroupCategory import java.util.UUID diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt index 81bcaf6..76f3dbf 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt @@ -34,6 +34,12 @@ internal class UUIDEntityClassRepository( override suspend fun findById(id: UUID, relations: List>): E? = entityConversion(relations) { entityClass.findById(id).bindNullable() } + suspend fun findById2(id: UUID) { + entityConversion { + entityClass.findById(id).bindNullable() + } + } + override suspend fun deleteById(id: UUID): Boolean = newSuspendedTransaction { entityClass.findById(id) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt index e24ae47..2685062 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt @@ -30,8 +30,8 @@ import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.sourcegrade.lab.hub.db.ConversionBody -import org.sourcegrade.lab.hub.db.Courses -import org.sourcegrade.lab.hub.db.DBCourse +import org.sourcegrade.lab.hub.db.course.Courses +import org.sourcegrade.lab.hub.db.course.DBCourse import org.sourcegrade.lab.hub.db.DBSubmissionGroupCategory import org.sourcegrade.lab.hub.db.EntityConversionContext import org.sourcegrade.lab.hub.db.EntityConversionContextImpl diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/CourseSnapshot.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/CourseSnapshot.kt new file mode 100644 index 0000000..82fd1c7 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/CourseSnapshot.kt @@ -0,0 +1,52 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.db.course + +import kotlinx.datetime.Instant +import org.sourcegrade.lab.hub.db.RelationOption +import org.sourcegrade.lab.hub.domain.Term +import org.sourcegrade.lab.hub.domain.User +import java.util.UUID + +//internal class CourseSnapshot( +// uuidOption: RelationOption, +// createdUtcOption: RelationOption, +// termOption: RelationOption, +// submissionGroupCategoriesOption: RelationOption, +// displaynameOption: RelationOption, +//) : User { +// +// override val uuid: UUID by uuidOption +// override val createdUtc: Instant by createdUtcOption +// override val email: String by emailOption +// override val username: String by usernameOption +// override val displayname: String by displaynameOption +// +// companion object { +// fun of(user: User, relations: Set): CourseSnapshot = with(relations) { +// CourseSnapshot( +// RelationOption.of(user::uuid), +// RelationOption.of(user::createdUtc), +// RelationOption.of(user::email), +// RelationOption.of(user::username), +// RelationOption.of(user::displayname), +// ) +// } +// } +//} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/Courses.kt similarity index 77% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/Courses.kt index 40ed716..4bc98b7 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/Courses.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/Courses.kt @@ -16,17 +16,24 @@ * along with this program. If not, see . */ -package org.sourcegrade.lab.hub.db +package org.sourcegrade.lab.hub.db.course import com.expediagroup.graphql.generator.annotations.GraphQLIgnore import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import org.apache.logging.log4j.Logger import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.DBSubmissionGroupCategory +import org.sourcegrade.lab.hub.db.DBTerm +import org.sourcegrade.lab.hub.db.EntityConversionContextImpl +import org.sourcegrade.lab.hub.db.SubmissionGroupCategories +import org.sourcegrade.lab.hub.db.Terms +import org.sourcegrade.lab.hub.db.UUIDEntityClassRepository import org.sourcegrade.lab.hub.db.assignment.Assignments import org.sourcegrade.lab.hub.db.assignment.DBAssignment import org.sourcegrade.lab.hub.db.user.DBUser @@ -34,6 +41,8 @@ import org.sourcegrade.lab.hub.db.user.Users import org.sourcegrade.lab.hub.domain.Assignment import org.sourcegrade.lab.hub.domain.Course import org.sourcegrade.lab.hub.domain.Term +import org.sourcegrade.lab.hub.domain.repo.MutableCourseRepository +import org.sourcegrade.lab.hub.domain.repo.Repository import java.util.UUID internal object Courses : UUIDTable("sgl_courses") { @@ -58,3 +67,10 @@ internal class DBCourse(id: EntityID) : UUIDEntity(id), Course { companion object : EntityClass(Courses) } + + +//private val conversionContext = EntityConversionContextImpl() + +//internal class DBCourseRepository( +// private val logger: Logger, +//) : MutableCourseRepository, Repository by UUIDEntityClassRepository(DBAssignment) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserOps.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserOps.kt index e67290c..a0b9223 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserOps.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserOps.kt @@ -28,7 +28,7 @@ import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.sourcegrade.lab.hub.db.assignment.Assignments import org.sourcegrade.lab.hub.db.CourseMemberships -import org.sourcegrade.lab.hub.db.Courses +import org.sourcegrade.lab.hub.db.course.Courses import org.sourcegrade.lab.hub.db.assignment.DBAssignment import org.sourcegrade.lab.hub.db.DBCourseMembership import org.sourcegrade.lab.hub.db.DBSubmission diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt index 14234a6..46ce3d8 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt @@ -19,6 +19,7 @@ package org.sourcegrade.lab.hub.db.user import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import com.expediagroup.graphql.generator.execution.OptionalInput import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.apache.logging.log4j.Logger @@ -74,10 +75,8 @@ internal class DBUserRepository( DBUser.find { Users.username eq username }.firstOrNull().bindNullable() } - override suspend fun findAllByUsername(partialUsername: String, relations: List>): SizedIterable = - entityConversion(relations) { - DBUser.find { Users.username like "%$partialUsername%" }.bindIterable() - } + override suspend fun findAllByUsername(partialUsername: String): UserCollection = + DBUserCollection { DBUser.find { Users.username like "%$partialUsername%" }.bindIterable() } override suspend fun findByEmail(email: String, relations: List>): User? = entityConversion(relations) { @@ -88,9 +87,11 @@ internal class DBUserRepository( override suspend fun create(item: User.CreateDto): User = entityConversion(emptyList()) { DBUser.new { - username = item.username - displayname = item.displayname email = item.email + username = item.username + if (item.displayname is OptionalInput.Defined) { + displayname = requireNotNull(item.displayname.value) { "displayname" } + } }.also { logger.info("Created new user ${it.uuid} with data $item") }.bind() diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt index 116116f..221853d 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt @@ -18,13 +18,38 @@ package org.sourcegrade.lab.hub.domain +import com.expediagroup.graphql.generator.execution.OptionalInput +import graphql.schema.DataFetchingEnvironment import org.jetbrains.exposed.sql.SizedIterable +import org.sourcegrade.lab.hub.graphql.extractRelations +import java.util.UUID interface Course : TermScoped { val submissionGroupCategories: SizedIterable val assignments: SizedIterable val owner: User - var name: String - var description: String + val name: String + val description: String + + data class CreateDto( + val ownerUUID: UUID, + val name: String, + val description: OptionalInput, + ) : Creates +} + +interface MutableCourse : Course { + override var name: String + override var description: String +} + +interface CourseCollection : DomainEntityCollection { + override fun limit(num: Int, offset: Long): CourseCollection + override fun page(page: Int, pageSize: Int): CourseCollection + override fun orderBy(orders: List): CourseCollection + override suspend fun count(): Long + override suspend fun empty(): Boolean + + suspend fun list(dfe: DataFetchingEnvironment): List = list(dfe.extractRelations()) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt index b1e376c..357a5c9 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntity.kt @@ -20,6 +20,7 @@ package org.sourcegrade.lab.hub.domain import kotlinx.datetime.Instant import java.util.UUID +import kotlin.reflect.KProperty0 import kotlin.reflect.KProperty1 interface DomainEntity { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index c551c7c..4ec9165 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -18,6 +18,7 @@ package org.sourcegrade.lab.hub.domain +import com.expediagroup.graphql.generator.execution.OptionalInput import graphql.schema.DataFetchingEnvironment import org.sourcegrade.lab.hub.graphql.extractRelations @@ -29,7 +30,7 @@ interface User : DomainEntity { data class CreateDto( val email: String, val username: String, - val displayname: String = username, + val displayname: OptionalInput = OptionalInput.Defined(username), ) : Creates } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CourseRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CourseRepository.kt new file mode 100644 index 0000000..b740555 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CourseRepository.kt @@ -0,0 +1,38 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.domain.repo + +import org.sourcegrade.lab.hub.domain.Course +import org.sourcegrade.lab.hub.domain.CourseCollection +import org.sourcegrade.lab.hub.domain.Relation +import org.sourcegrade.lab.hub.domain.User +import java.util.UUID + +interface CourseRepository : CollectionRepository { + + suspend fun findByName(name: String, relations: List> = emptyList()): Course? + + suspend fun findAllByName(partialName: String): CourseCollection + + suspend fun findAllByDescription(partialDescription: String): CourseCollection // TODO: maybe instead CollectionRepo.search? + + suspend fun findAllByOwner(ownerId: UUID): CourseCollection +} + +interface MutableCourseRepository : CourseCollection, MutableRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt index c6a979a..00f4b4a 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt @@ -18,15 +18,15 @@ package org.sourcegrade.lab.hub.domain.repo -import org.jetbrains.exposed.sql.SizedIterable import org.sourcegrade.lab.hub.domain.Relation import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserCollection interface UserRepository : CollectionRepository { + suspend fun findByUsername(username: String, relations: List> = emptyList()): User? - suspend fun findAllByUsername(partialUsername: String, relations: List> = emptyList()): SizedIterable + suspend fun findAllByUsername(partialUsername: String): UserCollection suspend fun findByEmail(email: String, relations: List> = emptyList()): User? } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLExecutionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLExecutionContext.kt new file mode 100644 index 0000000..56353c3 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLExecutionContext.kt @@ -0,0 +1,39 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.graphql + +import org.jetbrains.exposed.sql.Transaction +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction + +interface GraphQLExecutionContext { + fun submitTransaction(transaction: suspend Transaction.() -> Unit) + + suspend fun execute() +} + +class GraphQLExecutionContextImpl : GraphQLExecutionContext { + + private val transactions = mutableListOf Unit>() + + override fun submitTransaction(transaction: suspend Transaction.() -> Unit) { + transactions += transaction + } + + override suspend fun execute() = newSuspendedTransaction { transactions.forEach { it() } } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt index bd36e95..315644a 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt @@ -23,9 +23,11 @@ import com.expediagroup.graphql.server.operations.Mutation import com.expediagroup.graphql.server.operations.Query import graphql.schema.DataFetchingEnvironment import org.apache.logging.log4j.Logger +import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserCollection import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository import org.sourcegrade.lab.hub.domain.repo.UserRepository +import java.util.UUID class UserQueries( private val logger: Logger, @@ -39,22 +41,18 @@ class UserQuery( private val logger: Logger, private val repository: UserRepository, ) { + suspend fun findAll(): UserCollection = repository.findAll() + suspend fun findById(dfe: DataFetchingEnvironment, id: UUID): User? = repository.findById(id, dfe.extractRelations()) + suspend fun deleteById(id: UUID): Boolean = repository.deleteById(id) + suspend fun exists(id: UUID): Boolean = repository.exists(id) + suspend fun countAll(): Long = repository.countAll() - suspend fun findAll(dfe: DataFetchingEnvironment): UserCollection { - val relations = dfe.selectionSet.fields.map { it.name } -// logger.info("Relations: $relations") + suspend fun findByUsername(dfe: DataFetchingEnvironment, username: String): User? = + repository.findByUsername(username, dfe.extractRelations()) - - dfe.selectionSet.immediateFields - return repository.findAll() + suspend fun findAllByUsername(dfe: DataFetchingEnvironment, partialUsername: String): UserCollection { + return repository.findAllByUsername(partialUsername) } - -// suspend fun findById(id: UUID): User? = repository.findById(id) - - // suspend fun deleteById(id: UUID): Boolean = repository.deleteById(id) -// suspend fun exists(id: UUID): Boolean = repository.exists(id) -// suspend fun countAll(): Long = repository.countAll() - fun hello1(): String = "Hello, World!" } class UserMutations( From b3a86e00bc4bd6ab38acad73391f93f622a4e264 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Mon, 8 Jul 2024 14:43:29 +0200 Subject: [PATCH 24/29] More stuff --- gradle/libs.versions.toml | 3 +- hub/build.gradle.kts | 2 +- .../kotlin/org/sourcegrade/lab/hub/Module.kt | 24 ++++++++- .../lab/hub/db/EntityConversionContext.kt | 13 +++-- .../lab/hub/db/SuspendedExecutionContext.kt | 49 +++++++++++++++++++ .../lab/hub/db/UUIDEntityClassRepository.kt | 17 +++---- .../UnconfinedExecutionContext.kt} | 22 +++------ .../lab/hub/db/assignment/Assignments.kt | 10 ++-- .../org/sourcegrade/lab/hub/db/user/Users.kt | 27 ++++++---- .../lab/hub/domain/DomainEntityCollection.kt | 19 ++++--- .../lab/hub/domain/ExecutionContext.kt | 25 ++++++++++ .../hub/domain/repo/CollectionRepository.kt | 22 ++++++++- .../lab/hub/domain/repo/MutableRepository.kt | 6 ++- .../lab/hub/domain/repo/Repository.kt | 10 ++-- .../lab/hub/domain/repo/UserRepository.kt | 19 +++++-- .../org/sourcegrade/lab/hub/graphql/User.kt | 18 +++++-- .../lab/hub/http/AuthenticationModule.kt | 2 +- 17 files changed, 217 insertions(+), 71 deletions(-) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SuspendedExecutionContext.kt rename hub/src/main/kotlin/org/sourcegrade/lab/hub/{graphql/GraphQLExecutionContext.kt => db/UnconfinedExecutionContext.kt} (60%) create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/ExecutionContext.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index bdcbd2f..234a847 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,7 +6,6 @@ exposed = "0.49.0" [libraries] annotations = "org.jetbrains:annotations:24.0.1" -coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0" # com.expediagroup:graphql-kotlin-ktor-server:7.0.2 graphql-server = { module = "com.expediagroup:graphql-kotlin-ktor-server", version.ref = "graphql" } graphql-client = { module = "com.expediagroup:graphql-kotlin-ktor-client", version.ref = "graphql" } @@ -25,7 +24,7 @@ ktor-server-status-pages = { module = "io.ktor:ktor-server-status-pages" } ktor-serialization-kotlinx = { module = "io.ktor:ktor-serialization-kotlinx" } ktor-serialization-kotlinx-json = { module = "io.ktor:ktor-serialization-kotlinx-json" } # TODO: Remove kotlin-reflect = { module = "org.jetbrains.kotlin:kotlin-reflect", version.ref = "kotlin" } - +kotlinx-coroutines = "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0" kubernetes-client = "io.fabric8:kubernetes-client:6.11.0" logging-api = { module = "org.apache.logging.log4j:log4j-api", version.ref = "log4j" } logging-core = { module = "org.apache.logging.log4j:log4j-core", version.ref = "log4j" } diff --git a/hub/build.gradle.kts b/hub/build.gradle.kts index be83a82..60dbabc 100644 --- a/hub/build.gradle.kts +++ b/hub/build.gradle.kts @@ -29,7 +29,7 @@ dependencies { implementation(libs.ktor.server.call.logging) implementation(libs.ktor.client.logging) implementation(libs.kotlin.reflect) - implementation(libs.shiro) + implementation(libs.kotlinx.coroutines) } application { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt index 0167c90..e409bf7 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt @@ -24,7 +24,26 @@ import com.expediagroup.graphql.server.ktor.graphQLGetRoute import com.expediagroup.graphql.server.ktor.graphQLPostRoute import com.expediagroup.graphql.server.ktor.graphQLSDLRoute import com.expediagroup.graphql.server.ktor.graphiQLRoute +import graphql.ExecutionInput +import graphql.ExecutionResult +import graphql.execution.ExecutionContext +import graphql.execution.instrumentation.DocumentAndVariables +import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext +import graphql.execution.instrumentation.Instrumentation +import graphql.execution.instrumentation.InstrumentationContext +import graphql.execution.instrumentation.InstrumentationState +import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters +import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters +import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters +import graphql.execution.instrumentation.parameters.InstrumentationFieldCompleteParameters +import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters +import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters +import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters +import graphql.language.Document +import graphql.schema.DataFetcher +import graphql.schema.GraphQLSchema import graphql.schema.GraphQLType +import graphql.validation.ValidationError import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod import io.ktor.server.application.Application @@ -43,9 +62,7 @@ import org.koin.core.module.dsl.singleOf import org.koin.dsl.module import org.koin.ktor.ext.inject import org.koin.ktor.plugin.Koin -import org.sourcegrade.lab.hub.db.assignment.Assignments import org.sourcegrade.lab.hub.db.CourseMemberships -import org.sourcegrade.lab.hub.db.course.Courses import org.sourcegrade.lab.hub.db.Criteria import org.sourcegrade.lab.hub.db.DBModule import org.sourcegrade.lab.hub.db.GradedCriteria @@ -56,11 +73,14 @@ import org.sourcegrade.lab.hub.db.SubmissionGroupCategories import org.sourcegrade.lab.hub.db.SubmissionGroupMemberships import org.sourcegrade.lab.hub.db.Submissions import org.sourcegrade.lab.hub.db.Terms +import org.sourcegrade.lab.hub.db.assignment.Assignments +import org.sourcegrade.lab.hub.db.course.Courses import org.sourcegrade.lab.hub.db.user.Users import org.sourcegrade.lab.hub.graphql.Scalars import org.sourcegrade.lab.hub.graphql.UserMutations import org.sourcegrade.lab.hub.graphql.UserQueries import org.sourcegrade.lab.hub.http.authenticationModule +import java.util.concurrent.CompletableFuture import kotlin.reflect.KType fun Application.module() { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt index 5aa441d..5b14ef5 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt @@ -23,8 +23,8 @@ import org.jetbrains.exposed.dao.load import org.jetbrains.exposed.dao.with import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.mapLazy -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.sourcegrade.lab.hub.domain.DomainEntity +import org.sourcegrade.lab.hub.domain.ExecutionContext import org.sourcegrade.lab.hub.domain.Relation interface EntityConversionContext { @@ -33,7 +33,11 @@ interface EntityConversionContext { fun convertRelation(relation: Relation): Relation - suspend fun entityConversion(relations: List> = emptyList(), statement: ConversionBody): T + suspend fun entityConversion( + executionContext: ExecutionContext, + relations: List> = emptyList(), + statement: ConversionBody, + ): T } typealias ConversionBody = suspend EntityConversion.() -> EntityConversion.BindResult @@ -50,11 +54,12 @@ class EntityConversionContextImpl( } override suspend fun entityConversion( + executionContext: ExecutionContext, relations: List>, statement: ConversionBody, - ): T { + ): T = executionContext.execute { val ec = EntityConversion(context = this, relations) - return newSuspendedTransaction { statement(ec).result } + statement(ec).result } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SuspendedExecutionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SuspendedExecutionContext.kt new file mode 100644 index 0000000..4b923c1 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SuspendedExecutionContext.kt @@ -0,0 +1,49 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.db + +import kotlinx.coroutines.CompletableDeferred +import kotlinx.coroutines.completeWith +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.sourcegrade.lab.hub.domain.ExecutionContext + +class SuspendedExecutionContext : ExecutionContext { + + private val executionScopes = mutableListOf>() + + private class ExecutionScope(private val statement: suspend () -> T) { + val future = CompletableDeferred() + suspend fun execute() { + future.completeWith(runCatching { statement() }) + } + } + + override suspend fun execute(statement: suspend () -> T): T { + val scope = ExecutionScope(statement) + executionScopes.add(scope) + + return scope.future.await() + } + + override suspend fun execute() = newSuspendedTransaction { + executionScopes.forEach { scope -> + scope.execute() + } + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt index 76f3dbf..42dac90 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt @@ -22,6 +22,7 @@ import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.sourcegrade.lab.hub.domain.DomainEntity +import org.sourcegrade.lab.hub.domain.ExecutionContext import org.sourcegrade.lab.hub.domain.Relation import org.sourcegrade.lab.hub.domain.repo.Repository import java.util.UUID @@ -31,25 +32,19 @@ internal class UUIDEntityClassRepository( private val conversionContext: EntityConversionContext, ) : Repository, EntityConversionContext by conversionContext { - override suspend fun findById(id: UUID, relations: List>): E? = - entityConversion(relations) { entityClass.findById(id).bindNullable() } + override suspend fun findById(id: UUID, context: ExecutionContext, relations: List>): E? = + entityConversion(context, relations) { entityClass.findById(id).bindNullable() } - suspend fun findById2(id: UUID) { - entityConversion { - entityClass.findById(id).bindNullable() - } - } - - override suspend fun deleteById(id: UUID): Boolean = + override suspend fun deleteById(id: UUID, context: ExecutionContext): Boolean = newSuspendedTransaction { entityClass.findById(id) ?.let { it.delete(); true } ?: false } - override suspend fun exists(id: UUID): Boolean = + override suspend fun exists(id: UUID, context: ExecutionContext): Boolean = newSuspendedTransaction { entityClass.findById(id) != null } - override suspend fun countAll(): Long = + override suspend fun countAll(context: ExecutionContext): Long = newSuspendedTransaction { entityClass.all().count() } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLExecutionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UnconfinedExecutionContext.kt similarity index 60% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLExecutionContext.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UnconfinedExecutionContext.kt index 56353c3..a477aa6 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/GraphQLExecutionContext.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UnconfinedExecutionContext.kt @@ -16,24 +16,14 @@ * along with this program. If not, see . */ -package org.sourcegrade.lab.hub.graphql +package org.sourcegrade.lab.hub.db -import org.jetbrains.exposed.sql.Transaction import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.sourcegrade.lab.hub.domain.ExecutionContext -interface GraphQLExecutionContext { - fun submitTransaction(transaction: suspend Transaction.() -> Unit) +object UnconfinedExecutionContext : ExecutionContext { + override suspend fun execute(statement: suspend () -> T): T = + newSuspendedTransaction { statement() } - suspend fun execute() -} - -class GraphQLExecutionContextImpl : GraphQLExecutionContext { - - private val transactions = mutableListOf Unit>() - - override fun submitTransaction(transaction: suspend Transaction.() -> Unit) { - transactions += transaction - } - - override suspend fun execute() = newSuspendedTransaction { transactions.forEach { it() } } + override suspend fun execute() = Unit } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt index 2685062..9055cfc 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt @@ -46,6 +46,7 @@ import org.sourcegrade.lab.hub.domain.SizedIterableCollection import org.sourcegrade.lab.hub.domain.repo.MutableAssignmentRepository import org.sourcegrade.lab.hub.domain.repo.MutableRepository import org.sourcegrade.lab.hub.domain.repo.Repository +import org.sourcegrade.lab.hub.domain.ExecutionContext import java.util.UUID internal object Assignments : UUIDTable("sgl_assignments") { @@ -85,9 +86,9 @@ internal class DBAssignmentRepository( DBAssignment.find { Assignments.courseId eq courseId } } - override suspend fun findAll(): AssignmentCollection = DBAssignmentCollection { DBAssignment.all().bindIterable() } + override suspend fun findAll(context: ExecutionContext): AssignmentCollection = DBAssignmentCollection(context) { DBAssignment.all().bindIterable() } - override suspend fun create(item: Assignment.CreateDto): Assignment = newSuspendedTransaction { + override suspend fun create(item: Assignment.CreateDto, context: ExecutionContext): Assignment = newSuspendedTransaction { DBAssignment.new { course = DBCourse.findByIdNotNull(item.courseId) submissionGroupCategory = DBSubmissionGroupCategory.findByIdNotNull(item.submissionGroupCategoryId) @@ -97,15 +98,16 @@ internal class DBAssignmentRepository( } } - override suspend fun put(item: Assignment.CreateDto): MutableRepository.PutResult { + override suspend fun put(item: Assignment.CreateDto, context: ExecutionContext): MutableRepository.PutResult { TODO("Not yet implemented") } } internal class DBAssignmentCollection( + private val context: ExecutionContext, private val limit: Pair? = null, private val orders: List = emptyList(), private val body: ConversionBody>, ) : AssignmentCollection, DomainEntityCollection - by SizedIterableCollection(Assignments, conversionContext, ::DBAssignmentCollection, limit, orders, body) + by SizedIterableCollection(Assignments, conversionContext, ::DBAssignmentCollection, context, limit, orders, body) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt index 46ce3d8..bdc6c89 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt @@ -35,6 +35,7 @@ import org.sourcegrade.lab.hub.db.EntityConversionContext import org.sourcegrade.lab.hub.db.EntityConversionContextImpl import org.sourcegrade.lab.hub.db.UUIDEntityClassRepository import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.ExecutionContext import org.sourcegrade.lab.hub.domain.MutableUser import org.sourcegrade.lab.hub.domain.Relation import org.sourcegrade.lab.hub.domain.SizedIterableCollection @@ -70,22 +71,27 @@ internal class DBUserRepository( ) : MutableUserRepository, Repository by UUIDEntityClassRepository(DBUser, conversionContext), EntityConversionContext by conversionContext { - override suspend fun findByUsername(username: String, relations: List>): User? = - entityConversion(relations) { + override suspend fun findByUsername(username: String, context: ExecutionContext, relations: List>): User? = + entityConversion(context, relations) { DBUser.find { Users.username eq username }.firstOrNull().bindNullable() } - override suspend fun findAllByUsername(partialUsername: String): UserCollection = - DBUserCollection { DBUser.find { Users.username like "%$partialUsername%" }.bindIterable() } + override suspend fun findAllByUsername(partialUsername: String, context: ExecutionContext): UserCollection = + DBUserCollection(context) { DBUser.find { Users.username like "%$partialUsername%" }.bindIterable() } - override suspend fun findByEmail(email: String, relations: List>): User? = - entityConversion(relations) { + override suspend fun findByEmail(email: String, context: ExecutionContext, relations: List>): User? = + entityConversion(context, relations) { DBUser.find { Users.email eq email }.firstOrNull().bindNullable() } - override suspend fun findAll(): UserCollection = DBUserCollection { DBUser.all().bindIterable() } + override suspend fun findAll(context: ExecutionContext): UserCollection { + println("findAll start: ${Thread.currentThread().name}") + val result = DBUserCollection(context) { DBUser.all().bindIterable() } + println("findAll end: ${Thread.currentThread().name}") + return result + } - override suspend fun create(item: User.CreateDto): User = entityConversion(emptyList()) { + override suspend fun create(item: User.CreateDto, context: ExecutionContext): User = entityConversion(context, emptyList()) { DBUser.new { email = item.email username = item.username @@ -97,7 +103,7 @@ internal class DBUserRepository( }.bind() } - override suspend fun put(item: User.CreateDto): MutableRepository.PutResult = newSuspendedTransaction { + override suspend fun put(item: User.CreateDto, context: ExecutionContext): MutableRepository.PutResult = newSuspendedTransaction { val existingUser = findByUsername(item.username) if (existingUser == null) { MutableRepository.PutResult(create(item), created = true) @@ -112,9 +118,10 @@ internal class DBUserRepository( } internal class DBUserCollection( + private val context: ExecutionContext, private val limit: Pair? = null, private val orders: List = emptyList(), private val body: ConversionBody>, ) : UserCollection, DomainEntityCollection - by SizedIterableCollection(Users, conversionContext, ::DBUserCollection, limit, orders, body) + by SizedIterableCollection(Users, conversionContext, ::DBUserCollection, context, limit, orders, body) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt index 672c9c0..b029694 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt @@ -58,30 +58,35 @@ interface DomainEntityCollection>( private val table: Table, private val conversionContext: EntityConversionContext, - private val ctor: (Pair?, List, ConversionBody>) -> C, + private val ctor: (ExecutionContext, Pair?, List, ConversionBody>) -> C, + private val context: ExecutionContext, private val limit: Pair?, private val orders: List, private val body: ConversionBody>, ) : DomainEntityCollection, EntityConversionContext by conversionContext { override fun limit(num: Int, offset: Long): C = - ctor(limit?.let { (n, o) -> minOf(num, n) to offset + o } ?: (num to offset), orders, body) + ctor(context, limit?.let { (n, o) -> minOf(num, n) to offset + o } ?: (num to offset), orders, body) // Note that this is not *technically* correct because nested orderings are not supported // But its close enough, because it would only be noticeable in an order -> limit -> order scenario override fun orderBy(orders: List): C = - ctor(limit, orders, body) + ctor(context, limit, orders, body) - override suspend fun list(relations: List>): List = - entityConversion(relations) { + override suspend fun list(relations: List>): List { + println("list start: ${Thread.currentThread().name}") + val result = entityConversion(context, relations) { body().result .let { if (limit != null) it.limit(limit.first, limit.second) else it } .let { if (orders.isNotEmpty()) it.orderBy(table, orders) else it } .toList().bindT() } + println("list end: ${Thread.currentThread().name}") + return result + } - override suspend fun count(): Long = entityConversion { body().result.count().bindT() } + override suspend fun count(): Long = entityConversion(context) { body().result.count().bindT() } - override suspend fun empty(): Boolean = entityConversion { body().result.empty().bindT() } + override suspend fun empty(): Boolean = entityConversion(context) { body().result.empty().bindT() } } private fun SizedIterable.orderBy( diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/ExecutionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/ExecutionContext.kt new file mode 100644 index 0000000..af6147d --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/ExecutionContext.kt @@ -0,0 +1,25 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.domain + +interface ExecutionContext { + suspend fun execute(statement: suspend () -> T): T + + suspend fun execute() +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt index a39401d..5256213 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt @@ -18,9 +18,29 @@ package org.sourcegrade.lab.hub.domain.repo +import org.sourcegrade.lab.hub.db.UnconfinedExecutionContext import org.sourcegrade.lab.hub.domain.DomainEntity import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.ExecutionContext interface CollectionRepository> : Repository { - suspend fun findAll(): C + suspend fun findAll(context: ExecutionContext = UnconfinedExecutionContext): C } +// +//data class CollectionParameters( +// val page: OptionalInput, +// val order: List, +//) { +// data class Page(val num: Long, val size: OptionalInput) +// +// data class FieldOrdering(val field: String, val sortOrder: SortOrder = SortOrder.DESC) +// +// enum class SortOrder(val exposed: org.jetbrains.exposed.sql.SortOrder) { +// ASC(org.jetbrains.exposed.sql.SortOrder.ASC), +// DESC(org.jetbrains.exposed.sql.SortOrder.DESC), +// ASC_NULLS_FIRST(org.jetbrains.exposed.sql.SortOrder.ASC_NULLS_FIRST), +// DESC_NULLS_FIRST(org.jetbrains.exposed.sql.SortOrder.DESC_NULLS_FIRST), +// ASC_NULLS_LAST(org.jetbrains.exposed.sql.SortOrder.ASC_NULLS_LAST), +// DESC_NULLS_LAST(org.jetbrains.exposed.sql.SortOrder.DESC_NULLS_LAST) +// } +//} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt index 56f6eb0..332e496 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt @@ -19,13 +19,15 @@ package org.sourcegrade.lab.hub.domain.repo import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import org.sourcegrade.lab.hub.db.UnconfinedExecutionContext import org.sourcegrade.lab.hub.domain.Creates import org.sourcegrade.lab.hub.domain.DomainEntity +import org.sourcegrade.lab.hub.domain.ExecutionContext @GraphQLIgnore interface MutableRepository> : Repository { - suspend fun create(item: C): E - suspend fun put(item: C): PutResult + suspend fun create(item: C, context: ExecutionContext = UnconfinedExecutionContext): E + suspend fun put(item: C, context: ExecutionContext = UnconfinedExecutionContext): PutResult data class PutResult(val entity: E, val created: Boolean) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt index 9623388..f713583 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt @@ -18,13 +18,15 @@ package org.sourcegrade.lab.hub.domain.repo +import org.sourcegrade.lab.hub.db.UnconfinedExecutionContext import org.sourcegrade.lab.hub.domain.DomainEntity +import org.sourcegrade.lab.hub.domain.ExecutionContext import org.sourcegrade.lab.hub.domain.Relation import java.util.UUID interface Repository { - suspend fun findById(id: UUID, relations: List> = emptyList()): E? - suspend fun deleteById(id: UUID): Boolean - suspend fun exists(id: UUID): Boolean - suspend fun countAll(): Long + suspend fun findById(id: UUID, context: ExecutionContext = UnconfinedExecutionContext, relations: List> = emptyList()): E? + suspend fun deleteById(id: UUID, context: ExecutionContext = UnconfinedExecutionContext): Boolean + suspend fun exists(id: UUID, context: ExecutionContext = UnconfinedExecutionContext): Boolean + suspend fun countAll(context: ExecutionContext = UnconfinedExecutionContext): Long } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt index 00f4b4a..3c81d30 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt @@ -18,17 +18,30 @@ package org.sourcegrade.lab.hub.domain.repo +import org.sourcegrade.lab.hub.db.UnconfinedExecutionContext +import org.sourcegrade.lab.hub.domain.ExecutionContext import org.sourcegrade.lab.hub.domain.Relation import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserCollection interface UserRepository : CollectionRepository { - suspend fun findByUsername(username: String, relations: List> = emptyList()): User? + suspend fun findByUsername( + username: String, + context: ExecutionContext = UnconfinedExecutionContext, + relations: List> = emptyList(), + ): User? - suspend fun findAllByUsername(partialUsername: String): UserCollection + suspend fun findAllByUsername( + partialUsername: String, + context: ExecutionContext = UnconfinedExecutionContext, + ): UserCollection - suspend fun findByEmail(email: String, relations: List> = emptyList()): User? + suspend fun findByEmail( + email: String, + context: ExecutionContext = UnconfinedExecutionContext, + relations: List> = emptyList(), + ): User? } interface MutableUserRepository : UserRepository, MutableRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt index 315644a..caaf9c2 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt @@ -23,6 +23,9 @@ import com.expediagroup.graphql.server.operations.Mutation import com.expediagroup.graphql.server.operations.Query import graphql.schema.DataFetchingEnvironment import org.apache.logging.log4j.Logger +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.sourcegrade.lab.hub.db.SuspendedExecutionContext +import org.sourcegrade.lab.hub.db.UnconfinedExecutionContext import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserCollection import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository @@ -41,14 +44,23 @@ class UserQuery( private val logger: Logger, private val repository: UserRepository, ) { - suspend fun findAll(): UserCollection = repository.findAll() - suspend fun findById(dfe: DataFetchingEnvironment, id: UUID): User? = repository.findById(id, dfe.extractRelations()) + suspend fun findAll(): UserCollection { + val context = SuspendedExecutionContext() + val result = repository.findAll(context) + context.execute() + return result + } + + suspend fun findById(dfe: DataFetchingEnvironment, id: UUID): User? = newSuspendedTransaction { + repository.findById(id, UnconfinedExecutionContext, dfe.extractRelations()) + } + suspend fun deleteById(id: UUID): Boolean = repository.deleteById(id) suspend fun exists(id: UUID): Boolean = repository.exists(id) suspend fun countAll(): Long = repository.countAll() suspend fun findByUsername(dfe: DataFetchingEnvironment, username: String): User? = - repository.findByUsername(username, dfe.extractRelations()) + repository.findByUsername(username, relations = dfe.extractRelations()) suspend fun findAllByUsername(dfe: DataFetchingEnvironment, partialUsername: String): UserCollection { return repository.findAllByUsername(partialUsername) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt index bfd2e59..80bc171 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt @@ -207,7 +207,7 @@ suspend fun PipelineContext.withUserSession(block: su suspend fun PipelineContext.withUser(userRepository: UserRepository, block: suspend (User) -> Unit) { withUserSession { session -> - val user = userRepository.findById(UUID.fromString(session.userId)) + val user = userRepository.findById(UUID.fromString(session.userId),) checkNotNull(user) { "Could not find user ${session.email} in DB" } block(user) } From 0bc09fb2dbdc32f8fb7435b2c0f58ef2e895b887 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Mon, 8 Jul 2024 17:51:40 +0200 Subject: [PATCH 25/29] Remove ExecutionContext for now --- .../lab/hub/db/EntityConversionContext.kt | 7 +-- .../lab/hub/db/SuspendedExecutionContext.kt | 49 ------------------- .../lab/hub/db/UUIDEntityClassRepository.kt | 11 ++--- .../lab/hub/db/UnconfinedExecutionContext.kt | 29 ----------- .../lab/hub/db/assignment/Assignments.kt | 10 ++-- .../org/sourcegrade/lab/hub/db/user/Users.kt | 24 +++++---- .../lab/hub/domain/DomainEntityCollection.kt | 13 +++-- .../lab/hub/domain/ExecutionContext.kt | 25 ---------- .../hub/domain/repo/CollectionRepository.kt | 4 +- .../lab/hub/domain/repo/MutableRepository.kt | 6 +-- .../lab/hub/domain/repo/Repository.kt | 10 ++-- .../lab/hub/domain/repo/UserRepository.kt | 19 ++----- .../org/sourcegrade/lab/hub/graphql/User.kt | 11 +---- 13 files changed, 40 insertions(+), 178 deletions(-) delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SuspendedExecutionContext.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UnconfinedExecutionContext.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/ExecutionContext.kt diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt index 5b14ef5..ff761ac 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt @@ -24,7 +24,6 @@ import org.jetbrains.exposed.dao.with import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.mapLazy import org.sourcegrade.lab.hub.domain.DomainEntity -import org.sourcegrade.lab.hub.domain.ExecutionContext import org.sourcegrade.lab.hub.domain.Relation interface EntityConversionContext { @@ -34,7 +33,6 @@ interface EntityConversionContext { fun convertRelation(relation: Relation): Relation suspend fun entityConversion( - executionContext: ExecutionContext, relations: List> = emptyList(), statement: ConversionBody, ): T @@ -54,12 +52,11 @@ class EntityConversionContextImpl( } override suspend fun entityConversion( - executionContext: ExecutionContext, relations: List>, statement: ConversionBody, - ): T = executionContext.execute { + ): T { val ec = EntityConversion(context = this, relations) - statement(ec).result + return statement(ec).result } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SuspendedExecutionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SuspendedExecutionContext.kt deleted file mode 100644 index 4b923c1..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SuspendedExecutionContext.kt +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Lab - SourceGrade.org - * Copyright (C) 2019-2024 Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.sourcegrade.lab.hub.db - -import kotlinx.coroutines.CompletableDeferred -import kotlinx.coroutines.completeWith -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.domain.ExecutionContext - -class SuspendedExecutionContext : ExecutionContext { - - private val executionScopes = mutableListOf>() - - private class ExecutionScope(private val statement: suspend () -> T) { - val future = CompletableDeferred() - suspend fun execute() { - future.completeWith(runCatching { statement() }) - } - } - - override suspend fun execute(statement: suspend () -> T): T { - val scope = ExecutionScope(statement) - executionScopes.add(scope) - - return scope.future.await() - } - - override suspend fun execute() = newSuspendedTransaction { - executionScopes.forEach { scope -> - scope.execute() - } - } -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt index 42dac90..81bcaf6 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt @@ -22,7 +22,6 @@ import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.sourcegrade.lab.hub.domain.DomainEntity -import org.sourcegrade.lab.hub.domain.ExecutionContext import org.sourcegrade.lab.hub.domain.Relation import org.sourcegrade.lab.hub.domain.repo.Repository import java.util.UUID @@ -32,19 +31,19 @@ internal class UUIDEntityClassRepository( private val conversionContext: EntityConversionContext, ) : Repository, EntityConversionContext by conversionContext { - override suspend fun findById(id: UUID, context: ExecutionContext, relations: List>): E? = - entityConversion(context, relations) { entityClass.findById(id).bindNullable() } + override suspend fun findById(id: UUID, relations: List>): E? = + entityConversion(relations) { entityClass.findById(id).bindNullable() } - override suspend fun deleteById(id: UUID, context: ExecutionContext): Boolean = + override suspend fun deleteById(id: UUID): Boolean = newSuspendedTransaction { entityClass.findById(id) ?.let { it.delete(); true } ?: false } - override suspend fun exists(id: UUID, context: ExecutionContext): Boolean = + override suspend fun exists(id: UUID): Boolean = newSuspendedTransaction { entityClass.findById(id) != null } - override suspend fun countAll(context: ExecutionContext): Long = + override suspend fun countAll(): Long = newSuspendedTransaction { entityClass.all().count() } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UnconfinedExecutionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UnconfinedExecutionContext.kt deleted file mode 100644 index a477aa6..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UnconfinedExecutionContext.kt +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Lab - SourceGrade.org - * Copyright (C) 2019-2024 Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.sourcegrade.lab.hub.db - -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.domain.ExecutionContext - -object UnconfinedExecutionContext : ExecutionContext { - override suspend fun execute(statement: suspend () -> T): T = - newSuspendedTransaction { statement() } - - override suspend fun execute() = Unit -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt index 9055cfc..2685062 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt @@ -46,7 +46,6 @@ import org.sourcegrade.lab.hub.domain.SizedIterableCollection import org.sourcegrade.lab.hub.domain.repo.MutableAssignmentRepository import org.sourcegrade.lab.hub.domain.repo.MutableRepository import org.sourcegrade.lab.hub.domain.repo.Repository -import org.sourcegrade.lab.hub.domain.ExecutionContext import java.util.UUID internal object Assignments : UUIDTable("sgl_assignments") { @@ -86,9 +85,9 @@ internal class DBAssignmentRepository( DBAssignment.find { Assignments.courseId eq courseId } } - override suspend fun findAll(context: ExecutionContext): AssignmentCollection = DBAssignmentCollection(context) { DBAssignment.all().bindIterable() } + override suspend fun findAll(): AssignmentCollection = DBAssignmentCollection { DBAssignment.all().bindIterable() } - override suspend fun create(item: Assignment.CreateDto, context: ExecutionContext): Assignment = newSuspendedTransaction { + override suspend fun create(item: Assignment.CreateDto): Assignment = newSuspendedTransaction { DBAssignment.new { course = DBCourse.findByIdNotNull(item.courseId) submissionGroupCategory = DBSubmissionGroupCategory.findByIdNotNull(item.submissionGroupCategoryId) @@ -98,16 +97,15 @@ internal class DBAssignmentRepository( } } - override suspend fun put(item: Assignment.CreateDto, context: ExecutionContext): MutableRepository.PutResult { + override suspend fun put(item: Assignment.CreateDto): MutableRepository.PutResult { TODO("Not yet implemented") } } internal class DBAssignmentCollection( - private val context: ExecutionContext, private val limit: Pair? = null, private val orders: List = emptyList(), private val body: ConversionBody>, ) : AssignmentCollection, DomainEntityCollection - by SizedIterableCollection(Assignments, conversionContext, ::DBAssignmentCollection, context, limit, orders, body) + by SizedIterableCollection(Assignments, conversionContext, ::DBAssignmentCollection, limit, orders, body) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt index bdc6c89..891959f 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt @@ -35,7 +35,6 @@ import org.sourcegrade.lab.hub.db.EntityConversionContext import org.sourcegrade.lab.hub.db.EntityConversionContextImpl import org.sourcegrade.lab.hub.db.UUIDEntityClassRepository import org.sourcegrade.lab.hub.domain.DomainEntityCollection -import org.sourcegrade.lab.hub.domain.ExecutionContext import org.sourcegrade.lab.hub.domain.MutableUser import org.sourcegrade.lab.hub.domain.Relation import org.sourcegrade.lab.hub.domain.SizedIterableCollection @@ -71,27 +70,27 @@ internal class DBUserRepository( ) : MutableUserRepository, Repository by UUIDEntityClassRepository(DBUser, conversionContext), EntityConversionContext by conversionContext { - override suspend fun findByUsername(username: String, context: ExecutionContext, relations: List>): User? = - entityConversion(context, relations) { + override suspend fun findByUsername(username: String, relations: List>): User? = + entityConversion { DBUser.find { Users.username eq username }.firstOrNull().bindNullable() } - override suspend fun findAllByUsername(partialUsername: String, context: ExecutionContext): UserCollection = - DBUserCollection(context) { DBUser.find { Users.username like "%$partialUsername%" }.bindIterable() } + override suspend fun findAllByUsername(partialUsername: String): UserCollection = + DBUserCollection { DBUser.find { Users.username like "%$partialUsername%" }.bindIterable() } - override suspend fun findByEmail(email: String, context: ExecutionContext, relations: List>): User? = - entityConversion(context, relations) { + override suspend fun findByEmail(email: String, relations: List>): User? = + entityConversion { DBUser.find { Users.email eq email }.firstOrNull().bindNullable() } - override suspend fun findAll(context: ExecutionContext): UserCollection { + override suspend fun findAll(): UserCollection { println("findAll start: ${Thread.currentThread().name}") - val result = DBUserCollection(context) { DBUser.all().bindIterable() } + val result = DBUserCollection { DBUser.all().bindIterable() } println("findAll end: ${Thread.currentThread().name}") return result } - override suspend fun create(item: User.CreateDto, context: ExecutionContext): User = entityConversion(context, emptyList()) { + override suspend fun create(item: User.CreateDto): User = entityConversion(emptyList()) { DBUser.new { email = item.email username = item.username @@ -103,7 +102,7 @@ internal class DBUserRepository( }.bind() } - override suspend fun put(item: User.CreateDto, context: ExecutionContext): MutableRepository.PutResult = newSuspendedTransaction { + override suspend fun put(item: User.CreateDto): MutableRepository.PutResult = newSuspendedTransaction { val existingUser = findByUsername(item.username) if (existingUser == null) { MutableRepository.PutResult(create(item), created = true) @@ -118,10 +117,9 @@ internal class DBUserRepository( } internal class DBUserCollection( - private val context: ExecutionContext, private val limit: Pair? = null, private val orders: List = emptyList(), private val body: ConversionBody>, ) : UserCollection, DomainEntityCollection - by SizedIterableCollection(Users, conversionContext, ::DBUserCollection, context, limit, orders, body) + by SizedIterableCollection(Users, conversionContext, ::DBUserCollection, limit, orders, body) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt index b029694..9358621 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt @@ -58,23 +58,22 @@ interface DomainEntityCollection>( private val table: Table, private val conversionContext: EntityConversionContext, - private val ctor: (ExecutionContext, Pair?, List, ConversionBody>) -> C, - private val context: ExecutionContext, + private val ctor: (Pair?, List, ConversionBody>) -> C, private val limit: Pair?, private val orders: List, private val body: ConversionBody>, ) : DomainEntityCollection, EntityConversionContext by conversionContext { override fun limit(num: Int, offset: Long): C = - ctor(context, limit?.let { (n, o) -> minOf(num, n) to offset + o } ?: (num to offset), orders, body) + ctor(limit?.let { (n, o) -> minOf(num, n) to offset + o } ?: (num to offset), orders, body) // Note that this is not *technically* correct because nested orderings are not supported // But its close enough, because it would only be noticeable in an order -> limit -> order scenario override fun orderBy(orders: List): C = - ctor(context, limit, orders, body) + ctor(limit, orders, body) override suspend fun list(relations: List>): List { println("list start: ${Thread.currentThread().name}") - val result = entityConversion(context, relations) { + val result = entityConversion { body().result .let { if (limit != null) it.limit(limit.first, limit.second) else it } .let { if (orders.isNotEmpty()) it.orderBy(table, orders) else it } @@ -84,9 +83,9 @@ internal class SizedIterableCollection SizedIterable.orderBy( diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/ExecutionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/ExecutionContext.kt deleted file mode 100644 index af6147d..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/ExecutionContext.kt +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Lab - SourceGrade.org - * Copyright (C) 2019-2024 Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.sourcegrade.lab.hub.domain - -interface ExecutionContext { - suspend fun execute(statement: suspend () -> T): T - - suspend fun execute() -} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt index 5256213..94e8adb 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt @@ -18,13 +18,11 @@ package org.sourcegrade.lab.hub.domain.repo -import org.sourcegrade.lab.hub.db.UnconfinedExecutionContext import org.sourcegrade.lab.hub.domain.DomainEntity import org.sourcegrade.lab.hub.domain.DomainEntityCollection -import org.sourcegrade.lab.hub.domain.ExecutionContext interface CollectionRepository> : Repository { - suspend fun findAll(context: ExecutionContext = UnconfinedExecutionContext): C + suspend fun findAll(): C } // //data class CollectionParameters( diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt index 332e496..56f6eb0 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt @@ -19,15 +19,13 @@ package org.sourcegrade.lab.hub.domain.repo import com.expediagroup.graphql.generator.annotations.GraphQLIgnore -import org.sourcegrade.lab.hub.db.UnconfinedExecutionContext import org.sourcegrade.lab.hub.domain.Creates import org.sourcegrade.lab.hub.domain.DomainEntity -import org.sourcegrade.lab.hub.domain.ExecutionContext @GraphQLIgnore interface MutableRepository> : Repository { - suspend fun create(item: C, context: ExecutionContext = UnconfinedExecutionContext): E - suspend fun put(item: C, context: ExecutionContext = UnconfinedExecutionContext): PutResult + suspend fun create(item: C): E + suspend fun put(item: C): PutResult data class PutResult(val entity: E, val created: Boolean) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt index f713583..9623388 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt @@ -18,15 +18,13 @@ package org.sourcegrade.lab.hub.domain.repo -import org.sourcegrade.lab.hub.db.UnconfinedExecutionContext import org.sourcegrade.lab.hub.domain.DomainEntity -import org.sourcegrade.lab.hub.domain.ExecutionContext import org.sourcegrade.lab.hub.domain.Relation import java.util.UUID interface Repository { - suspend fun findById(id: UUID, context: ExecutionContext = UnconfinedExecutionContext, relations: List> = emptyList()): E? - suspend fun deleteById(id: UUID, context: ExecutionContext = UnconfinedExecutionContext): Boolean - suspend fun exists(id: UUID, context: ExecutionContext = UnconfinedExecutionContext): Boolean - suspend fun countAll(context: ExecutionContext = UnconfinedExecutionContext): Long + suspend fun findById(id: UUID, relations: List> = emptyList()): E? + suspend fun deleteById(id: UUID): Boolean + suspend fun exists(id: UUID): Boolean + suspend fun countAll(): Long } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt index 3c81d30..9e7fdca 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt @@ -18,30 +18,17 @@ package org.sourcegrade.lab.hub.domain.repo -import org.sourcegrade.lab.hub.db.UnconfinedExecutionContext -import org.sourcegrade.lab.hub.domain.ExecutionContext import org.sourcegrade.lab.hub.domain.Relation import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserCollection interface UserRepository : CollectionRepository { - suspend fun findByUsername( - username: String, - context: ExecutionContext = UnconfinedExecutionContext, - relations: List> = emptyList(), - ): User? + suspend fun findByUsername(username: String, relations: List> = emptyList(), ): User? - suspend fun findAllByUsername( - partialUsername: String, - context: ExecutionContext = UnconfinedExecutionContext, - ): UserCollection + suspend fun findAllByUsername(partialUsername: String, ): UserCollection - suspend fun findByEmail( - email: String, - context: ExecutionContext = UnconfinedExecutionContext, - relations: List> = emptyList(), - ): User? + suspend fun findByEmail(email: String, relations: List> = emptyList(), ): User? } interface MutableUserRepository : UserRepository, MutableRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt index caaf9c2..d18f6d9 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt @@ -24,8 +24,6 @@ import com.expediagroup.graphql.server.operations.Query import graphql.schema.DataFetchingEnvironment import org.apache.logging.log4j.Logger import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.db.SuspendedExecutionContext -import org.sourcegrade.lab.hub.db.UnconfinedExecutionContext import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserCollection import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository @@ -44,15 +42,10 @@ class UserQuery( private val logger: Logger, private val repository: UserRepository, ) { - suspend fun findAll(): UserCollection { - val context = SuspendedExecutionContext() - val result = repository.findAll(context) - context.execute() - return result - } + suspend fun findAll(): UserCollection = repository.findAll() suspend fun findById(dfe: DataFetchingEnvironment, id: UUID): User? = newSuspendedTransaction { - repository.findById(id, UnconfinedExecutionContext, dfe.extractRelations()) + repository.findById(id, dfe.extractRelations()) } suspend fun deleteById(id: UUID): Boolean = repository.deleteById(id) From 41a920f27750affad0496551230428a1b81b1288 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Tue, 9 Jul 2024 10:56:13 +0200 Subject: [PATCH 26/29] Clean up graphql endpoints for user --- docker-compose.dev.yml | 2 +- .../kotlin/org/sourcegrade/lab/hub/Main.kt | 2 +- .../lab/hub/db/EntityConversionContext.kt | 3 +- .../lab/hub/db/assignment/Assignments.kt | 5 ++- .../org/sourcegrade/lab/hub/db/user/Users.kt | 17 +++++--- .../lab/hub/domain/repo/MutableRepository.kt | 5 ++- .../org/sourcegrade/lab/hub/graphql/User.kt | 40 +++++++++---------- 7 files changed, 39 insertions(+), 35 deletions(-) diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 64569e6..4b4db20 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -5,7 +5,7 @@ volumes: services: db: - image: postgres:latest + image: postgres:16.3 volumes: - postgres-data:/var/lib/postgresql/data environment: diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt index 74800d0..1577283 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt @@ -22,5 +22,5 @@ import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty fun main() { - embeddedServer(Netty) { module() }.start(wait = true) + embeddedServer(Netty, port = 8080) { module() }.start(wait = true) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt index ff761ac..62c1e00 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt @@ -23,6 +23,7 @@ import org.jetbrains.exposed.dao.load import org.jetbrains.exposed.dao.with import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.mapLazy +import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.sourcegrade.lab.hub.domain.DomainEntity import org.sourcegrade.lab.hub.domain.Relation @@ -56,7 +57,7 @@ class EntityConversionContextImpl( statement: ConversionBody, ): T { val ec = EntityConversion(context = this, relations) - return statement(ec).result + return newSuspendedTransaction { statement(ec).result } } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt index 2685062..546cf74 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt @@ -42,6 +42,7 @@ import org.sourcegrade.lab.hub.domain.Assignment import org.sourcegrade.lab.hub.domain.AssignmentCollection import org.sourcegrade.lab.hub.domain.DomainEntityCollection import org.sourcegrade.lab.hub.domain.MutableAssignment +import org.sourcegrade.lab.hub.domain.Relation import org.sourcegrade.lab.hub.domain.SizedIterableCollection import org.sourcegrade.lab.hub.domain.repo.MutableAssignmentRepository import org.sourcegrade.lab.hub.domain.repo.MutableRepository @@ -87,7 +88,7 @@ internal class DBAssignmentRepository( override suspend fun findAll(): AssignmentCollection = DBAssignmentCollection { DBAssignment.all().bindIterable() } - override suspend fun create(item: Assignment.CreateDto): Assignment = newSuspendedTransaction { + override suspend fun create(item: Assignment.CreateDto, relations: List>): Assignment = newSuspendedTransaction { DBAssignment.new { course = DBCourse.findByIdNotNull(item.courseId) submissionGroupCategory = DBSubmissionGroupCategory.findByIdNotNull(item.submissionGroupCategoryId) @@ -97,7 +98,7 @@ internal class DBAssignmentRepository( } } - override suspend fun put(item: Assignment.CreateDto): MutableRepository.PutResult { + override suspend fun put(item: Assignment.CreateDto, relations: List>): MutableRepository.PutResult { TODO("Not yet implemented") } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt index 891959f..229ef2f 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt @@ -90,22 +90,27 @@ internal class DBUserRepository( return result } - override suspend fun create(item: User.CreateDto): User = entityConversion(emptyList()) { + override suspend fun create(item: User.CreateDto, relations: List>): User = entityConversion(relations) { DBUser.new { email = item.email username = item.username - if (item.displayname is OptionalInput.Defined) { - displayname = requireNotNull(item.displayname.value) { "displayname" } + displayname = if (item.displayname is OptionalInput.Defined) { + requireNotNull(item.displayname.value) { "displayname" } + } else { + item.username } }.also { logger.info("Created new user ${it.uuid} with data $item") }.bind() } - override suspend fun put(item: User.CreateDto): MutableRepository.PutResult = newSuspendedTransaction { - val existingUser = findByUsername(item.username) + override suspend fun put( + item: User.CreateDto, + relations: List>, + ): MutableRepository.PutResult = newSuspendedTransaction { + val existingUser = findByUsername(item.username, relations) if (existingUser == null) { - MutableRepository.PutResult(create(item), created = true) + MutableRepository.PutResult(create(item, relations), created = true) } else { logger.info( "Loaded existing user ${existingUser.username} (${existingUser.uuid}) with" + diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt index 56f6eb0..eb179ff 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt @@ -21,11 +21,12 @@ package org.sourcegrade.lab.hub.domain.repo import com.expediagroup.graphql.generator.annotations.GraphQLIgnore import org.sourcegrade.lab.hub.domain.Creates import org.sourcegrade.lab.hub.domain.DomainEntity +import org.sourcegrade.lab.hub.domain.Relation @GraphQLIgnore interface MutableRepository> : Repository { - suspend fun create(item: C): E - suspend fun put(item: C): PutResult + suspend fun create(item: C, relations: List> = emptyList()): E + suspend fun put(item: C, relations: List> = emptyList()): PutResult data class PutResult(val entity: E, val created: Boolean) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt index d18f6d9..87ad265 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt @@ -23,9 +23,9 @@ import com.expediagroup.graphql.server.operations.Mutation import com.expediagroup.graphql.server.operations.Query import graphql.schema.DataFetchingEnvironment import org.apache.logging.log4j.Logger -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserCollection +import org.sourcegrade.lab.hub.domain.repo.MutableRepository import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository import org.sourcegrade.lab.hub.domain.repo.UserRepository import java.util.UUID @@ -43,38 +43,34 @@ class UserQuery( private val repository: UserRepository, ) { suspend fun findAll(): UserCollection = repository.findAll() - - suspend fun findById(dfe: DataFetchingEnvironment, id: UUID): User? = newSuspendedTransaction { - repository.findById(id, dfe.extractRelations()) - } - + suspend fun findById(dfe: DataFetchingEnvironment, id: UUID): User? = repository.findById(id, dfe.extractRelations()) suspend fun deleteById(id: UUID): Boolean = repository.deleteById(id) suspend fun exists(id: UUID): Boolean = repository.exists(id) suspend fun countAll(): Long = repository.countAll() suspend fun findByUsername(dfe: DataFetchingEnvironment, username: String): User? = - repository.findByUsername(username, relations = dfe.extractRelations()) + repository.findByUsername(username, dfe.extractRelations()) - suspend fun findAllByUsername(dfe: DataFetchingEnvironment, partialUsername: String): UserCollection { - return repository.findAllByUsername(partialUsername) - } + suspend fun findAllByUsername(dfe: DataFetchingEnvironment, partialUsername: String): UserCollection = + repository.findAllByUsername(partialUsername) } class UserMutations( private val logger: Logger, private val repository: MutableUserRepository, ) : Mutation { - fun hello2(): String = "Hello, World!" -// fun user(): UserMutation = UserMutation(logger, repository) + fun user(): UserMutation = UserMutation(logger, repository) } -//@GraphQLDescription("Mutation user collection") -//class UserMutation( -// private val logger: Logger, -// private val repository: MutableUserRepository, -//) { -//// suspend fun create(dfe: DataFetchingEnvironment, item: User.CreateDto): User.Snapshot { -//// logger.info("SelectionSet: ${dfe.selectionSet.fields.joinToString { it.name }}") -//// return repository.create(item) -//// } -//} +@GraphQLDescription("Mutation user collection") +class UserMutation( + private val logger: Logger, + private val repository: MutableUserRepository, +) { + suspend fun create(dfe: DataFetchingEnvironment, item: User.CreateDto): User = repository.create(item, dfe.extractRelations()) + suspend fun put(dfe: DataFetchingEnvironment, item: User.CreateDto): PutResult = repository.put(item, dfe.extractRelations()).convert() + + data class PutResult(val entity: User, val created: Boolean) + + private fun MutableRepository.PutResult.convert(): PutResult = PutResult(entity, created) +} From d2a027218f8211601b39cf838a7e2c2bd488dbe7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Tue, 9 Jul 2024 11:35:00 +0200 Subject: [PATCH 27/29] Remove recursion for list query --- .../lab/hub/db/EntityConversionContext.kt | 5 ++- .../lab/hub/db/assignment/Assignments.kt | 9 ++--- .../org/sourcegrade/lab/hub/db/user/Users.kt | 12 +++---- .../sourcegrade/lab/hub/domain/Assignment.kt | 2 -- .../org/sourcegrade/lab/hub/domain/Course.kt | 3 -- .../lab/hub/domain/DomainEntityCollection.kt | 36 +++++++------------ .../org/sourcegrade/lab/hub/domain/User.kt | 3 -- .../hub/domain/repo/CollectionRepository.kt | 5 ++- .../org/sourcegrade/lab/hub/graphql/User.kt | 11 +++++- 9 files changed, 40 insertions(+), 46 deletions(-) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt index 62c1e00..16de4e8 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt @@ -86,7 +86,10 @@ class EntityConversion( BindResult((if (relationArray.isNotEmpty()) with(*relationArray) else this).mapLazy { it.createSnapshot() }) @EntityConversionDsl - fun T.bindT(): BindResult = BindResult(this) + fun SizedIterable.bindToList(): BindResult> = bindIterable().result.toList().bindNoop() + + @EntityConversionDsl + fun T.bindNoop(): BindResult = BindResult(this) class BindResult(val result: T) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt index 546cf74..34e7bca 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt @@ -86,7 +86,8 @@ internal class DBAssignmentRepository( DBAssignment.find { Assignments.courseId eq courseId } } - override suspend fun findAll(): AssignmentCollection = DBAssignmentCollection { DBAssignment.all().bindIterable() } + override suspend fun findAll(limit: DomainEntityCollection.Limit?, orders: List): AssignmentCollection = + DBAssignmentCollection(limit, orders) { DBAssignment.all().bindIterable() } override suspend fun create(item: Assignment.CreateDto, relations: List>): Assignment = newSuspendedTransaction { DBAssignment.new { @@ -104,9 +105,9 @@ internal class DBAssignmentRepository( } internal class DBAssignmentCollection( - private val limit: Pair? = null, - private val orders: List = emptyList(), + private val limit: DomainEntityCollection.Limit?, + private val orders: List, private val body: ConversionBody>, ) : AssignmentCollection, DomainEntityCollection - by SizedIterableCollection(Assignments, conversionContext, ::DBAssignmentCollection, limit, orders, body) + by SizedIterableCollection(Assignments, conversionContext, limit, orders, body) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt index 229ef2f..4ea7f97 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt @@ -83,12 +83,8 @@ internal class DBUserRepository( DBUser.find { Users.email eq email }.firstOrNull().bindNullable() } - override suspend fun findAll(): UserCollection { - println("findAll start: ${Thread.currentThread().name}") - val result = DBUserCollection { DBUser.all().bindIterable() } - println("findAll end: ${Thread.currentThread().name}") - return result - } + override suspend fun findAll(limit: DomainEntityCollection.Limit?, orders: List): UserCollection = + DBUserCollection(limit, orders) { DBUser.all().bindIterable() } override suspend fun create(item: User.CreateDto, relations: List>): User = entityConversion(relations) { DBUser.new { @@ -122,9 +118,9 @@ internal class DBUserRepository( } internal class DBUserCollection( - private val limit: Pair? = null, + private val limit: DomainEntityCollection.Limit? = null, private val orders: List = emptyList(), private val body: ConversionBody>, ) : UserCollection, DomainEntityCollection - by SizedIterableCollection(Users, conversionContext, ::DBUserCollection, limit, orders, body) + by SizedIterableCollection(Users, conversionContext, limit, orders, body) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt index 64ee65f..5e7899e 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -47,8 +47,6 @@ interface MutableAssignment : Assignment { } interface AssignmentCollection : DomainEntityCollection { - override fun limit(num: Int, offset: Long): AssignmentCollection - override fun orderBy(orders: List): AssignmentCollection override suspend fun count(): Long override suspend fun empty(): Boolean diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt index 221853d..064b7e3 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt @@ -45,9 +45,6 @@ interface MutableCourse : Course { } interface CourseCollection : DomainEntityCollection { - override fun limit(num: Int, offset: Long): CourseCollection - override fun page(page: Int, pageSize: Int): CourseCollection - override fun orderBy(orders: List): CourseCollection override suspend fun count(): Long override suspend fun empty(): Boolean diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt index 9358621..c5eadb4 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt @@ -30,19 +30,13 @@ import org.jetbrains.exposed.sql.SortOrder as ExposedSortOrder @GraphQLIgnore interface DomainEntityCollection> { - fun limit(num: Int, offset: Long = 0): C - - fun page(page: Int, pageSize: Int): C = limit(pageSize, (page - 1) * pageSize.toLong()) + suspend fun count(): Long - fun orderBy(orders: List): C + suspend fun empty(): Boolean @GraphQLIgnore suspend fun list(relations: List> = emptyList()): List - suspend fun count(): Long - - suspend fun empty(): Boolean - data class FieldOrdering(val field: String, val sortOrder: SortOrder = SortOrder.DESC) enum class SortOrder(val exposed: ExposedSortOrder) { @@ -53,39 +47,33 @@ interface DomainEntityCollection>( private val table: Table, private val conversionContext: EntityConversionContext, - private val ctor: (Pair?, List, ConversionBody>) -> C, - private val limit: Pair?, + private val limit: DomainEntityCollection.Limit?, private val orders: List, private val body: ConversionBody>, ) : DomainEntityCollection, EntityConversionContext by conversionContext { - override fun limit(num: Int, offset: Long): C = - ctor(limit?.let { (n, o) -> minOf(num, n) to offset + o } ?: (num to offset), orders, body) - - // Note that this is not *technically* correct because nested orderings are not supported - // But its close enough, because it would only be noticeable in an order -> limit -> order scenario - override fun orderBy(orders: List): C = - ctor(limit, orders, body) override suspend fun list(relations: List>): List { println("list start: ${Thread.currentThread().name}") - val result = entityConversion { + val result = entityConversion(relations) { body().result - .let { if (limit != null) it.limit(limit.first, limit.second) else it } + .let { if (limit != null) it.limit(limit.num, limit.offset) else it } .let { if (orders.isNotEmpty()) it.orderBy(table, orders) else it } - .toList().bindT() + .toList().bindNoop() // eager loading is done in body } println("list end: ${Thread.currentThread().name}") return result } - override suspend fun count(): Long = entityConversion { body().result.count().bindT() } + override suspend fun count(): Long = entityConversion { body().result.count().bindNoop() } - override suspend fun empty(): Boolean = entityConversion { body().result.empty().bindT() } + override suspend fun empty(): Boolean = entityConversion { body().result.empty().bindNoop() } } private fun SizedIterable.orderBy( @@ -93,6 +81,8 @@ private fun SizedIterable.orderBy( orders: List, ): SizedIterable = orderBy( *orders.map { order -> - table::class.members.find { it.name == order.field }?.call(table) as Expression<*> to order.sortOrder.exposed + val field = (table::class.members.find { it.name == order.field }?.call(table) as? Expression<*> + ?: throw NoSuchFieldException("Field '${order.field}' does not exist on table '${table::class.simpleName}'")) + field to order.sortOrder.exposed }.toTypedArray(), ) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index 4ec9165..0c0366b 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -41,9 +41,6 @@ interface MutableUser : User { } interface UserCollection : DomainEntityCollection { - override fun limit(num: Int, offset: Long): UserCollection - override fun page(page: Int, pageSize: Int): UserCollection - override fun orderBy(orders: List): UserCollection override suspend fun count(): Long override suspend fun empty(): Boolean diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt index 94e8adb..bf055ef 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt @@ -22,7 +22,10 @@ import org.sourcegrade.lab.hub.domain.DomainEntity import org.sourcegrade.lab.hub.domain.DomainEntityCollection interface CollectionRepository> : Repository { - suspend fun findAll(): C + suspend fun findAll( + limit: DomainEntityCollection.Limit? = null, + orders: List = emptyList(), + ): C } // //data class CollectionParameters( diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt index 87ad265..04f0807 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt @@ -19,10 +19,12 @@ package org.sourcegrade.lab.hub.graphql import com.expediagroup.graphql.generator.annotations.GraphQLDescription +import com.expediagroup.graphql.generator.execution.OptionalInput import com.expediagroup.graphql.server.operations.Mutation import com.expediagroup.graphql.server.operations.Query import graphql.schema.DataFetchingEnvironment import org.apache.logging.log4j.Logger +import org.sourcegrade.lab.hub.domain.DomainEntityCollection import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserCollection import org.sourcegrade.lab.hub.domain.repo.MutableRepository @@ -42,7 +44,14 @@ class UserQuery( private val logger: Logger, private val repository: UserRepository, ) { - suspend fun findAll(): UserCollection = repository.findAll() + suspend fun findAll( + limit: OptionalInput, + orders: OptionalInput>, + ): UserCollection = repository.findAll( + limit = (limit as? OptionalInput.Defined)?.value, + orders = (orders as? OptionalInput.Defined)?.value ?: emptyList(), + ) + suspend fun findById(dfe: DataFetchingEnvironment, id: UUID): User? = repository.findById(id, dfe.extractRelations()) suspend fun deleteById(id: UUID): Boolean = repository.deleteById(id) suspend fun exists(id: UUID): Boolean = repository.exists(id) From 522568a8f665478ebdb6b99e0529bb4d90ff8f3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Tue, 9 Jul 2024 11:50:05 +0200 Subject: [PATCH 28/29] Clean up Users class --- .../main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt index 4ea7f97..5cc0053 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt @@ -71,17 +71,13 @@ internal class DBUserRepository( EntityConversionContext by conversionContext { override suspend fun findByUsername(username: String, relations: List>): User? = - entityConversion { - DBUser.find { Users.username eq username }.firstOrNull().bindNullable() - } + entityConversion { DBUser.find { Users.username eq username }.firstOrNull().bindNullable() } override suspend fun findAllByUsername(partialUsername: String): UserCollection = DBUserCollection { DBUser.find { Users.username like "%$partialUsername%" }.bindIterable() } override suspend fun findByEmail(email: String, relations: List>): User? = - entityConversion { - DBUser.find { Users.email eq email }.firstOrNull().bindNullable() - } + entityConversion { DBUser.find { Users.email eq email }.firstOrNull().bindNullable() } override suspend fun findAll(limit: DomainEntityCollection.Limit?, orders: List): UserCollection = DBUserCollection(limit, orders) { DBUser.all().bindIterable() } From 874e9e877288f354face87aeb3b6a9d359f0df12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alexander=20St=C3=A4ding?= Date: Tue, 9 Jul 2024 21:49:49 +0200 Subject: [PATCH 29/29] More work --- .../kotlin/org/sourcegrade/lab/hub/Main.kt | 2 +- .../kotlin/org/sourcegrade/lab/hub/Module.kt | 32 ++---- .../org/sourcegrade/lab/hub/db/DBModule.kt | 57 ++++++++-- .../lab/hub/db/EntityConversionContext.kt | 1 + .../lab/hub/db/ReferenceMutatorDelegate.kt | 2 +- .../lab/hub/db/SubmissionGroupCategories.kt | 72 +++++++++++- .../lab/hub/db/UUIDEntityClassRepository.kt | 2 +- .../lab/hub/db/assignment/Assignments.kt | 103 +++++++++++++---- .../lab/hub/db/course/CourseSnapshot.kt | 62 +++++++---- .../sourcegrade/lab/hub/db/course/Courses.kt | 99 ++++++++++++++--- .../sourcegrade/lab/hub/db/user/UserOps.kt | 63 ++++++----- .../lab/hub/db/user/UserSnapshot.kt | 36 +++++- .../org/sourcegrade/lab/hub/db/user/Users.kt | 38 ++++--- .../sourcegrade/lab/hub/domain/Assignment.kt | 28 ++++- .../domain/{repo => }/CollectionRepository.kt | 5 +- .../org/sourcegrade/lab/hub/domain/Course.kt | 29 ++++- .../lab/hub/domain/DomainEntityCollection.kt | 1 + .../domain/{repo => }/MutableRepository.kt | 5 +- .../lab/hub/domain/{repo => }/Repository.kt | 4 +- .../sourcegrade/lab/hub/domain/Submission.kt | 7 ++ .../lab/hub/domain/SubmissionGroupCategory.kt | 29 +++++ .../org/sourcegrade/lab/hub/domain/Term.kt | 40 +++++-- .../org/sourcegrade/lab/hub/domain/User.kt | 19 +++- .../lab/hub/domain/UserMembership.kt | 13 +++ .../hub/domain/repo/AssignmentRepository.kt | 31 ------ .../lab/hub/domain/repo/CourseRepository.kt | 38 ------- .../hub/domain/repo/SubmissionRepository.kt | 30 ----- .../domain/repo/UserMembershipRepository.kt | 37 ------ .../lab/hub/domain/repo/UserRepository.kt | 34 ------ .../lab/hub/graphql/AssignmentQueries.kt | 105 ++++++++++++++++++ .../lab/hub/graphql/CourseQueries.kt | 75 +++++++++++++ .../lab/hub/graphql/RelationOps.kt | 22 +++- .../lab/hub/graphql/TermQueries.kt | 62 +++++++++++ .../hub/graphql/{User.kt => UserQueries.kt} | 19 ++-- .../lab/hub/http/AuthenticationModule.kt | 6 +- 35 files changed, 853 insertions(+), 355 deletions(-) rename hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/{repo => }/CollectionRepository.kt (92%) rename hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/{repo => }/MutableRepository.kt (86%) rename hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/{repo => }/Repository.kt (88%) delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CourseRepository.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt delete mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/AssignmentQueries.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/CourseQueries.kt create mode 100644 hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/TermQueries.kt rename hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/{User.kt => UserQueries.kt} (81%) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt index 1577283..44c51cd 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Main.kt @@ -22,5 +22,5 @@ import io.ktor.server.engine.embeddedServer import io.ktor.server.netty.Netty fun main() { - embeddedServer(Netty, port = 8080) { module() }.start(wait = true) + embeddedServer(Netty, port = 7500) { module() }.start(wait = true) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt index e409bf7..683e73a 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/Module.kt @@ -24,26 +24,7 @@ import com.expediagroup.graphql.server.ktor.graphQLGetRoute import com.expediagroup.graphql.server.ktor.graphQLPostRoute import com.expediagroup.graphql.server.ktor.graphQLSDLRoute import com.expediagroup.graphql.server.ktor.graphiQLRoute -import graphql.ExecutionInput -import graphql.ExecutionResult -import graphql.execution.ExecutionContext -import graphql.execution.instrumentation.DocumentAndVariables -import graphql.execution.instrumentation.ExecutionStrategyInstrumentationContext -import graphql.execution.instrumentation.Instrumentation -import graphql.execution.instrumentation.InstrumentationContext -import graphql.execution.instrumentation.InstrumentationState -import graphql.execution.instrumentation.parameters.InstrumentationExecuteOperationParameters -import graphql.execution.instrumentation.parameters.InstrumentationExecutionParameters -import graphql.execution.instrumentation.parameters.InstrumentationExecutionStrategyParameters -import graphql.execution.instrumentation.parameters.InstrumentationFieldCompleteParameters -import graphql.execution.instrumentation.parameters.InstrumentationFieldFetchParameters -import graphql.execution.instrumentation.parameters.InstrumentationFieldParameters -import graphql.execution.instrumentation.parameters.InstrumentationValidationParameters -import graphql.language.Document -import graphql.schema.DataFetcher -import graphql.schema.GraphQLSchema import graphql.schema.GraphQLType -import graphql.validation.ValidationError import io.ktor.http.HttpHeaders import io.ktor.http.HttpMethod import io.ktor.server.application.Application @@ -76,11 +57,14 @@ import org.sourcegrade.lab.hub.db.Terms import org.sourcegrade.lab.hub.db.assignment.Assignments import org.sourcegrade.lab.hub.db.course.Courses import org.sourcegrade.lab.hub.db.user.Users +import org.sourcegrade.lab.hub.graphql.AssignmentMutations +import org.sourcegrade.lab.hub.graphql.AssignmentQueries +import org.sourcegrade.lab.hub.graphql.CourseMutations +import org.sourcegrade.lab.hub.graphql.CourseQueries import org.sourcegrade.lab.hub.graphql.Scalars import org.sourcegrade.lab.hub.graphql.UserMutations import org.sourcegrade.lab.hub.graphql.UserQueries import org.sourcegrade.lab.hub.http.authenticationModule -import java.util.concurrent.CompletableFuture import kotlin.reflect.KType fun Application.module() { @@ -90,6 +74,10 @@ fun Application.module() { single { LogManager.getLogger("SGL Supervisor") } singleOf(::UserQueries) singleOf(::UserMutations) + singleOf(::AssignmentQueries) + singleOf(::AssignmentMutations) + singleOf(::CourseQueries) + singleOf(::CourseMutations) }, DBModule, ) @@ -148,10 +136,14 @@ fun Application.module() { queries = listOf( inject().value, + inject().value, + inject().value, ) mutations = listOf( inject().value, + inject().value, + inject().value, ) } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DBModule.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DBModule.kt index eb36e64..7a96268 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DBModule.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/DBModule.kt @@ -19,22 +19,61 @@ package org.sourcegrade.lab.hub.db import org.koin.core.module.dsl.bind -import org.koin.core.module.dsl.singleOf +import org.koin.core.module.dsl.named +import org.koin.core.module.dsl.withOptions +import org.koin.core.qualifier.named import org.koin.dsl.module +import org.sourcegrade.lab.hub.db.assignment.AssignmentSnapshot +import org.sourcegrade.lab.hub.db.assignment.DBAssignment import org.sourcegrade.lab.hub.db.assignment.DBAssignmentRepository +import org.sourcegrade.lab.hub.db.course.CourseSnapshot +import org.sourcegrade.lab.hub.db.course.DBCourse +import org.sourcegrade.lab.hub.db.course.DBCourseRepository +import org.sourcegrade.lab.hub.db.user.DBUser import org.sourcegrade.lab.hub.db.user.DBUserRepository -import org.sourcegrade.lab.hub.domain.repo.AssignmentRepository -import org.sourcegrade.lab.hub.domain.repo.MutableAssignmentRepository -import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository -import org.sourcegrade.lab.hub.domain.repo.UserRepository +import org.sourcegrade.lab.hub.db.user.UserSnapshot +import org.sourcegrade.lab.hub.domain.Assignment +import org.sourcegrade.lab.hub.domain.AssignmentRepository +import org.sourcegrade.lab.hub.domain.Course +import org.sourcegrade.lab.hub.domain.CourseRepository +import org.sourcegrade.lab.hub.domain.MutableAssignmentRepository +import org.sourcegrade.lab.hub.domain.MutableCourseRepository +import org.sourcegrade.lab.hub.domain.MutableUserRepository +import org.sourcegrade.lab.hub.domain.User +import org.sourcegrade.lab.hub.domain.UserRepository val DBModule = module { - singleOf(::DBAssignmentRepository) { - bind() - bind() + + single> { + EntityConversionContextImpl { user, relations -> UserSnapshot.of(user, relations, get()) } + }.withOptions { + named("user") + } + + single> { + EntityConversionContextImpl(AssignmentSnapshot::of) + }.withOptions { + named("assignment") + } + + single> { + EntityConversionContextImpl { course, relations -> CourseSnapshot.of(course, relations) } + }.withOptions { + named("course") } - singleOf(::DBUserRepository) { + + single { DBUserRepository(get(), get(named("user"))) }.withOptions { bind() bind() } + + single { DBCourseRepository(get(), get(named("course"))) }.withOptions { + bind() + bind() + } + + single { DBAssignmentRepository(get(), get(named("assignment"))) }.withOptions { + bind() + bind() + } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt index 16de4e8..c1cd7c8 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/EntityConversionContext.kt @@ -48,6 +48,7 @@ class EntityConversionContextImpl( override fun createSnapshot(entity: N, relations: Set): E = snapshotFun(entity, relations) override fun convertRelation(relation: Relation): Relation { + @Suppress("UNCHECKED_CAST") return (relation as Relation) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutatorDelegate.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutatorDelegate.kt index 8cc0fe6..d5a8aa0 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutatorDelegate.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/ReferenceMutatorDelegate.kt @@ -43,4 +43,4 @@ internal suspend inline fun , reified E : Entity> KMutab } internal inline fun , reified E : Entity> EntityClass.findByIdNotNull(id: ID): E = - findById(id) ?: error("${E::class.simpleName} $id not found") + findById(id) ?: error("${E::class.simpleName} with uuid $id not found") diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt index c57255d..0ef86ee 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/SubmissionGroupCategories.kt @@ -21,14 +21,23 @@ package org.sourcegrade.lab.hub.db import com.expediagroup.graphql.generator.annotations.GraphQLIgnore import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import org.apache.logging.log4j.Logger import org.jetbrains.exposed.dao.EntityClass import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable +import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp import org.sourcegrade.lab.hub.db.course.Courses import org.sourcegrade.lab.hub.db.course.DBCourse +import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.MutableRepository +import org.sourcegrade.lab.hub.domain.MutableSubmissionGroupCategoryRepository +import org.sourcegrade.lab.hub.domain.Relation +import org.sourcegrade.lab.hub.domain.Repository +import org.sourcegrade.lab.hub.domain.SizedIterableCollection import org.sourcegrade.lab.hub.domain.SubmissionGroupCategory +import org.sourcegrade.lab.hub.domain.SubmissionGroupCategoryCollection import java.util.UUID internal object SubmissionGroupCategories : UUIDTable("sgl_submission_group_categories") { @@ -43,10 +52,71 @@ internal object SubmissionGroupCategories : UUIDTable("sgl_submission_group_cate internal class DBSubmissionGroupCategory(id: EntityID) : UUIDEntity(id), SubmissionGroupCategory { override val uuid: UUID = id.value override val createdUtc: Instant by SubmissionGroupCategories.createdUtc - override val course: DBCourse by DBCourse referencedOn SubmissionGroupCategories.courseId + override var course: DBCourse by DBCourse referencedOn SubmissionGroupCategories.courseId override var name: String by SubmissionGroupCategories.name override var minSize: Int by SubmissionGroupCategories.minSize override var maxSize: Int by SubmissionGroupCategories.maxSize companion object : EntityClass(SubmissionGroupCategories) } + +internal class SubmissionGroupCategoryRepository( + private val logger: Logger, + private val conversionContext: EntityConversionContext, +) : MutableSubmissionGroupCategoryRepository, + Repository by UUIDEntityClassRepository(DBSubmissionGroupCategory, conversionContext), + EntityConversionContext by conversionContext { + + override suspend fun findByName(name: String, relations: List>): SubmissionGroupCategory? = + entityConversion(relations) { + DBSubmissionGroupCategory.find { SubmissionGroupCategories.name eq name }.firstOrNull().bindNullable() + } + + override suspend fun findAllByName( + partialName: String, + limit: DomainEntityCollection.Limit?, + orders: List, + ): SubmissionGroupCategoryCollection = + DBSubmissionGroupCategoryCollection(conversionContext, limit, orders) { + DBSubmissionGroupCategory.find { SubmissionGroupCategories.name like "%$partialName%" }.bindIterable() + } + + override suspend fun findAll( + limit: DomainEntityCollection.Limit?, + orders: List, + ): SubmissionGroupCategoryCollection = + DBSubmissionGroupCategoryCollection(conversionContext, limit, orders) { + DBSubmissionGroupCategory.all().bindIterable() + } + + override suspend fun create( + item: SubmissionGroupCategory.CreateDto, + relations: List>, + ): SubmissionGroupCategory = entityConversion(relations) { + val itemCourse = DBCourse.findByIdNotNull(item.courseUuid) + DBSubmissionGroupCategory.new { + course = itemCourse + name = item.name + minSize = item.minSize + maxSize = item.maxSize + }.also { + logger.info("Created new SubmissionGroupCategory ${it.id} with data $item") + }.bind() + } + + override suspend fun put( + item: SubmissionGroupCategory.CreateDto, + relations: List>, + ): MutableRepository.PutResult { + TODO("Not yet implemented") + } +} + +internal class DBSubmissionGroupCategoryCollection( + private val conversionContext: EntityConversionContext, + private val limit: DomainEntityCollection.Limit?, + private val orders: List, + private val body: ConversionBody>, +) : SubmissionGroupCategoryCollection, + DomainEntityCollection + by SizedIterableCollection(SubmissionGroupCategories, conversionContext, limit, orders, body) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt index 81bcaf6..19aebb5 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/UUIDEntityClassRepository.kt @@ -23,7 +23,7 @@ import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.sourcegrade.lab.hub.domain.DomainEntity import org.sourcegrade.lab.hub.domain.Relation -import org.sourcegrade.lab.hub.domain.repo.Repository +import org.sourcegrade.lab.hub.domain.Repository import java.util.UUID internal class UUIDEntityClassRepository( diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt index 34e7bca..37eca04 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/assignment/Assignments.kt @@ -27,26 +27,34 @@ import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable +import org.jetbrains.exposed.sql.SortOrder +import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.kotlin.datetime.timestamp -import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction +import org.jetbrains.exposed.sql.mapLazy +import org.jetbrains.exposed.sql.selectAll import org.sourcegrade.lab.hub.db.ConversionBody -import org.sourcegrade.lab.hub.db.course.Courses -import org.sourcegrade.lab.hub.db.course.DBCourse +import org.sourcegrade.lab.hub.db.CourseMemberships import org.sourcegrade.lab.hub.db.DBSubmissionGroupCategory import org.sourcegrade.lab.hub.db.EntityConversionContext -import org.sourcegrade.lab.hub.db.EntityConversionContextImpl import org.sourcegrade.lab.hub.db.SubmissionGroupCategories +import org.sourcegrade.lab.hub.db.Terms import org.sourcegrade.lab.hub.db.UUIDEntityClassRepository +import org.sourcegrade.lab.hub.db.course.Courses +import org.sourcegrade.lab.hub.db.course.DBCourse import org.sourcegrade.lab.hub.db.findByIdNotNull +import org.sourcegrade.lab.hub.db.user.membershipStatusPredicate import org.sourcegrade.lab.hub.domain.Assignment import org.sourcegrade.lab.hub.domain.AssignmentCollection import org.sourcegrade.lab.hub.domain.DomainEntityCollection import org.sourcegrade.lab.hub.domain.MutableAssignment +import org.sourcegrade.lab.hub.domain.MutableAssignmentRepository +import org.sourcegrade.lab.hub.domain.MutableRepository import org.sourcegrade.lab.hub.domain.Relation +import org.sourcegrade.lab.hub.domain.Repository import org.sourcegrade.lab.hub.domain.SizedIterableCollection -import org.sourcegrade.lab.hub.domain.repo.MutableAssignmentRepository -import org.sourcegrade.lab.hub.domain.repo.MutableRepository -import org.sourcegrade.lab.hub.domain.repo.Repository +import org.sourcegrade.lab.hub.domain.Term +import org.sourcegrade.lab.hub.domain.UserMembership +import org.sourcegrade.lab.hub.domain.termPredicate import java.util.UUID internal object Assignments : UUIDTable("sgl_assignments") { @@ -76,35 +84,82 @@ internal class DBAssignment(id: EntityID) : UUIDEntity(id), MutableAssignm companion object : EntityClass(Assignments) } -private val conversionContext = EntityConversionContextImpl(AssignmentSnapshot::of) - internal class DBAssignmentRepository( private val logger: Logger, + private val conversionContext: EntityConversionContext, ) : MutableAssignmentRepository, Repository by UUIDEntityClassRepository(DBAssignment, conversionContext), EntityConversionContext by conversionContext { - override suspend fun findByCourse(courseId: UUID): SizedIterable = newSuspendedTransaction { - DBAssignment.find { Assignments.courseId eq courseId } - } + override suspend fun findAllByCourse( + courseId: UUID, + limit: DomainEntityCollection.Limit?, + orders: List, + ): AssignmentCollection = + DBAssignmentCollection(conversionContext, limit, orders) { DBAssignment.find { Assignments.courseId eq courseId }.bindIterable() } - override suspend fun findAll(limit: DomainEntityCollection.Limit?, orders: List): AssignmentCollection = - DBAssignmentCollection(limit, orders) { DBAssignment.all().bindIterable() } + override suspend fun findAllByName( + partialName: String, + limit: DomainEntityCollection.Limit?, + orders: List, + ): AssignmentCollection = + DBAssignmentCollection(conversionContext, limit, orders) { + DBAssignment.find { Assignments.name like "%$partialName%" }.bindIterable() + } - override suspend fun create(item: Assignment.CreateDto, relations: List>): Assignment = newSuspendedTransaction { - DBAssignment.new { - course = DBCourse.findByIdNotNull(item.courseId) - submissionGroupCategory = DBSubmissionGroupCategory.findByIdNotNull(item.submissionGroupCategoryId) - name = item.name - description = item.description - submissionDeadlineUtc = item.submissionDeadlineUtc + override suspend fun findAllByUser( + userId: UUID, + term: Term.Matcher, + now: Instant, + limit: DomainEntityCollection.Limit?, + orders: List, + ): AssignmentCollection = + DBAssignmentCollection(conversionContext, limit, orders) { + Assignments.innerJoin(Courses).innerJoin(Terms).innerJoin(CourseMemberships).selectAll() + .where { + (CourseMemberships.userId eq userId) + .and(termPredicate(term, now)) + .and(membershipStatusPredicate(UserMembership.UserMembershipStatus.CURRENT, now)) + } + .mapLazy { DBAssignment.wrapRow(it) } + .orderBy(Assignments.submissionDeadline to SortOrder.DESC) + .bindIterable() } - } - override suspend fun put(item: Assignment.CreateDto, relations: List>): MutableRepository.PutResult { - TODO("Not yet implemented") + override suspend fun findAll( + limit: DomainEntityCollection.Limit?, + orders: List, + ): AssignmentCollection = + DBAssignmentCollection(conversionContext, limit, orders) { DBAssignment.all().bindIterable() } + + override suspend fun create(item: Assignment.CreateAssignmentDto, relations: List>): Assignment = + entityConversion(relations) { + DBAssignment.new { + course = DBCourse.findByIdNotNull(item.courseId) + submissionGroupCategory = DBSubmissionGroupCategory.findByIdNotNull(item.submissionGroupCategoryId) + name = item.name + description = item.description + submissionDeadlineUtc = item.submissionDeadlineUtc + }.also { + logger.info("Created new Assignment ${it.uuid} with data $item") + }.bind() + } + + override suspend fun put( + item: Assignment.CreateAssignmentDto, + relations: List>, + ): MutableRepository.PutResult { + val existingAssignment = + DBAssignment.find { (Assignments.courseId eq item.courseId) and (Assignments.name eq item.name) }.firstOrNull() + return if (existingAssignment == null) { + MutableRepository.PutResult(create(item, relations), created = true) + } else { + logger.info("Loaded existing assignment ${existingAssignment.name} (${existingAssignment.uuid})") + MutableRepository.PutResult(existingAssignment, created = false) + } } } internal class DBAssignmentCollection( + private val conversionContext: EntityConversionContext, private val limit: DomainEntityCollection.Limit?, private val orders: List, private val body: ConversionBody>, diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/CourseSnapshot.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/CourseSnapshot.kt index 82fd1c7..9ac8be7 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/CourseSnapshot.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/CourseSnapshot.kt @@ -20,26 +20,50 @@ package org.sourcegrade.lab.hub.db.course import kotlinx.datetime.Instant import org.sourcegrade.lab.hub.db.RelationOption +import org.sourcegrade.lab.hub.domain.AssignmentCollection +import org.sourcegrade.lab.hub.domain.Course +import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.SubmissionGroupCategoryCollection import org.sourcegrade.lab.hub.domain.Term import org.sourcegrade.lab.hub.domain.User import java.util.UUID -//internal class CourseSnapshot( -// uuidOption: RelationOption, -// createdUtcOption: RelationOption, -// termOption: RelationOption, -// submissionGroupCategoriesOption: RelationOption, -// displaynameOption: RelationOption, -//) : User { -// -// override val uuid: UUID by uuidOption -// override val createdUtc: Instant by createdUtcOption -// override val email: String by emailOption -// override val username: String by usernameOption -// override val displayname: String by displaynameOption -// -// companion object { -// fun of(user: User, relations: Set): CourseSnapshot = with(relations) { +internal class CourseSnapshot( + uuidOption: RelationOption, + createdUtcOption: RelationOption, + termOption: RelationOption, + submissionGroupCategoriesOption: RelationOption, + displaynameOption: RelationOption, +) : Course { + + override val uuid: UUID by uuidOption + override val createdUtc: Instant by createdUtcOption + override val term: Term by termOption + + override val owner: User + get() = TODO("Not yet implemented") + override val name: String + get() = TODO("Not yet implemented") + override val description: String + get() = TODO("Not yet implemented") + + override fun submissionGroupCategories( + limit: DomainEntityCollection.Limit?, + orders: List, + ): SubmissionGroupCategoryCollection { + TODO("Not yet implemented") + } + + override fun assignments( + limit: DomainEntityCollection.Limit?, + orders: List, + ): AssignmentCollection { + TODO("Not yet implemented") + } + + companion object { + fun of(user: Course, relations: Set): CourseSnapshot = with(relations) { + TODO() // CourseSnapshot( // RelationOption.of(user::uuid), // RelationOption.of(user::createdUtc), @@ -47,6 +71,6 @@ import java.util.UUID // RelationOption.of(user::username), // RelationOption.of(user::displayname), // ) -// } -// } -//} + } + } +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/Courses.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/Courses.kt index 4bc98b7..146d76b 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/Courses.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/course/Courses.kt @@ -19,6 +19,7 @@ package org.sourcegrade.lab.hub.db.course import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import com.expediagroup.graphql.generator.execution.OptionalInput import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.apache.logging.log4j.Logger @@ -28,21 +29,31 @@ import org.jetbrains.exposed.dao.id.EntityID import org.jetbrains.exposed.dao.id.UUIDTable import org.jetbrains.exposed.sql.SizedIterable import org.jetbrains.exposed.sql.kotlin.datetime.timestamp +import org.sourcegrade.lab.hub.db.ConversionBody import org.sourcegrade.lab.hub.db.DBSubmissionGroupCategory +import org.sourcegrade.lab.hub.db.DBSubmissionGroupCategoryCollection import org.sourcegrade.lab.hub.db.DBTerm -import org.sourcegrade.lab.hub.db.EntityConversionContextImpl +import org.sourcegrade.lab.hub.db.EntityConversionContext import org.sourcegrade.lab.hub.db.SubmissionGroupCategories import org.sourcegrade.lab.hub.db.Terms import org.sourcegrade.lab.hub.db.UUIDEntityClassRepository import org.sourcegrade.lab.hub.db.assignment.Assignments import org.sourcegrade.lab.hub.db.assignment.DBAssignment +import org.sourcegrade.lab.hub.db.findByIdNotNull import org.sourcegrade.lab.hub.db.user.DBUser import org.sourcegrade.lab.hub.db.user.Users import org.sourcegrade.lab.hub.domain.Assignment +import org.sourcegrade.lab.hub.domain.AssignmentCollection import org.sourcegrade.lab.hub.domain.Course -import org.sourcegrade.lab.hub.domain.Term -import org.sourcegrade.lab.hub.domain.repo.MutableCourseRepository -import org.sourcegrade.lab.hub.domain.repo.Repository +import org.sourcegrade.lab.hub.domain.CourseCollection +import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.MutableCourse +import org.sourcegrade.lab.hub.domain.MutableCourseRepository +import org.sourcegrade.lab.hub.domain.MutableRepository +import org.sourcegrade.lab.hub.domain.Relation +import org.sourcegrade.lab.hub.domain.Repository +import org.sourcegrade.lab.hub.domain.SizedIterableCollection +import org.sourcegrade.lab.hub.domain.SubmissionGroupCategoryCollection import java.util.UUID internal object Courses : UUIDTable("sgl_courses") { @@ -54,23 +65,85 @@ internal object Courses : UUIDTable("sgl_courses") { } @GraphQLIgnore -internal class DBCourse(id: EntityID) : UUIDEntity(id), Course { +internal class DBCourse(id: EntityID) : UUIDEntity(id), MutableCourse { override val uuid: UUID = id.value override val createdUtc: Instant by Courses.createdUtc - override val term: Term by DBTerm referencedOn Courses.term - override val submissionGroupCategories: SizedIterable by DBSubmissionGroupCategory referrersOn SubmissionGroupCategories.courseId - override val assignments: SizedIterable by DBAssignment referrersOn Assignments.courseId - override val owner: DBUser by DBUser referencedOn Courses.ownerId // TODO: Multiple owners + override var term: DBTerm by DBTerm referencedOn Courses.term + val submissionGroupCategories: SizedIterable by DBSubmissionGroupCategory referrersOn SubmissionGroupCategories.courseId + val assignments: SizedIterable by DBAssignment referrersOn Assignments.courseId + override var owner: DBUser by DBUser referencedOn Courses.ownerId // TODO: Multiple owners override var name: String by Courses.name override var description: String by Courses.description + override fun submissionGroupCategories( + limit: DomainEntityCollection.Limit?, + orders: List, + ): SubmissionGroupCategoryCollection { + TODO() +// return DBSubmissionGroupCategoryCollection(conversionContext) { +// submissionGroupCategories.bindIterable() +// } + } + + override fun assignments( + limit: DomainEntityCollection.Limit?, + orders: List, + ): AssignmentCollection { + TODO("Not yet implemented") + } + companion object : EntityClass(Courses) } +internal class DBCourseRepository( + private val logger: Logger, + private val conversionContext: EntityConversionContext, +) : MutableCourseRepository, Repository by UUIDEntityClassRepository(DBCourse, conversionContext), + EntityConversionContext by conversionContext { + override suspend fun findAll( + limit: DomainEntityCollection.Limit?, + orders: List, + ): CourseCollection = DBCourseCollection(conversionContext, limit, orders) { DBCourse.all().bindIterable() } + + override suspend fun findByName(name: String, relations: List>): Course? = + entityConversion(relations) { DBCourse.find { Courses.name eq name }.firstOrNull().bindNullable() } + + override suspend fun findAllByName(partialName: String): CourseCollection = + DBCourseCollection(conversionContext) { DBCourse.find { Courses.name like "%$partialName%" }.bindIterable() } -//private val conversionContext = EntityConversionContextImpl() + override suspend fun findAllByDescription(partialDescription: String): CourseCollection = + DBCourseCollection(conversionContext) { DBCourse.find { Courses.description like "%$partialDescription%" }.bindIterable() } + + override suspend fun findAllByOwner(ownerId: UUID): CourseCollection = + DBCourseCollection(conversionContext) { DBCourse.find { Courses.ownerId eq ownerId }.bindIterable() } + + override suspend fun create(item: Course.CreateDto, relations: List>): Course = entityConversion(relations) { + val itemOwner = DBUser.findByIdNotNull(item.ownerUuid) + val itemTerm = DBTerm.findByIdNotNull(item.termUuid) + DBCourse.new { + name = item.name + owner = itemOwner + term = itemTerm + description = if (item.description is OptionalInput.Defined) { + requireNotNull(item.description.value) { "description" } + } else { + item.name + } + }.also { + logger.info("Created course ${it.uuid}") + }.bind() + } + + override suspend fun put(item: Course.CreateDto, relations: List>): MutableRepository.PutResult { + TODO("Not yet implemented") + } +} -//internal class DBCourseRepository( -// private val logger: Logger, -//) : MutableCourseRepository, Repository by UUIDEntityClassRepository(DBAssignment) +internal class DBCourseCollection( + private val conversionContext: EntityConversionContext, + private val limit: DomainEntityCollection.Limit? = null, + private val orders: List = emptyList(), + private val body: ConversionBody>, +) : CourseCollection, + DomainEntityCollection by SizedIterableCollection(Courses, conversionContext, limit, orders, body) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserOps.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserOps.kt index a0b9223..7877464 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserOps.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserOps.kt @@ -18,18 +18,17 @@ package org.sourcegrade.lab.hub.db.user +import kotlinx.datetime.Clock import kotlinx.datetime.Instant -import org.jetbrains.exposed.sql.Query +import org.jetbrains.exposed.sql.Op import org.jetbrains.exposed.sql.SizedIterable -import org.jetbrains.exposed.sql.SortOrder +import org.jetbrains.exposed.sql.SqlExpressionBuilder +import org.jetbrains.exposed.sql.and import org.jetbrains.exposed.sql.innerJoin import org.jetbrains.exposed.sql.mapLazy import org.jetbrains.exposed.sql.selectAll import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction -import org.sourcegrade.lab.hub.db.assignment.Assignments import org.sourcegrade.lab.hub.db.CourseMemberships -import org.sourcegrade.lab.hub.db.course.Courses -import org.sourcegrade.lab.hub.db.assignment.DBAssignment import org.sourcegrade.lab.hub.db.DBCourseMembership import org.sourcegrade.lab.hub.db.DBSubmission import org.sourcegrade.lab.hub.db.DBSubmissionGroupMembership @@ -37,8 +36,11 @@ import org.sourcegrade.lab.hub.db.SubmissionGroupMemberships import org.sourcegrade.lab.hub.db.SubmissionGroups import org.sourcegrade.lab.hub.db.Submissions import org.sourcegrade.lab.hub.db.Terms -import org.sourcegrade.lab.hub.domain.Assignment +import org.sourcegrade.lab.hub.db.course.Courses +import org.sourcegrade.lab.hub.domain.AssignmentCollection +import org.sourcegrade.lab.hub.domain.AssignmentRepository import org.sourcegrade.lab.hub.domain.Course +import org.sourcegrade.lab.hub.domain.DomainEntityCollection import org.sourcegrade.lab.hub.domain.Submission import org.sourcegrade.lab.hub.domain.SubmissionGroup import org.sourcegrade.lab.hub.domain.Term @@ -46,24 +48,21 @@ import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserMembership import org.sourcegrade.lab.hub.domain.termPredicate -private fun Query.membershipStatusPredicate(status: UserMembership.UserMembershipStatus, now: Instant): Query = when (status) { - UserMembership.UserMembershipStatus.ALL -> this - UserMembership.UserMembershipStatus.FUTURE -> where { CourseMemberships.startUtc greater now } - UserMembership.UserMembershipStatus.PAST -> where { CourseMemberships.endUtc less now } - UserMembership.UserMembershipStatus.CURRENT -> where { CourseMemberships.endUtc.isNull() } -} +internal fun SqlExpressionBuilder.membershipStatusPredicate(status: UserMembership.UserMembershipStatus, now: Instant): Op = + when (status) { + UserMembership.UserMembershipStatus.ALL -> Op.TRUE + UserMembership.UserMembershipStatus.FUTURE -> CourseMemberships.startUtc greater now + UserMembership.UserMembershipStatus.PAST -> CourseMemberships.endUtc less now + UserMembership.UserMembershipStatus.CURRENT -> CourseMemberships.endUtc.isNull() + } suspend fun User.assignments( - term: Term.Matcher, - now: Instant, -): SizedIterable = newSuspendedTransaction { - Assignments.innerJoin(Courses).innerJoin(Terms).innerJoin(CourseMemberships).selectAll() - .where { CourseMemberships.userId eq uuid } - .termPredicate(term, now) - .membershipStatusPredicate(UserMembership.UserMembershipStatus.CURRENT, now) - .mapLazy { DBAssignment.wrapRow(it) } - .orderBy(Assignments.submissionDeadline to SortOrder.DESC) -} + assignmentRepository: AssignmentRepository, + term: Term.Matcher = Term.Matcher.Current, + now: Instant = Clock.System.now(), + limit: DomainEntityCollection.Limit? = null, + orders: List = emptyList(), +): AssignmentCollection = assignmentRepository.findAllByUser(uuid, term, now, limit, orders) suspend fun User.courseMemberships( status: UserMembership.UserMembershipStatus, @@ -71,9 +70,11 @@ suspend fun User.courseMemberships( now: Instant, ): SizedIterable> = newSuspendedTransaction { CourseMemberships.innerJoin(Courses).innerJoin(Terms).selectAll() - .where { (CourseMemberships.userId eq uuid) } - .termPredicate(term, now) - .membershipStatusPredicate(status, now) + .where { + (CourseMemberships.userId eq uuid) + .and(termPredicate(term, now)) + .and(membershipStatusPredicate(status, now)) + } .mapLazy { DBCourseMembership.wrapRow(it) } } @@ -83,9 +84,11 @@ suspend fun User.submissionGroupMemberships( now: Instant, ): SizedIterable> = newSuspendedTransaction { SubmissionGroupMemberships.innerJoin(SubmissionGroups).innerJoin(Terms).selectAll() - .where { (CourseMemberships.userId eq uuid) } - .termPredicate(term, now) - .membershipStatusPredicate(status, now) + .where { + (CourseMemberships.userId eq uuid) + .and(termPredicate(term, now)) + .and(membershipStatusPredicate(status, now)) + } .mapLazy { DBSubmissionGroupMembership.wrapRow(it) } } @@ -94,8 +97,8 @@ suspend fun User.submissions( term: Term.Matcher, now: Instant, ): SizedIterable = newSuspendedTransaction { - Submissions.innerJoin(SubmissionGroupMemberships) { SubmissionGroupMemberships.userId eq uuid }.selectAll() - .termPredicate(term, now) + Submissions.innerJoin(SubmissionGroupMemberships) { (SubmissionGroupMemberships.userId eq uuid) and termPredicate(term, now) } + .selectAll() .mapLazy { DBSubmission.wrapRow(it) } } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserSnapshot.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserSnapshot.kt index ba25f5e..a702757 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserSnapshot.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/UserSnapshot.kt @@ -18,25 +18,46 @@ package org.sourcegrade.lab.hub.db.user +import com.expediagroup.graphql.generator.execution.OptionalInput +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.sourcegrade.lab.hub.db.RelationOption +import org.sourcegrade.lab.hub.domain.AssignmentCollection +import org.sourcegrade.lab.hub.domain.AssignmentRepository +import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.Term import org.sourcegrade.lab.hub.domain.User +import org.sourcegrade.lab.hub.graphql.flatten +import org.sourcegrade.lab.hub.graphql.flattenList import java.util.UUID internal class UserSnapshot( - uuidOption: RelationOption, + private val assignmentRepository: AssignmentRepository, + override val uuid: UUID, createdUtcOption: RelationOption, emailOption: RelationOption, usernameOption: RelationOption, displaynameOption: RelationOption, ) : User { - override val uuid: UUID by uuidOption override val createdUtc: Instant by createdUtcOption override val email: String by emailOption override val username: String by usernameOption override val displayname: String by displaynameOption + override suspend fun assignments( + term: OptionalInput, + now: OptionalInput, + limit: OptionalInput, + orders: OptionalInput>, + ): AssignmentCollection = assignmentRepository.findAllByUser( + uuid, + term = Term.Matcher.fromNullableString(term.flatten()), + now = now.flatten { Clock.System.now() }, + limit = limit.flatten(), + orders = orders.flattenList(), + ) + // TODO: Split into UserActions // TODO: Get from UserMembershipRepository // suspend fun assignments( @@ -63,9 +84,10 @@ internal class UserSnapshot( // ): SizedIterable = submissions(status, Term.Matcher.fromString(term), now) companion object { - fun of(user: User, relations: Set): UserSnapshot = with(relations) { + fun of(user: User, relations: Set, assignmentRepository: AssignmentRepository): UserSnapshot = with(relations) { UserSnapshot( - RelationOption.of(user::uuid), + assignmentRepository, + user.uuid, RelationOption.of(user::createdUtc), RelationOption.of(user::email), RelationOption.of(user::username), @@ -74,3 +96,9 @@ internal class UserSnapshot( } } } + +class PermissibleUser( + delegate: User, subject: Subject, +) : User { + val uuid by delegate.hasPermission("user.uuid", delegate.uuid) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt index 5cc0053..4d4c7b0 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/db/user/Users.kt @@ -32,17 +32,17 @@ import org.jetbrains.exposed.sql.kotlin.datetime.timestamp import org.jetbrains.exposed.sql.transactions.experimental.newSuspendedTransaction import org.sourcegrade.lab.hub.db.ConversionBody import org.sourcegrade.lab.hub.db.EntityConversionContext -import org.sourcegrade.lab.hub.db.EntityConversionContextImpl import org.sourcegrade.lab.hub.db.UUIDEntityClassRepository +import org.sourcegrade.lab.hub.domain.AssignmentCollection import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.MutableRepository import org.sourcegrade.lab.hub.domain.MutableUser +import org.sourcegrade.lab.hub.domain.MutableUserRepository import org.sourcegrade.lab.hub.domain.Relation +import org.sourcegrade.lab.hub.domain.Repository import org.sourcegrade.lab.hub.domain.SizedIterableCollection import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserCollection -import org.sourcegrade.lab.hub.domain.repo.MutableRepository -import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository -import org.sourcegrade.lab.hub.domain.repo.Repository import java.util.UUID internal object Users : UUIDTable("sgl_users") { @@ -60,29 +60,37 @@ internal class DBUser(id: EntityID) : UUIDEntity(id), MutableUser { override var username: String by Users.username override var displayname: String by Users.displayname + override suspend fun assignments( + term: OptionalInput, + now: OptionalInput, + limit: OptionalInput, + orders: OptionalInput>, + ): AssignmentCollection { + TODO("Not yet implemented") + } + companion object : EntityClass(Users) } -private val conversionContext = EntityConversionContextImpl(UserSnapshot::of) - internal class DBUserRepository( private val logger: Logger, + private val conversionContext: EntityConversionContext, ) : MutableUserRepository, Repository by UUIDEntityClassRepository(DBUser, conversionContext), EntityConversionContext by conversionContext { override suspend fun findByUsername(username: String, relations: List>): User? = - entityConversion { DBUser.find { Users.username eq username }.firstOrNull().bindNullable() } - - override suspend fun findAllByUsername(partialUsername: String): UserCollection = - DBUserCollection { DBUser.find { Users.username like "%$partialUsername%" }.bindIterable() } + entityConversion(relations) { DBUser.find { Users.username eq username }.firstOrNull().bindNullable() } override suspend fun findByEmail(email: String, relations: List>): User? = entityConversion { DBUser.find { Users.email eq email }.firstOrNull().bindNullable() } + override suspend fun findAllByUsername(partialUsername: String): UserCollection = + DBUserCollection(conversionContext) { DBUser.find { Users.username like "%$partialUsername%" }.bindIterable() } + override suspend fun findAll(limit: DomainEntityCollection.Limit?, orders: List): UserCollection = - DBUserCollection(limit, orders) { DBUser.all().bindIterable() } + DBUserCollection(conversionContext, limit, orders) { DBUser.all().bindIterable() } - override suspend fun create(item: User.CreateDto, relations: List>): User = entityConversion(relations) { + override suspend fun create(item: User.CreateUserDto, relations: List>): User = entityConversion(relations) { DBUser.new { email = item.email username = item.username @@ -97,7 +105,7 @@ internal class DBUserRepository( } override suspend fun put( - item: User.CreateDto, + item: User.CreateUserDto, relations: List>, ): MutableRepository.PutResult = newSuspendedTransaction { val existingUser = findByUsername(item.username, relations) @@ -114,9 +122,9 @@ internal class DBUserRepository( } internal class DBUserCollection( + private val conversionContext: EntityConversionContext, private val limit: DomainEntityCollection.Limit? = null, private val orders: List = emptyList(), private val body: ConversionBody>, ) : UserCollection, - DomainEntityCollection - by SizedIterableCollection(Users, conversionContext, limit, orders, body) + DomainEntityCollection by SizedIterableCollection(Users, conversionContext, limit, orders, body) diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt index 5e7899e..7d32975 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Assignment.kt @@ -19,6 +19,7 @@ package org.sourcegrade.lab.hub.domain import graphql.schema.DataFetchingEnvironment +import kotlinx.datetime.Clock import kotlinx.datetime.Instant import org.sourcegrade.lab.hub.graphql.extractRelations import java.util.UUID @@ -31,7 +32,7 @@ interface Assignment : DomainEntity { val description: String val submissionDeadlineUtc: Instant - data class CreateDto( + data class CreateAssignmentDto( val courseId: UUID, val submissionGroupCategoryId: UUID, val name: String, @@ -46,9 +47,32 @@ interface MutableAssignment : Assignment { override var submissionDeadlineUtc: Instant } +interface AssignmentRepository : CollectionRepository { + suspend fun findAllByCourse( + courseId: UUID, + limit: DomainEntityCollection.Limit? = null, + orders: List = emptyList(), + ): AssignmentCollection + + suspend fun findAllByName( + partialName: String, + limit: DomainEntityCollection.Limit? = null, + orders: List = emptyList(), + ): AssignmentCollection + + suspend fun findAllByUser( + userId: UUID, + term: Term.Matcher = Term.Matcher.Current, + now: Instant = Clock.System.now(), + limit: DomainEntityCollection.Limit? = null, + orders: List = emptyList(), + ): AssignmentCollection +} + +interface MutableAssignmentRepository : AssignmentRepository, MutableRepository + interface AssignmentCollection : DomainEntityCollection { override suspend fun count(): Long override suspend fun empty(): Boolean - suspend fun list(dfe: DataFetchingEnvironment): List = list(dfe.extractRelations()) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/CollectionRepository.kt similarity index 92% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/CollectionRepository.kt index bf055ef..1f0d545 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CollectionRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/CollectionRepository.kt @@ -16,10 +16,7 @@ * along with this program. If not, see . */ -package org.sourcegrade.lab.hub.domain.repo - -import org.sourcegrade.lab.hub.domain.DomainEntity -import org.sourcegrade.lab.hub.domain.DomainEntityCollection +package org.sourcegrade.lab.hub.domain interface CollectionRepository> : Repository { suspend fun findAll( diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt index 064b7e3..81ae6fb 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Course.kt @@ -20,20 +20,28 @@ package org.sourcegrade.lab.hub.domain import com.expediagroup.graphql.generator.execution.OptionalInput import graphql.schema.DataFetchingEnvironment -import org.jetbrains.exposed.sql.SizedIterable import org.sourcegrade.lab.hub.graphql.extractRelations import java.util.UUID interface Course : TermScoped { - val submissionGroupCategories: SizedIterable - val assignments: SizedIterable val owner: User val name: String val description: String + fun submissionGroupCategories( + limit: DomainEntityCollection.Limit? = null, + orders: List = emptyList(), + ): SubmissionGroupCategoryCollection + + fun assignments( + limit: DomainEntityCollection.Limit? = null, + orders: List = emptyList(), + ): AssignmentCollection + data class CreateDto( - val ownerUUID: UUID, + val ownerUuid: UUID, + val termUuid: UUID, val name: String, val description: OptionalInput, ) : Creates @@ -44,6 +52,19 @@ interface MutableCourse : Course { override var description: String } +interface CourseRepository : CollectionRepository { + + suspend fun findByName(name: String, relations: List> = emptyList()): Course? + + suspend fun findAllByName(partialName: String): CourseCollection + + suspend fun findAllByDescription(partialDescription: String): CourseCollection // TODO: maybe instead CollectionRepo.search? + + suspend fun findAllByOwner(ownerId: UUID): CourseCollection +} + +interface MutableCourseRepository : CourseRepository, MutableRepository + interface CourseCollection : DomainEntityCollection { override suspend fun count(): Long override suspend fun empty(): Boolean diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt index c5eadb4..ebf78bc 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/DomainEntityCollection.kt @@ -19,6 +19,7 @@ package org.sourcegrade.lab.hub.domain import com.expediagroup.graphql.generator.annotations.GraphQLIgnore +import com.expediagroup.graphql.generator.execution.OptionalInput import org.jetbrains.exposed.dao.UUIDEntity import org.jetbrains.exposed.sql.Expression import org.jetbrains.exposed.sql.SizedIterable diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt similarity index 86% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt index eb179ff..b616cb8 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/MutableRepository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/MutableRepository.kt @@ -16,12 +16,9 @@ * along with this program. If not, see . */ -package org.sourcegrade.lab.hub.domain.repo +package org.sourcegrade.lab.hub.domain import com.expediagroup.graphql.generator.annotations.GraphQLIgnore -import org.sourcegrade.lab.hub.domain.Creates -import org.sourcegrade.lab.hub.domain.DomainEntity -import org.sourcegrade.lab.hub.domain.Relation @GraphQLIgnore interface MutableRepository> : Repository { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt similarity index 88% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt index 9623388..2cc91c3 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/Repository.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Repository.kt @@ -16,10 +16,8 @@ * along with this program. If not, see . */ -package org.sourcegrade.lab.hub.domain.repo +package org.sourcegrade.lab.hub.domain -import org.sourcegrade.lab.hub.domain.DomainEntity -import org.sourcegrade.lab.hub.domain.Relation import java.util.UUID interface Repository { diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt index ed87515..01197e3 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Submission.kt @@ -41,3 +41,10 @@ interface Submission : DomainEntity { val bytes: ByteArray, ) : Creates } + +interface SubmissionRepository : Repository { + suspend fun findByAssignment(assignmentId: UUID): SizedIterable + suspend fun findByUserAndAssignment(userId: UUID, assignmentId: UUID): SizedIterable +} + +interface MutableSubmissionRepository : SubmissionRepository, MutableRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt index 8a8f9f0..918d972 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/SubmissionGroupCategory.kt @@ -18,9 +18,38 @@ package org.sourcegrade.lab.hub.domain +import graphql.schema.DataFetchingEnvironment +import org.sourcegrade.lab.hub.graphql.extractRelations +import java.util.UUID + interface SubmissionGroupCategory : DomainEntity { val course: Course var name: String var minSize: Int var maxSize: Int + + data class CreateDto( + val courseUuid: UUID, + val name: String, + val minSize: Int, + val maxSize: Int, + ) : Creates +} + +interface SubmissionGroupCategoryRepository : CollectionRepository { + suspend fun findByName(name: String, relations: List> = emptyList()): SubmissionGroupCategory? + suspend fun findAllByName( + partialName: String, + limit: DomainEntityCollection.Limit? = null, + orders: List = emptyList(), + ): SubmissionGroupCategoryCollection +} + +interface MutableSubmissionGroupCategoryRepository : MutableRepository, + SubmissionGroupCategoryRepository + +interface SubmissionGroupCategoryCollection : DomainEntityCollection { + override suspend fun count(): Long + override suspend fun empty(): Boolean + suspend fun list(dfe: DataFetchingEnvironment): List = list(dfe.extractRelations()) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt index 6addf94..0023c9b 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/Term.kt @@ -18,13 +18,13 @@ package org.sourcegrade.lab.hub.domain +import graphql.schema.DataFetchingEnvironment import kotlinx.datetime.Instant -import org.jetbrains.exposed.sql.Query -import org.jetbrains.exposed.sql.SqlExpressionBuilder.eq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.greaterEq -import org.jetbrains.exposed.sql.SqlExpressionBuilder.lessEq +import org.jetbrains.exposed.sql.Op +import org.jetbrains.exposed.sql.SqlExpressionBuilder import org.jetbrains.exposed.sql.and import org.sourcegrade.lab.hub.db.Terms +import org.sourcegrade.lab.hub.graphql.extractRelations import java.util.UUID interface Term : DomainEntity { @@ -32,6 +32,12 @@ interface Term : DomainEntity { val start: Instant val end: Instant? + data class CreateDto( + val name: String, + val start: Instant, + val end: Instant?, + ) : Creates + sealed interface Matcher { data object All : Matcher data object Current : Matcher @@ -64,14 +70,30 @@ interface Term : DomainEntity { } } + fun fromNullableString(value: String?): Matcher = value?.let { fromString(it) } ?: Current + private val regex = "(?[a-zA-Z])\\((?.+\\))".toRegex() } } } -internal fun Query.termPredicate(term: Term.Matcher, now: Instant): Query = when (term) { - is Term.Matcher.All -> this - is Term.Matcher.Current -> where { (Terms.start lessEq now) and (Terms.end greaterEq now) } - is Term.Matcher.ByName -> where { Terms.name.eq(term.name) } - is Term.Matcher.ById -> where { Terms.id.eq(term.id) } +interface TermRepository : CollectionRepository { + suspend fun findByName(name: String, relations: List>): Term? + suspend fun findByTime(now: Instant, relations: List>): Term? +} + +interface MutableTermRepository : TermRepository, MutableRepository + +interface TermCollection : DomainEntityCollection { + override suspend fun count(): Long + override suspend fun empty(): Boolean + + suspend fun list(dfe: DataFetchingEnvironment): List = list(dfe.extractRelations()) +} + +internal fun SqlExpressionBuilder.termPredicate(term: Term.Matcher, now: Instant): Op = when (term) { + is Term.Matcher.All -> Op.TRUE + is Term.Matcher.Current -> (Terms.start lessEq now) and (Terms.end greaterEq now) + is Term.Matcher.ByName -> Terms.name.eq(term.name) + is Term.Matcher.ById -> Terms.id.eq(term.id) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt index 0c0366b..2ade876 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/User.kt @@ -20,6 +20,7 @@ package org.sourcegrade.lab.hub.domain import com.expediagroup.graphql.generator.execution.OptionalInput import graphql.schema.DataFetchingEnvironment +import kotlinx.datetime.Instant import org.sourcegrade.lab.hub.graphql.extractRelations interface User : DomainEntity { @@ -27,7 +28,14 @@ interface User : DomainEntity { val username: String val displayname: String - data class CreateDto( + suspend fun assignments( + term: OptionalInput, + now: OptionalInput, + limit: OptionalInput, + orders: OptionalInput>, + ): AssignmentCollection + + data class CreateUserDto( val email: String, val username: String, val displayname: OptionalInput = OptionalInput.Defined(username), @@ -40,9 +48,16 @@ interface MutableUser : User { override var displayname: String } +interface UserRepository : CollectionRepository { + suspend fun findByUsername(username: String, relations: List> = emptyList()): User? + suspend fun findByEmail(email: String, relations: List> = emptyList()): User? + suspend fun findAllByUsername(partialUsername: String): UserCollection +} + +interface MutableUserRepository : UserRepository, MutableRepository + interface UserCollection : DomainEntityCollection { override suspend fun count(): Long override suspend fun empty(): Boolean - suspend fun list(dfe: DataFetchingEnvironment): List = list(dfe.extractRelations()) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt index 3aaa33d..492758a 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/UserMembership.kt @@ -18,7 +18,9 @@ package org.sourcegrade.lab.hub.domain +import kotlinx.datetime.Clock import kotlinx.datetime.Instant +import org.jetbrains.exposed.sql.SizedIterable import java.util.UUID interface UserMembership : DomainEntity { @@ -40,3 +42,14 @@ interface UserMembership : DomainEntity { PAST, } } + +interface UserMembershipRepository : Repository> { + suspend fun find( + status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, + term: Term.Matcher = Term.Matcher.Current, + now: Instant = Clock.System.now(), + ): SizedIterable +} + +interface MutableUserMembershipRepository : UserMembershipRepository, + MutableRepository, UserMembership.CreateDto> diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt deleted file mode 100644 index b8dd082..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/AssignmentRepository.kt +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Lab - SourceGrade.org - * Copyright (C) 2019-2024 Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.sourcegrade.lab.hub.domain.repo - -import org.jetbrains.exposed.sql.SizedIterable -import org.sourcegrade.lab.hub.domain.Assignment -import org.sourcegrade.lab.hub.domain.AssignmentCollection -import java.util.UUID -import kotlin.reflect.KProperty1 - -interface AssignmentRepository : CollectionRepository { - suspend fun findByCourse(courseId: UUID): SizedIterable -} - -interface MutableAssignmentRepository : AssignmentRepository, MutableRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CourseRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CourseRepository.kt deleted file mode 100644 index b740555..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/CourseRepository.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Lab - SourceGrade.org - * Copyright (C) 2019-2024 Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.sourcegrade.lab.hub.domain.repo - -import org.sourcegrade.lab.hub.domain.Course -import org.sourcegrade.lab.hub.domain.CourseCollection -import org.sourcegrade.lab.hub.domain.Relation -import org.sourcegrade.lab.hub.domain.User -import java.util.UUID - -interface CourseRepository : CollectionRepository { - - suspend fun findByName(name: String, relations: List> = emptyList()): Course? - - suspend fun findAllByName(partialName: String): CourseCollection - - suspend fun findAllByDescription(partialDescription: String): CourseCollection // TODO: maybe instead CollectionRepo.search? - - suspend fun findAllByOwner(ownerId: UUID): CourseCollection -} - -interface MutableCourseRepository : CourseCollection, MutableRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt deleted file mode 100644 index e06c43a..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/SubmissionRepository.kt +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Lab - SourceGrade.org - * Copyright (C) 2019-2024 Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.sourcegrade.lab.hub.domain.repo - -import org.jetbrains.exposed.sql.SizedIterable -import org.sourcegrade.lab.hub.domain.Submission -import java.util.UUID - -interface SubmissionRepository : Repository { - suspend fun findByAssignment(assignmentId: UUID): SizedIterable - suspend fun findByUserAndAssignment(userId: UUID, assignmentId: UUID): SizedIterable -} - -interface MutableSubmissionRepository : SubmissionRepository, MutableRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt deleted file mode 100644 index e5b0a88..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserMembershipRepository.kt +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Lab - SourceGrade.org - * Copyright (C) 2019-2024 Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.sourcegrade.lab.hub.domain.repo - -import kotlinx.datetime.Clock -import kotlinx.datetime.Instant -import org.jetbrains.exposed.sql.SizedIterable -import org.sourcegrade.lab.hub.domain.Term -import org.sourcegrade.lab.hub.domain.TermScoped -import org.sourcegrade.lab.hub.domain.UserMembership - -interface UserMembershipRepository : Repository> { - suspend fun find( - status: UserMembership.UserMembershipStatus = UserMembership.UserMembershipStatus.CURRENT, - term: Term.Matcher = Term.Matcher.Current, - now: Instant = Clock.System.now(), - ): SizedIterable -} - -interface MutableUserMembershipRepository : UserMembershipRepository, - MutableRepository, UserMembership.CreateDto> diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt deleted file mode 100644 index 9e7fdca..0000000 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/domain/repo/UserRepository.kt +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Lab - SourceGrade.org - * Copyright (C) 2019-2024 Contributors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Affero General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with this program. If not, see . - */ - -package org.sourcegrade.lab.hub.domain.repo - -import org.sourcegrade.lab.hub.domain.Relation -import org.sourcegrade.lab.hub.domain.User -import org.sourcegrade.lab.hub.domain.UserCollection - -interface UserRepository : CollectionRepository { - - suspend fun findByUsername(username: String, relations: List> = emptyList(), ): User? - - suspend fun findAllByUsername(partialUsername: String, ): UserCollection - - suspend fun findByEmail(email: String, relations: List> = emptyList(), ): User? -} - -interface MutableUserRepository : UserRepository, MutableRepository diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/AssignmentQueries.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/AssignmentQueries.kt new file mode 100644 index 0000000..1a11678 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/AssignmentQueries.kt @@ -0,0 +1,105 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.graphql + +import com.expediagroup.graphql.generator.execution.OptionalInput +import com.expediagroup.graphql.server.operations.Mutation +import com.expediagroup.graphql.server.operations.Query +import graphql.schema.DataFetchingEnvironment +import kotlinx.datetime.Clock +import kotlinx.datetime.Instant +import org.apache.logging.log4j.Logger +import org.sourcegrade.lab.hub.domain.Assignment +import org.sourcegrade.lab.hub.domain.AssignmentCollection +import org.sourcegrade.lab.hub.domain.AssignmentRepository +import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.MutableAssignmentRepository +import org.sourcegrade.lab.hub.domain.MutableRepository +import org.sourcegrade.lab.hub.domain.Term +import java.util.UUID + +class AssignmentQueries( + private val logger: Logger, + private val repository: AssignmentRepository, +) : Query { + fun assignment(): AssignmentQuery = AssignmentQuery(logger, repository) +} + +class AssignmentQuery( + private val logger: Logger, + private val repository: AssignmentRepository, +) { + suspend fun findAll( + limit: OptionalInput, + orders: OptionalInput>, + ): AssignmentCollection = repository.findAll(limit = limit.flatten(), orders = orders.flattenList()) + + suspend fun findById(dfe: DataFetchingEnvironment, id: UUID): Assignment? = repository.findById(id, dfe.extractRelations()) + suspend fun deleteById(id: UUID): Boolean = repository.deleteById(id) + suspend fun exists(id: UUID): Boolean = repository.exists(id) + suspend fun countAll(): Long = repository.countAll() + + suspend fun findAllByCourse( + courseId: UUID, + limit: OptionalInput, + orders: OptionalInput>, + ): AssignmentCollection = repository.findAllByCourse(courseId, limit.flatten(), orders.flattenList()) + + suspend fun findAllByName( + partialName: String, + limit: OptionalInput, + orders: OptionalInput>, + ): AssignmentCollection = repository.findAllByName(partialName, limit.flatten(), orders.flattenList()) + + suspend fun findAllByUser( + userId: UUID, + term: OptionalInput, + now: OptionalInput, + limit: OptionalInput, + orders: OptionalInput>, + ): AssignmentCollection = repository.findAllByUser( + userId, + term = Term.Matcher.fromNullableString(term.flatten()), + now = now.flatten { Clock.System.now() }, + limit = limit.flatten(), + orders = orders.flattenList(), + ) +} + +class AssignmentMutations( + private val logger: Logger, + private val repository: MutableAssignmentRepository, +) : Mutation { + fun assignment(): AssignmentMutation = AssignmentMutation(logger, repository) +} + +class AssignmentMutation( + private val logger: Logger, + private val repository: MutableAssignmentRepository, +) { + suspend fun createAssignment(dfe: DataFetchingEnvironment, input: Assignment.CreateAssignmentDto): Assignment = + repository.create(input, dfe.extractRelations()) + + suspend fun put(dfe: DataFetchingEnvironment, item: Assignment.CreateAssignmentDto): AssignmentPutResult = + repository.put(item, dfe.extractRelations()).convert() + + data class AssignmentPutResult(val entity: Assignment, val created: Boolean) + + private fun MutableRepository.PutResult.convert(): AssignmentPutResult = AssignmentPutResult(entity, created) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/CourseQueries.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/CourseQueries.kt new file mode 100644 index 0000000..4a796b3 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/CourseQueries.kt @@ -0,0 +1,75 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.graphql + +import com.expediagroup.graphql.generator.execution.OptionalInput +import com.expediagroup.graphql.server.operations.Mutation +import com.expediagroup.graphql.server.operations.Query +import graphql.schema.DataFetchingEnvironment +import org.apache.logging.log4j.Logger +import org.sourcegrade.lab.hub.domain.Course +import org.sourcegrade.lab.hub.domain.CourseCollection +import org.sourcegrade.lab.hub.domain.CourseRepository +import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.MutableCourseRepository +import org.sourcegrade.lab.hub.domain.MutableRepository +import java.util.UUID + +class CourseQueries( + private val logger: Logger, + private val repository: CourseRepository, +) : Query { + fun course(): CourseQuery = CourseQuery(logger, repository) +} + +class CourseQuery( + private val logger: Logger, + private val repository: CourseRepository, +) { + suspend fun findAll( + limit: OptionalInput, + orders: OptionalInput>, + ): CourseCollection = repository.findAll(limit.flatten(), orders.flattenList()) + + suspend fun findById(dfe: DataFetchingEnvironment, id: UUID): Course? = repository.findById(id, dfe.extractRelations()) + suspend fun exists(id: UUID): Boolean = repository.exists(id) + suspend fun countAll(): Long = repository.countAll() +} + +class CourseMutations( + private val logger: Logger, + private val repository: MutableCourseRepository, +) : Mutation { + fun course(): CourseMutation = CourseMutation(logger, repository) +} + +class CourseMutation( + private val logger: Logger, + private val repository: MutableCourseRepository, +) { + suspend fun create(dfe: DataFetchingEnvironment, item: Course.CreateDto): Course = + repository.create(item, dfe.extractRelations()) + + suspend fun put(dfe: DataFetchingEnvironment, item: Course.CreateDto): PutResult = + repository.put(item, dfe.extractRelations()).convert() + + data class PutResult(val entity: Course, val created: Boolean) + + private fun MutableRepository.PutResult.convert(): PutResult = PutResult(entity, created) +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RelationOps.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RelationOps.kt index 8e558c5..93271d3 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RelationOps.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/RelationOps.kt @@ -18,16 +18,28 @@ package org.sourcegrade.lab.hub.graphql +import com.expediagroup.graphql.generator.execution.OptionalInput import graphql.schema.DataFetchingEnvironment import org.sourcegrade.lab.hub.domain.DomainEntity import org.sourcegrade.lab.hub.domain.Relation +import kotlin.reflect.KFunction import kotlin.reflect.KProperty1 internal inline fun DataFetchingEnvironment.extractRelations(): List> = - selectionSet.immediateFields.map { field -> coerceRelation(field.name) } + selectionSet.immediateFields.mapNotNull { field -> coerceRelation(field.name) } -internal inline fun coerceRelation(relation: String): Relation = - E::class.members.find { it.name == relation }?.let { +internal inline fun coerceRelation(relation: String): Relation? = + E::class.members.find { it.name == relation }?.let { prop -> @Suppress("UNCHECKED_CAST") - it as KProperty1 - } ?: error("No relation $relation found on ${E::class.simpleName}, available relations are: ${E::class.members.map { it.name }}") + when (prop) { + is KProperty1<*, *> -> (prop as KProperty1) + is KFunction<*> -> null + else -> error("No relation $relation found on ${E::class.simpleName}, available relations are: ${E::class.members.map { it.name }}") + } + } + +fun OptionalInput.flatten(): T? = (this as? OptionalInput.Defined)?.value + +fun OptionalInput.flatten(default: () -> T): T = flatten() ?: default() + +fun OptionalInput>.flattenList(): List = (this as? OptionalInput.Defined)?.value ?: emptyList() diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/TermQueries.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/TermQueries.kt new file mode 100644 index 0000000..adc6669 --- /dev/null +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/TermQueries.kt @@ -0,0 +1,62 @@ +/* + * Lab - SourceGrade.org + * Copyright (C) 2019-2024 Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package org.sourcegrade.lab.hub.graphql + +import com.expediagroup.graphql.generator.execution.OptionalInput +import graphql.schema.DataFetchingEnvironment +import org.apache.logging.log4j.Logger +import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.MutableTermRepository +import org.sourcegrade.lab.hub.domain.Term +import org.sourcegrade.lab.hub.domain.TermCollection +import org.sourcegrade.lab.hub.domain.TermRepository +import java.util.UUID + +class TermQueries( + private val logger: Logger, + private val repository: TermRepository, +) { + fun term(): TermQuery = TermQuery(logger, repository) +} + +class TermQuery( + private val logger: Logger, + private val repository: TermRepository, +) { + suspend fun findAll( + limit: OptionalInput, + orders: OptionalInput>, + ): TermCollection = repository.findAll(limit.flatten(), orders.flattenList()) + + suspend fun findById(dfe: DataFetchingEnvironment, id: UUID): Term? = repository.findById(id, dfe.extractRelations()) + suspend fun exists(id: UUID): Boolean = repository.exists(id) + suspend fun countAll(): Long = repository.countAll() +} + +class TermMutations( + private val logger: Logger, + private val repository: MutableTermRepository, +) + +class TermMutation( + private val logger: Logger, + private val repository: MutableTermRepository, +) { + +} diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserQueries.kt similarity index 81% rename from hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt rename to hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserQueries.kt index 04f0807..e4c4576 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/User.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/graphql/UserQueries.kt @@ -25,11 +25,11 @@ import com.expediagroup.graphql.server.operations.Query import graphql.schema.DataFetchingEnvironment import org.apache.logging.log4j.Logger import org.sourcegrade.lab.hub.domain.DomainEntityCollection +import org.sourcegrade.lab.hub.domain.MutableRepository +import org.sourcegrade.lab.hub.domain.MutableUserRepository import org.sourcegrade.lab.hub.domain.User import org.sourcegrade.lab.hub.domain.UserCollection -import org.sourcegrade.lab.hub.domain.repo.MutableRepository -import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository -import org.sourcegrade.lab.hub.domain.repo.UserRepository +import org.sourcegrade.lab.hub.domain.UserRepository import java.util.UUID class UserQueries( @@ -47,10 +47,7 @@ class UserQuery( suspend fun findAll( limit: OptionalInput, orders: OptionalInput>, - ): UserCollection = repository.findAll( - limit = (limit as? OptionalInput.Defined)?.value, - orders = (orders as? OptionalInput.Defined)?.value ?: emptyList(), - ) + ): UserCollection = repository.findAll(limit.flatten(), orders.flattenList()) suspend fun findById(dfe: DataFetchingEnvironment, id: UUID): User? = repository.findById(id, dfe.extractRelations()) suspend fun deleteById(id: UUID): Boolean = repository.deleteById(id) @@ -76,10 +73,10 @@ class UserMutation( private val logger: Logger, private val repository: MutableUserRepository, ) { - suspend fun create(dfe: DataFetchingEnvironment, item: User.CreateDto): User = repository.create(item, dfe.extractRelations()) - suspend fun put(dfe: DataFetchingEnvironment, item: User.CreateDto): PutResult = repository.put(item, dfe.extractRelations()).convert() + suspend fun create(dfe: DataFetchingEnvironment, item: User.CreateUserDto): User = repository.create(item, dfe.extractRelations()) + suspend fun put(dfe: DataFetchingEnvironment, item: User.CreateUserDto): UserPutResult = repository.put(item, dfe.extractRelations()).convert() - data class PutResult(val entity: User, val created: Boolean) + data class UserPutResult(val entity: User, val created: Boolean) - private fun MutableRepository.PutResult.convert(): PutResult = PutResult(entity, created) + private fun MutableRepository.PutResult.convert(): UserPutResult = UserPutResult(entity, created) } diff --git a/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt b/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt index 80bc171..18da5d7 100644 --- a/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt +++ b/hub/src/main/kotlin/org/sourcegrade/lab/hub/http/AuthenticationModule.kt @@ -58,9 +58,9 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.json.Json import org.koin.ktor.ext.inject +import org.sourcegrade.lab.hub.domain.MutableUserRepository import org.sourcegrade.lab.hub.domain.User -import org.sourcegrade.lab.hub.domain.repo.MutableUserRepository -import org.sourcegrade.lab.hub.domain.repo.UserRepository +import org.sourcegrade.lab.hub.domain.UserRepository import org.sourcegrade.lab.hub.getEnv import java.io.File import java.util.UUID @@ -156,7 +156,7 @@ fun Application.authenticationModule() { header("Authorization", "Bearer ${principal.accessToken}") }.body() - val createDto = User.CreateDto( + val createDto = User.CreateUserDto( email = userInfo.email, username = userInfo.preferredUsername, )