mirror of
https://github.com/MewoLab/AquaDX.git
synced 2025-12-14 11:56:15 +08:00
Compare commits
3 Commits
43b7ea65a5
...
5aca650602
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5aca650602 | ||
|
|
7c72348016 | ||
|
|
5eee6505f9 |
@@ -15,6 +15,7 @@ import kotlinx.coroutines.withContext
|
||||
import org.apache.tika.Tika
|
||||
import org.apache.tika.mime.MimeTypes
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.http.HttpHeaders
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.ResponseEntity.BodyBuilder
|
||||
@@ -264,3 +265,6 @@ val <S> Pair<*, S>.r get() = component2()
|
||||
val Query.exec get() = resultList.map { (it as Array<*>).toList() }
|
||||
fun List<List<Any?>>.numCsv(vararg head: Str) = head.joinToString(",") + "\n" +
|
||||
joinToString("\n") { it.joinToString(",") }
|
||||
|
||||
// DI
|
||||
inline fun <reified T> ApplicationContext.lazy() = lazy { getBean(T::class.java) }
|
||||
|
||||
@@ -21,15 +21,15 @@ val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, obj
|
||||
else -> 400 - "Invalid boolean value ${parser.text}"
|
||||
}
|
||||
})
|
||||
val JSON_DATETIME = SimpleModule().addDeserializer(java.time.LocalDateTime::class.java, object : JsonDeserializer<java.time.LocalDateTime>() {
|
||||
val JSON_DATETIME = SimpleModule().addDeserializer(java.time.LocalDateTime::class.java, object : JsonDeserializer<LocalDateTime>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext) =
|
||||
// First try standard formats via asDateTime() method
|
||||
parser.text.asDateTime() ?: try {
|
||||
parser.text.takeIf { it.isNotEmpty() }?.run { asDateTime() ?: try {
|
||||
// Try maimai2 format (yyyy-MM-dd HH:mm:ss.0)
|
||||
LocalDateTime.parse(parser.text, DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0"))
|
||||
} catch (e: Exception) {
|
||||
400 - "Invalid date time value ${parser.text}"
|
||||
}
|
||||
} }
|
||||
})
|
||||
val JACKSON = jacksonObjectMapper().apply {
|
||||
setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
|
||||
@@ -101,8 +101,7 @@ class CardController(
|
||||
val games = migrate.split(',')
|
||||
cardGameService.migrate(card, games)
|
||||
|
||||
fedy.onCardLinked(card.luid, oldExtId = card.extId, ghostExtId = u.ghostCard.extId,
|
||||
games.map { Fedy.getGameName(it) }.filterNotNull())
|
||||
fedy.onCardLinked(card.luid, oldExtId = card.extId, ghostExtId = u.ghostCard.extId, games)
|
||||
|
||||
log.info("Net /card/link : Linked card ${card.id} to user ${u.username} and migrated data to ${games.joinToString()}")
|
||||
|
||||
@@ -207,7 +206,8 @@ class CardGameService(
|
||||
val diva: icu.samnyan.aqua.sega.diva.dao.userdata.PlayerProfileRepository,
|
||||
val safety: AquaNetSafetyService,
|
||||
val cardRepo: CardRepository,
|
||||
val em: EntityManager
|
||||
val em: EntityManager,
|
||||
val cardService: CardService
|
||||
) {
|
||||
companion object {
|
||||
val log = logger()
|
||||
@@ -225,7 +225,9 @@ class CardGameService(
|
||||
val remainingGames = dataRepos.keys.toMutableSet()
|
||||
games.forEach { game ->
|
||||
val dataRepo = dataRepos[game] ?: return@forEach
|
||||
migrateCard(game, dataRepo, cardRepo, crd)
|
||||
if (migrateCard(game, dataRepo, cardRepo, crd))
|
||||
// Update timestamp for the ghost card (data migrated in)
|
||||
cardService.updateCardTimestamp(crd.aquaUser!!.ghostCard, game, resetCreatedAt = true)
|
||||
remainingGames.remove(game)
|
||||
}
|
||||
// For remaining games, orphan the data by assigning them to a dummy card
|
||||
|
||||
@@ -27,7 +27,9 @@ import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import icu.samnyan.aqua.sega.ongeki.OgkUserDataRepo
|
||||
import icu.samnyan.aqua.sega.wacca.model.db.WcUserRepo
|
||||
import org.springframework.context.ApplicationContext
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import java.time.Instant
|
||||
import java.util.concurrent.CompletableFuture
|
||||
import kotlin.io.path.getLastModifiedTime
|
||||
import kotlin.io.path.isRegularFile
|
||||
@@ -41,7 +43,7 @@ class FedyProps {
|
||||
var remote: String = ""
|
||||
}
|
||||
|
||||
data class UserProfilePicture(val url: Str, val lastUpdatedMs: Long)
|
||||
data class UserProfilePicture(val url: Str, val updatedAtMs: Long)
|
||||
data class UserBasicInfo(
|
||||
val auId: Long, val ghostExtId: Long, val registrationTimeMs: Long,
|
||||
val username: Str, val displayName: Str, val email: Str, val passwordHash: Str, val profileBio: Str,
|
||||
@@ -66,21 +68,23 @@ private data class FedyEvent(
|
||||
@API("/api/v2/fedy", consumes = ["multipart/form-data"])
|
||||
class Fedy(
|
||||
val jwt: JWT,
|
||||
val us: AquaUserServices,
|
||||
val emailProps: EmailProperties,
|
||||
val cardRepo: CardRepository,
|
||||
val cardService: CardService,
|
||||
val mai2Import: Mai2Import,
|
||||
val mai2UserDataRepo: Mai2UserDataRepo,
|
||||
val mai2UploadUserPlaylog: Mai2UploadUserPlaylogHandler,
|
||||
val mai2UpsertUserAll: Mai2UpsertUserAllHandler,
|
||||
val chu3UserDataRepo: Chu3UserDataRepo,
|
||||
val ongekiUserDataRepo: OgkUserDataRepo,
|
||||
val waccaUserDataRepo: WcUserRepo,
|
||||
val props: FedyProps,
|
||||
val paths: PathProps,
|
||||
val transactionManager: PlatformTransactionManager
|
||||
val transactionManager: PlatformTransactionManager,
|
||||
ctx: ApplicationContext
|
||||
) {
|
||||
val us by ctx.lazy<AquaUserServices>()
|
||||
val cardService by ctx.lazy<CardService>()
|
||||
val mai2Import by ctx.lazy<Mai2Import>()
|
||||
val mai2UploadUserPlaylog by ctx.lazy<Mai2UploadUserPlaylogHandler>()
|
||||
val mai2UpsertUserAll by ctx.lazy<Mai2UpsertUserAllHandler>()
|
||||
|
||||
val transaction by lazy { TransactionTemplate(transactionManager) }
|
||||
|
||||
private fun Str.checkKey() {
|
||||
@@ -157,25 +161,30 @@ class Fedy(
|
||||
?.let { paths.aquaNetPortrait.path() / it }?.takeIf { it.isRegularFile() }
|
||||
?.let { UserProfilePicture(
|
||||
url = "/uploads/net/portrait/${profilePicture}",
|
||||
lastUpdatedMs = it.getLastModifiedTime().toMillis()
|
||||
updatedAtMs = it.getLastModifiedTime().toMillis()
|
||||
) }
|
||||
)
|
||||
|
||||
data class DataPullReq(val extId: Long, val game: Str, val exportOptions: ExportOptions)
|
||||
data class DataPullRes(val error: FedyErr? = null, val result: Any? = null)
|
||||
data class DataPullReq(val extId: Long, val game: Str, val createdAtMs: Long, val updatedAtMs: Long, val exportOptions: ExportOptions)
|
||||
data class DataPullResult(val data: Any?, val createdAtMs: Long, val updatedAtMs: Long, val isRebased: Bool)
|
||||
data class DataPullRes(val error: FedyErr? = null, val result: DataPullResult? = null)
|
||||
@API("/data/pull")
|
||||
fun handleDataPull(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: DataPullReq): DataPullRes = handleFedy(key) {
|
||||
val card = cardRepo.findByExtId(req.extId).orElse(null)
|
||||
?: (404 - "Card with extId ${req.extId} not found")
|
||||
val cardTimestamp = cardService.getCardTimestamp(card, req.game)
|
||||
if (cardTimestamp.updatedAt.toEpochMilli() == req.updatedAtMs) return@handleFedy DataPullRes(error = null, result = null) // No changes
|
||||
val isRebased = req.createdAtMs > 0 && cardTimestamp.createdAt.toEpochMilli() > req.createdAtMs
|
||||
val exportOptions = if (!isRebased) { req.exportOptions } else { req.exportOptions.copy(playlogAfter = null) }
|
||||
{
|
||||
DataPullRes(result = when (req.game) {
|
||||
"mai2" -> mai2Import.export(card, req.exportOptions)
|
||||
DataPullRes(result = DataPullResult(data = when (req.game) {
|
||||
"mai2" -> mai2Import.export(card, exportOptions)
|
||||
else -> 406 - "Unsupported game"
|
||||
})
|
||||
}, createdAtMs = cardTimestamp.createdAt.toEpochMilli(), updatedAtMs = cardTimestamp.updatedAt.toEpochMilli(), isRebased = isRebased))
|
||||
} caught { DataPullRes(error = it) }
|
||||
}
|
||||
|
||||
data class DataPushReq(val extId: Long, val game: Str, val data: JDict, val removeOldData: Bool)
|
||||
data class DataPushReq(val extId: Long, val game: Str, val data: JDict, val removeOldData: Bool, val updatedAtMs: Long)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@API("/data/push")
|
||||
fun handleDataPush(@RH(KEY_HEADER) key: Str, @RT(REQ_PART) req: DataPushReq): Any = handleFedy(key) {
|
||||
@@ -188,6 +197,7 @@ class Fedy(
|
||||
repo.flush()
|
||||
}
|
||||
}
|
||||
val card = cardRepo.findByExtId(extId).orElse(null) ?: (404 - "Card not found")
|
||||
transaction.execute { when (req.game) {
|
||||
"mai2" -> {
|
||||
if (req.removeOldData) { removeOldData(mai2UserDataRepo) }
|
||||
@@ -198,7 +208,7 @@ class Fedy(
|
||||
}
|
||||
else -> 406 - "Unsupported game"
|
||||
} }
|
||||
|
||||
cardService.updateCardTimestamp(card, req.game, now = Instant.ofEpochMilli(req.updatedAtMs), resetCreatedAt = req.removeOldData)
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
@@ -221,9 +231,9 @@ class Fedy(
|
||||
var pairedCard = cardService.tryLookup(req.pairedLuid)?.maybeGhost()
|
||||
if (pairedCard?.extId != card?.extId) {
|
||||
var isGhost = pairedCard?.isGhost == true
|
||||
var isFresh = pairedCard != null && isCardFresh(pairedCard)
|
||||
if (isGhost && isFresh) isPairedLuidDiverged = true
|
||||
else if (!isGhost && card?.isGhost == true) {
|
||||
var isNonFresh = pairedCard != null && !isCardFresh(pairedCard)
|
||||
if (isGhost || isNonFresh) isPairedLuidDiverged = true
|
||||
else if (card?.isGhost == true) {
|
||||
// Ensure paired card is linked, if the main card is linked
|
||||
// If the main card is not linked, there's nothing Fedy can do. It's Fedy's best effort.
|
||||
if (pairedCard == null) { pairedCard = cardService.registerByAccessCode(req.pairedLuid, card.aquaUser) }
|
||||
@@ -326,7 +336,7 @@ class Fedy(
|
||||
|
||||
// Apparently existing cards could possibly be fresh and never used in any game. Treat them as new cards.
|
||||
private fun isCardFresh(c: Card): Bool {
|
||||
fun <T : IUserData> checkForGame(repo: GenericUserDataRepo<T>, card: Card): Bool = repo.findByCard(card) == null
|
||||
fun <T : IUserData> checkForGame(repo: GenericUserDataRepo<T>, card: Card): Bool = repo.findByCard(card) != null
|
||||
return when {
|
||||
checkForGame(mai2UserDataRepo, c) -> false
|
||||
checkForGame(chu3UserDataRepo, c) -> false
|
||||
@@ -346,17 +356,5 @@ class Fedy(
|
||||
const val REQ_PART = "request"
|
||||
const val PFP_PART = "profilePicture"
|
||||
val log = logger()
|
||||
|
||||
fun getGameName(gameId: Str) = when (gameId) {
|
||||
"mai2" -> "mai2"
|
||||
"SDEZ" -> "mai2"
|
||||
"chu3" -> "chu3"
|
||||
"SDHD" -> "chu3"
|
||||
"ongeki" -> "mu3"
|
||||
"SDDT" -> "mu3"
|
||||
"wacca" -> "wacca"
|
||||
"SDFE" -> "wacca"
|
||||
else -> null // Not supported
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,22 +3,15 @@ package icu.samnyan.aqua.net
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.components.*
|
||||
import icu.samnyan.aqua.net.db.*
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices.Companion.SETTING_FIELDS
|
||||
import icu.samnyan.aqua.net.utils.PathProps
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.general.dao.CardRepository
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.model.CardStatus
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.security.crypto.password.PasswordEncoder
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.multipart.MultipartFile
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import kotlin.io.path.writeBytes
|
||||
|
||||
@RestController
|
||||
@@ -28,6 +21,7 @@ class UserRegistrar(
|
||||
val hasher: PasswordEncoder,
|
||||
val turnstileService: TurnstileService,
|
||||
val emailService: EmailService,
|
||||
val fedy: Fedy,
|
||||
val geoIP: GeoIP,
|
||||
val jwt: JWT,
|
||||
val confirmationRepo: EmailConfirmationRepo,
|
||||
@@ -37,7 +31,6 @@ class UserRegistrar(
|
||||
val emailProps: EmailProperties,
|
||||
final val paths: PathProps
|
||||
) {
|
||||
@Autowired @Lazy lateinit var fedy: Fedy
|
||||
val portraitPath = paths.aquaNetPortrait.path()
|
||||
|
||||
companion object {
|
||||
|
||||
@@ -5,6 +5,7 @@ import icu.samnyan.aqua.net.BotProps
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import jakarta.annotation.PostConstruct
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
@@ -29,6 +30,8 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
||||
abstract val settableFields: Map<String, (T, String) -> Unit>
|
||||
open val gettableFields: Set<String> = setOf()
|
||||
|
||||
@Autowired lateinit var cardService: CardService
|
||||
|
||||
@API("trend")
|
||||
abstract suspend fun trend(@RP username: String): List<TrendOut>
|
||||
@API("user-summary")
|
||||
@@ -138,6 +141,7 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
||||
val user = async { userDataRepo.findByCard(u.ghostCard) } ?: (404 - "User not found")
|
||||
prop(user, value)
|
||||
async { userDataRepo.save(user) }
|
||||
cardService.updateCardTimestamp(u.ghostCard, name)
|
||||
SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import icu.samnyan.aqua.net.Fedy
|
||||
import icu.samnyan.aqua.net.utils.AquaNetProps
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.repository.NoRepositoryBean
|
||||
@@ -17,7 +18,6 @@ import java.util.*
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.writeText
|
||||
import kotlin.reflect.KClass
|
||||
import org.springframework.context.annotation.Lazy
|
||||
|
||||
data class ExportOptions(
|
||||
val playlogAfter: String? = null
|
||||
@@ -50,6 +50,7 @@ interface IUserRepo<UserModel, ThisModel>: JpaRepository<ThisModel, Long> {
|
||||
* Import controller for a game
|
||||
*
|
||||
* @param game: 4-letter Game ID
|
||||
* @param gameName: mai2/chu3/ongeki
|
||||
* @param exportFields: Mapping of type names to variables in the export model
|
||||
* (e.g. "Mai2UserCharacter" -> Mai2DataExport::userCharacterList)
|
||||
* @param exportRepos: Mapping of variables to repositories that can be used to find the data
|
||||
@@ -57,6 +58,7 @@ interface IUserRepo<UserModel, ThisModel>: JpaRepository<ThisModel, Long> {
|
||||
*/
|
||||
abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel: IUserData>(
|
||||
val game: String,
|
||||
val gameName: String,
|
||||
val exportClass: KClass<ExportModel>,
|
||||
val exportFields: Map<String, Var<ExportModel, Any>>,
|
||||
val exportRepos: Map<Var<ExportModel, Any>, IUserRepo<UserModel, *>>,
|
||||
@@ -71,7 +73,7 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
|
||||
@Autowired lateinit var netProps: AquaNetProps
|
||||
@Autowired lateinit var transManager: PlatformTransactionManager
|
||||
val trans by lazy { TransactionTemplate(transManager) }
|
||||
@Autowired @Lazy lateinit var fedy: Fedy
|
||||
@Autowired lateinit var cardService: CardService
|
||||
|
||||
init {
|
||||
artemisRenames.values.forEach {
|
||||
@@ -148,7 +150,7 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
|
||||
}
|
||||
}
|
||||
|
||||
Fedy.getGameName(game)?.let { fedy.onDataUpdated(u.ghostCard.extId, it, true) }
|
||||
cardService.updateCardTimestamp(u.ghostCard, gameName, resetCreatedAt = true)
|
||||
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ import kotlin.reflect.full.declaredMembers
|
||||
class Chu3Import(
|
||||
val repos: Chu3Repos,
|
||||
) : ImportController<Chu3DataExport, Chu3UserData>(
|
||||
"SDHD", Chu3DataExport::class,
|
||||
"SDHD", "chu3", Chu3DataExport::class,
|
||||
exportFields = Chu3DataExport::class.vars().associateBy {
|
||||
it.name.replace("List", "").lowercase()
|
||||
},
|
||||
|
||||
@@ -19,7 +19,7 @@ import kotlin.reflect.full.declaredMembers
|
||||
class Mai2Import(
|
||||
val repos: Mai2Repos,
|
||||
) : ImportController<Maimai2DataExport, Mai2UserDetail>(
|
||||
"SDEZ", Maimai2DataExport::class,
|
||||
"SDEZ", "mai2", Maimai2DataExport::class,
|
||||
exportFields = Maimai2DataExport::class.vars().associateBy {
|
||||
it.name.replace("List", "").lowercase()
|
||||
},
|
||||
|
||||
@@ -3,6 +3,7 @@ package icu.samnyan.aqua.net.games.mai2
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserMusicDetail
|
||||
import org.springframework.web.bind.annotation.PostMapping
|
||||
@@ -13,6 +14,7 @@ import org.springframework.web.bind.annotation.RestController
|
||||
class Mai2MusicDetailImport(
|
||||
val us: AquaUserServices,
|
||||
val repos: Mai2Repos,
|
||||
val cardService: CardService,
|
||||
) {
|
||||
@PostMapping("import-music-detail")
|
||||
suspend fun importMusicDetail(@RP token: String, @RB data: List<Mai2UserMusicDetail>) = us.jwt.auth(token) { u ->
|
||||
@@ -39,6 +41,7 @@ class Mai2MusicDetailImport(
|
||||
}
|
||||
}
|
||||
repos.userMusicDetail.saveAll(data)
|
||||
cardService.updateCardTimestamp(card, "mai2")
|
||||
SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ class Maimai2(
|
||||
val user = userDataRepo.findByCard(card) ?: (404 - "User not found")
|
||||
user.userName = newNameFull
|
||||
userDataRepo.save(user)
|
||||
cardService.updateCardTimestamp(card, "mai2")
|
||||
}
|
||||
mapOf("newName" to newNameFull)
|
||||
}
|
||||
@@ -139,6 +140,7 @@ class Maimai2(
|
||||
loginBonus.add(newBonus)
|
||||
}
|
||||
repos.userLoginBonus.saveAll(loginBonus)
|
||||
cardService.updateCardTimestamp(card, "mai2")
|
||||
}
|
||||
SUCCESS
|
||||
}
|
||||
@@ -172,6 +174,7 @@ class Maimai2(
|
||||
|
||||
myRival.propertyValue = myRivalList.joinToString(",")
|
||||
repos.userGeneralData.save(myRival)
|
||||
cardService.updateCardTimestamp(myCard, "mai2")
|
||||
}
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
|
||||
import icu.samnyan.aqua.sega.general.GameMusicPopularity
|
||||
import icu.samnyan.aqua.sega.general.MeowApi
|
||||
import icu.samnyan.aqua.sega.general.RequestContext
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
|
||||
import icu.samnyan.aqua.sega.util.jackson.StringMapper
|
||||
import icu.samnyan.aqua.spring.Metrics
|
||||
@@ -29,6 +30,7 @@ class ChusanController(
|
||||
val cmMapper: BasicMapper,
|
||||
val db: Chu3Repos,
|
||||
val us: AquaUserServices,
|
||||
val cardService: CardService,
|
||||
val versionHelper: ChusanVersionHelper,
|
||||
val props: ChusanProps,
|
||||
val pop: GameMusicPopularity,
|
||||
|
||||
@@ -75,6 +75,8 @@ fun ChusanController.cmApiInit() {
|
||||
)
|
||||
}
|
||||
|
||||
u.card?.let { cardService.updateCardTimestamp(it, "chu3") }
|
||||
|
||||
mapOf(
|
||||
"returnCode" to 1,
|
||||
"apiName" to "CMUpsertUserGachaApi",
|
||||
@@ -85,13 +87,15 @@ fun ChusanController.cmApiInit() {
|
||||
"CMUpsertUserPrintCancel" {
|
||||
val orderIdList: List<Long> = cmMapper.convert<List<Long>>(parsing { data["orderIdList"]!! })
|
||||
|
||||
db.userCardPrintState.saveAll(orderIdList.mapNotNull {
|
||||
val states = db.userCardPrintState.saveAll(orderIdList.mapNotNull {
|
||||
// TODO: The original code by Eori writes findById but I don't think that is correct...
|
||||
db.userCardPrintState.findById(it)()?.apply {
|
||||
hasCompleted = true
|
||||
}
|
||||
})
|
||||
|
||||
states.firstOrNull()?.user?.card?.let { cardService.updateCardTimestamp(it, "chu3") }
|
||||
|
||||
mapOf("returnCode" to 1, "apiName" to "CMUpsertUserPrintCancelApi")
|
||||
}
|
||||
|
||||
@@ -114,6 +118,8 @@ fun ChusanController.cmApiInit() {
|
||||
db.userCardPrintState.save(this)
|
||||
}
|
||||
|
||||
u.card?.let { cardService.updateCardTimestamp(it, "chu3") }
|
||||
|
||||
mapOf("returnCode" to 1, "apiName" to "CMUpsertUserPrintSubtractApi")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ fun ChusanController.upsertApiInit() {
|
||||
charge.user = db.userData.findByCard_ExtId(uid)() ?: (400 - "User not found")
|
||||
charge.id = db.userCharge.findByUser_Card_ExtIdAndChargeId(uid, charge.chargeId)?.id ?: 0
|
||||
db.userCharge.save(charge)
|
||||
charge.user.card?.let { cardService.updateCardTimestamp(it, "chu3") }
|
||||
"""{"returnCode":"1"}"""
|
||||
}
|
||||
|
||||
@@ -192,8 +193,10 @@ fun ChusanController.upsertApiInit() {
|
||||
}.also { db.userCMissionProgress.save(it) }
|
||||
}
|
||||
}
|
||||
|
||||
u.card?.let { cardService.updateCardTimestamp(it, "chu3") }
|
||||
}
|
||||
|
||||
"""{"returnCode":1}"""
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
package icu.samnyan.aqua.sega.general.model
|
||||
|
||||
import ext.Str
|
||||
import jakarta.persistence.*
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.time.Instant
|
||||
|
||||
@Entity(name = "SegaCardTimestamp")
|
||||
@Table(name = "sega_card_timestamp")
|
||||
class CardTimestamp(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
var id: Long = 0,
|
||||
|
||||
@Column(nullable = false)
|
||||
var createdAt: Instant = Instant.now(),
|
||||
|
||||
@Column(nullable = false)
|
||||
var updatedAt: Instant = Instant.now(),
|
||||
|
||||
var game: Str,
|
||||
|
||||
@ManyToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "card_id")
|
||||
var card: Card? = null,
|
||||
)
|
||||
|
||||
@Repository
|
||||
interface CardTimestampRepo : JpaRepository<CardTimestamp, Long> {
|
||||
fun findByCardIdAndGame(cardId: Long, game: Str): CardTimestamp?
|
||||
}
|
||||
@@ -1,10 +1,16 @@
|
||||
package icu.samnyan.aqua.sega.general.service
|
||||
|
||||
import ext.Bool
|
||||
import ext.Str
|
||||
import ext.minus
|
||||
import icu.samnyan.aqua.net.Fedy
|
||||
import icu.samnyan.aqua.net.db.AquaNetUser
|
||||
import icu.samnyan.aqua.sega.general.dao.CardRepository
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.general.model.CardTimestamp
|
||||
import icu.samnyan.aqua.sega.general.model.CardTimestampRepo
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.Instant
|
||||
import java.time.LocalDateTime
|
||||
import java.util.*
|
||||
import java.util.concurrent.ThreadLocalRandom
|
||||
@@ -14,7 +20,7 @@ import kotlin.jvm.optionals.getOrNull
|
||||
* @author samnyan (privateamusement@protonmail.com)
|
||||
*/
|
||||
@Service
|
||||
class CardService(val cardRepo: CardRepository)
|
||||
class CardService(val cardRepo: CardRepository, val cardTimestampRepo: CardTimestampRepo, val fedy: Fedy)
|
||||
{
|
||||
/**
|
||||
* Find a card by External ID
|
||||
@@ -106,4 +112,13 @@ class CardService(val cardRepo: CardRepository)
|
||||
}
|
||||
return eid
|
||||
}
|
||||
|
||||
fun getCardTimestamp(card: Card, game: Str, now: Instant = Instant.now()) =
|
||||
cardTimestampRepo.findByCardIdAndGame(card.id, game) ?: CardTimestamp(game = game, card = card, createdAt = now, updatedAt = now);
|
||||
|
||||
fun updateCardTimestamp(card: Card, game: Str, now: Instant = Instant.now(), resetCreatedAt: Bool = false) {
|
||||
cardTimestampRepo.save(getCardTimestamp(card, game, now).apply { updatedAt = now }
|
||||
.apply { if (resetCreatedAt) createdAt = now });
|
||||
fedy.onDataUpdated(card.extId, game, resetCreatedAt)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,6 +143,7 @@ fun Maimai2ServletController.initApis() {
|
||||
regionId = region
|
||||
}
|
||||
db.userRegions.save(region)
|
||||
// d.card?.let { cardService.updateCardTimestamp(it, "mai2") } // TODO: why save regions on login?
|
||||
}
|
||||
|
||||
res
|
||||
@@ -224,12 +225,16 @@ fun Maimai2ServletController.initApis() {
|
||||
// Kaleidoscope, added on 1.50
|
||||
// [{gateId, phaseId}]
|
||||
"GetGameKaleidxScope" { mapOf("gameKaleidxScopeList" to ls(
|
||||
mapOf("gateId" to 1, "phaseId" to findPhase(LocalDate.of(2025, 1, 18))),
|
||||
mapOf("gateId" to 2, "phaseId" to 2),
|
||||
mapOf("gateId" to 3, "phaseId" to 2),
|
||||
mapOf("gateId" to 4, "phaseId" to findPhase(LocalDate.of(2025, 2, 25))),
|
||||
mapOf("gateId" to 5, "phaseId" to 2),
|
||||
mapOf("gateId" to 6, "phaseId" to 2),
|
||||
mapOf("gateId" to 1, "phaseId" to 6),
|
||||
mapOf("gateId" to 2, "phaseId" to 6),
|
||||
mapOf("gateId" to 3, "phaseId" to 6),
|
||||
mapOf("gateId" to 4, "phaseId" to 6),
|
||||
mapOf("gateId" to 5, "phaseId" to 6),
|
||||
mapOf("gateId" to 6, "phaseId" to 6),
|
||||
mapOf("gateId" to 7, "phaseId" to 6),
|
||||
mapOf("gateId" to 8, "phaseId" to 6),
|
||||
mapOf("gateId" to 9, "phaseId" to 6),
|
||||
mapOf("gateId" to 10, "phaseId" to 13),
|
||||
)) }
|
||||
// Request: {userId}
|
||||
// Response: {userId, userKaleidxScopeList}
|
||||
|
||||
@@ -40,9 +40,6 @@ class Maimai2ServletController(
|
||||
val db: Mai2Repos,
|
||||
val net: Maimai2,
|
||||
): MeowApi(serialize = { _, resp -> if (resp is String) resp else resp.toJson() }) {
|
||||
|
||||
@Autowired @Lazy lateinit var fedy: Fedy
|
||||
|
||||
companion object {
|
||||
private val log = logger()
|
||||
private val empty = listOf<Any>()
|
||||
@@ -95,7 +92,6 @@ class Maimai2ServletController(
|
||||
val ctx = RequestContext(req, data.mut)
|
||||
serialize(api, handlers[api]!!(ctx) ?: noop).also {
|
||||
log.info("$token : $api > ${it.truncate(500)}")
|
||||
if (api == "UpsertUserAllApi") { data["userId"]?.long?.let { fedy.onDataUpdated(it, "mai2", false) } }
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -6,6 +6,7 @@ import ext.millis
|
||||
import ext.parsing
|
||||
import icu.samnyan.aqua.sega.allnet.TokenChecker
|
||||
import icu.samnyan.aqua.sega.general.BaseHandler
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserPlaylogRepo
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPlaylog
|
||||
@@ -22,7 +23,8 @@ import kotlin.jvm.optionals.getOrNull
|
||||
class UploadUserPlaylogHandler(
|
||||
private val userDataRepository: Mai2UserDataRepo,
|
||||
private val playlogRepo: Mai2UserPlaylogRepo,
|
||||
private val mapper: BasicMapper
|
||||
private val mapper: BasicMapper,
|
||||
private val cardService: CardService
|
||||
) : BaseHandler {
|
||||
data class BacklogEntry(val time: Long, val playlog: Mai2UserPlaylog)
|
||||
companion object {
|
||||
@@ -60,7 +62,10 @@ class UploadUserPlaylogHandler(
|
||||
|
||||
// Save if the user is registered
|
||||
val u = userDataRepository.findByCardExtId(uid).getOrNull()
|
||||
if (u != null) playlogRepo.save(playlog.apply { user = u })
|
||||
if (u != null) {
|
||||
playlogRepo.save(playlog.apply { user = u })
|
||||
// u.card?.let { cardService.updateCardTimestamp(it, "mai2") } // No need: always followed by an UpsertUserAll
|
||||
}
|
||||
|
||||
// If the user hasn't registered (first play), save the playlog to a backlog
|
||||
else {
|
||||
|
||||
@@ -28,7 +28,6 @@ class UpsertUserAllHandler(
|
||||
val cardService: CardService,
|
||||
val repos: Mai2Repos
|
||||
) : BaseHandler {
|
||||
|
||||
fun String.isValidUsername() = isNotBlank() && length <= 8
|
||||
|
||||
@Throws(JsonProcessingException::class)
|
||||
@@ -172,6 +171,8 @@ class UpsertUserAllHandler(
|
||||
})
|
||||
}
|
||||
|
||||
u.card?.let { cardService.updateCardTimestamp(it, "mai2") }
|
||||
|
||||
return SUCCESS
|
||||
}
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ import ext.logger
|
||||
import ext.long
|
||||
import ext.parsing
|
||||
import icu.samnyan.aqua.sega.general.BaseHandler
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserPrintDetail
|
||||
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
|
||||
@@ -18,6 +19,7 @@ import java.util.concurrent.ThreadLocalRandom
|
||||
class UpsertUserPrintHandler(
|
||||
val mapper: BasicMapper,
|
||||
val db: Mai2Repos,
|
||||
val cardService: CardService,
|
||||
@param:Value("\${game.cardmaker.card.expiration:15}") val expirationTime: Long,
|
||||
) : BaseHandler {
|
||||
val log = logger()
|
||||
@@ -43,6 +45,8 @@ class UpsertUserPrintHandler(
|
||||
}
|
||||
db.userPrintDetail.save(userPrint)
|
||||
|
||||
userData.card?.let { cardService.updateCardTimestamp(it, "mai2") }
|
||||
|
||||
return mapOf(
|
||||
"returnCode" to 1,
|
||||
"orderId" to 0,
|
||||
|
||||
@@ -197,6 +197,8 @@ fun OngekiController.cmApiInit() {
|
||||
}
|
||||
}
|
||||
|
||||
u.card?.let { cardService.updateCardTimestamp(it, "ongeki") }
|
||||
|
||||
null
|
||||
}
|
||||
|
||||
@@ -250,6 +252,8 @@ fun OngekiController.cmApiInit() {
|
||||
}
|
||||
}
|
||||
|
||||
u.card?.let { cardService.updateCardTimestamp(it, "ongeki") }
|
||||
|
||||
null
|
||||
}
|
||||
|
||||
@@ -313,6 +317,8 @@ fun OngekiController.cmApiInit() {
|
||||
}
|
||||
}
|
||||
|
||||
u.card?.let { cardService.updateCardTimestamp(it, "ongeki") }
|
||||
|
||||
null
|
||||
}
|
||||
|
||||
@@ -342,4 +348,4 @@ fun OngekiController.cmApiInit() {
|
||||
|
||||
mapOf("length" to 0, "gameTheaterList" to emptyList<Any>(), "registIdList" to emptyList<Any>())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import icu.samnyan.aqua.sega.allnet.TokenChecker
|
||||
import icu.samnyan.aqua.sega.general.GameMusicPopularity
|
||||
import icu.samnyan.aqua.sega.general.MeowApi
|
||||
import icu.samnyan.aqua.sega.general.RequestContext
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import icu.samnyan.aqua.sega.util.jackson.BasicMapper
|
||||
import icu.samnyan.aqua.spring.Metrics
|
||||
import jakarta.servlet.http.HttpServletRequest
|
||||
@@ -22,6 +23,7 @@ class OngekiController(
|
||||
val gdb: OngekiGameRepos,
|
||||
val us: AquaUserServices,
|
||||
val pop: GameMusicPopularity,
|
||||
val cardService: CardService,
|
||||
): MeowApi({ _, resp -> if (resp is String) resp else mapper.write(resp) }) {
|
||||
|
||||
val log = logger()
|
||||
|
||||
@@ -209,6 +209,8 @@ fun OngekiController.initUpsertAll() {
|
||||
id = db.kop.findByUserAndKopIdAndAreaId(u, kopId, areaId)()?.id ?: 0 }) }
|
||||
}
|
||||
|
||||
u.card?.let { cardService.updateCardTimestamp(it, "ongeki") }
|
||||
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
11
src/main/resources/db/80/V1000_62__sega_card_timestamp.sql
Normal file
11
src/main/resources/db/80/V1000_62__sega_card_timestamp.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
CREATE TABLE sega_card_timestamp
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
created_at datetime(3) NOT NULL,
|
||||
updated_at datetime(3) NOT NULL,
|
||||
game VARCHAR(255) NOT NULL,
|
||||
card_id BIGINT NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT fk_sega_card_timestamp_on_sega_card FOREIGN KEY (card_id) REFERENCES sega_card (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT unq_sega_card_timestamp_on_game_card UNIQUE (game, card_id)
|
||||
);
|
||||
Reference in New Issue
Block a user