diff --git a/src/main/java/ext/Ext.kt b/src/main/java/ext/Ext.kt index db9ff96a..ced96103 100644 --- a/src/main/java/ext/Ext.kt +++ b/src/main/java/ext/Ext.kt @@ -2,6 +2,7 @@ package ext import com.fasterxml.jackson.core.JsonParser import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.DeserializationFeature import com.fasterxml.jackson.databind.JsonDeserializer import com.fasterxml.jackson.databind.ObjectMapper import com.fasterxml.jackson.databind.module.SimpleModule @@ -19,6 +20,7 @@ import org.apache.tika.Tika import org.apache.tika.mime.MimeTypes import org.slf4j.LoggerFactory import org.springframework.http.HttpStatus +import org.springframework.web.bind.annotation.PathVariable import org.springframework.web.bind.annotation.RequestBody import org.springframework.web.bind.annotation.RequestHeader import org.springframework.web.bind.annotation.RequestMapping @@ -37,6 +39,7 @@ import kotlin.reflect.jvm.jvmErasure typealias RP = RequestParam typealias RB = RequestBody typealias RH = RequestHeader +typealias PV = PathVariable typealias API = RequestMapping typealias Str = String typealias Bool = Boolean @@ -83,7 +86,7 @@ fun Str.isValidEmail(): Bool = emailRegex.matches(this) val ACCEPTABLE_FALSE = setOf("0", "false", "no", "off", "False", "None", "null") val ACCEPTABLE_TRUE = setOf("1", "true", "yes", "on", "True") @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST") -val jackson = ObjectMapper().apply { +val JACKSON = ObjectMapper().apply { findAndRegisterModules() registerModule(SimpleModule().addDeserializer(Boolean::class.java, object : JsonDeserializer() { override fun deserialize(parser: JsonParser, context: DeserializationContext) = when(parser.text) { @@ -103,7 +106,10 @@ val jackson = ObjectMapper().apply { override fun deserialize(parser: JsonParser, context: DeserializationContext) = parser.text.asDateTime() ?: (400 - "Invalid date time value ${parser.text}") })) + configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) } +inline fun Str.parseJson() = JACKSON.readValue(this, T::class.java) +fun T.toJson() = JACKSON.writeValueAsString(this) @OptIn(ExperimentalSerializationApi::class) val JSON = Json { ignoreUnknownKeys = true @@ -144,10 +150,12 @@ catch (e: Exception) { null } } fun Long.toHex(len: Int = 16): Str = "0x${this.toString(len).padStart(len, '0').uppercase()}" fun Map.toUrl() = entries.joinToString("&") { (k, v) -> "$k=$v" } -// Map +// Collections 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 MutableList.popAll(list: List) = list.also { removeAll(it) } +fun MutableList.popAll(vararg items: T) = popAll(items.toList()) // Strings operator fun Str.get(range: IntRange) = substring(range.first, (range.last + 1).coerceAtMost(length)) diff --git a/src/main/java/icu/samnyan/aqua/net/SettingsApi.kt b/src/main/java/icu/samnyan/aqua/net/SettingsApi.kt index 94324ce3..f88a6860 100644 --- a/src/main/java/icu/samnyan/aqua/net/SettingsApi.kt +++ b/src/main/java/icu/samnyan/aqua/net/SettingsApi.kt @@ -40,14 +40,6 @@ class SettingsApi( userRepo.save(u.apply { gameOptions = it }) } // Check field type -// val type = field.returnType -// val newValue = when (type.classifier) { -// String::class -> value -// Int::class -> value.toInt() -// Boolean::class -> value.toBoolean() -// else -> (400 - "Invalid field type $type") -// } -// field.set(options, newValue) field.setCast(options, value) goRepo.save(options) } diff --git a/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt b/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt index 3cfd54f2..f3dbc173 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt @@ -1,6 +1,6 @@ package icu.samnyan.aqua.net.games -import ext.jackson +import ext.JACKSON import ext.splitLines import java.lang.reflect.Field import kotlin.reflect.KClass @@ -54,7 +54,7 @@ abstract class ImportController( lists[tb.name]?.add(obj) ?: field.set(data, obj) } - return ImportResult(errors, warnings, jackson.writeValueAsString(data)) + return ImportResult(errors, warnings, JACKSON.writeValueAsString(data)) } companion object @@ -69,7 +69,7 @@ abstract class ImportController( // Process Nones dict = dict.filterValues { it != "None" } - return jackson.convertValue(dict, type.java) + return JACKSON.convertValue(dict, type.java) } } } diff --git a/src/main/java/icu/samnyan/aqua/net/games/chu3/Chu3Import.kt b/src/main/java/icu/samnyan/aqua/net/games/chu3/Chu3Import.kt index ebe32ebd..ba130a51 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/chu3/Chu3Import.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/chu3/Chu3Import.kt @@ -1,13 +1,14 @@ package icu.samnyan.aqua.net.games.chu3 +import ext.API import icu.samnyan.aqua.api.model.resp.sega.chuni.v2.external.Chu3DataExport import icu.samnyan.aqua.net.games.ImportClass import icu.samnyan.aqua.net.games.ImportController import icu.samnyan.aqua.sega.chusan.model.userdata.* -import kotlin.io.path.Path -import kotlin.io.path.readText - +import org.springframework.web.bind.annotation.RestController +@RestController +@API("api/v2/game/chu3") class Chu3Import : ImportController( exportFields = Chu3DataExport::class.java.declaredFields.associateBy { var name = it.name @@ -34,8 +35,4 @@ class Chu3Import : ImportController( ) { override fun createEmpty() = Chu3DataExport("SDEZ", UserData(), UserGameOption(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList()) -} - -fun main() { - Chu3Import().importArtemisSql(Path("C:\\Users\\Azalea\\Downloads\\all_inserts (2).sql").readText()) } \ No newline at end of file diff --git a/src/main/java/icu/samnyan/aqua/net/games/mai2/Mai2Import.kt b/src/main/java/icu/samnyan/aqua/net/games/mai2/Mai2Import.kt index 2a17c058..0d742db5 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/mai2/Mai2Import.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/mai2/Mai2Import.kt @@ -1,10 +1,14 @@ package icu.samnyan.aqua.net.games.mai2 +import ext.API import icu.samnyan.aqua.api.model.resp.sega.maimai2.external.Maimai2DataExport import icu.samnyan.aqua.net.games.ImportClass import icu.samnyan.aqua.net.games.ImportController import icu.samnyan.aqua.sega.maimai2.model.userdata.* +import org.springframework.web.bind.annotation.RestController +@RestController +@API("api/v2/game/mai2") class Mai2Import : ImportController( exportFields = Maimai2DataExport::class.java.declaredFields.associateBy { it.name.replace("List", "").lowercase() 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 5f189084..e031ba4d 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 @@ -2,6 +2,7 @@ package icu.samnyan.aqua.net.games.mai2 import ext.* import icu.samnyan.aqua.api.model.resp.sega.maimai2.external.Maimai2DataExport +import icu.samnyan.aqua.net.db.AquaNetUser import icu.samnyan.aqua.net.db.AquaUserServices import icu.samnyan.aqua.net.games.* import icu.samnyan.aqua.net.utils.* @@ -9,7 +10,10 @@ import icu.samnyan.aqua.sega.maimai2.model.* import icu.samnyan.aqua.sega.maimai2.model.userdata.UserDetail import org.springframework.web.bind.annotation.RestController import java.lang.reflect.Field +import java.time.LocalDateTime import java.util.* +import kotlin.io.path.Path +import kotlin.io.path.writeText import kotlin.reflect.full.declaredMembers @RestController @@ -19,7 +23,8 @@ class Maimai2( override val playlogRepo: Mai2UserPlaylogRepo, override val userDataRepo: Mai2UserDataRepo, val userGeneralDataRepository: Mai2UserGeneralDataRepo, - val repos: Mai2Repos + val repos: Mai2Repos, + val netProps: AquaNetProps ): GameApiController("mai2", UserDetail::class) { override suspend fun trend(@RP username: Str): List = us.cardByName(username) { card -> findTrend(playlogRepo.findByUserCardExtId(card.extId) @@ -49,23 +54,40 @@ class Maimai2( // Use reflection to get all properties in Mai2Repos with matching names in Maimai2DataExport val exportFields: Map> = listOf(*Maimai2DataExport::class.java.declaredFields) .associateWith { Mai2Repos::class.declaredMembers + .filter { f -> f.name !in setOf("gameId", "userData") } .filter { f -> f returns UserLinked::class } .firstOrNull { f -> f.name == it.name || f.name == it.name.replace("List", "") } ?.call(repos) as UserLinked<*>? ?: error("No matching field found for ${it.name}") } + fun export(u: AquaNetUser) = Maimai2DataExport().apply { + gameId = "SDEZ" + userData = repos.userData.findByCard(u.ghostCard) ?: (404 - "User not found") + exportFields.forEach { (f, u) -> + if (f.name == "gameId" || f.name == "userData") return@forEach + f.set(this, if (f.type == List::class.java) u.findByUser(userData) + else u.findSingleByUser(userData).orElse(null)) + } + } + @API("export") - fun exportAllUserData(@RP token: Str) = us.jwt.auth(token) { u -> - try { - Maimai2DataExport().apply { - gameId = "SDEZ" - userData = repos.userData.findByCard(u.ghostCard) ?: (404 - "User not found") - exportFields.forEach { (f, u) -> - if (f.name == "gameId" || f.name == "userData") return@forEach - f.set(this, if (f.type == List::class.java) u.findByUser(userData) - else u.findSingleByUser(userData).orElse(null)) - } - } - } catch (e: Exception) { 500 - "Error during data export. Reason: ${e.message}" } + fun exportAllUserData(@RP token: Str) = us.jwt.auth(token) { u -> export(u) } + + @API("import") + fun importUserData(@RP token: Str, @RP json: Str) = us.jwt.auth(token) { u -> + val export = json.parseJson() + if (!export.gameId.equals("SDEZ", true)) 400 - "Invalid game ID" + + // Check existing data + if (repos.userData.findByCard(u.ghostCard) != null) { + // Store a backup of the old data + val fl = "mai2-backup-${u.auId}-${LocalDateTime.now().isoDateTime()}.json" + (Path(netProps.importBackupPath) / fl).writeText(export(u).toJson()) + + // Delete the old data + TODO() + } + + TODO() } } \ 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 b2fcd328..fc2f5854 100644 --- a/src/main/java/icu/samnyan/aqua/net/utils/AquaNetProps.kt +++ b/src/main/java/icu/samnyan/aqua/net/utils/AquaNetProps.kt @@ -7,4 +7,5 @@ import org.springframework.context.annotation.Configuration @ConfigurationProperties(prefix = "aqua-net") class AquaNetProps { var linkCardLimit: Int = 10 + var importBackupPath = "data/import-backups" } \ No newline at end of file