Database reorganize, protocol version bump, migrations
This commit is contained in:
@@ -7,7 +7,10 @@ plugins {
|
||||
}
|
||||
|
||||
group = "com.jaytux.phoebench"
|
||||
version = rootProject.version
|
||||
version = rootProject.version as PhoebenchVersion
|
||||
|
||||
if((version as PhoebenchVersion) < (rootProject.version as PhoebenchVersion))
|
||||
throw GradleException("Server version must be at least as high as protocol/common version")
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
|
||||
@@ -2,6 +2,8 @@ package com.jaytux.phoebench.server
|
||||
|
||||
import com.jaytux.phoebench.common.*
|
||||
import com.jaytux.phoebench.server.db.DB
|
||||
import com.jaytux.phoebench.server.db.Meta
|
||||
import com.jaytux.phoebench.server.db.Metas
|
||||
import com.jaytux.phoebench.server.db.User
|
||||
import com.jaytux.phoebench.server.handlers.*
|
||||
import com.jaytux.phoebench.server.handlers.ProjectHandler.accessibleProject
|
||||
@@ -27,6 +29,10 @@ fun Application.module() {
|
||||
DB.db
|
||||
println("Entering module for PhoeBench Server version ${ServerVersion.VERSION}...")
|
||||
|
||||
if(Meta.single.revision < Metas.CURRENT_REVISION) {
|
||||
DB.migrate(Meta.single.revision, Metas.CURRENT_REVISION)
|
||||
}
|
||||
|
||||
install(ContentNegotiation) {
|
||||
json()
|
||||
}
|
||||
@@ -120,12 +126,17 @@ fun Application.module() {
|
||||
deleteAdmin(Routes.Auth.User.delete, AuthHandler::deleteUser)
|
||||
|
||||
getAuth(Routes.home, ProjectHandler::home)
|
||||
postAuth(Routes.migrate0To1, ProjectHandler::migrate01)
|
||||
|
||||
postAuth(Routes.Project.new, ProjectHandler::createProject)
|
||||
getAuth(Routes.Project.get, ProjectHandler::getProject)
|
||||
patchAuth(Routes.Project.update, ProjectHandler::updateProject)
|
||||
deleteAuth(Routes.Project.delete, ProjectHandler::deleteProject)
|
||||
|
||||
postAuth(Routes.Benchmark.new, ProjectHandler::createBenchmark)
|
||||
patchAuth(Routes.Benchmark.update, ProjectHandler::updateBenchmark)
|
||||
deleteAuth(Routes.Benchmark.delete, ProjectHandler::deleteBenchmark)
|
||||
|
||||
postAuth(Routes.Version.new, ProjectHandler::createVersion)
|
||||
patchAuth(Routes.Version.update, ProjectHandler::updateVersion)
|
||||
deleteAuth(Routes.Version.delete, ProjectHandler::deleteVersion)
|
||||
|
||||
@@ -21,4 +21,6 @@ fun nowPlusDays(days: Int): Instant = nowPlus(days, DateTimeUnit.DAY)
|
||||
fun nowPlus(time: Int, unit: DateTimeUnit): Instant {
|
||||
val now = Clock.System.now()
|
||||
return now.plus(time, unit, systemTZ)
|
||||
}
|
||||
}
|
||||
|
||||
infix fun <T1, T2, T3> Pair<T1, T2>.app(t3: T3) = Triple(first, second, t3)
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.jaytux.phoebench.server.db
|
||||
|
||||
import com.jaytux.phoebench.server.DotEnv
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.jdbc.Database
|
||||
import org.jetbrains.exposed.v1.jdbc.SchemaUtils
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import org.jetbrains.exposed.v1.jdbc.update
|
||||
import org.jetbrains.exposed.v1.migration.jdbc.MigrationUtils
|
||||
|
||||
object DB {
|
||||
@@ -17,7 +19,7 @@ object DB {
|
||||
transaction {
|
||||
SchemaUtils.create(Users, Invites, RefreshTokens, Projects, Versions, Entries)
|
||||
|
||||
val migration = MigrationUtils.statementsRequiredForDatabaseMigration(Users, Invites, RefreshTokens, Projects, Versions, Entries)
|
||||
val migration = MigrationUtils.statementsRequiredForDatabaseMigration(Users, Invites, RefreshTokens, Projects, Versions, Entries, Metas, EntriesV2, Benchmarks)
|
||||
try {
|
||||
migration.forEach {
|
||||
exec(it)
|
||||
@@ -29,4 +31,29 @@ object DB {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun migrate(from: Int, to: Int) = when(from) {
|
||||
0 -> if(to == 1) migrate0To1() else null
|
||||
else -> null
|
||||
} ?: throw IllegalArgumentException("Migration $from -> $to is not defined")
|
||||
|
||||
private fun migrate0To1() {
|
||||
transaction {
|
||||
Users.update(where = { Users.isAdmin eq true }) { it[benchmarkLimit] = -1 }
|
||||
Users.update(where = { Users.isAdmin eq false }) { it.update(benchmarkLimit, projectLimit) }
|
||||
}
|
||||
|
||||
transaction {
|
||||
User.all().forEach {
|
||||
if(it.projects.count() == 0L) {
|
||||
it.projectVersion = Projects.CURRENT_VERSION
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
transaction {
|
||||
Meta.single.revision = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,25 @@
|
||||
package com.jaytux.phoebench.server.db
|
||||
|
||||
import org.jetbrains.exposed.v1.core.Transaction
|
||||
import org.jetbrains.exposed.v1.core.dao.id.EntityID
|
||||
import org.jetbrains.exposed.v1.dao.Entity
|
||||
import org.jetbrains.exposed.v1.dao.EntityClass
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
class Meta(id: EntityID<Int>) : Entity<Int>(id) {
|
||||
companion object : EntityClass<Int, Meta>(Metas) {
|
||||
val single by lazy {
|
||||
transaction {
|
||||
val got = all().limit(1).toList()
|
||||
got.firstOrNull() ?: new {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var revision by Metas.revision
|
||||
}
|
||||
|
||||
class User(id: EntityID<Uuid>) : Entity<Uuid>(id) {
|
||||
companion object : EntityClass<Uuid, User>(Users)
|
||||
|
||||
@@ -13,6 +28,8 @@ class User(id: EntityID<Uuid>) : Entity<Uuid>(id) {
|
||||
var isAdmin by Users.isAdmin
|
||||
var isOwner by Users.isOwner
|
||||
var projectLimit by Users.projectLimit
|
||||
var benchmarkLimit by Users.benchmarkLimit
|
||||
var projectVersion by Users.projectsVersion
|
||||
|
||||
val projects by Project referrersOn Projects.ownerId
|
||||
val sessions by RefreshToken referrersOn RefreshTokens.userId
|
||||
@@ -42,8 +59,18 @@ class Project(id: EntityID<Uuid>) : Entity<Uuid>(id) {
|
||||
var isPublic by Projects.isPublic
|
||||
|
||||
var owner by User referencedOn Projects.ownerId
|
||||
val labels by Version referrersOn Versions.projectId
|
||||
val entries by Entry referrersOn Entries.projectId
|
||||
val versions by Version referrersOn Versions.projectId
|
||||
val benchmarks by Benchmark referrersOn Benchmarks.projectId
|
||||
}
|
||||
|
||||
class Benchmark(id: EntityID<Uuid>) : Entity<Uuid>(id) {
|
||||
companion object : EntityClass<Uuid, Benchmark>(Benchmarks)
|
||||
|
||||
var projectId by Benchmarks.projectId
|
||||
var name by Benchmarks.name
|
||||
|
||||
val project by Project referencedOn Benchmarks.projectId
|
||||
val entries by EntryV2 referrersOn EntriesV2.benchmarkId
|
||||
}
|
||||
|
||||
class Version(id: EntityID<Uuid>) : Entity<Uuid>(id) {
|
||||
@@ -71,4 +98,20 @@ class Entry(id: EntityID<Uuid>) : Entity<Uuid>(id) {
|
||||
|
||||
var project by Project referencedOn Entries.projectId
|
||||
var version by Version referencedOn Entries.versionId
|
||||
}
|
||||
|
||||
class EntryV2(id: EntityID<Uuid>) : Entity<Uuid>(id) {
|
||||
companion object : EntityClass<Uuid, EntryV2>(EntriesV2)
|
||||
|
||||
var benchmarkId by EntriesV2.benchmarkId
|
||||
var versionId by EntriesV2.versionId
|
||||
var timestamp by EntriesV2.timestamp
|
||||
var warmups by EntriesV2.warmups
|
||||
var measurements by EntriesV2.measurements
|
||||
var unit by EntriesV2.unit
|
||||
var input by EntriesV2.input
|
||||
var hardware by EntriesV2.hardware
|
||||
|
||||
var benchmark by Benchmark referencedOn EntriesV2.benchmarkId
|
||||
var version by Version referencedOn EntriesV2.versionId
|
||||
}
|
||||
@@ -2,16 +2,26 @@ package com.jaytux.phoebench.server.db
|
||||
|
||||
import com.jaytux.phoebench.common.TimeUnit
|
||||
import org.jetbrains.exposed.v1.core.ReferenceOption
|
||||
import org.jetbrains.exposed.v1.core.dao.id.IntIdTable
|
||||
import org.jetbrains.exposed.v1.core.dao.id.UuidTable
|
||||
import org.jetbrains.exposed.v1.datetime.timestamp
|
||||
import kotlin.time.Clock
|
||||
|
||||
object Metas : IntIdTable() {
|
||||
const val CURRENT_REVISION = 1
|
||||
|
||||
val revision = integer("revision").default(0)
|
||||
}
|
||||
|
||||
object Users : UuidTable() {
|
||||
val username = varchar("username", 255).uniqueIndex()
|
||||
val password = varchar("password", 255)
|
||||
val isAdmin = bool("is_admin").default(false)
|
||||
val isOwner = bool("is_owner").default(false)
|
||||
val projectLimit = integer("project_limit").default(1)
|
||||
val benchmarkLimit = integer("benchmark_limit").default(1)
|
||||
|
||||
val projectsVersion = integer("project_version").default(0)
|
||||
}
|
||||
|
||||
object Invites : UuidTable() {
|
||||
@@ -25,6 +35,8 @@ object RefreshTokens : UuidTable() {
|
||||
}
|
||||
|
||||
object Projects : UuidTable() {
|
||||
const val CURRENT_VERSION = 1
|
||||
|
||||
val ownerId = reference("owner_id", Users, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
|
||||
val name = varchar("name", 255)
|
||||
val isPublic = bool("is_public").default(false)
|
||||
@@ -34,6 +46,15 @@ object Projects : UuidTable() {
|
||||
}
|
||||
}
|
||||
|
||||
object Benchmarks : UuidTable() {
|
||||
val projectId = reference("project_id", Projects, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
|
||||
val name = varchar("name", 255)
|
||||
|
||||
init {
|
||||
uniqueIndex(projectId, name)
|
||||
}
|
||||
}
|
||||
|
||||
object Versions : UuidTable() {
|
||||
val label = varchar("label", 255)
|
||||
val color = varchar("color", 7)
|
||||
@@ -50,4 +71,15 @@ object Entries : UuidTable() {
|
||||
val unit = enumeration<TimeUnit>("unit")
|
||||
val input = varchar("input", 255)
|
||||
val hardware = varchar("hardware", 255)
|
||||
}
|
||||
|
||||
object EntriesV2 : UuidTable() {
|
||||
val benchmarkId = reference("benchmark_id", Benchmarks, onDelete = ReferenceOption.CASCADE, onUpdate = ReferenceOption.CASCADE)
|
||||
val versionId = reference("version_id", 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)
|
||||
val hardware = varchar("hardware", 255)
|
||||
}
|
||||
@@ -1,22 +1,31 @@
|
||||
package com.jaytux.phoebench.server.handlers
|
||||
|
||||
import com.jaytux.phoebench.common.BenchmarkRequest
|
||||
import com.jaytux.phoebench.common.BenchmarkResponse
|
||||
import com.jaytux.phoebench.common.BenchmarkSummary
|
||||
import com.jaytux.phoebench.common.EmptyRequest
|
||||
import com.jaytux.phoebench.common.EmptyResponse
|
||||
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.Migrate0To1Request
|
||||
import com.jaytux.phoebench.common.VersionRequest
|
||||
import com.jaytux.phoebench.common.VersionResponse
|
||||
import com.jaytux.phoebench.common.NamedID
|
||||
import com.jaytux.phoebench.common.PartialBenchmarkRequest
|
||||
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.app
|
||||
import com.jaytux.phoebench.server.db.Benchmark
|
||||
import com.jaytux.phoebench.server.db.Benchmarks
|
||||
import com.jaytux.phoebench.server.db.Entries
|
||||
import com.jaytux.phoebench.server.db.Entry
|
||||
import com.jaytux.phoebench.server.db.EntryV2
|
||||
import com.jaytux.phoebench.server.db.Version
|
||||
import com.jaytux.phoebench.server.db.Versions
|
||||
import com.jaytux.phoebench.server.db.Project
|
||||
@@ -27,7 +36,12 @@ import io.ktor.http.HttpStatusCode
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jetbrains.exposed.v1.core.SortOrder
|
||||
import org.jetbrains.exposed.v1.core.Transaction
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.dao.id.EntityID
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.inList
|
||||
import org.jetbrains.exposed.v1.core.notInList
|
||||
import org.jetbrains.exposed.v1.jdbc.deleteWhere
|
||||
import org.jetbrains.exposed.v1.jdbc.transactions.transaction
|
||||
import kotlin.uuid.Uuid
|
||||
|
||||
@@ -40,23 +54,44 @@ object ProjectHandler {
|
||||
return when {
|
||||
isEditableBy(user) -> this
|
||||
isPublic && !forEditing -> this
|
||||
else -> throw RouteError("Invalid project ID.", HttpStatusCode.NotFound)
|
||||
else -> throw RouteError("Invalid ID.", HttpStatusCode.NotFound)
|
||||
}
|
||||
}
|
||||
|
||||
context(trns: Transaction)
|
||||
fun Benchmark.isAccessible(user: User, forEditing: Boolean): Pair<Project, Benchmark> {
|
||||
return project.isAccessible(user, forEditing) to this
|
||||
}
|
||||
|
||||
fun Transaction.accessibleProject(user: User, id: Uuid, forEditing: Boolean): Project {
|
||||
val project = Project.findById(id) ?: throw RouteError("Invalid project ID.", HttpStatusCode.NotFound)
|
||||
return project.isAccessible(user, forEditing)
|
||||
}
|
||||
|
||||
fun Transaction.accessibleBenchmark(user: User, id: Uuid, forEditing: Boolean): Pair<Project, Benchmark> {
|
||||
val bench = Benchmark.findById(id) ?: throw RouteError("Invalid benchmark ID.", HttpStatusCode.NotFound)
|
||||
return bench.isAccessible(user, forEditing)
|
||||
}
|
||||
|
||||
fun Transaction.checkMigration(user: User) {
|
||||
if(user.projectVersion < Projects.CURRENT_VERSION)
|
||||
throw RouteError("You need to migrate your projects first.", HttpStatusCode.Conflict)
|
||||
}
|
||||
|
||||
context(trns: Transaction)
|
||||
private fun Project.toResponse(user: User) = ProjectResponse(
|
||||
id.value, name, NamedID(owner.username, owner.id.value), isPublic, isEditableBy(user),
|
||||
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) })
|
||||
benchmarks.orderBy(Benchmarks.name to SortOrder.ASC).map { b -> BenchmarkResponse(
|
||||
b.id.value, b.name, b.entries.map { e ->
|
||||
EntryResponse(e.id.value, e.benchmarkId.value, e.versionId.value, e.timestamp, e.warmups, e.measurements, e.unit, e.input, e.hardware)
|
||||
}
|
||||
) },
|
||||
versions.orderBy(Versions.label to SortOrder.ASC).map { VersionResponse(it.id.value, it.label, it.color, it.meta) }
|
||||
)
|
||||
|
||||
fun home(user: User, req: EmptyRequest) = transaction {
|
||||
val own = user.projects.orderBy(Projects.name to SortOrder.ASC).map {
|
||||
val own = user.projects.orderBy(Projects.name to SortOrder.ASC).mapNotNull {
|
||||
if(it.name.startsWith("<migrate")) return@mapNotNull null
|
||||
HomeResponse.ProjectSummary(it.id.value, it.name, it.isPublic, NamedID(it.owner.username, it.ownerId.value))
|
||||
}
|
||||
|
||||
@@ -64,10 +99,23 @@ object ProjectHandler {
|
||||
HomeResponse.ProjectSummary(it.id.value, it.name, it.isPublic, NamedID(it.owner.username, it.ownerId.value))
|
||||
}
|
||||
|
||||
success(HomeResponse(user.username, user.isAdmin, user.projectLimit, own, publics))
|
||||
val migrations = mutableListOf<HomeResponse.NeededMigration>()
|
||||
var version = user.projectVersion
|
||||
while(version < Projects.CURRENT_VERSION) {
|
||||
migrations += when(version) {
|
||||
0 -> HomeResponse.NeededMigration.Migrate0To1(user.projects.map { p ->
|
||||
HomeResponse.NeededMigration.Migrate0To1.OldProject(p.id.value, p.name, p.versions.map { VersionResponse(it.id.value, it.label, it.color, it.meta) })
|
||||
})
|
||||
else -> throw IllegalArgumentException("No migration starting from $version possible.")
|
||||
}
|
||||
++version
|
||||
}
|
||||
|
||||
success(HomeResponse(user.username, user.isAdmin, user.projectLimit, own, publics, migrations))
|
||||
}
|
||||
|
||||
suspend fun createProject(user: User, req: ProjectRequest) = transaction {
|
||||
checkMigration(user)
|
||||
if(user.projectLimit != -1 && (user.projects.count() >= user.projectLimit))
|
||||
throw RouteError("You have reached your project limit.", HttpStatusCode.Conflict)
|
||||
|
||||
@@ -88,11 +136,13 @@ object ProjectHandler {
|
||||
}
|
||||
|
||||
fun getProject(user: User, req: Uuid) = transaction {
|
||||
checkMigration(user)
|
||||
val proj = accessibleProject(user, req, false)
|
||||
success(proj.toResponse(user))
|
||||
}
|
||||
|
||||
fun updateProject(user: User, req: Pair<Uuid, PartialProjectRequest>) = transaction {
|
||||
checkMigration(user)
|
||||
val proj = accessibleProject(user, req.first, true)
|
||||
val changes = req.second
|
||||
changes.name?.let { proj.name = it }
|
||||
@@ -113,6 +163,7 @@ object ProjectHandler {
|
||||
}
|
||||
|
||||
fun deleteProject(user: User, req: Uuid) = transaction {
|
||||
checkMigration(user)
|
||||
val proj = accessibleProject(user, req, true)
|
||||
proj.delete()
|
||||
ServerScope.launch {
|
||||
@@ -124,8 +175,57 @@ object ProjectHandler {
|
||||
success(EmptyResponse())
|
||||
}
|
||||
|
||||
fun createVersion(user: User, req: VersionRequest) = transaction {
|
||||
fun createBenchmark(user: User, req: BenchmarkRequest) = transaction {
|
||||
checkMigration(user)
|
||||
val proj = accessibleProject(user, req.projectId, true)
|
||||
val bench = Benchmark.new {
|
||||
name = req.name
|
||||
projectId = proj.id
|
||||
}
|
||||
|
||||
val res = BenchmarkResponse(bench.id.value, bench.name, bench.entries.map {
|
||||
EntryResponse(it.id.value, it.benchmarkId.value, it.versionId.value, it.timestamp, it.warmups, it.measurements, it.unit, it.input, it.hardware)
|
||||
})
|
||||
|
||||
ServerScope.launch {
|
||||
val projectBus = Buses.projectBus(proj.id.value)
|
||||
if(proj.isPublic) projectBus.sendAll(ProjectEvent.NewBenchmark(res))
|
||||
else projectBus.send(user.id.value, ProjectEvent.NewBenchmark(res))
|
||||
}
|
||||
|
||||
success(res)
|
||||
}
|
||||
|
||||
fun updateBenchmark(user: User, req: Pair<Uuid, PartialBenchmarkRequest>) = transaction {
|
||||
checkMigration(user)
|
||||
val (proj, bench) = accessibleBenchmark(user, req.first, true)
|
||||
val changes = req.second
|
||||
changes.name?.let { proj.name = it }
|
||||
|
||||
ServerScope.launch {
|
||||
Buses.projectBus(proj.id.value).sendAll(ProjectEvent.BenchmarkChanged(
|
||||
BenchmarkSummary(bench.id.value, bench.name)
|
||||
))
|
||||
}
|
||||
|
||||
success(EmptyResponse())
|
||||
}
|
||||
|
||||
fun deleteBenchmark(user: User, req: Uuid) = transaction {
|
||||
checkMigration(user)
|
||||
val (proj, bench) = accessibleBenchmark(user, req, true)
|
||||
bench.delete()
|
||||
|
||||
ServerScope.launch {
|
||||
Buses.projectBus(proj.id.value).sendAll(ProjectEvent.BenchmarkDeleted(req))
|
||||
}
|
||||
|
||||
success(EmptyResponse())
|
||||
}
|
||||
|
||||
fun createVersion(user: User, req: VersionRequest) = transaction {
|
||||
checkMigration(user)
|
||||
val proj = accessibleProject(user, req.benchmarkId, true)
|
||||
if(req.color.length != 7) throw RouteError("Expected Hex-RGB color (7 characters).", HttpStatusCode.BadRequest)
|
||||
val ver = Version.new {
|
||||
label = req.name
|
||||
@@ -136,13 +236,14 @@ object ProjectHandler {
|
||||
|
||||
val res = VersionResponse(ver.id.value, ver.label, ver.color, ver.meta)
|
||||
ServerScope.launch {
|
||||
Buses.projectBus(req.projectId).sendAll(ProjectEvent.NewVersion(res))
|
||||
Buses.projectBus(req.benchmarkId).sendAll(ProjectEvent.NewVersion(res))
|
||||
}
|
||||
|
||||
success(res)
|
||||
}
|
||||
|
||||
fun updateVersion(user: User, req: Pair<Uuid, PartialVersionRequest>) = transaction {
|
||||
checkMigration(user)
|
||||
val ver = Version.findById(req.first) ?: throw RouteError("Invalid version ID.", HttpStatusCode.NotFound)
|
||||
ver.project.isAccessible(user, true)
|
||||
val changes = req.second
|
||||
@@ -163,6 +264,7 @@ object ProjectHandler {
|
||||
}
|
||||
|
||||
fun deleteVersion(user: User, req: Uuid) = transaction {
|
||||
checkMigration(user)
|
||||
val ver = Version.findById(req) ?: throw RouteError("Invalid version ID.", HttpStatusCode.NotFound)
|
||||
ver.project.isAccessible(user, true)
|
||||
ver.delete()
|
||||
@@ -175,14 +277,15 @@ object ProjectHandler {
|
||||
}
|
||||
|
||||
fun createEntry(user: User, req: EntryRequest) = transaction {
|
||||
val proj = accessibleProject(user, req.projectId, true)
|
||||
val entry = Entry.new {
|
||||
checkMigration(user)
|
||||
val (proj, bench) = accessibleBenchmark(user, req.benchmarkId, true)
|
||||
val entry = EntryV2.new {
|
||||
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
|
||||
benchmarkId = bench.id
|
||||
measurements = req.measurements
|
||||
timestamp = req.timestamp
|
||||
warmups = req.warmups
|
||||
@@ -191,7 +294,8 @@ object ProjectHandler {
|
||||
hardware = req.hardware
|
||||
}
|
||||
|
||||
val response = EntryResponse(entry.id.value, entry.version.id.value, entry.timestamp, entry.warmups, entry.measurements, entry.unit, entry.input, entry.hardware)
|
||||
val response = EntryResponse(entry.id.value, entry.benchmarkId.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))
|
||||
}
|
||||
@@ -199,12 +303,82 @@ object ProjectHandler {
|
||||
}
|
||||
|
||||
fun deleteEntry(user: User, req: Uuid) = transaction {
|
||||
val entry = Entry.findById(req) ?: throw RouteError("Invalid entry ID.", HttpStatusCode.NotFound)
|
||||
entry.project.isAccessible(user, true)
|
||||
checkMigration(user)
|
||||
val entry = EntryV2.findById(req) ?: throw RouteError("Invalid entry ID.", HttpStatusCode.NotFound)
|
||||
val (proj, bench) = entry.benchmark.isAccessible(user, true)
|
||||
entry.delete()
|
||||
ServerScope.launch {
|
||||
Buses.projectBus(entry.project.id.value).sendAll(ProjectEvent.EntryDeleted(req))
|
||||
Buses.projectBus(proj.id.value).sendAll(ProjectEvent.EntryDeleted(req, bench.id.value))
|
||||
}
|
||||
success(EmptyResponse())
|
||||
}
|
||||
|
||||
fun migrate01(user: User, req: Migrate0To1Request): Pair<HttpStatusCode, HomeResponse> {
|
||||
val givenProjectIds = req.conversions.flatMap { it.oldProjectsAsBenchmarks }.toSet()
|
||||
val totalProjectCount = req.conversions.sumOf { it.oldProjectsAsBenchmarks.size }
|
||||
if(totalProjectCount != givenProjectIds.size)
|
||||
throw RouteError("Each (legacy) project should only be bound to a single (migrated) project", HttpStatusCode.BadRequest)
|
||||
|
||||
val userProjects = transaction { user.projects.toList() }.map { it.id.value }.toSet()
|
||||
if(givenProjectIds.any { it !in userProjects })
|
||||
throw RouteError("Only a user's own projects should be migrated.", HttpStatusCode.Conflict)
|
||||
|
||||
transaction {
|
||||
user.projects.forEach { it.name = "${it.name}#legacy" }
|
||||
}
|
||||
|
||||
transaction {
|
||||
req.conversions.forEach { (newName, isPublic, olds) ->
|
||||
val proj = Project.new {
|
||||
ownerId = user.id
|
||||
name = newName
|
||||
this.isPublic = isPublic
|
||||
}
|
||||
|
||||
val versionMap = mutableMapOf<Uuid, EntityID<Uuid>>()
|
||||
val duplicateMap = mutableMapOf<String, EntityID<Uuid>>()
|
||||
Version.find { Versions.projectId inList olds }.forEach { oldVersion ->
|
||||
val targetId = duplicateMap.getOrPut(oldVersion.label) {
|
||||
oldVersion.projectId = proj.id
|
||||
oldVersion.id
|
||||
}
|
||||
versionMap[oldVersion.id.value] = targetId
|
||||
}
|
||||
|
||||
olds.forEach { oldId ->
|
||||
val oldProj = accessibleProject(user, oldId, true)
|
||||
val benchmark = Benchmark.new {
|
||||
projectId = proj.id
|
||||
name = oldProj.name.removeSuffix("#legacy")
|
||||
}
|
||||
|
||||
Entry.find { Entries.projectId eq oldId }.forEach {
|
||||
EntryV2.new {
|
||||
benchmarkId = benchmark.id
|
||||
versionId = versionMap[it.versionId.value]!!
|
||||
timestamp = it.timestamp
|
||||
warmups = it.warmups
|
||||
measurements = it.measurements
|
||||
unit = it.unit
|
||||
input = it.input
|
||||
hardware = it.hardware
|
||||
}
|
||||
it.delete()
|
||||
}
|
||||
}
|
||||
|
||||
Versions.deleteWhere {
|
||||
(Versions.projectId inList olds) and (Versions.id notInList versionMap.values.toList())
|
||||
}
|
||||
|
||||
Projects.deleteWhere {
|
||||
Projects.id inList olds
|
||||
}
|
||||
}
|
||||
|
||||
user.projectVersion = 1
|
||||
}
|
||||
|
||||
return home(user, EmptyRequest())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user