[+] Generalize data import for chusan

This commit is contained in:
Azalea
2024-03-30 23:52:29 -04:00
parent d4178c85a9
commit 3a8616e225
59 changed files with 390 additions and 333 deletions

View File

@@ -1,20 +1,34 @@
package icu.samnyan.aqua.net.games.mai2
import ext.API
import ext.returns
import ext.vars
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.Mai2Repos
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserLinked
import icu.samnyan.aqua.sega.maimai2.model.userdata.*
import org.springframework.web.bind.annotation.RestController
import kotlin.reflect.full.declaredMembers
@RestController
@API("api/v2/game/mai2")
class Mai2Import : ImportController<Maimai2DataExport>(
class Mai2Import(
val repos: Mai2Repos,
) : ImportController<Maimai2DataExport, Mai2UserDetail>(
"SDEZ", Maimai2DataExport::class,
exportFields = Maimai2DataExport::class.vars().associateBy {
it.name.replace("List", "").lowercase()
},
renameTable = mapOf(
exportRepos = Maimai2DataExport::class.vars()
.filter { f -> f.name !in setOf("gameId", "userData") }
.associateWith { Mai2Repos::class.declaredMembers
.filter { f -> f returns Mai2UserLinked::class }
.firstOrNull { f -> f.name == it.name || f.name == it.name.replace("List", "") }
?.call(repos) as Mai2UserLinked<*>? ?: error("No matching field found for ${it.name}")
},
artemisRenames = mapOf(
"mai2_item_character" to ImportClass(Mai2UserCharacter::class),
"mai2_item_charge" to ImportClass(Mai2UserCharge::class),
"mai2_item_friend_season_ranking" to ImportClass(Mai2UserFriendSeasonRanking::class),
@@ -30,10 +44,8 @@ class Mai2Import : ImportController<Maimai2DataExport>(
"mai2_profile_option" to ImportClass(Mai2UserOption::class, mapOf("version" to null)),
"mai2_score_best" to ImportClass(Mai2UserMusicDetail::class),
"mai2_score_course" to ImportClass(Mai2UserCourse::class),
// "mai2_profile_ghost" to ImportClass(UserGhost::class),
// "mai2_profile_rating" to ImportClass(UserRating::class),
// "mai2_profile_region" to ImportClass(UserRegion::class),
)
) {
override fun createEmpty() = Maimai2DataExport()
override val userDataRepo = repos.userData
}

View File

@@ -1,23 +1,13 @@
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.*
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 icu.samnyan.aqua.sega.maimai2.model.userdata.*
import org.springframework.web.bind.annotation.RestController
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
@API("api/v2/game/mai2")
@@ -25,13 +15,8 @@ class Maimai2(
override val us: AquaUserServices,
override val playlogRepo: Mai2UserPlaylogRepo,
override val userDataRepo: Mai2UserDataRepo,
val userGeneralDataRepository: Mai2UserGeneralDataRepo,
val repos: Mai2Repos,
val netProps: AquaNetProps,
transManager: PlatformTransactionManager
): GameApiController<Mai2UserDetail>("mai2", Mai2UserDetail::class) {
val trans = TransactionTemplate(transManager)
override suspend fun trend(@RP username: Str): List<TrendOut> = us.cardByName(username) { card ->
findTrend(playlogRepo.findByUserCardExtId(card.extId)
.map { TrendLog(it.playDate, it.afterRating) })
@@ -46,7 +31,7 @@ class Maimai2(
) }
override suspend fun userSummary(@RP username: Str) = us.cardByName(username) { card ->
val extra = userGeneralDataRepository.findByUser_Card_ExtId(card.extId)
val extra = repos.userGeneralData.findByUser_Card_ExtId(card.extId)
.associate { it.propertyKey to it.propertyValue }
val ratingComposition = mapOf(
@@ -56,72 +41,4 @@ class Maimai2(
genericUserSummary(card, ratingComposition)
}
// Use reflection to get all properties in Mai2Repos with matching names in Maimai2DataExport
val exportFields: Map<KMutableProperty1<Maimai2DataExport, Any>, UserLinked<*>> = Maimai2DataExport::class.vars()
.filter { f -> f.name !in setOf("gameId", "userData") }
.associateWith { Mai2Repos::class.declaredMembers
.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}")
}
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) ->
if (f returns List::class) f.set(this, u.findByUser(userData))
else u.findSingleByUser(userData)()?.let { f.set(this, it) }
}
}
@API("export")
fun exportAllUserData(@RP token: Str) = us.jwt.auth(token) { u -> export(u) }
@Suppress("UNCHECKED_CAST")
@API("import")
fun importUserData(@RP token: Str, @RB json: Str) = us.jwt.auth(token) { u ->
val export = json.parseJackson<Maimai2DataExport>()
if (!export.gameId.equals("SDEZ", true)) 400 - "Invalid game ID"
val lists = listFields.toList().associate { (f, r) -> r to f.get(export) as List<Mai2UserEntity> }.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)
if (export.userData.id != 0L) 400 - "User 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
// Check existing data
repos.userData.findByCard(u.ghostCard)?.also { gu ->
// Store a backup of the old data
val fl = "mai2-backup-${u.auId}-${LocalDateTime.now().urlSafeStr()}.json"
(Path(netProps.importBackupPath) / fl).writeText(export(u).toJson())
// Delete the old data (After migration v1000.7, all user-linked entities have ON DELETE CASCADE)
logger.info("Mai2 Import: Deleting old data for user ${u.auId}")
repos.userData.delete(gu)
repos.userData.flush()
}
trans.execute {
// 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
singles.forEach { (repo, single) -> (repo as UserLinked<Any>).save(single) }
lists.forEach { (repo, list) -> (repo as UserLinked<Any>).saveAll(list) }
}
SUCCESS
}
}