[+] Card Timestamp

This commit is contained in:
Menci
2025-12-10 04:27:54 +08:00
committed by Azalea
parent 5eee6505f9
commit 7c72348016
25 changed files with 158 additions and 63 deletions

View File

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