From 63e3ac4ecba0ae2ffcda2cc1c7b9f33d2836e8be Mon Sep 17 00:00:00 2001 From: Azalea Date: Sun, 2 Aug 2026 04:49:01 +0000 Subject: [PATCH] [F] Fix incomplete export --- .../samnyan/aqua/net/games/chu3/Chu3Import.kt | 31 +++++- .../samnyan/aqua/net/games/mai2/Mai2Import.kt | 98 ++++++++++++++++++- ...V1000_86__maimai2_print_detail_cascade.sql | 11 +++ 3 files changed, 135 insertions(+), 5 deletions(-) create mode 100644 src/main/resources/db/120/V1000_86__maimai2_print_detail_cascade.sql 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 d2fd3dba..56eb8b1b 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 @@ -3,6 +3,7 @@ package icu.samnyan.aqua.net.games.chu3 import ext.API import ext.returns import ext.vars +import icu.samnyan.aqua.net.games.ExportOptions import icu.samnyan.aqua.net.games.IExportClass import icu.samnyan.aqua.net.games.ImportClass import icu.samnyan.aqua.net.games.ImportController @@ -12,6 +13,7 @@ import icu.samnyan.aqua.sega.chusan.model.userdata.* import org.springframework.web.bind.annotation.RestController import kotlin.reflect.full.declaredMembers +@Suppress("UNCHECKED_CAST") @RestController @API("api/v2/game/chu3") class Chu3Import( @@ -22,7 +24,7 @@ class Chu3Import( it.name.replace("List", "").lowercase() }, exportRepos = Chu3DataExport::class.vars() - .filter { f -> f.name !in setOf("gameId", "userData") } + .filter { f -> f.name !in setOf("gameId", "userData", "userLoginBonusList") } .associateWith { Chu3Repos::class.declaredMembers .filter { f -> f returns Chu3UserLinked::class } .firstOrNull { f -> f.name == it.name || f.name == it.name.replace("List", "") } @@ -44,7 +46,22 @@ class Chu3Import( // "chuni_profile_emoney" to ImportClass(UserEmoney::class), // "chuni_profile_overpower" to ImportClass(UserOverpower::class), // "chuni_profile_recent_rating" to ImportClass(UserRecentRating::class), - ) + ), + customExporters = mapOf( + Chu3DataExport::userLoginBonusList to { user: Chu3UserData, _: ExportOptions -> + repos.userLoginBonus.findByUser(user.card!!.extId.toInt()) + }, + ) as Map, (Chu3UserData, ExportOptions) -> Any?>, + customImporters = mapOf( + Chu3DataExport::userLoginBonusList to { export: Chu3DataExport, user: Chu3UserData -> + val userId = user.card!!.extId.toInt() + repos.userLoginBonus.deleteAll(repos.userLoginBonus.findByUser(userId)) + repos.userLoginBonus.saveAll(export.userLoginBonusList.map { it.apply { + id = 0 + this.user = userId + } }) + }, + ) as Map, (Chu3DataExport, Chu3UserData) -> Unit>, ) { override fun createEmpty() = Chu3DataExport() override val userDataRepo = repos.userData @@ -67,6 +84,16 @@ data class Chu3DataExport( var userMateList: List = emptyList(), var userGeneralDataList: List = emptyList(), var userMiscList: Chu3UserMisc = Chu3UserMisc(), + var userCardPrintStateList: List = emptyList(), + var userGachaList: List = emptyList(), + var userRegionsList: List = emptyList(), + var userCMissionList: List = emptyList(), + var userCMissionProgressList: List = emptyList(), + var netBattleLogList: List = emptyList(), + var userChallengeList: List = emptyList(), + var userLinkedVerseList: List = emptyList(), + var userVoteList: List = emptyList(), + var userLoginBonusList: List = emptyList(), ): IExportClass { constructor() : this("SDHD", Chu3UserData(), UserGameOption(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), ArrayList(), Chu3UserMisc()) 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 850d0bb6..709cd125 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 @@ -25,7 +25,9 @@ class Mai2Import( it.name.replace("List", "").lowercase() }, exportRepos = Maimai2DataExport::class.vars() - .filter { f -> f.name !in setOf("gameId", "userData", "userPlaylogList", "userFavoriteMusicList") } + .filter { f -> f.name !in setOf( + "gameId", "userData", "userPlaylogList", "userFavoriteMusicList", "userPrintDetailList" + ) } .associateWith { field -> val repoName = when (field.name) { "userKaleidxScopeList" -> "userKaleidx" @@ -68,7 +70,10 @@ class Mai2Import( ?.split(",") ?.mapIndexed { index, id -> Mai2UserFavoriteItem().apply { orderId = index; this.id = id.toInt() } } ?: emptyList() - } + }, + Maimai2DataExport::userPrintDetailList to { user: Mai2UserDetail, _: ExportOptions -> + repos.userPrintDetail.findByUser(user).map(::Mai2UserPrintDetailExport) + }, ) as Map, (Mai2UserDetail, ExportOptions) -> Any?>, customImporters = mapOf( Maimai2DataExport::userPlaylogList to { export: Maimai2DataExport, user: Mai2UserDetail -> @@ -85,7 +90,18 @@ class Mai2Import( propertyValue = favoriteMusicList.sortedBy { it.orderId }.map { it.id }.joinToString(",") }) } - } + }, + Maimai2DataExport::userPrintDetailList to { export: Maimai2DataExport, user: Mai2UserDetail -> + val userCards = repos.userCard.findByUser(user).associateBy { it.cardId } + repos.userPrintDetail.saveAll(export.userPrintDetailList.map { detail -> + detail.toEntity( + user, + detail.userCardId?.let { cardId -> + userCards[cardId] ?: error("Missing Mai2 user card $cardId for print detail") + }, + ) + }) + }, ) as Map, (Maimai2DataExport, Mai2UserDetail) -> Unit> ) { override fun createEmpty() = Maimai2DataExport() @@ -113,6 +129,9 @@ data class Maimai2DataExport( var userFavoriteMusicList: List = mutableListOf(), var userKaleidxScopeList: List = mutableListOf(), var userPlaylogList: List = mutableListOf(), + var userCardList: List = mutableListOf(), + var userPrintDetailList: List = mutableListOf(), + var userRegionsList: List = mutableListOf(), // Not supported yet: // var userWeeklyData // var userMissionDataList @@ -120,3 +139,76 @@ data class Maimai2DataExport( // var userTradeItemList override var gameId: String = "SDEZ", ): IExportClass + +data class Mai2UserPrintDetailExport( + var orderId: Long = 0, + var printNumber: Int = 0, + var printDate: String = "", + var serialId: String = "", + var placeId: Int = 0, + var clientId: String = "", + var printerSerialId: String = "", + var userCardId: Int? = null, + var cardRomVersion: Int = 0, + var isHolograph: Boolean = false, + var printOption1: Boolean = false, + var printOption2: Boolean = false, + var printOption3: Boolean = false, + var printOption4: Boolean = false, + var printOption5: Boolean = false, + var printOption6: Boolean = false, + var printOption7: Boolean = false, + var printOption8: Boolean = false, + var printOption9: Boolean = false, + var printOption10: Boolean = false, + var created: String = "", +) { + constructor(detail: Mai2UserPrintDetail) : this( + orderId = detail.orderId, + printNumber = detail.printNumber, + printDate = detail.printDate, + serialId = detail.serialId, + placeId = detail.placeId, + clientId = detail.clientId, + printerSerialId = detail.printerSerialId, + userCardId = detail.userCard?.cardId, + cardRomVersion = detail.cardRomVersion, + isHolograph = detail.isHolograph, + printOption1 = detail.printOption1, + printOption2 = detail.printOption2, + printOption3 = detail.printOption3, + printOption4 = detail.printOption4, + printOption5 = detail.printOption5, + printOption6 = detail.printOption6, + printOption7 = detail.printOption7, + printOption8 = detail.printOption8, + printOption9 = detail.printOption9, + printOption10 = detail.printOption10, + created = detail.created, + ) + + fun toEntity(user: Mai2UserDetail, userCard: Mai2UserCard?) = Mai2UserPrintDetail().also { detail -> + detail.user = user + detail.userCard = userCard + detail.orderId = orderId + detail.printNumber = printNumber + detail.printDate = printDate + detail.serialId = serialId + detail.placeId = placeId + detail.clientId = clientId + detail.printerSerialId = printerSerialId + detail.cardRomVersion = cardRomVersion + detail.isHolograph = isHolograph + detail.printOption1 = printOption1 + detail.printOption2 = printOption2 + detail.printOption3 = printOption3 + detail.printOption4 = printOption4 + detail.printOption5 = printOption5 + detail.printOption6 = printOption6 + detail.printOption7 = printOption7 + detail.printOption8 = printOption8 + detail.printOption9 = printOption9 + detail.printOption10 = printOption10 + detail.created = created + } +} diff --git a/src/main/resources/db/120/V1000_86__maimai2_print_detail_cascade.sql b/src/main/resources/db/120/V1000_86__maimai2_print_detail_cascade.sql new file mode 100644 index 00000000..e89890f6 --- /dev/null +++ b/src/main/resources/db/120/V1000_86__maimai2_print_detail_cascade.sql @@ -0,0 +1,11 @@ +-- V1000.7 accidentally replaced the user foreign key with a second user-card +-- foreign key. Restore the direct ownership link so every print detail is +-- removed with its Mai2 user, including legacy rows without a user card. +DELETE detail FROM maimai2_user_print_detail detail + LEFT JOIN maimai2_user_detail user_data ON detail.user_id = user_data.id + WHERE user_data.id IS NULL; + +ALTER TABLE maimai2_user_print_detail + ADD CONSTRAINT fku_maimai2_user_print_detail_user + FOREIGN KEY (user_id) REFERENCES maimai2_user_detail (id) + ON DELETE CASCADE ON UPDATE CASCADE;