diff --git a/src/main/java/icu/samnyan/aqua/api/model/resp/sega/maimai2/external/Maimai2DataExport.java b/src/main/java/icu/samnyan/aqua/api/model/resp/sega/maimai2/external/Maimai2DataExport.java deleted file mode 100644 index b0e499e8..00000000 --- a/src/main/java/icu/samnyan/aqua/api/model/resp/sega/maimai2/external/Maimai2DataExport.java +++ /dev/null @@ -1,35 +0,0 @@ -package icu.samnyan.aqua.api.model.resp.sega.maimai2.external; - -import icu.samnyan.aqua.sega.maimai2.model.userdata.*; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.NoArgsConstructor; - -import java.util.List; - -/** - * @author samnyan (privateamusement@protonmail.com) - */ -@Data -@AllArgsConstructor -@NoArgsConstructor -public class Maimai2DataExport { - public String gameId = "SDEZ"; - public Mai2UserDetail userData; - public Mai2UserExtend userExtend; - public Mai2UserOption userOption; - public List mapEncountNpcList; - public List userActList; - public List userCharacterList; - public List userChargeList; - public List userCourseList; - public List userFavoriteList; - public List userFriendSeasonRankingList; - public List userGeneralDataList; - public List userItemList; - public List userLoginBonusList; - public List userMapList; - public List userMusicDetailList; - public List userPlaylogList; - public Mai2UserUdemae userUdemae; -} diff --git a/src/main/java/icu/samnyan/aqua/api/model/resp/sega/maimai2/external/Maimai2DataExport.kt b/src/main/java/icu/samnyan/aqua/api/model/resp/sega/maimai2/external/Maimai2DataExport.kt new file mode 100644 index 00000000..6bd4356e --- /dev/null +++ b/src/main/java/icu/samnyan/aqua/api/model/resp/sega/maimai2/external/Maimai2DataExport.kt @@ -0,0 +1,29 @@ +package icu.samnyan.aqua.api.model.resp.sega.maimai2.external + +import icu.samnyan.aqua.sega.maimai2.model.userdata.* + +data class Maimai2DataExport( + var userData: Mai2UserDetail, + var userExtend: Mai2UserExtend, + var userOption: Mai2UserOption, + var userUdemae: Mai2UserUdemae, + var mapEncountNpcList: List, + var userActList: List, + var userCharacterList: List, + var userChargeList: List, + var userCourseList: List, + var userFavoriteList: List, + var userFriendSeasonRankingList: List, + var userGeneralDataList: List, + var userItemList: List, + var userLoginBonusList: List, + var userMapList: List, + var userMusicDetailList: List, + var userPlaylogList: List, + var gameId: String = "SDEZ", +) { + constructor() : this(Mai2UserDetail(), Mai2UserExtend(), Mai2UserOption(), Mai2UserUdemae(), + mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), + mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), + mutableListOf()) +} 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 c9df4e5a..22173eb6 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,11 @@ package icu.samnyan.aqua.net.games +import com.fasterxml.jackson.core.JsonParser +import com.fasterxml.jackson.databind.DeserializationContext +import com.fasterxml.jackson.databind.JsonDeserializer +import com.fasterxml.jackson.databind.module.SimpleModule import ext.JACKSON +import ext.minus import ext.splitLines import java.lang.reflect.Field import kotlin.reflect.KClass @@ -54,7 +59,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_ARTEMIS.writeValueAsString(data)) } companion object @@ -69,7 +74,7 @@ abstract class ImportController( // Process Nones dict = dict.filterValues { it != "None" } - return JACKSON.convertValue(dict, type.java) + return JACKSON_ARTEMIS.convertValue(dict, type.java) } } } @@ -96,3 +101,18 @@ fun String.asSqlInsert(): SqlInsert { assert(cols.size == vals.size) { "Column and value count mismatch" } return SqlInsert(table, cols.zip(vals).toMap()) } + +@Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN", "UNCHECKED_CAST") +val JSON_INT_LIST_STR = SimpleModule().addDeserializer(List::class.java, object : JsonDeserializer>() { + override fun deserialize(parser: JsonParser, context: DeserializationContext) = + try { + val text = parser.text.trim('[', ']') + if (text.isEmpty()) emptyList() + else text.split(',').map { it.trim().toInt() } as List + } catch (e: Exception) { + 400 - "Invalid list value ${parser.text}: $e" } +}) + +val JACKSON_ARTEMIS = JACKSON.copy().apply { + registerModule(JSON_INT_LIST_STR) +} diff --git a/src/main/java/icu/samnyan/aqua/net/games/Models.kt b/src/main/java/icu/samnyan/aqua/net/games/Models.kt index 9e2edcfa..65bc3459 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/Models.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/Models.kt @@ -1,6 +1,7 @@ package icu.samnyan.aqua.net.games import com.fasterxml.jackson.annotation.JsonIgnore +import ext.JACKSON import ext.JavaSerializable import icu.samnyan.aqua.sega.general.model.Card import jakarta.persistence.* @@ -11,6 +12,8 @@ import org.springframework.data.jpa.repository.JpaRepository import org.springframework.data.jpa.repository.Query import org.springframework.data.repository.NoRepositoryBean import java.util.* +import kotlin.reflect.full.memberProperties +import kotlin.reflect.jvm.isAccessible data class TrendOut(val date: String, val rating: Int, val plays: Int) @@ -99,13 +102,16 @@ interface IGenericGamePlaylog { val isAllPerfect: Boolean } +@Serializable @MappedSuperclass open class BaseEntity( @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @JsonIgnore var id: Long = 0 -) : JavaSerializable +) : JavaSerializable { + override fun toString() = JACKSON.writeValueAsString(this) +} @NoRepositoryBean interface GenericUserDataRepo : JpaRepository { 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 529de2ad..3f9c235f 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 @@ -34,8 +34,5 @@ class Mai2Import : ImportController( // "mai2_profile_region" to ImportClass(UserRegion::class), ) ) { - override fun createEmpty() = Maimai2DataExport("SDEZ", Mai2UserDetail(), Mai2UserExtend(), Mai2UserOption(), - ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), - ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), - ArrayList(), Mai2UserUdemae()) + override fun createEmpty() = Maimai2DataExport() } \ No newline at end of file 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 741d22f1..8106b4a8 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 @@ -12,11 +12,11 @@ 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 -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.KMutableProperty1 import kotlin.reflect.full.declaredMembers @RestController @@ -58,7 +58,7 @@ class Maimai2( } // Use reflection to get all properties in Mai2Repos with matching names in Maimai2DataExport - val exportFields: Map> = listOf(*Maimai2DataExport::class.java.declaredFields) + val exportFields: Map, UserLinked<*>> = Maimai2DataExport::class.vars() .filter { f -> f.name !in setOf("gameId", "userData") } .associateWith { Mai2Repos::class.declaredMembers .filter { f -> f returns UserLinked::class } @@ -66,15 +66,15 @@ 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 } + val listFields = exportFields.filter { it.key returns List::class } + val singleFields = exportFields.filter { !(it.key returns List::class) } fun export(u: AquaNetUser) = Maimai2DataExport().apply { gameId = "SDEZ" userData = repos.userData.findByCard(u.ghostCard) ?: (404 - "User not found") exportFields.forEach { (f, u) -> - f.set(this, if (f.type == List::class.java) u.findByUser(userData) - else u.findSingleByUser(userData).orElse(null)) + if (f returns List::class) f.set(this, u.findByUser(userData)) + else u.findSingleByUser(userData)()?.let { f.set(this, it) } } } @@ -84,13 +84,11 @@ class Maimai2( @Suppress("UNCHECKED_CAST") @API("import") fun importUserData(@RP token: Str, @RB json: Str) = us.jwt.auth(token) { u -> - val export = json.parseJson() + val export = json.parseJackson() 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" + val lists = listFields.toList().associate { (f, r) -> r to f.get(export) as List }.vNotNull() + val singles = singleFields.toList().associate { (f, r) -> r to f.get(export) as Mai2UserEntity }.vNotNull() // Validate new user data // Check that all ids are 0 (this should be true since all ids are @JsonIgnore) @@ -120,8 +118,8 @@ class Maimai2( 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) } + lists.forEach { (repo, list) -> (repo as UserLinked).saveAll(list) } } SUCCESS