Rename Label->Version, add version meta field

This commit is contained in:
2026-08-09 20:18:11 +02:00
parent 60b1a69bc6
commit a90b2ebeca
15 changed files with 168 additions and 176 deletions
@@ -129,9 +129,9 @@ fun Application.module() {
patchAuth(Routes.Project.update, ProjectHandler::updateProject)
deleteAuth(Routes.Project.delete, ProjectHandler::deleteProject)
postAuth(Routes.Label.new, ProjectHandler::createLabel)
patchAuth(Routes.Label.update, ProjectHandler::updateLabel)
deleteAuth(Routes.Label.delete, ProjectHandler::deleteLabel)
postAuth(Routes.Version.new, ProjectHandler::createVersion)
patchAuth(Routes.Version.update, ProjectHandler::updateVersion)
deleteAuth(Routes.Version.delete, ProjectHandler::deleteVersion)
postAuth(Routes.Entry.new, ProjectHandler::createEntry)
deleteAuth(Routes.Entry.delete, ProjectHandler::deleteEntry)
@@ -1,7 +1,6 @@
package com.jaytux.phoebench.server.db
import com.jaytux.phoebench.server.DotEnv
import io.github.cdimascio.dotenv.Dotenv
import org.jetbrains.exposed.v1.jdbc.Database
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
@@ -16,9 +15,9 @@ object DB {
)
transaction {
SchemaUtils.create(Users, Invites, RefreshTokens, Projects, Labels, Entries)
SchemaUtils.create(Users, Invites, RefreshTokens, Projects, Versions, Entries)
val migration = MigrationUtils.statementsRequiredForDatabaseMigration(Users, Invites, RefreshTokens, Projects, Labels, Entries)
val migration = MigrationUtils.statementsRequiredForDatabaseMigration(Users, Invites, RefreshTokens, Projects, Versions, Entries)
try {
migration.forEach {
exec(it)
@@ -42,25 +42,26 @@ class Project(id: EntityID<Uuid>) : Entity<Uuid>(id) {
var isPublic by Projects.isPublic
var owner by User referencedOn Projects.ownerId
val labels by Label referrersOn Labels.projectId
val labels by Version referrersOn Versions.projectId
val entries by Entry referrersOn Entries.projectId
}
class Label(id: EntityID<Uuid>) : Entity<Uuid>(id) {
companion object : EntityClass<Uuid, Label>(Labels)
class Version(id: EntityID<Uuid>) : Entity<Uuid>(id) {
companion object : EntityClass<Uuid, Version>(Versions)
var label by Labels.label
var color by Labels.color
var projectId by Labels.projectId
var label by Versions.label
var color by Versions.color
var meta by Versions.meta
var projectId by Versions.projectId
var project by Project referencedOn Labels.projectId
var project by Project referencedOn Versions.projectId
}
class Entry(id: EntityID<Uuid>) : Entity<Uuid>(id) {
companion object : EntityClass<Uuid, Entry>(Entries)
var projectId by Entries.projectId
var labelId by Entries.labelId
var versionId by Entries.versionId
var timestamp by Entries.timestamp
var warmups by Entries.warmups
var measurements by Entries.measurements
@@ -69,5 +70,5 @@ class Entry(id: EntityID<Uuid>) : Entity<Uuid>(id) {
var hardware by Entries.hardware
var project by Project referencedOn Entries.projectId
var label by Label referencedOn Entries.labelId
var version by Version referencedOn Entries.versionId
}
@@ -34,19 +34,20 @@ object Projects : UuidTable() {
}
}
object Labels : UuidTable() {
object Versions : UuidTable() {
val label = varchar("label", 255)
val color = varchar("color", 7)
val meta = text("meta", eagerLoading = true)
val projectId = reference("project_id", Projects, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
}
object Entries : UuidTable() {
val projectId = reference("project_id", Projects, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
val labelId = reference("label", Labels, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
val versionId = reference("version", Versions, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
val timestamp = timestamp("timestamp").default(Clock.System.now())
val warmups = list<Float>("warmups")
val measurements = list<Float>("measurements")
val unit = enumeration<TimeUnit>("unit")
val input = varchar("input", 255).default("")
val hardware = varchar("hardware", 255).default("")
val input = varchar("input", 255)
val hardware = varchar("hardware", 255)
}
@@ -6,27 +6,24 @@ import com.jaytux.phoebench.common.EntryRequest
import com.jaytux.phoebench.common.EntryResponse
import com.jaytux.phoebench.common.HomeEvent
import com.jaytux.phoebench.common.HomeResponse
import com.jaytux.phoebench.common.LabelRequest
import com.jaytux.phoebench.common.LabelResponse
import com.jaytux.phoebench.common.VersionRequest
import com.jaytux.phoebench.common.VersionResponse
import com.jaytux.phoebench.common.NamedID
import com.jaytux.phoebench.common.PartialEntryRequest
import com.jaytux.phoebench.common.PartialLabelRequest
import com.jaytux.phoebench.common.PartialProjectRequest
import com.jaytux.phoebench.common.PartialVersionRequest
import com.jaytux.phoebench.common.ProjectEvent
import com.jaytux.phoebench.common.ProjectRequest
import com.jaytux.phoebench.common.ProjectResponse
import com.jaytux.phoebench.server.Buses
import com.jaytux.phoebench.server.SSEBus
import com.jaytux.phoebench.server.db.Entries
import com.jaytux.phoebench.server.db.Entry
import com.jaytux.phoebench.server.db.Label
import com.jaytux.phoebench.server.db.Labels
import com.jaytux.phoebench.server.db.Version
import com.jaytux.phoebench.server.db.Versions
import com.jaytux.phoebench.server.db.Project
import com.jaytux.phoebench.server.db.Projects
import com.jaytux.phoebench.server.db.User
import com.jaytux.phoebench.server.handlers.RouteError.Companion.success
import io.ktor.http.HttpStatusCode
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.jetbrains.exposed.v1.core.SortOrder
import org.jetbrains.exposed.v1.core.Transaction
@@ -55,8 +52,8 @@ object ProjectHandler {
context(trns: Transaction)
private fun Project.toResponse(user: User) = ProjectResponse(
id.value, name, NamedID(owner.username, owner.id.value), isPublic, isEditableBy(user),
labels.orderBy(Labels.label to SortOrder.ASC).map { LabelResponse(it.id.value, it.label, it.color) },
entries.orderBy(Entries.timestamp to SortOrder.ASC).map { EntryResponse(it.id.value, it.label.id.value, it.timestamp, it.warmups, it.measurements, it.unit, it.input, it.hardware) })
labels.orderBy(Versions.label to SortOrder.ASC).map { VersionResponse(it.id.value, it.label, it.color, it.meta) },
entries.orderBy(Entries.timestamp to SortOrder.ASC).map { EntryResponse(it.id.value, it.version.id.value, it.timestamp, it.warmups, it.measurements, it.unit, it.input, it.hardware) })
fun home(user: User, req: EmptyRequest) = transaction {
val own = user.projects.orderBy(Projects.name to SortOrder.ASC).map {
@@ -127,49 +124,51 @@ object ProjectHandler {
success(EmptyResponse())
}
fun createLabel(user: User, req: LabelRequest) = transaction {
fun createVersion(user: User, req: VersionRequest) = transaction {
val proj = accessibleProject(user, req.projectId, true)
if(req.color.length != 7) throw RouteError("Expected Hex-RGB color (7 characters).", HttpStatusCode.BadRequest)
val lbl = Label.new {
val ver = Version.new {
label = req.name
color = req.color
project = proj
meta = req.meta
}
val res = LabelResponse(lbl.id.value, lbl.label, lbl.color)
val res = VersionResponse(ver.id.value, ver.label, ver.color, ver.meta)
ServerScope.launch {
Buses.projectBus(req.projectId).sendAll(ProjectEvent.NewLabel(res))
Buses.projectBus(req.projectId).sendAll(ProjectEvent.NewVersion(res))
}
success(res)
}
fun updateLabel(user: User, req: Pair<Uuid, PartialLabelRequest>) = transaction {
val lbl = Label.findById(req.first) ?: throw RouteError("Invalid label ID.", HttpStatusCode.NotFound)
lbl.project.isAccessible(user, true)
fun updateVersion(user: User, req: Pair<Uuid, PartialVersionRequest>) = transaction {
val ver = Version.findById(req.first) ?: throw RouteError("Invalid version ID.", HttpStatusCode.NotFound)
ver.project.isAccessible(user, true)
val changes = req.second
changes.name?.let { lbl.label = it }
changes.name?.let { ver.label = it }
changes.color?.let {
if(it.length != 7) throw RouteError("Expected Hex-RGB color (7 characters).", HttpStatusCode.BadRequest)
lbl.color = it
ver.color = it
}
changes.meta?.let { ver.meta = it }
ServerScope.launch {
Buses.projectBus(lbl.projectId.value).sendAll(ProjectEvent.LabelChanged(
LabelResponse(lbl.id.value, lbl.label, lbl.color)
Buses.projectBus(ver.projectId.value).sendAll(ProjectEvent.VersionChanged(
VersionResponse(ver.id.value, ver.label, ver.color, ver.meta)
))
}
success(EmptyResponse())
}
fun deleteLabel(user: User, req: Uuid) = transaction {
val lbl = Label.findById(req) ?: throw RouteError("Invalid label ID.", HttpStatusCode.NotFound)
lbl.project.isAccessible(user, true)
lbl.delete()
fun deleteVersion(user: User, req: Uuid) = transaction {
val ver = Version.findById(req) ?: throw RouteError("Invalid version ID.", HttpStatusCode.NotFound)
ver.project.isAccessible(user, true)
ver.delete()
ServerScope.launch {
Buses.projectBus(lbl.id.value).sendAll(ProjectEvent.LabelDeleted(req))
Buses.projectBus(ver.id.value).sendAll(ProjectEvent.VersionDeleted(req))
}
success(EmptyResponse())
@@ -178,9 +177,9 @@ object ProjectHandler {
fun createEntry(user: User, req: EntryRequest) = transaction {
val proj = accessibleProject(user, req.projectId, true)
val entry = Entry.new {
label = when(val l = Label.findById(req.label)) {
null -> throw RouteError("Invalid label ID.", HttpStatusCode.NotFound)
is Label if l.projectId.value != proj.id.value -> throw RouteError("Label is attached to a different project.", HttpStatusCode.Conflict)
version = when(val l = Version.findById(req.version)) {
null -> throw RouteError("Invalid version ID.", HttpStatusCode.NotFound)
is Version if l.projectId.value != proj.id.value -> throw RouteError("Version is attached to a different project.", HttpStatusCode.Conflict)
else -> l
}
project = proj
@@ -192,7 +191,7 @@ object ProjectHandler {
hardware = req.hardware
}
val response = EntryResponse(entry.id.value, entry.label.id.value, entry.timestamp, entry.warmups, entry.measurements, entry.unit, entry.input, entry.hardware)
val response = EntryResponse(entry.id.value, entry.version.id.value, entry.timestamp, entry.warmups, entry.measurements, entry.unit, entry.input, entry.hardware)
ServerScope.launch {
Buses.projectBus(proj.id.value).sendAll(ProjectEvent.NewEntry(response))
}