forked from SteamWar/SteamWar
Add world-based permissions and migration commands
- Switch bau logic from owner-scoped to world-scoped data - Add CLI commands for world migration, templating, and archiving - Extend world storage with team worlds and lock state support
This commit is contained in:
@@ -19,7 +19,6 @@
|
||||
|
||||
package de.steamwar.sql
|
||||
|
||||
import de.steamwar.sql.BauweltMemberTable.bauweltId
|
||||
import de.steamwar.sql.internal.useDb
|
||||
import org.jetbrains.exposed.v1.core.and
|
||||
import org.jetbrains.exposed.v1.core.dao.id.CompositeID
|
||||
@@ -32,45 +31,37 @@ import org.jetbrains.exposed.v1.jdbc.insertIgnore
|
||||
import java.util.*
|
||||
|
||||
object BauweltMemberTable : CompositeIdTable("BauweltMember") {
|
||||
val bauweltId = reference("BauweltID", SteamwarUserTable).index()
|
||||
val worldId = reference("WorldID", WorldTable).index()
|
||||
val memberId = reference("MemberID", SteamwarUserTable).index()
|
||||
val build = bool("Build")
|
||||
val worldEdit = bool("WorldEdit")
|
||||
val world = bool("World")
|
||||
|
||||
override val primaryKey = PrimaryKey(bauweltId, memberId)
|
||||
override val primaryKey = PrimaryKey(worldId, memberId)
|
||||
|
||||
init {
|
||||
addIdColumn(bauweltId)
|
||||
addIdColumn(worldId)
|
||||
addIdColumn(memberId)
|
||||
}
|
||||
}
|
||||
|
||||
class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
|
||||
companion object : CompositeEntityClass<BauweltMember>(BauweltMemberTable) {
|
||||
private val cache = mutableMapOf<Int, BauweltMember>()
|
||||
private val cache = mutableMapOf<Pair<UUID, Int>, BauweltMember>()
|
||||
|
||||
private fun cache(member: BauweltMember) =
|
||||
cache.put(member.memberID, member)
|
||||
cache.put(member.worldID to member.memberID, member)
|
||||
|
||||
@JvmStatic
|
||||
fun clear() =
|
||||
cache.clear()
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("Use addMember(ownerId: Int, newMemberId: Int)")
|
||||
fun addMember(ownerId: UUID, newMemberId: UUID) =
|
||||
addMember(SteamwarUser.get(ownerId)!!.id, SteamwarUser.get(newMemberId)!!.id)
|
||||
|
||||
@JvmStatic
|
||||
fun addMember(ownerId: Int, newMemberId: Int) =
|
||||
addMember(EntityID(ownerId, SteamwarUserTable), EntityID(newMemberId, SteamwarUserTable))
|
||||
|
||||
fun addMember(ownerId: EntityID<Int>, newMemberId: EntityID<Int>) =
|
||||
fun addMember(worldId: UUID, newMemberId: Int) =
|
||||
useDb {
|
||||
BauweltMemberTable.insertIgnore {
|
||||
it[bauweltId] = ownerId
|
||||
it[memberId] = newMemberId
|
||||
it[BauweltMemberTable.worldId] = EntityID(worldId, WorldTable)
|
||||
it[memberId] = EntityID(newMemberId, SteamwarUserTable)
|
||||
it[build] = false
|
||||
it[worldEdit] = false
|
||||
it[world] = false
|
||||
@@ -78,31 +69,32 @@ class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("Use getBauMember(bauwelt: Int, member: Int)")
|
||||
fun getBauMember(bauwelt: UUID, member: UUID) =
|
||||
fun addMember(worldId: UUID, newMemberId: UUID) =
|
||||
addMember(worldId, SteamwarUser.get(newMemberId)!!.id.value)
|
||||
|
||||
@JvmStatic
|
||||
fun getBauMember(world: UUID, member: Int) =
|
||||
cache[world to member]
|
||||
?: useDb {
|
||||
find { (BauweltMemberTable.worldId eq world) and (BauweltMemberTable.memberId eq member) }.firstOrNull()?.also { cache(it) }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getBauMember(world: UUID, member: UUID) =
|
||||
getBauMember(world, SteamwarUser.get(member)!!.id.value)
|
||||
|
||||
@JvmStatic
|
||||
fun getWorldMembers(world: UUID) =
|
||||
useDb {
|
||||
find { (bauweltId eq SteamwarUser.get(bauwelt)!!.id) and (BauweltMemberTable.memberId eq SteamwarUser.get(member)!!.id) }.firstOrNull()?.also { cache(it) }
|
||||
find { BauweltMemberTable.worldId eq world }.toList().also { it.forEach { cache(it) } }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getBauMember(bauwelt: Int, member: Int) =
|
||||
useDb {
|
||||
find { (bauweltId eq bauwelt) and (BauweltMemberTable.memberId eq member) }.firstOrNull()?.also { cache(it) }
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@Deprecated("Use getMembers(bauwelt: Int)")
|
||||
fun getMembers(bauwelt: UUID) =
|
||||
getMembers(SteamwarUser.get(bauwelt)!!.id.value)
|
||||
|
||||
@JvmStatic
|
||||
fun getMembers(bauwelt: Int) =
|
||||
useDb {
|
||||
find { bauweltId eq bauwelt }.toList().also { it.forEach { cache(it) } }
|
||||
}
|
||||
fun getMembers(world: UUID) =
|
||||
getWorldMembers(world)
|
||||
}
|
||||
|
||||
val bauweltID by BauweltMemberTable.bauweltId.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
|
||||
val worldID by BauweltMemberTable.worldId.transform({ EntityID(it, WorldTable) }, { it.value })
|
||||
val memberID by BauweltMemberTable.memberId.transform({ EntityID(it, SteamwarUserTable) }, { it.value })
|
||||
private var worldEditInternal by BauweltMemberTable.worldEdit
|
||||
var worldEdit: Boolean
|
||||
@@ -144,4 +136,4 @@ class BauweltMember(id: EntityID<CompositeID>) : CompositeEntity(id) {
|
||||
useDb {
|
||||
delete()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,6 +50,8 @@ enum class UserPerm {
|
||||
PUNISHMENTS,
|
||||
TICKET_LOG,
|
||||
BUILD,
|
||||
BUILD_EXTRA_WORLDS,
|
||||
BUILD_UNLIMITED_WORLDS,
|
||||
CHECK,
|
||||
MODERATION,
|
||||
ADMINISTRATION;
|
||||
@@ -95,4 +97,4 @@ enum class UserPerm {
|
||||
}
|
||||
|
||||
data class Prefix(val colorCode: String, val chatPrefix: String)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ import org.jetbrains.exposed.v1.core.dao.id.EntityID
|
||||
import org.jetbrains.exposed.v1.core.dao.id.java.UUIDTable
|
||||
import org.jetbrains.exposed.v1.core.eq
|
||||
import org.jetbrains.exposed.v1.core.not
|
||||
import org.jetbrains.exposed.v1.core.or
|
||||
import org.jetbrains.exposed.v1.core.SortOrder
|
||||
import org.jetbrains.exposed.v1.dao.Entity
|
||||
import org.jetbrains.exposed.v1.dao.EntityClass
|
||||
import org.jetbrains.exposed.v1.javatime.CurrentTimestamp
|
||||
@@ -39,6 +41,8 @@ object WorldTable : UUIDTable("world") {
|
||||
val version = integer("Version")
|
||||
val type = enumeration<WorldType>("Type")
|
||||
val owner = reference("Owner", SteamwarUserTable).nullable()
|
||||
val team = reference("Team", TeamTable).nullable()
|
||||
val lockState = varchar("LockState", 32).nullable()
|
||||
val deleted = bool("Deleted").default(false)
|
||||
val created = timestamp("Created").defaultExpression(CurrentTimestamp)
|
||||
val lastUsed = timestamp("LastUsed").defaultExpression(CurrentTimestamp)
|
||||
@@ -47,6 +51,9 @@ object WorldTable : UUIDTable("world") {
|
||||
enum class WorldType {
|
||||
BAU,
|
||||
BUILDER,
|
||||
TEAM,
|
||||
ARENA,
|
||||
TEMPLATE,
|
||||
}
|
||||
|
||||
class SteamwarWorld(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
@@ -58,6 +65,10 @@ class SteamwarWorld(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
private set
|
||||
var owner by WorldTable.owner
|
||||
private set
|
||||
var team by WorldTable.team
|
||||
private set
|
||||
var lockState by WorldTable.lockState
|
||||
private set
|
||||
var deleted by WorldTable.deleted
|
||||
private set
|
||||
val created by WorldTable.created
|
||||
@@ -109,6 +120,21 @@ class SteamwarWorld(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
name = newName
|
||||
}
|
||||
|
||||
fun archiveIfNeeded(): Boolean {
|
||||
if (!shouldArchive) return false
|
||||
|
||||
File(ARCHIVE_WORLD_STORAGE).mkdirs()
|
||||
return archiveWorld() == 0
|
||||
}
|
||||
|
||||
fun changeVersion(newVersion: Int) = useDb {
|
||||
version = newVersion
|
||||
}
|
||||
|
||||
fun changeLockState(newLockState: String?) = useDb {
|
||||
lockState = newLockState
|
||||
}
|
||||
|
||||
private fun archiveWorld() = ProcessBuilder("zip", "-u9oymrqq", "$ARCHIVE_WORLD_STORAGE/${id.value}.zip", id.value.toString())
|
||||
.directory(File(WORLD_STORAGE))
|
||||
.inheritIO()
|
||||
@@ -124,6 +150,14 @@ class SteamwarWorld(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
companion object : EntityClass<UUID, SteamwarWorld>(WorldTable) {
|
||||
const val WORLD_STORAGE = "/worlds/storage"
|
||||
const val ARCHIVE_WORLD_STORAGE = "/mnt/storage/worlds/storage"
|
||||
const val DEFAULT_BAU_WORLD_LIMIT = 3
|
||||
const val EXTRA_BAU_WORLD_LIMIT = 10
|
||||
const val TEAM_WORLD_LIMIT = 2
|
||||
|
||||
@JvmStatic
|
||||
fun getWorld(uuid: UUID) = useDb {
|
||||
findById(uuid)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getBauWorld(user: SteamwarUser, version: Int) = useDb {
|
||||
@@ -135,11 +169,45 @@ class SteamwarWorld(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
}.firstOrNull()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getBauWorld(user: SteamwarUser, name: String, version: Int) = useDb {
|
||||
find {
|
||||
(WorldTable.owner eq user.id) and
|
||||
(WorldTable.name eq name) and
|
||||
(WorldTable.version eq version) and
|
||||
(WorldTable.type eq WorldType.BAU) and
|
||||
not(WorldTable.deleted)
|
||||
}.firstOrNull()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getBauWorlds(user: SteamwarUser) = useDb {
|
||||
find {
|
||||
(WorldTable.owner eq user.id) and
|
||||
(WorldTable.type eq WorldType.BAU) and
|
||||
not(WorldTable.deleted)
|
||||
}.orderBy(WorldTable.created to SortOrder.ASC).toList()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun countBauWorlds(user: SteamwarUser) = useDb {
|
||||
find {
|
||||
(WorldTable.owner eq user.id) and
|
||||
(WorldTable.type eq WorldType.BAU) and
|
||||
not(WorldTable.deleted)
|
||||
}.count()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun getOrCreateBauWorld(user: SteamwarUser, version: Int, prototype: File? = null): SteamwarWorld =
|
||||
getBauWorld(user, version) ?: createWorld(user, user.userName, version, WorldType.BAU, prototype)
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun getOrCreateBauWorld(user: SteamwarUser, name: String, version: Int, prototype: File? = null): SteamwarWorld =
|
||||
getBauWorld(user, name, version) ?: createWorld(user, name, version, WorldType.BAU, prototype)
|
||||
|
||||
@JvmStatic
|
||||
fun getBuilderWorld(name: String, version: Int) = useDb {
|
||||
find {
|
||||
@@ -164,14 +232,110 @@ class SteamwarWorld(id: EntityID<UUID>) : Entity<UUID>(id) {
|
||||
fun getOrCreateBuilderWorld(name: String, version: Int, prototype: File? = null): SteamwarWorld =
|
||||
getBuilderWorld(name, version) ?: createWorld(null, name, version, WorldType.BUILDER, prototype)
|
||||
|
||||
@JvmStatic
|
||||
fun getTeamWorld(team: Team, name: String, version: Int) = useDb {
|
||||
find {
|
||||
(WorldTable.team eq team.id) and
|
||||
(WorldTable.name eq name) and
|
||||
(WorldTable.version eq version) and
|
||||
(WorldTable.type eq WorldType.TEAM) and
|
||||
not(WorldTable.deleted)
|
||||
}.firstOrNull()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getTeamWorlds(team: Team) = useDb {
|
||||
find {
|
||||
(WorldTable.team eq team.id) and
|
||||
(WorldTable.type eq WorldType.TEAM) and
|
||||
not(WorldTable.deleted)
|
||||
}.orderBy(WorldTable.created to SortOrder.ASC).toList()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun createWorld(user: SteamwarUser?, name: String, version: Int, type: WorldType, prototype: File? = null): SteamwarWorld {
|
||||
fun getOrCreateTeamWorld(team: Team, name: String, version: Int, prototype: File? = null): SteamwarWorld =
|
||||
getTeamWorld(team, name, version) ?: createWorld(null, name, version, WorldType.TEAM, prototype, team)
|
||||
|
||||
@JvmStatic
|
||||
fun countTeamWorlds(team: Team) = useDb {
|
||||
find {
|
||||
(WorldTable.team eq team.id) and
|
||||
(WorldTable.type eq WorldType.TEAM) and
|
||||
not(WorldTable.deleted)
|
||||
}.count()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getArenaWorld(mode: String, map: String, version: Int) = useDb {
|
||||
find {
|
||||
(WorldTable.name eq arenaWorldName(mode, map)) and
|
||||
(WorldTable.version eq version) and
|
||||
(WorldTable.type eq WorldType.ARENA) and
|
||||
not(WorldTable.deleted)
|
||||
}.firstOrNull()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun getOrCreateArenaWorld(mode: String, map: String, version: Int, prototype: File? = null): SteamwarWorld =
|
||||
getArenaWorld(mode, map, version) ?: createWorld(null, arenaWorldName(mode, map), version, WorldType.ARENA, prototype)
|
||||
|
||||
@JvmStatic
|
||||
fun getTemplateWorld(name: String, version: Int) = useDb {
|
||||
find {
|
||||
(WorldTable.name eq name) and
|
||||
(WorldTable.version eq version) and
|
||||
(WorldTable.type eq WorldType.TEMPLATE) and
|
||||
not(WorldTable.deleted)
|
||||
}.firstOrNull()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getTemplateWorlds() = useDb {
|
||||
find {
|
||||
(WorldTable.type eq WorldType.TEMPLATE) and
|
||||
not(WorldTable.deleted)
|
||||
}.orderBy(WorldTable.name to SortOrder.ASC).toList()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getTemplateWorlds(version: Int) = useDb {
|
||||
find {
|
||||
(WorldTable.version eq version) and
|
||||
(WorldTable.type eq WorldType.TEMPLATE) and
|
||||
not(WorldTable.deleted)
|
||||
}.orderBy(WorldTable.name to SortOrder.ASC).toList()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun getOrCreateTemplateWorld(name: String, version: Int, prototype: File? = null): SteamwarWorld =
|
||||
getTemplateWorld(name, version) ?: createWorld(null, name, version, WorldType.TEMPLATE, prototype)
|
||||
|
||||
@JvmStatic
|
||||
fun getAllActiveWorlds() = useDb {
|
||||
find { not(WorldTable.deleted) }.toList()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getWorldsToArchive() = useDb {
|
||||
find {
|
||||
not(WorldTable.deleted) or (WorldTable.deleted eq true)
|
||||
}.toList().filter { it.shouldArchive }
|
||||
}
|
||||
|
||||
private fun arenaWorldName(mode: String, map: String) = "$mode/$map"
|
||||
|
||||
@JvmStatic
|
||||
@JvmOverloads
|
||||
fun createWorld(user: SteamwarUser?, name: String, version: Int, type: WorldType, prototype: File? = null, team: Team? = null): SteamwarWorld {
|
||||
val world = useDb { new {
|
||||
this.name = name
|
||||
this.version = version
|
||||
this.type = type
|
||||
this.owner = user?.id
|
||||
this.team = team?.id
|
||||
} }
|
||||
|
||||
world.setupAndGetStoragePath(prototype)
|
||||
|
||||
Reference in New Issue
Block a user