diff --git a/botfest/src/main/kotlin/net/modfest/botfest/App.kt b/botfest/src/main/kotlin/net/modfest/botfest/App.kt index 5b36e2e..c38f869 100644 --- a/botfest/src/main/kotlin/net/modfest/botfest/App.kt +++ b/botfest/src/main/kotlin/net/modfest/botfest/App.kt @@ -4,9 +4,9 @@ import com.google.gson.Gson import com.google.gson.JsonElement import com.google.gson.JsonPrimitive import dev.kord.common.entity.Snowflake +import dev.kord.rest.builder.message.allowedMentions import dev.kord.rest.builder.message.embed import dev.kordex.core.ExtensibleBot -import dev.kordex.core.i18n.withContext import dev.kordex.core.utils.env import dev.kordex.core.utils.envOrNull import dev.kordex.core.utils.loadModule @@ -29,6 +29,10 @@ private val TOKEN = env("TOKEN") // Get the bot's token from the env vars or a suspend fun main() { val bot = ExtensibleBot(TOKEN) { + kord { + stackTraceRecovery = devMode + } + hooks { beforeKoinSetup { loadModule { @@ -56,6 +60,8 @@ suspend fun main() { } errorResponse { message, type -> + allowedMentions { } + if (type.error is PlatformException) { var data = (type.error as PlatformException).data content = when (data.type) { @@ -73,6 +79,8 @@ suspend fun main() { PlatformErrorResponse.ErrorType.INTERNAL -> Translations.Apierror.internal .translateNamed("error" to data.data.stringified()) } + } else { + content = message.translate() } } diff --git a/botfest/src/main/kotlin/net/modfest/botfest/Platform.kt b/botfest/src/main/kotlin/net/modfest/botfest/Platform.kt index d14998e..4d2b95a 100644 --- a/botfest/src/main/kotlin/net/modfest/botfest/Platform.kt +++ b/botfest/src/main/kotlin/net/modfest/botfest/Platform.kt @@ -18,6 +18,7 @@ import net.modfest.platform.pojo.EventData import net.modfest.platform.pojo.HealthData import net.modfest.platform.pojo.MinecraftEditResponse import net.modfest.platform.pojo.PlatformErrorResponse +import net.modfest.platform.pojo.SubmissionData.BoothData import net.modfest.platform.pojo.SubmissionPatchData import net.modfest.platform.pojo.SubmissionResponseData import net.modfest.platform.pojo.SubmitRequestModrinth @@ -209,6 +210,13 @@ class PlatformAuthenticated(var client: HttpClient, var discordUser: Snowflake) }.unwrapErrors().body() } + suspend fun editSubmissionBoothData(eventId: String, subId: String, edit: BoothData): SubmissionResponseData { + return client.patch("/event/$eventId/booth/$subId") { + addAuth() + setBody(edit) + }.unwrapErrors().body() + } + suspend fun updateSubmissionVersion(eventId: String, subId: String): SubmissionResponseData { return client.put("/event/$eventId/submission/$subId/updateVersion") { addAuth() diff --git a/botfest/src/main/kotlin/net/modfest/botfest/extensions/SubmissionCommands.kt b/botfest/src/main/kotlin/net/modfest/botfest/extensions/SubmissionCommands.kt index 9d0a9fe..6d9b340 100644 --- a/botfest/src/main/kotlin/net/modfest/botfest/extensions/SubmissionCommands.kt +++ b/botfest/src/main/kotlin/net/modfest/botfest/extensions/SubmissionCommands.kt @@ -5,9 +5,15 @@ import dev.kord.core.behavior.interaction.response.edit import dev.kordex.core.commands.Arguments import dev.kordex.core.commands.application.slash.EphemeralSlashCommand import dev.kordex.core.commands.application.slash.EphemeralSlashCommandContext +import dev.kordex.core.commands.application.slash.converters.ChoiceEnum +import dev.kordex.core.commands.application.slash.converters.impl.optionalEnumChoice +import dev.kordex.core.commands.application.slash.converters.impl.optionalNumberChoice +import dev.kordex.core.commands.application.slash.converters.impl.optionalStringChoice import dev.kordex.core.commands.application.slash.ephemeralSubCommand import dev.kordex.core.commands.application.slash.group import dev.kordex.core.commands.converters.impl.attachment +import dev.kordex.core.commands.converters.impl.optionalAttachment +import dev.kordex.core.commands.converters.impl.optionalInt import dev.kordex.core.commands.converters.impl.string import dev.kordex.core.commands.converters.impl.user import dev.kordex.core.components.components @@ -31,8 +37,11 @@ import kotlinx.serialization.json.putJsonArray import net.modfest.botfest.MAIN_GUILD_ID import net.modfest.botfest.Platform import net.modfest.botfest.i18n.Translations +import net.modfest.platform.pojo.EventData import net.modfest.platform.pojo.SubmissionData.AssociatedData.Modrinth import net.modfest.platform.pojo.SubmissionData.AssociatedData.Other +import net.modfest.platform.pojo.SubmissionData.BoothData +import net.modfest.platform.pojo.SubmissionData.BoothData.Column import net.modfest.platform.pojo.SubmissionPatchData import net.modfest.platform.pojo.SubmitRequestOther import org.koin.core.component.inject @@ -53,49 +62,37 @@ class SubmissionCommands : Extension(), KordExKoinComponent { var slashScreenshot: EphemeralSlashCommand? = null // Commands for submitting ephemeralSlashCommand { - name = Translations.Commands.Event.Submit.name - description = Translations.Commands.Event.Submit.description + name = Translations.Commands.Submit.name + description = Translations.Commands.Submit.description guild(MAIN_GUILD_ID) // Submitting a modrinth project - ephemeralSubCommand(::SubmitModalModrinth) { - name = Translations.Commands.Event.Submit.Modrinth.name - description = Translations.Commands.Event.Submit.Modrinth.description + ephemeralSubCommand(::ModrinthArg) { + name = Translations.Commands.Submit.Modrinth.name + description = Translations.Commands.Submit.Modrinth.description - action { modal -> - if (modal == null) return@action + action { val curEvent = platform.getCurrentEvent().event if (curEvent == null) { respond { - content = Translations.Commands.Event.Submit.Response.unavailable + content = Translations.Commands.Submit.Response.unavailable .withContext(this@action) .translateNamed() } return@action } - val matcher = MODRINTH_REGEX.matcher(modal.modrinthUrl.value!!) - - if (!matcher.find()) { - respond { - content = Translations.Commands.Event.Submit.Response.invalid - .withContext(this@action) - .translateNamed( - "url" to modal.modrinthUrl.value - ) - } - return@action - } - + val matcher = MODRINTH_REGEX.matcher(arguments.url) + matcher.find() val projectSlug = matcher.group(2) val eventInfo = platform.getEvent(curEvent) val submission = platform.withAuth(this.user).submitModrinth(curEvent, projectSlug) respond { - content = Translations.Commands.Event.Submit.Response.success + content = Translations.Commands.Submit.Response.success .withContext(this@action) .translateNamed( "event" to eventInfo.name, @@ -107,8 +104,8 @@ class SubmissionCommands : Extension(), KordExKoinComponent { // Submitting a non-modrinth project ephemeralSubCommand(::SubmitModalOther) { - name = Translations.Commands.Event.Submit.Other.name - description = Translations.Commands.Event.Submit.Other.description + name = Translations.Commands.Submit.Other.name + description = Translations.Commands.Submit.Other.description action { modal -> if (modal == null) return@action @@ -116,7 +113,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { if (curEvent == null) { respond { - content = Translations.Commands.Event.Submit.Response.unavailable + content = Translations.Commands.Submit.Response.unavailable .withContext(this@action) .translateNamed() } @@ -135,7 +132,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { val eventInfo = platform.getEvent(curEvent) respond { - content = Translations.Commands.Event.Submit.Other.Response.success + content = Translations.Commands.Submit.Other.Response.success .withContext(this@action) .translateNamed( "event" to eventInfo.name, @@ -150,8 +147,8 @@ class SubmissionCommands : Extension(), KordExKoinComponent { // Submit an event / panel ephemeralSubCommand { - name = Translations.Commands.Event.Submit.Event.name - description = Translations.Commands.Event.Submit.Event.description + name = Translations.Commands.Submit.Event.name + description = Translations.Commands.Submit.Event.description action { respond { @@ -232,7 +229,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { val curEvent = platform.getCurrentEvent().event if (curEvent == null) { ackEphemeral { - content = Translations.Commands.Event.Submit.Response.unavailable + content = Translations.Commands.Submit.Response.unavailable .withContext(this@action) .translateNamed() } @@ -289,18 +286,16 @@ class SubmissionCommands : Extension(), KordExKoinComponent { } // Delete submission data - unsafeSubCommand(::SubmissionArg) { + ephemeralSubCommand(::SubmissionArg) { name = Translations.Commands.Submission.Delete.name description = Translations.Commands.Submission.Delete.description - initialResponse = InitialSlashCommandResponse.None - action { val subId = this.arguments.submission val curEvent = platform.getCurrentEvent().event if (curEvent == null) { - ackEphemeral { - content = Translations.Commands.Event.Submit.Response.unavailable + respond { + content = Translations.Commands.Submit.Response.unavailable .withContext(this@action) .translateNamed() } @@ -310,7 +305,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { val submission = platform.getEventSubmissions(curEvent).find { it.id == subId } if (submission == null) { - ackEphemeral { + respond { content = Translations.Commands.Submission.Delete.Response.notfound .withContext(this@action) .translateNamed( @@ -322,7 +317,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { platform.withAuth(this.user).deleteSubmission(curEvent, subId) - ackEphemeral { + respond { content = Translations.Commands.Submission.Delete.Response.success .withContext(this@action) .translateNamed( @@ -336,18 +331,16 @@ class SubmissionCommands : Extension(), KordExKoinComponent { description = Translations.Commands.Group.Submission.Update.description // Update submission version - unsafeSubCommand(::SubmissionArg) { + ephemeralSubCommand(::SubmissionArg) { name = Translations.Commands.Submission.Update.Version.name description = Translations.Commands.Submission.Update.Version.description - initialResponse = InitialSlashCommandResponse.None - action { val subId = this.arguments.submission val curEvent = platform.getCurrentEvent().event if (curEvent == null) { - ackEphemeral { - content = Translations.Commands.Event.Submit.Response.unavailable + respond { + content = Translations.Commands.Submit.Response.unavailable .withContext(this@action) .translateNamed() } @@ -357,7 +350,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { val submission = platform.getEventSubmissions(curEvent).find { it.id == subId } if (submission == null) { - ackEphemeral { + respond { content = Translations.Commands.Submission.Update.Version.Response.notfound .withContext(this@action) .translateNamed( @@ -390,17 +383,17 @@ class SubmissionCommands : Extension(), KordExKoinComponent { ) } } else if (submission.platform.inner !is Modrinth) { - ackEphemeral { - content = Translations.Commands.Submission.Update.Version.Response.notmodrinth - .withContext(this@action) - .translateNamed( - "subId" to subId - ) - } - } else { + respond { + content = Translations.Commands.Submission.Update.Version.Response.notmodrinth + .withContext(this@action) + .translateNamed( + "subId" to subId + ) + } + } else { val updatedSubmission = platform.withAuth(this.user).updateSubmissionVersion(curEvent, subId) - ackEphemeral { + respond { content = Translations.Commands.Submission.Update.Version.Response.success .withContext(this@action) .translateNamed( @@ -412,18 +405,16 @@ class SubmissionCommands : Extension(), KordExKoinComponent { } } // Update submission meta - unsafeSubCommand(::SubmissionArg) { + ephemeralSubCommand(::SubmissionArg) { name = Translations.Commands.Submission.Update.Meta.name description = Translations.Commands.Submission.Update.Meta.description - initialResponse = InitialSlashCommandResponse.None - action { val subId = this.arguments.submission val curEvent = platform.getCurrentEvent().event if (curEvent == null) { - ackEphemeral { - content = Translations.Commands.Event.Submit.Response.unavailable + respond { + content = Translations.Commands.Submit.Response.unavailable .withContext(this@action) .translateNamed() } @@ -433,7 +424,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { val submission = platform.getEventSubmissions(curEvent).find { it.id == subId } if (submission == null) { - ackEphemeral { + respond { content = Translations.Commands.Submission.Update.Meta.Response.notfound .withContext(this@action) .translateNamed( @@ -444,7 +435,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { } if (submission.platform.inner !is Modrinth) { - ackEphemeral { + respond { content = Translations.Commands.Submission.Update.Meta.Response.notmodrinth .withContext(this@action) .translateNamed( @@ -456,7 +447,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { platform.withAuth(this.user).updateSubmissionMeta(curEvent, subId) - ackEphemeral { + respond { content = Translations.Commands.Submission.Update.Meta.Response.success .withContext(this@action) .translateNamed( @@ -468,19 +459,17 @@ class SubmissionCommands : Extension(), KordExKoinComponent { } // Leave submission - unsafeSubCommand(::SubmissionArg) { + ephemeralSubCommand(::SubmissionArg) { name = Translations.Commands.Submission.Leave.name description = Translations.Commands.Submission.Leave.description - initialResponse = InitialSlashCommandResponse.None - action { val userId = this.user val subId = this.arguments.submission val curEvent = platform.getCurrentEvent().event if (curEvent == null) { - ackEphemeral { - content = Translations.Commands.Event.Submit.Response.unavailable + respond { + content = Translations.Commands.Submit.Response.unavailable .withContext(this@action) .translateNamed() } @@ -490,7 +479,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { val submission = platform.getEventSubmissions(curEvent).find { it.id == subId } if (submission == null) { - ackEphemeral { + respond { content = Translations.Commands.Submission.Leave.Response.notfound .withContext(this@action) .translateNamed( @@ -503,7 +492,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { val author = platform.getUser(userId) if (author == null) { - ackEphemeral { + respond { content = Translations.Commands.Submission.Invite.Response.usernotfound .withContext(this@action) .translateNamed( @@ -514,7 +503,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { } if (!submission.authors.contains(author.id)) { - ackEphemeral { + respond { content = Translations.Commands.Submission.Leave.Response.notfound .withContext(this@action) .translateNamed( @@ -525,7 +514,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { } if (submission.authors.size < 2) { - ackEphemeral { + respond { content = Translations.Commands.Submission.Leave.Response.last .withContext(this@action) .translateNamed( @@ -537,7 +526,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { platform.withAuth(this.user).leaveSubmission(curEvent, subId) - ackEphemeral { + respond { content = Translations.Commands.Submission.Leave.Response.success .withContext(this@action) .translateNamed( @@ -548,19 +537,17 @@ class SubmissionCommands : Extension(), KordExKoinComponent { } // Invite user to submission - unsafeSubCommand(::InviteSubmissionArgs) { + ephemeralSubCommand(::InviteSubmissionArgs) { name = Translations.Commands.Submission.Invite.name description = Translations.Commands.Submission.Invite.description - initialResponse = InitialSlashCommandResponse.None - action { val subId = this.arguments.submission val userId = this.arguments.user val curEvent = platform.getCurrentEvent().event if (curEvent == null) { - ackEphemeral { - content = Translations.Commands.Event.Submit.Response.unavailable + respond { + content = Translations.Commands.Submit.Response.unavailable .withContext(this@action) .translateNamed() } @@ -570,7 +557,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { val submission = platform.getEventSubmissions(curEvent).find { it.id == subId } if (submission == null) { - ackEphemeral { + respond { content = Translations.Commands.Submission.Invite.Response.notfound .withContext(this@action) .translateNamed( @@ -583,7 +570,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { val author = platform.getUser(this.arguments.user) if (author == null) { - ackEphemeral { + respond { content = Translations.Commands.Submission.Invite.Response.usernotfound .withContext(this@action) .translateNamed( @@ -594,7 +581,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { } if (submission.authors.contains(author.id)) { - ackEphemeral { + respond { content = Translations.Commands.Submission.Invite.Response.already .withContext(this@action) .translateNamed( @@ -607,7 +594,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { platform.withAuth(this.user).inviteSubmissionAuthor(curEvent, subId, author.id) - ackEphemeral { + respond { content = Translations.Commands.Submission.Invite.Response.success .withContext(this@action) .translateNamed( @@ -640,6 +627,266 @@ class SubmissionCommands : Extension(), KordExKoinComponent { } } } + + ephemeralSubCommand(::ImageArg) { + name = Translations.Commands.Submission.Test.name + description = Translations.Commands.Submission.Test.description + + action { + val subId = this.arguments.submission + val curEvent = platform.getCurrentEvent().event + if (curEvent == null) { + respond { + content = Translations.Commands.Submit.Response.unavailable + .withContext(this@action) + .translateNamed() + } + return@action + } + + val event = platform.getEvent(curEvent) + + if (event.phase in setOf(EventData.Phase.PLANNING, EventData.Phase.MODDING)) { + respond { + content = Translations.Commands.Submission.Test.Response.early + .withContext(this@action) + .translateNamed() + } + return@action + } + + val submission = platform.getEventSubmissions(curEvent).find { it.id == subId } + + if (submission == null) { + respond { + content = Translations.Commands.Submission.Edit.Response.notfound + .withContext(this@action) + .translateNamed( + "subId" to subId + ) + } + return@action + } + + platform.withAuth(this.user).editSubmissionImage(curEvent, subId, "test", this.arguments.image.url) + + respond { + content = Translations.Commands.Submission.Test.Response.success + .withContext(this@action) + .translateNamed( + "subId" to subId + ) + } + } + } + + ephemeralSubCommand(::ClaimArg) { + name = Translations.Commands.Submission.Claim.name + description = Translations.Commands.Submission.Claim.description + + action { + val subId = this.arguments.submission + val curEvent = platform.getCurrentEvent().event + if (curEvent == null) { + respond { + content = Translations.Commands.Submit.Response.unavailable + .withContext(this@action) + .translateNamed() + } + return@action + } + + val event = platform.getEvent(curEvent) + + if (event.phase in setOf(EventData.Phase.PLANNING, EventData.Phase.MODDING)) { + respond { + content = Translations.Commands.Submission.Claim.Response.early + .withContext(this@action) + .translateNamed() + } + return@action + } + + val submission = platform.getEventSubmissions(curEvent).find { it.id == subId } + + if (submission == null) { + respond { + content = Translations.Commands.Submission.Edit.Response.notfound + .withContext(this@action) + .translateNamed( + "subId" to subId + ) + } + return@action + } + + if (submission.boothData == null || submission.boothData?.markerPos == null) { + // Must provide ALL data initially + if (this.arguments.image == null || + this.arguments.markerX == null || + this.arguments.markerZ == null || + this.arguments.warpX == null || + this.arguments.warpY == null || + this.arguments.warpZ == null || + this.arguments.warpDirection == null + ) { + respond { + content = Translations.Commands.Submission.Claim.Response.incomplete + .withContext(this@action) + .translateNamed( + "subId" to subId + ) + } + return@action + } + } + + if (this.arguments.image != null) { + platform.withAuth(this.user).editSubmissionImage(curEvent, subId, "claim", this.arguments.image!!.url) + } + + if (this.arguments.image != null || + this.arguments.markerX != null || + this.arguments.markerZ != null || + this.arguments.warpX != null || + this.arguments.warpY != null || + this.arguments.warpZ != null || + this.arguments.warpDirection != null + ) { + platform.withAuth(this.user).editSubmissionBoothData(curEvent, subId, BoothData( + Column( + this.arguments.markerX ?: submission.boothData!!.markerPos!!.x, + this.arguments.markerZ ?: submission.boothData!!.markerPos!!.z + ), + BoothData.WarpCoordinates( + this.arguments.warpX ?: submission.boothData!!.warp!!.x, + this.arguments.warpY ?: submission.boothData!!.warp!!.y, + this.arguments.warpZ ?: submission.boothData!!.warp!!.z, + BoothData.WarpCoordinates.Direction.valueOf(this.arguments.warpDirection?.name ?: submission.boothData!!.warp!!.direction.name) + ), + null, + null, + null, + null + )) + } else if (this.arguments.image == null) { + respond { + content = Translations.Commands.Submission.Claim.Response.unchanged + .withContext(this@action) + .translateNamed( + "subId" to subId + ) + } + return@action + } + + respond { + content = Translations.Commands.Submission.Claim.Response.success + .withContext(this@action) + .translateNamed( + "subId" to subId + ) + } + } + } + + ephemeralSubCommand(::BuildArg) { + name = Translations.Commands.Submission.Build.name + description = Translations.Commands.Submission.Build.description + + action { + val subId = this.arguments.submission + val curEvent = platform.getCurrentEvent().event + if (curEvent == null) { + respond { + content = Translations.Commands.Submit.Response.unavailable + .withContext(this@action) + .translateNamed() + } + return@action + } + + val event = platform.getEvent(curEvent) + + if (event.phase in setOf(EventData.Phase.PLANNING, EventData.Phase.MODDING, EventData.Phase.TESTING)) { + respond { + content = Translations.Commands.Submission.Build.Response.early + .withContext(this@action) + .translateNamed() + } + return@action + } + + val submission = platform.getEventSubmissions(curEvent).find { it.id == subId } + + if (submission == null) { + respond { + content = Translations.Commands.Submission.Edit.Response.notfound + .withContext(this@action) + .translateNamed( + "subId" to subId + ) + } + return@action + } + + if (submission.boothData == null || submission.boothData?.itemIcon == null) { + // Must provide ALL data initially + if (this.arguments.image == null || + this.arguments.itemIcon == null || + this.arguments.shards == null || + this.arguments.eta == null || + this.arguments.status == null + ) { + respond { + content = Translations.Commands.Submission.Build.Response.incomplete + .withContext(this@action) + .translateNamed( + "subId" to subId + ) + } + return@action + } + } + + if (this.arguments.image != null) { + platform.withAuth(this.user).editSubmissionImage(curEvent, subId, "build", this.arguments.image!!.url) + } + + if (this.arguments.image != null || + this.arguments.itemIcon != null || + this.arguments.shards != null || + this.arguments.eta != null || + this.arguments.status != null + ) { + platform.withAuth(this.user).editSubmissionBoothData(curEvent, subId, BoothData( + null, + null, + this.arguments.itemIcon, + this.arguments.shards?.toInt(), + this.arguments.eta, + if (this.arguments.status == null) null else BoothData.BoothStatus.valueOf(this.arguments.status!!.name) + )) + } else if (this.arguments.image == null) { + respond { + content = Translations.Commands.Submission.Build.Response.unchanged + .withContext(this@action) + .translateNamed( + "subId" to subId + ) + } + return@action + } + + respond { + content = Translations.Commands.Submission.Build.Response.success + .withContext(this@action) + .translateNamed( + "subId" to subId + ) + } + } + } } } @@ -648,7 +895,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { val curEvent = platform.getCurrentEvent().event if (curEvent == null) { respond { - content = Translations.Commands.Event.Submit.Response.unavailable + content = Translations.Commands.Submit.Response.unavailable .withContext(this@imageCommandAction) .translateNamed() } @@ -677,7 +924,7 @@ class SubmissionCommands : Extension(), KordExKoinComponent { } } - open inner class InviteSubmissionArgs : Arguments() { + open inner class SubmissionArg : Arguments() { val submission by string { name = Translations.Arguments.Submission.Edit.name description = Translations.Arguments.Submission.Edit.description @@ -691,24 +938,114 @@ class SubmissionCommands : Extension(), KordExKoinComponent { ) } } + } + + open inner class ModrinthArg : Arguments() { + val url by string { + name = Translations.Arguments.Submit.Modrinth.Url.name + description = Translations.Arguments.Submit.Modrinth.Url.description + validate { + failIfNot(Translations.Arguments.Submit.Modrinth.Url.Validation.invalid) { + MODRINTH_REGEX.matcher(value).find() + } + } + } + } + + open inner class InviteSubmissionArgs : SubmissionArg() { val user by user { name = Translations.Arguments.Submission.Invite.User.name description = Translations.Arguments.Submission.Invite.User.description } } - open inner class SubmissionArg : Arguments() { - val submission by string { - name = Translations.Arguments.Submission.Edit.name - description = Translations.Arguments.Submission.Edit.description + inner class ClaimArg : OptionalImageArg() { + val markerX by optionalInt { + name = Translations.Arguments.Submission.Claim.MarkerX.name + description = Translations.Arguments.Submission.Claim.MarkerX.description + } + val markerZ by optionalInt { + name = Translations.Arguments.Submission.Claim.MarkerZ.name + description = Translations.Arguments.Submission.Claim.MarkerZ.description + } + val warpX by optionalInt { + name = Translations.Arguments.Submission.Claim.WarpX.name + description = Translations.Arguments.Submission.Claim.WarpX.description + } + val warpY by optionalInt { + name = Translations.Arguments.Submission.Claim.WarpY.name + description = Translations.Arguments.Submission.Claim.WarpY.description + } + val warpZ by optionalInt { + name = Translations.Arguments.Submission.Claim.WarpZ.name + description = Translations.Arguments.Submission.Claim.WarpZ.description + } + val warpDirection by optionalEnumChoice { + name = Translations.Arguments.Submission.Claim.Direction.name + description = Translations.Arguments.Submission.Claim.Direction.description + typeName = Translations.Arguments.Submission.Claim.Direction.type + } + } - autoComplete { - val curEvent = platform.getCurrentEvent().event ?: return@autoComplete - suggestStringCollection( - platform.getUserSubmissions(this.user.id) - .filter { it.event == curEvent } - .map { it.id } - ) + inner class BuildArg : OptionalImageArg() { + val itemIcon by optionalStringChoice { + name = Translations.Arguments.Submission.Build.ItemIcon.name + description = Translations.Arguments.Submission.Build.ItemIcon.description + } + val shards by optionalNumberChoice { + name = Translations.Arguments.Submission.Build.Shards.name + description = Translations.Arguments.Submission.Build.Shards.description + choices = linkedMapOf( + Translations.Arguments.Submission.Build.Shards.Choice.one to 1, + Translations.Arguments.Submission.Build.Shards.Choice.two to 2, + Translations.Arguments.Submission.Build.Shards.Choice.three to 3, + Translations.Arguments.Submission.Build.Shards.Choice.four to 4 + ) + } + val eta by optionalInt { + name = Translations.Arguments.Submission.Build.Eta.name + description = Translations.Arguments.Submission.Build.Eta.description + } + val status by optionalEnumChoice { + name = Translations.Arguments.Submission.Build.Status.name + description = Translations.Arguments.Submission.Build.Status.description + typeName = Translations.Arguments.Submission.Build.Status.type + } + } + + enum class WarpDirection(override val readableName: Key) : ChoiceEnum { + NORTH(Translations.Arguments.Submission.Claim.Direction.Choice.north), + NORTH_NORTH_EAST(Translations.Arguments.Submission.Claim.Direction.Choice.northNorthEast), + NORTH_EAST(Translations.Arguments.Submission.Claim.Direction.Choice.northEast), + EAST_NORTH_EAST(Translations.Arguments.Submission.Claim.Direction.Choice.eastNorthEast), + EAST(Translations.Arguments.Submission.Claim.Direction.Choice.east), + EAST_SOUTH_EAST(Translations.Arguments.Submission.Claim.Direction.Choice.eastSouthEast), + SOUTH_EAST(Translations.Arguments.Submission.Claim.Direction.Choice.southEast), + SOUTH_SOUTH_EAST(Translations.Arguments.Submission.Claim.Direction.Choice.southSouthEast), + SOUTH(Translations.Arguments.Submission.Claim.Direction.Choice.south), + SOUTH_SOUTH_WEST(Translations.Arguments.Submission.Claim.Direction.Choice.southSouthWest), + SOUTH_WEST(Translations.Arguments.Submission.Claim.Direction.Choice.southWest), + WEST_SOUTH_WEST(Translations.Arguments.Submission.Claim.Direction.Choice.westSouthWest), + WEST(Translations.Arguments.Submission.Claim.Direction.Choice.west), + WEST_NORTH_WEST(Translations.Arguments.Submission.Claim.Direction.Choice.westNorthWest), + NORTH_WEST(Translations.Arguments.Submission.Claim.Direction.Choice.northWest), + NORTH_NORTH_WEST(Translations.Arguments.Submission.Claim.Direction.Choice.northNorthWest); + } + + enum class BoothStatus(override val readableName: Key) : ChoiceEnum { + UNDER_CONSTRUCTION(Translations.Arguments.Submission.Build.Status.Choice.constructing), + PLAYABLE(Translations.Arguments.Submission.Build.Status.Choice.playable), + COMPLETE(Translations.Arguments.Submission.Build.Status.Choice.complete) + } + + open inner class OptionalImageArg : SubmissionArg() { + val image by optionalAttachment { + name = Translations.Arguments.Submission.EditImage.name + description = Translations.Arguments.Submission.EditImage.description + validate { + failIfNot(Translations.Arguments.Submission.EditImage.Validation.invalid) { + value?.isImage ?: true + } } } } @@ -718,7 +1055,9 @@ class SubmissionCommands : Extension(), KordExKoinComponent { name = Translations.Arguments.Submission.EditImage.name description = Translations.Arguments.Submission.EditImage.description validate { - value.isImage + failIfNot(Translations.Arguments.Submission.EditImage.Validation.invalid) { + value.isImage + } } } } @@ -768,18 +1107,6 @@ class SubmissionCommands : Extension(), KordExKoinComponent { } } - class SubmitModalModrinth : ModalForm() { - override var title: Key = Translations.Modal.Submit.title - - val modrinthUrl = lineText { - label = Translations.Modal.Submit.Url.label - placeholder = Translations.Modal.Submit.Url.placeholder - minLength = 10 - maxLength = 1024 - required = true - } - } - class SubmitModalOther : ModalForm() { override var title: Key = Translations.Modal.Submit.title diff --git a/botfest/src/main/resources/translations/botfest/strings.properties b/botfest/src/main/resources/translations/botfest/strings.properties index 7e00577..0b03103 100644 --- a/botfest/src/main/resources/translations/botfest/strings.properties +++ b/botfest/src/main/resources/translations/botfest/strings.properties @@ -70,18 +70,18 @@ commands.event.unregister.response.success=You've been deregistered for {event} commands.event.unregister.response.none=You weren't registered for {event} commands.event.unregister.response.unavailable=There's no current event happening. \ You can't deregister for events that have already closed submissions. -commands.event.submit.name=submit -commands.event.submit.description=submit a project -commands.event.submit.modrinth.name=modrinth -commands.event.submit.modrinth.description=Submit a booth for a modrinth project -commands.event.submit.other.name=other -commands.event.submit.other.description=Submit a booth to show off a project -commands.event.submit.event.name=event -commands.event.submit.event.description=Submit an event for groups to participate in -commands.event.submit.response.invalid=`{url}` is not a valid Modrinth URL -commands.event.submit.response.unavailable=There's no current event happening. -commands.event.submit.response.success=Successfully submitted {mod} to {event} :tada: -commands.event.submit.other.response.success=Successfully submitted {mod} (`{subId}`) to {event} :tada:\n\ +commands.submit.name=submit +commands.submit.description=submit a project +commands.submit.modrinth.name=modrinth +commands.submit.modrinth.description=Submit a booth for a modrinth project +commands.submit.other.name=other +commands.submit.other.description=Submit a booth to show off a project +commands.submit.event.name=event +commands.submit.event.description=Submit an event for groups to participate in +commands.submit.response.invalid=`{url}` is not a valid Modrinth URL +commands.submit.response.unavailable=There's no current event happening. +commands.submit.response.success=Successfully submitted {mod} to {event} :tada: +commands.submit.other.response.success=Successfully submitted {mod} (`{subId}`) to {event} :tada:\n\ Please use {slashIcon} and {slashScreenshot} to add images to your submission commands.submission.edit.name=edit commands.submission.edit.description=Edit your submission's data @@ -97,6 +97,15 @@ commands.submission.update.meta.description=Update modrinth submission with new commands.submission.update.meta.response.notfound=Unknown submission {subId} commands.submission.update.meta.response.notmodrinth=Can't update a non-modrinth submission! commands.submission.update.meta.response.success=Updated the metadata and images for {subId} +commands.submission.update.marker.name=marker +commands.submission.update.marker.description=Update your booth map marker location and icon +commands.submission.update.marker.response.success=Updated the booth map marker for {subId}! +commands.submission.update.warp.name=warp +commands.submission.update.warp.description=Update your booth warp location and direction +commands.submission.update.warp.response.success=Updated the booth warp location for {subId}! +commands.submission.update.booth.name=booth +commands.submission.update.booth.description=Update your booth progress and size +commands.submission.update.booth.response.success=Updated the booth progress for {subId}! commands.submission.delete.name=unsubmit commands.submission.delete.description=Withdraw your submission commands.submission.delete.response.notfound=Unknown submission {subId} @@ -112,6 +121,22 @@ commands.submission.invite.response.notfound=Unknown submission {subId} commands.submission.invite.response.usernotfound=Unknown user {userId} commands.submission.invite.response.already=User {userId} is already associated with {subId}! commands.submission.invite.response.success=Successfully added user {userId} to {subId} +commands.submission.test.name=test +commands.submission.test.description=Submit or update your testing phase screenshot ("test lot") +commands.submission.test.response.early=The testing phase hasn't started yet! Test lots must be created on the event test server to check for incompatibilities. +commands.submission.test.response.success=Updated {subId} with new proof of testing! +commands.submission.claim.name=claim +commands.submission.claim.description=Submit or update your booth claim +commands.submission.claim.response.early=The claiming phase hasn't started yet! Claims are only valid once they've been built on the build server. +commands.submission.claim.response.incomplete=Incomplete submission rejected! you must fill ALL arguments when initially submitting your claim. +commands.submission.claim.response.unchanged=No arguments entered - nothing changed! +commands.submission.claim.response.success=Updated {subId} with new claim data! +commands.submission.build.name=build +commands.submission.build.description=Submit or update your booth build info +commands.submission.build.response.early=The building phase hasn't started yet! No booth building is permitted before this point. +commands.submission.build.response.incomplete=Incomplete submission rejected! you must fill ALL arguments when initially submitting your build. +commands.submission.build.response.unchanged=No arguments entered - nothing changed! +commands.submission.build.response.success=Updated {subId} with new build data! commands.submission.edit_image.label=images commands.submission.edit_image.description=Change your submission's images commands.submission.edit_image.icon.label=icon @@ -139,12 +164,61 @@ arguments.setuser.pronouns.name=pronouns arguments.setuser.pronouns.description=Change your pronouns arguments.setuser.name.name=name arguments.setuser.name.description=Change your name +arguments.submit.modrinth.url.name=url +arguments.submit.modrinth.url.description=Your submission's modrinth URL, e.g. modrinth.com/project/slug +arguments.submit.modrinth.url.validation.invalid=Enter a valid modrinth project URL! e.g. modrinth.com/project/slug arguments.submission.edit.name=submission arguments.submission.edit.description=The submission you want to edit arguments.submission.edit_image.name=image arguments.submission.edit_image.description=Your image +arguments.submission.edit_image.validation.invalid=Attached file must be an image! arguments.submission.invite.user.name=user arguments.submission.invite.user.description=The registered user to add to your submission +arguments.submission.claim.marker_x.name=marker_x +arguments.submission.claim.marker_x.description=x coordinate at the center of your claim +arguments.submission.claim.marker_z.name=marker_z +arguments.submission.claim.marker_z.description=z coordinate at the center of your claim +arguments.submission.claim.warp_x.name=warp_x +arguments.submission.claim.warp_x.description=x coordinate on the path near your claim +arguments.submission.claim.warp_y.name=warp_y +arguments.submission.claim.warp_y.description=y coordinate on the path near your claim +arguments.submission.claim.warp_z.name=warp_z +arguments.submission.claim.warp_z.description=z coordinate on the path near your claim +arguments.submission.claim.direction.name=direction +arguments.submission.claim.direction.description=which direction your booth is in compared to your warp +arguments.submission.claim.direction.type=direction +arguments.submission.claim.direction.choice.north=North (-180) +arguments.submission.claim.direction.choice.north_north_east=North-Northeast (-150) +arguments.submission.claim.direction.choice.north_east=Northeast (-135) +arguments.submission.claim.direction.choice.east_north_east=East-Northeast (-120) +arguments.submission.claim.direction.choice.east=East (-90) +arguments.submission.claim.direction.choice.east_south_east=East-Southeast (-60) +arguments.submission.claim.direction.choice.south_east=Southeast (-45) +arguments.submission.claim.direction.choice.south_south_east=South-Southeast (-30) +arguments.submission.claim.direction.choice.south=South (0) +arguments.submission.claim.direction.choice.south_south_west=South-Southwest (30) +arguments.submission.claim.direction.choice.south_west=Southwest (45) +arguments.submission.claim.direction.choice.west_south_west=West-Southwest (60) +arguments.submission.claim.direction.choice.west=West (90) +arguments.submission.claim.direction.choice.west_north_west=West-Northwest (120) +arguments.submission.claim.direction.choice.north_west=Northwest (135) +arguments.submission.claim.direction.choice.north_north_west=North-Northwest (150) +arguments.submission.build.item_icon.name=item_icon +arguments.submission.build.item_icon.description=the ID of an item that represents your booth +arguments.submission.build.shards.name=shard_count +arguments.submission.build.shards.description=The number of shards placed in your booth +arguments.submission.build.shards.choice.one=One shard (visitor) +arguments.submission.build.shards.choice.two=Two shards (visitor + 1 bonus) +arguments.submission.build.shards.choice.three=Three shards (visitor + 2 bonus) +arguments.submission.build.shards.choice.four=Four shards (visitor + 3 bonus) +arguments.submission.build.eta.name=eta +arguments.submission.build.eta.description=The average time (in minutes) it might take to tour your booth +arguments.submission.build.status.name=status +arguments.submission.build.status.type=status +arguments.submission.build.status.description=How ready the booth is to tour +arguments.submission.build.status.choice.constructing=Under Construction +arguments.submission.build.status.choice.playable=Playable +arguments.submission.build.status.choice.complete=Functionally Complete arguments.minecraft.username.name=username arguments.minecraft.username.description=Minecraft username @@ -156,8 +230,6 @@ modal.register.modrinthslug.placeholder=... modal.register.pronouns.label=What are your preferred pronouns? modal.register.pronouns.placeholder=they/them modal.submit.title=Submit a mod -modal.submit.url.label=Enter your project's Modrinth URL -modal.submit.url.placeholder=https://modrinth.com/project/slug modal.update.title=Update a non-Modrinth mod modal.submission.edit.title=Edit your mod's data modal.submission.name.label=Name @@ -191,6 +263,10 @@ modal.submit.event.extra.placeholder=Enter any important information about your modal.submit.event.type.panel.label=Panel modal.submit.event.response.error=No response received. modal.submit.event.response.success=Thank you for submitting an event! A team member will contact you +modal.booth.shards.label=Number of shards +modal.booth.shards.placeholder=1-4 +modal.booth.eta.label=Time to complete (minutes) +modal.booth.eta.placeholder=3 apierror.event_no_exists=An event named {n} doesn't exists apierror.user_no_exists=A user named {n} doesn't exists diff --git a/common/src/main/java/net/modfest/platform/pojo/EventData.java b/common/src/main/java/net/modfest/platform/pojo/EventData.java index f1806ac..b267650 100644 --- a/common/src/main/java/net/modfest/platform/pojo/EventData.java +++ b/common/src/main/java/net/modfest/platform/pojo/EventData.java @@ -26,6 +26,7 @@ public enum Type { public enum Phase { PLANNING(true, false, false), MODDING(true, true, true), + TESTING(false, false, true), BUILDING(false, false, true), SHOWCASE(false, false, false), COMPLETE(false, false, false); diff --git a/common/src/main/java/net/modfest/platform/pojo/SubmissionData.java b/common/src/main/java/net/modfest/platform/pojo/SubmissionData.java index 044a580..8f4e9f4 100644 --- a/common/src/main/java/net/modfest/platform/pojo/SubmissionData.java +++ b/common/src/main/java/net/modfest/platform/pojo/SubmissionData.java @@ -1,6 +1,7 @@ package net.modfest.platform.pojo; import com.google.gson.*; +import lombok.Getter; import lombok.With; import org.jspecify.annotations.NonNull; import org.jspecify.annotations.Nullable; @@ -14,8 +15,9 @@ public record SubmissionData(@NonNull String id, @NonNull String name, @NonNull String description, @NonNull Set authors, - SubmissionData.@NonNull AssociatedData platform, + @NonNull AssociatedData platform, @Nullable String source, + @Nullable BoothData boothData, @NonNull Awards awards ) implements Data { @@ -75,4 +77,50 @@ public JsonElement serialize(AssociatedData src, } } } + + @With + public record BoothData( + @Nullable Column markerPos, + @Nullable WarpCoordinates warp, + @Nullable String itemIcon, + @Nullable Integer shards, + @Nullable Integer minutesToComplete, + @Nullable BoothStatus status + ) { + public enum BoothStatus { + UNDER_CONSTRUCTION, + PLAYABLE, + COMPLETE + } + + public record Column(int x, int z) {} + + public record WarpCoordinates(int x, int y, int z, @NonNull Direction direction) { + @Getter + public enum Direction { + NORTH(180), + NORTH_NORTH_EAST(-150), + NORTH_EAST(-135), + EAST_NORTH_EAST(-120), + EAST(-90), + EAST_SOUTH_EAST(-60), + SOUTH_EAST(-45), + SOUTH_SOUTH_EAST(-30), + SOUTH(0), + SOUTH_SOUTH_WEST(30), + SOUTH_WEST(45), + WEST_SOUTH_WEST(60), + WEST(90), + WEST_NORTH_WEST(120), + NORTH_WEST(135), + NORTH_NORTH_WEST(150); + + private final int yaw; + + Direction(int yaw) { + this.yaw = yaw; + } + } + } + } } diff --git a/common/src/main/java/net/modfest/platform/pojo/SubmissionResponseData.java b/common/src/main/java/net/modfest/platform/pojo/SubmissionResponseData.java index 4c5e7cd..1a8292a 100644 --- a/common/src/main/java/net/modfest/platform/pojo/SubmissionResponseData.java +++ b/common/src/main/java/net/modfest/platform/pojo/SubmissionResponseData.java @@ -15,10 +15,11 @@ public record SubmissionResponseData(@NonNull String id, SubmissionData.@NonNull AssociatedData platform, @NonNull Images images, @Nullable String source, + SubmissionData.@Nullable BoothData boothData, SubmissionData.@NonNull Awards awards ) { - public record Images(@Nullable String icon, @Nullable String screenshot) { + public record Images(@Nullable String icon, @Nullable String screenshot, @Nullable String test, @Nullable String claim, @Nullable String build) { } public static SubmissionResponseData fromData(SubmissionData data, Images images) { @@ -31,6 +32,7 @@ public static SubmissionResponseData fromData(SubmissionData data, Images images data.platform(), images, data.source(), + data.boothData(), data.awards() ); } diff --git a/common/src/main/java/net/modfest/platform/pojo/UserRole.java b/common/src/main/java/net/modfest/platform/pojo/UserRole.java index ed48ed0..4452f91 100644 --- a/common/src/main/java/net/modfest/platform/pojo/UserRole.java +++ b/common/src/main/java/net/modfest/platform/pojo/UserRole.java @@ -5,5 +5,6 @@ public enum UserRole { * For users who don't have any specific role */ NONE, + VOLUNTEER, TEAM_MEMBER, } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e28d3b0..bcd3bc7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,6 @@ [versions] detekt = "1.23.6" -kotlin = "2.1.0" +kotlin = "2.1.21" groovy = "3.0.22" jansi = "2.4.1" @@ -8,8 +8,8 @@ kx-ser = "1.7.2" logback = "1.5.7" logback-groovy = "1.14.5" logging = "7.0.0" -kordex-gradle = "1.5.8" -kordex = "2.3.1-SNAPSHOT" +kordex-gradle = "1.7.1" +kordex = "2.3.3-SNAPSHOT" git-hooks = "0.0.2" shadow = "8.1.1" diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 09523c0..d4081da 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.3-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/panel/src/platform_types.ts b/panel/src/platform_types.ts index 7085162..128cfeb 100644 --- a/panel/src/platform_types.ts +++ b/panel/src/platform_types.ts @@ -121,6 +121,9 @@ export interface SubmissionData$FileData$Modrinth { export interface SubmissionData$Images { icon: string; screenshot: string; + test: string; + claim: string; + build: string; } export interface SubmitRequest { @@ -161,7 +164,7 @@ export interface Whoami { permissions: string[]; } -export type EventData$Phase = "planning" | "modding" | "building" | "showcase" | "complete"; +export type EventData$Phase = "planning" | "modding" | "testing" | "building" | "showcase" | "complete"; export type EventData$Type = "modfest" | "blanketcon"; diff --git a/platform_api/src/main/java/net/modfest/platform/controller/EventController.java b/platform_api/src/main/java/net/modfest/platform/controller/EventController.java index dafea14..b3998d1 100644 --- a/platform_api/src/main/java/net/modfest/platform/controller/EventController.java +++ b/platform_api/src/main/java/net/modfest/platform/controller/EventController.java @@ -175,6 +175,22 @@ public SubmissionResponseData editSubmissionData(HttpServletRequest request, @Pa ); } + @PatchMapping("/event/{eventId}/booth/{subId}") + public SubmissionResponseData editSubmissionBoothData(HttpServletRequest request, @PathVariable String eventId, @PathVariable String subId, @RequestBody SubmissionData.BoothData editData) throws PlatformStandardException { + var event = getEvent(eventId); + var submission = service.getSubmission(eventId, subId); + if (submission == null) { + throw new IllegalArgumentException();// TODO + } + + checkCanEdit(event, submission); + + return service.addResponseInfo( + request, + service.editSubmissionBooth(submission, editData) + ); + } + @PutMapping("/event/{eventId}/submission/{subId}/updateVersion") public SubmissionResponseData updateSubmissionVersion(HttpServletRequest request, @PathVariable String eventId, @PathVariable String subId) throws PlatformStandardException { var event = getEvent(eventId); @@ -272,6 +288,9 @@ public SubmissionResponseData editSubmissionImage(HttpServletRequest request, @P var typeEnum = switch (type) { case "icon" -> ImageService.SubmissionImageType.ICON; case "screenshot" -> ImageService.SubmissionImageType.SCREENSHOT; + case "test" -> ImageService.SubmissionImageType.TEST; + case "claim" -> ImageService.SubmissionImageType.CLAIM; + case "build" -> ImageService.SubmissionImageType.BUILD; case null, default -> throw new IllegalArgumentException("Invalid type " + type); }; diff --git a/platform_api/src/main/java/net/modfest/platform/controller/ImageCdn.java b/platform_api/src/main/java/net/modfest/platform/controller/ImageCdn.java index 9718289..8cbcf02 100644 --- a/platform_api/src/main/java/net/modfest/platform/controller/ImageCdn.java +++ b/platform_api/src/main/java/net/modfest/platform/controller/ImageCdn.java @@ -23,7 +23,7 @@ public class ImageCdn { @Autowired private ImageRepository repository; - @GetMapping(value = "/imageCdn/{*path}") + @GetMapping(value = "/imagecdn/{*path}") public ResponseEntity getImage(@PathVariable String path) { if (path.startsWith("/")) { path = path.substring(1); diff --git a/platform_api/src/main/java/net/modfest/platform/migrations/Migrator.java b/platform_api/src/main/java/net/modfest/platform/migrations/Migrator.java index e8863cc..dc786f3 100644 --- a/platform_api/src/main/java/net/modfest/platform/migrations/Migrator.java +++ b/platform_api/src/main/java/net/modfest/platform/migrations/Migrator.java @@ -309,7 +309,7 @@ public void migrateTo7() { } /** - * V6 + * V8 * The "minecraft_accounts" field inside user data has been added */ public void migrateTo8() { diff --git a/platform_api/src/main/java/net/modfest/platform/security/ModFestRealm.java b/platform_api/src/main/java/net/modfest/platform/security/ModFestRealm.java index d3f775d..42daae6 100644 --- a/platform_api/src/main/java/net/modfest/platform/security/ModFestRealm.java +++ b/platform_api/src/main/java/net/modfest/platform/security/ModFestRealm.java @@ -110,7 +110,7 @@ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal var userRole = user.role(); var group = switch (userRole) { case null -> PermissionGroup.UNPRIVILEGED_USERS; - case NONE -> PermissionGroup.UNPRIVILEGED_USERS; + case NONE, VOLUNTEER -> PermissionGroup.UNPRIVILEGED_USERS; case TEAM_MEMBER -> PermissionGroup.TEAM_MEMBERS; }; return new GroupBasedAuthorizationInfo(group); diff --git a/platform_api/src/main/java/net/modfest/platform/service/ImageService.java b/platform_api/src/main/java/net/modfest/platform/service/ImageService.java index 1193458..d004a85 100644 --- a/platform_api/src/main/java/net/modfest/platform/service/ImageService.java +++ b/platform_api/src/main/java/net/modfest/platform/service/ImageService.java @@ -46,7 +46,11 @@ private String getImageLocationKey(SubmissionRepository.SubmissionId subKey, Sub public enum SubmissionImageType { ICON("icon"), - SCREENSHOT("screenshot"); + SCREENSHOT("screenshot"), + TEST("test"), + CLAIM("claim"), + BUILD("build"); + private final String suffix; SubmissionImageType(String suffix) { diff --git a/platform_api/src/main/java/net/modfest/platform/service/SubmissionService.java b/platform_api/src/main/java/net/modfest/platform/service/SubmissionService.java index af93b6e..538a811 100644 --- a/platform_api/src/main/java/net/modfest/platform/service/SubmissionService.java +++ b/platform_api/src/main/java/net/modfest/platform/service/SubmissionService.java @@ -65,6 +65,20 @@ public SubmissionData editSubmission(SubmissionData data, SubmissionPatchData ed return getSubmission(data.event(), data.id()); } + public SubmissionData editSubmissionBooth(SubmissionData data, SubmissionData.BoothData edit) { + if (data.boothData() != null) { + if (edit.markerPos() == null) edit = edit.withMarkerPos(data.boothData().markerPos()); + if (edit.warp() == null) edit = edit.withWarp(data.boothData().warp()); + if (edit.itemIcon() == null) edit = edit.withItemIcon(data.boothData().itemIcon()); + if (edit.shards() == null) edit = edit.withShards(data.boothData().shards()); + if (edit.minutesToComplete() == null) edit = edit.withMinutesToComplete(data.boothData().minutesToComplete()); + if (edit.status() == null) edit = edit.withStatus(data.boothData().status()); + } + data = data.withBoothData(edit); + submissionRepository.save(data); + return getSubmission(data.event(), data.id()); + } + public SubmissionData updateSubmissionVersion(SubmissionData data) { if (!(data.platform().inner() instanceof SubmissionData.AssociatedData.Modrinth mr)) { throw new IllegalArgumentException("Update only works for modrinth submissions!"); @@ -116,6 +130,33 @@ public SubmissionData updateSubmissionMeta(SubmissionData data) { return getSubmission(newData.event(), newData.id()); } + public SubmissionData updateSubmissionBooth(SubmissionData data) { + if (!(data.platform().inner() instanceof SubmissionData.AssociatedData.Modrinth mr)) { + throw new IllegalArgumentException("Update only works for modrinth submissions!"); + } + + var project = modrinth.projects().getProject(mr.projectId()); + + if (project == null) { + throw new IllegalArgumentException("Modrinth project not found!"); + } + + var subKey = new SubmissionRepository.SubmissionId(data.event(), data.id()); + + if (project.iconUrl != null) { + imageService.downloadSubmissionImage(project.iconUrl, subKey, ImageService.SubmissionImageType.ICON); + } + var galleryUrl = getGalleryUrl(project); + if (galleryUrl != null) { + imageService.downloadSubmissionImage(galleryUrl, subKey, ImageService.SubmissionImageType.SCREENSHOT); + } + + var newData = data.withName(project.title).withDescription(project.description).withSource(project.sourceUrl); + + submissionRepository.save(newData); + return getSubmission(newData.event(), newData.id()); + } + public SubmissionData leaveSubmission(SubmissionData data, UserData author) { data.authors().remove(author.id()); submissionRepository.save(data); @@ -192,6 +233,7 @@ public SubmissionData makeSubmissionOther(EventData event, Set authors ) ), submitData.sourceUrl(), + null, new SubmissionData.Awards( Set.of(), Set.of() @@ -234,6 +276,7 @@ public SubmissionData makeSubmissionModrinth(String eventId, Set autho ) ), project.sourceUrl, + null, new SubmissionData.Awards( Set.of(), Set.of() @@ -277,7 +320,10 @@ public SubmissionResponseData addResponseInfo(HttpServletRequest request, Submis data, new SubmissionResponseData.Images( imageService.getImageUrl(request, subKey, ImageService.SubmissionImageType.ICON), - imageService.getImageUrl(request, subKey, ImageService.SubmissionImageType.SCREENSHOT) + imageService.getImageUrl(request, subKey, ImageService.SubmissionImageType.SCREENSHOT), + imageService.getImageUrl(request, subKey, ImageService.SubmissionImageType.TEST), + imageService.getImageUrl(request, subKey, ImageService.SubmissionImageType.CLAIM), + imageService.getImageUrl(request, subKey, ImageService.SubmissionImageType.BUILD) ) ); } diff --git a/platform_api/src/test/java/net/modfest/platform/JsonTests.java b/platform_api/src/test/java/net/modfest/platform/JsonTests.java index c84ded1..041460e 100644 --- a/platform_api/src/test/java/net/modfest/platform/JsonTests.java +++ b/platform_api/src/test/java/net/modfest/platform/JsonTests.java @@ -76,6 +76,14 @@ public static List testObjects() { "dwdadw" )), "bb", + new SubmissionData.BoothData( + new SubmissionData.BoothData.Column(500, -525), + new SubmissionData.BoothData.WarpCoordinates(10, -20, 100, SubmissionData.BoothData.WarpCoordinates.Direction.EAST), + "minecraft:gold_nugget", + 3, + 1, + SubmissionData.BoothData.BoothStatus.UNDER_CONSTRUCTION + ), new SubmissionData.Awards( Set.of(), Set.of()