From f44fe4def19dc8735cd4cae9ee8726659dc751a1 Mon Sep 17 00:00:00 2001 From: Azalea <22280294+hykilpikonna@users.noreply.github.com> Date: Thu, 21 Mar 2024 00:36:23 -0400 Subject: [PATCH] [+] Mai2 Import feature done! --- src/main/java/ext/Ext.kt | 5 ++- .../samnyan/aqua/net/games/mai2/Maimai2.kt | 31 ++++++++++++++----- .../samnyan/aqua/net/utils/AquaNetProps.kt | 8 +++++ 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/main/java/ext/Ext.kt b/src/main/java/ext/Ext.kt index ad3da660..c2767798 100644 --- a/src/main/java/ext/Ext.kt +++ b/src/main/java/ext/Ext.kt @@ -68,7 +68,7 @@ fun KMutableProperty1.setCast(obj: C, value: String) = set(obj Boolean::class -> value.toBoolean() else -> 400 - "Invalid field type $returnType" } as T) -inline fun Field.gets(obj: Any) = get(obj) as T +inline fun Field.gets(obj: Any): T? = get(obj)?.let { it as T } // Make it easier to throw a ResponseStatusException operator fun HttpStatus.invoke(message: String? = null): Nothing = throw ApiException(value(), message ?: this.reasonPhrase) @@ -140,6 +140,8 @@ fun millis() = System.currentTimeMillis() val DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd") fun LocalDate.isoDate() = format(DATE_FORMAT) fun LocalDateTime.isoDateTime() = format(DateTimeFormatter.ISO_LOCAL_DATE_TIME) +val URL_SAFE_DT = DateTimeFormatter.ofPattern("yyyy-MM-dd_HH-mm-ss") +fun LocalDateTime.urlSafeStr() = format(URL_SAFE_DT) val ALT_DATETIME_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") fun Str.asDateTime() = try { LocalDateTime.parse(this, DateTimeFormatter.ISO_LOCAL_DATE_TIME) } @@ -154,6 +156,7 @@ fun Map.toUrl() = entries.joinToString("&") { (k, v) -> "$k=$v" } operator fun Map.plus(map: Map) = (if (this is MutableMap) this else toMutableMap()).apply { putAll(map) } operator fun MutableMap.plusAssign(map: Map) { putAll(map) } +fun Map.vNotNull(): Map = filterValues { it != null }.mapValues { it.value!! } fun MutableList.popAll(list: List) = list.also { removeAll(it) } fun MutableList.popAll(vararg items: T) = popAll(items.toList()) inline fun Iterable.mapApply(block: T.() -> Unit) = map { it.apply(block) } diff --git a/src/main/java/icu/samnyan/aqua/net/games/mai2/Maimai2.kt b/src/main/java/icu/samnyan/aqua/net/games/mai2/Maimai2.kt index f70b9c15..6122966e 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/mai2/Maimai2.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/mai2/Maimai2.kt @@ -8,6 +8,7 @@ import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.net.utils.* import icu.samnyan.aqua.sega.maimai2.model.* import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserDetail +import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserEntity import org.springframework.transaction.PlatformTransactionManager import org.springframework.transaction.support.TransactionTemplate import org.springframework.web.bind.annotation.RestController @@ -65,6 +66,9 @@ class Maimai2( ?.call(repos) as UserLinked<*>? ?: error("No matching field found for ${it.name}") } + val listFields = exportFields.filter { it.key.type == List::class.java } + val singleFields = exportFields.filter { it.key.type != List::class.java } + fun export(u: AquaNetUser) = Maimai2DataExport().apply { gameId = "SDEZ" userData = repos.userData.findByCard(u.ghostCard) ?: (404 - "User not found") @@ -77,38 +81,49 @@ class Maimai2( @API("export") fun exportAllUserData(@RP token: Str) = us.jwt.auth(token) { u -> export(u) } + @Suppress("UNCHECKED_CAST") @API("import") - fun importUserData(@RP token: Str, @RP json: Str) = us.jwt.auth(token) { u -> + fun importUserData(@RP token: Str, @RB json: Str) = us.jwt.auth(token) { u -> val export = json.parseJson() if (!export.gameId.equals("SDEZ", true)) 400 - "Invalid game ID" + val lists = listFields.toList().associate { (f, r) -> r to f.gets>(export) }.vNotNull() + val singles = singleFields.toList().associate { (f, r) -> r to f.gets(export) }.vNotNull() + + if (export.userData == null) 400 - "Invalid user data" + // Validate new user data // Check that all ids are 0 (this should be true since all ids are @JsonIgnore) if (export.userData.id != 0L) 400 - "User ID must be 0" - exportFields.keys.forEach { it.gets(export).id.let { if (it != 0L) 400 - "ID must be 0" } } + lists.values.flatten().forEach { if (it.id != 0L) 400 - "ID must be 0" } + singles.values.forEach { if (it.id != 0L) 400 - "ID must be 0" } // Set user card export.userData.card = u.ghostCard - // Set user of the remaining data -// exportFields.values.forEach { it.setUser(export.userData) } // Check existing data val gu = repos.userData.findByCard(u.ghostCard)?.also { gu -> // Store a backup of the old data - val fl = "mai2-backup-${u.auId}-${LocalDateTime.now().isoDateTime()}.json" + val fl = "mai2-backup-${u.auId}-${LocalDateTime.now().urlSafeStr()}.json" (Path(netProps.importBackupPath) / fl).writeText(export(u).toJson()) } trans.execute { gu?.let { - // Delete the old data - exportFields.values.forEach { it.deleteByUser(gu) } + // Delete the old data (After migration v1000.7, all user-linked entities have ON DELETE CASCADE) repos.userData.deleteByCard(u.ghostCard) } // Insert new data + val nu = repos.userData.save(export.userData) + // Set user fields + lists.values.flatten().forEach { it.user = nu } + singles.values.forEach { it.user = nu } + // Save new data + lists.forEach { (repo, list) -> (repo as UserLinked).saveAll(list) } + singles.forEach { (repo, single) -> (repo as UserLinked).save(single) } } - TODO() + SUCCESS } } \ No newline at end of file diff --git a/src/main/java/icu/samnyan/aqua/net/utils/AquaNetProps.kt b/src/main/java/icu/samnyan/aqua/net/utils/AquaNetProps.kt index fc2f5854..6843da21 100644 --- a/src/main/java/icu/samnyan/aqua/net/utils/AquaNetProps.kt +++ b/src/main/java/icu/samnyan/aqua/net/utils/AquaNetProps.kt @@ -1,11 +1,19 @@ package icu.samnyan.aqua.net.utils +import jakarta.annotation.PostConstruct import org.springframework.boot.context.properties.ConfigurationProperties import org.springframework.context.annotation.Configuration +import kotlin.io.path.Path +import kotlin.io.path.createDirectories @Configuration @ConfigurationProperties(prefix = "aqua-net") class AquaNetProps { var linkCardLimit: Int = 10 var importBackupPath = "data/import-backups" + + @PostConstruct + fun init() { + Path(importBackupPath).createDirectories() + } } \ No newline at end of file