[+] Mai2 Import feature done!

This commit is contained in:
Azalea 2024-03-21 00:36:23 -04:00
parent 7c0a1ea089
commit f44fe4def1
3 changed files with 35 additions and 9 deletions

View File

@ -68,7 +68,7 @@ fun <C, T: Any> KMutableProperty1<C, T>.setCast(obj: C, value: String) = set(obj
Boolean::class -> value.toBoolean()
else -> 400 - "Invalid field type $returnType"
} as T)
inline fun <reified T: Any> Field.gets(obj: Any) = get(obj) as T
inline fun <reified T: Any> 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<String, Any>.toUrl() = entries.joinToString("&") { (k, v) -> "$k=$v" }
operator fun <K, V> Map<K, V>.plus(map: Map<K, V>) =
(if (this is MutableMap) this else toMutableMap()).apply { putAll(map) }
operator fun <K, V> MutableMap<K, V>.plusAssign(map: Map<K, V>) { putAll(map) }
fun <K, V: Any> Map<K, V?>.vNotNull(): Map<K, V> = filterValues { it != null }.mapValues { it.value!! }
fun <T> MutableList<T>.popAll(list: List<T>) = list.also { removeAll(it) }
fun <T> MutableList<T>.popAll(vararg items: T) = popAll(items.toList())
inline fun <T> Iterable<T>.mapApply(block: T.() -> Unit) = map { it.apply(block) }

View File

@ -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<Maimai2DataExport>()
if (!export.gameId.equals("SDEZ", true)) 400 - "Invalid game ID"
val lists = listFields.toList().associate { (f, r) -> r to f.gets<List<Mai2UserEntity>>(export) }.vNotNull()
val singles = singleFields.toList().associate { (f, r) -> r to f.gets<Mai2UserEntity>(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<BaseEntity>(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<Any>).saveAll(list) }
singles.forEach { (repo, single) -> (repo as UserLinked<Any>).save(single) }
}
TODO()
SUCCESS
}
}

View File

@ -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()
}
}