mirror of
https://github.com/MewoLab/AquaDX.git
synced 2025-10-25 12:02:40 +00:00
feat: some data management APIs (#176)
This commit is contained in:
parent
967d311ee4
commit
b0d0f8ef7d
@ -30,7 +30,8 @@ class CardController(
|
||||
val cardService: CardService,
|
||||
val cardGameService: CardGameService,
|
||||
val cardRepository: CardRepository,
|
||||
val props: AquaNetProps
|
||||
val props: AquaNetProps,
|
||||
val fedy: Fedy
|
||||
) {
|
||||
companion object {
|
||||
val log = logger()
|
||||
@ -80,10 +81,12 @@ class CardController(
|
||||
val id = cardService.sanitizeCardId(cardId)
|
||||
|
||||
// Create a new card
|
||||
cardService.registerByAccessCode(id, u)
|
||||
val newCard = cardService.registerByAccessCode(id, u)
|
||||
|
||||
log.info("Net /card/link : Created new card $id for user ${u.username}")
|
||||
|
||||
fedy.onCardLinked(newCard.luid, oldExtId = null, ghostExtId = u.ghostCard.extId, emptyList())
|
||||
|
||||
return SUCCESS
|
||||
}
|
||||
|
||||
@ -98,6 +101,9 @@ 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())
|
||||
|
||||
log.info("Net /card/link : Linked card ${card.id} to user ${u.username} and migrated data to ${games.joinToString()}")
|
||||
|
||||
SUCCESS
|
||||
@ -115,10 +121,14 @@ class CardController(
|
||||
// Ghost cards cannot be unlinked
|
||||
if (card.isGhost) 400 - "Account virtual cards cannot be unlinked"
|
||||
|
||||
val luid = card.luid
|
||||
|
||||
// Unbind the card
|
||||
card.aquaUser = null
|
||||
async { cardRepository.save(card) }
|
||||
|
||||
fedy.onCardUnlinked(luid)
|
||||
|
||||
log.info("Net /card/unlink : Unlinked card ${card.id} from user ${u.username}")
|
||||
|
||||
SUCCESS
|
||||
@ -136,7 +146,7 @@ class CardController(
|
||||
*
|
||||
* Assumption: The card is already linked to the user.
|
||||
*/
|
||||
suspend fun <T : IUserData> migrateCard(repo: GenericUserDataRepo<T>, cardRepo: CardRepository, card: Card): Bool {
|
||||
suspend fun <T : IUserData> migrateCard(gameName: Str, repo: GenericUserDataRepo<T>, cardRepo: CardRepository, card: Card): Bool {
|
||||
val ghost = card.aquaUser!!.ghostCard
|
||||
|
||||
// Check if data already exists in the user's ghost card
|
||||
@ -144,7 +154,7 @@ suspend fun <T : IUserData> migrateCard(repo: GenericUserDataRepo<T>, cardRepo:
|
||||
// Create a new dummy card for deleted data
|
||||
it.card = async {
|
||||
cardRepo.save(Card().apply {
|
||||
luid = "Migrated data of ghost card ${ghost.id} for user ${card.aquaUser!!.auId} on ${utcNow().isoDateTime()}"
|
||||
luid = "Migrated data of ghost card ${ghost.id} for user ${card.aquaUser!!.auId} on ${utcNow().isoDateTime()} (${gameName})"
|
||||
// Randomize an extId outside the normal range
|
||||
extId = Random.nextLong(0x7FFFFFF7L shl 32, 0x7FFFFFFFL shl 32)
|
||||
registerTime = LocalDateTime.now()
|
||||
@ -162,6 +172,23 @@ suspend fun <T : IUserData> migrateCard(repo: GenericUserDataRepo<T>, cardRepo:
|
||||
return true
|
||||
}
|
||||
|
||||
suspend fun <T : IUserData> orphanData(gameName: Str, repo: GenericUserDataRepo<T>, cardRepo: CardRepository, card: Card) {
|
||||
// Orphan the data by assigning them to a dummy card
|
||||
repo.findByCard(card)?.let {
|
||||
// Create a new dummy card for orphaned data
|
||||
it.card = async {
|
||||
cardRepo.save(Card().apply {
|
||||
luid = "Unmigrated data of card ${card.luid} for user ${card.aquaUser!!.auId} on ${utcNow().isoDateTime()} (${gameName})"
|
||||
// Randomize an extId outside the normal range
|
||||
extId = Random.nextLong(0x7FFFFFF7L shl 32, 0x7FFFFFFFL shl 32)
|
||||
registerTime = LocalDateTime.now()
|
||||
accessTime = registerTime
|
||||
})
|
||||
}
|
||||
async { repo.save(it) }
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getSummaryFor(repo: GenericUserDataRepo<*>, card: Card): Map<Str, Any>? {
|
||||
val data = async { repo.findByCard(card) } ?: return null
|
||||
return mapOf(
|
||||
@ -189,18 +216,20 @@ class CardGameService(
|
||||
suspend fun migrate(crd: Card, games: List<String>) = async {
|
||||
// Migrate data from the card to the user's ghost card
|
||||
// An easy migration is to change the UserData card field to the user's ghost card
|
||||
val dataRepos = mapOf(
|
||||
"mai2" to maimai2,
|
||||
"chu3" to chusan,
|
||||
"ongeki" to ongeki,
|
||||
"wacca" to wacca,
|
||||
)
|
||||
val remainingGames = dataRepos.keys.toMutableSet()
|
||||
games.forEach { game ->
|
||||
when (game) {
|
||||
"mai2" -> migrateCard(maimai2, cardRepo, crd)
|
||||
"chu3" -> migrateCard(chusan, cardRepo, crd)
|
||||
"ongeki" -> migrateCard(ongeki, cardRepo, crd)
|
||||
"wacca" -> migrateCard(wacca, cardRepo, crd)
|
||||
// TODO: diva
|
||||
// "diva" -> diva.findByPdId(card.extId.toInt()).getOrNull()?.let {
|
||||
// it.pdId = card.aquaUser!!.ghostCard
|
||||
// }
|
||||
}
|
||||
val dataRepo = dataRepos[game] ?: return@forEach
|
||||
migrateCard(game, dataRepo, cardRepo, crd)
|
||||
remainingGames.remove(game)
|
||||
}
|
||||
// For remaining games, orphan the data by assigning them to a dummy card
|
||||
remainingGames.forEach { game -> orphanData(game, dataRepos[game]!!, cardRepo, crd) }
|
||||
}
|
||||
|
||||
suspend fun getSummary(card: Card) = async {
|
||||
|
||||
@ -1,31 +1,29 @@
|
||||
package icu.samnyan.aqua.net
|
||||
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.sega.general.service.CardService
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.security.MessageDigest
|
||||
import icu.samnyan.aqua.net.db.AquaNetUserRepo
|
||||
import icu.samnyan.aqua.net.db.AquaNetUserFedyRepo
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.net.components.JWT
|
||||
import icu.samnyan.aqua.net.db.AquaNetUserFedy
|
||||
import icu.samnyan.aqua.net.db.AquaNetUser
|
||||
import icu.samnyan.aqua.net.games.ImportController
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.net.games.mai2.Mai2Import
|
||||
import icu.samnyan.aqua.net.games.ExportOptions
|
||||
import icu.samnyan.aqua.sega.maimai2.handler.UploadUserPlaylogHandler as Mai2UploadUserPlaylogHandler
|
||||
import icu.samnyan.aqua.sega.maimai2.handler.UpsertUserAllHandler as Mai2UpsertUserAllHandler
|
||||
import icu.samnyan.aqua.net.utils.ApiException
|
||||
import java.util.Arrays
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.transaction.PlatformTransactionManager
|
||||
import org.springframework.transaction.support.TransactionTemplate
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserDataRepo
|
||||
import icu.samnyan.aqua.net.games.GenericUserDataRepo
|
||||
import icu.samnyan.aqua.net.games.IUserData
|
||||
import icu.samnyan.aqua.sega.chusan.model.Chu3UserDataRepo
|
||||
import icu.samnyan.aqua.sega.general.dao.CardRepository
|
||||
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 java.util.concurrent.CompletableFuture
|
||||
|
||||
@Configuration
|
||||
@ -36,23 +34,32 @@ class FedyProps {
|
||||
var remote: String = ""
|
||||
}
|
||||
|
||||
enum class FedyEvent {
|
||||
Linked,
|
||||
Unlinked,
|
||||
Upserted,
|
||||
Imported,
|
||||
}
|
||||
private data class CardCreatedEvent(val luid: Str, val extId: Long)
|
||||
private data class CardLinkedEvent(val luid: Str, val oldExtId: Long?, val ghostExtId: Long, val migratedGames: List<Str>)
|
||||
private data class CardUnlinkedEvent(val luid: Str)
|
||||
private data class DataUpdatedEvent(val extId: Long, val isGhostCard: Bool, val game: Str, val removeOldData: Bool)
|
||||
|
||||
private data class FedyEvent(
|
||||
var cardCreated: CardCreatedEvent? = null,
|
||||
var cardLinked: CardLinkedEvent? = null,
|
||||
var cardUnlinked: CardUnlinkedEvent? = null,
|
||||
var dataUpdated: DataUpdatedEvent? = null,
|
||||
)
|
||||
|
||||
@RestController
|
||||
@API("/api/v2/fedy")
|
||||
class Fedy(
|
||||
val jwt: JWT,
|
||||
val userRepo: AquaNetUserRepo,
|
||||
val userFedyRepo: AquaNetUserFedyRepo,
|
||||
val us: AquaUserServices,
|
||||
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 transactionManager: PlatformTransactionManager
|
||||
) {
|
||||
@ -63,73 +70,37 @@ class Fedy(
|
||||
if (!MessageDigest.isEqual(this.toByteArray(), props.key.toByteArray())) 403 - "Invalid Key"
|
||||
}
|
||||
|
||||
@API("/status")
|
||||
fun handleStatus(@RP token: Str): Any {
|
||||
val user = jwt.auth(token)
|
||||
val userFedy = userFedyRepo.findByAquaNetUserAuId(user.auId)
|
||||
return mapOf("linkedAt" to (userFedy?.createdAt?.toEpochMilli() ?: 0))
|
||||
val suppressEvents = ThreadLocal.withInitial { false }
|
||||
private fun <T> handleFedy(key: Str, block: () -> T): T {
|
||||
val old = suppressEvents.get()
|
||||
suppressEvents.set(true)
|
||||
try {
|
||||
key.checkKey()
|
||||
return block()
|
||||
} finally { suppressEvents.set(old) }
|
||||
}
|
||||
|
||||
@API("/link")
|
||||
fun handleLink(@RP token: Str, @RP nonce: Str): Any {
|
||||
val user = jwt.auth(token)
|
||||
|
||||
if (userFedyRepo.findByAquaNetUserAuId(user.auId) != null) 412 - "User already linked"
|
||||
val userFedy = AquaNetUserFedy(aquaNetUser = user)
|
||||
userFedyRepo.save(userFedy)
|
||||
|
||||
notify(FedyEvent.Linked, mapOf("auId" to user.auId, "nonce" to nonce))
|
||||
return mapOf("linkedAt" to userFedy.createdAt.toEpochMilli())
|
||||
}
|
||||
|
||||
@API("/unlink")
|
||||
fun handleUnlink(@RP token: Str): Any {
|
||||
val user = jwt.auth(token)
|
||||
|
||||
val userFedy = userFedyRepo.findByAquaNetUserAuId(user.auId) ?: 412 - "User not linked"
|
||||
userFedyRepo.delete(userFedy)
|
||||
|
||||
notify(FedyEvent.Unlinked, mapOf("auId" to user.auId))
|
||||
return SUCCESS
|
||||
}
|
||||
|
||||
private fun ensureUser(auId: Long): AquaNetUser {
|
||||
val userFedy = userFedyRepo.findByAquaNetUserAuId(auId) ?: 404 - "User not linked"
|
||||
val user = userRepo.findByAuId(auId) ?: 404 - "User not found"
|
||||
return user
|
||||
}
|
||||
|
||||
data class UnlinkByRemoteReq(val auId: Long)
|
||||
@API("/unlink-by-remote")
|
||||
fun handleUnlinkByRemote(@RH(KEY_HEADER) key: Str, @RB req: UnlinkByRemoteReq): Any {
|
||||
key.checkKey()
|
||||
val user = ensureUser(req.auId)
|
||||
userFedyRepo.deleteByAquaNetUserAuId(user.auId)
|
||||
// No need to notify remote, because initiated by remote
|
||||
return SUCCESS
|
||||
}
|
||||
|
||||
data class PullReq(val auId: Long, val game: Str, val exportOptions: ExportOptions)
|
||||
@API("/pull")
|
||||
fun handlePull(@RH(KEY_HEADER) key: Str, @RB req: PullReq): Any {
|
||||
key.checkKey()
|
||||
val user = ensureUser(req.auId)
|
||||
fun catched(block: () -> Any) =
|
||||
try { mapOf("result" to block()) }
|
||||
catch (e: ApiException) { mapOf("error" to mapOf("code" to e.code, "message" to e.message.toString())) }
|
||||
return when (req.game) {
|
||||
"mai2" -> catched { mai2Import.export(user, req.exportOptions) }
|
||||
data class DataPullReq(val extId: Long, val game: Str, val exportOptions: ExportOptions)
|
||||
data class DataPullRes(val error: DataPullErr? = null, val result: Any? = null)
|
||||
data class DataPullErr(val code: Int, val message: Str)
|
||||
@API("/data/pull")
|
||||
fun handleDataPull(@RH(KEY_HEADER) key: Str, @RB req: DataPullReq): DataPullRes = handleFedy(key) {
|
||||
val card = cardRepo.findByExtId(req.extId).orElse(null)
|
||||
?: (404 - "Card with extId ${req.extId} not found")
|
||||
fun caught(block: () -> Any) =
|
||||
try { DataPullRes(result = block()) }
|
||||
catch (e: ApiException) { DataPullRes(error = DataPullErr(code = e.code, message = e.message.toString())) }
|
||||
when (req.game) {
|
||||
"mai2" -> caught { mai2Import.export(card, req.exportOptions) }
|
||||
else -> 406 - "Unsupported game"
|
||||
}
|
||||
}
|
||||
|
||||
data class PushReq(val auId: 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)
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
@API("/push")
|
||||
fun handlePush(@RH(KEY_HEADER) key: Str, @RB req: PushReq): Any {
|
||||
key.checkKey()
|
||||
val user = ensureUser(req.auId)
|
||||
val extId = user.ghostCard.extId
|
||||
@API("/data/push")
|
||||
fun handleDataPush(@RH(KEY_HEADER) key: Str, @RB req: DataPushReq): Any = handleFedy(key) {
|
||||
val extId = req.extId
|
||||
fun<UserData : IUserData, UserRepo : GenericUserDataRepo<UserData>> removeOldData(repo: UserRepo) {
|
||||
val oldData = repo.findByCard_ExtId(extId)
|
||||
if (oldData.isPresent) {
|
||||
@ -149,29 +120,111 @@ class Fedy(
|
||||
else -> 406 - "Unsupported game"
|
||||
} }
|
||||
|
||||
return SUCCESS
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
fun onUpserted(game: Str, maybeExtId: Any?) = maybeNotifyAsync(FedyEvent.Upserted, game, maybeExtId)
|
||||
fun onImported(game: Str, maybeExtId: Any?) = maybeNotifyAsync(FedyEvent.Imported, game, maybeExtId)
|
||||
data class CardResolveReq(val luid: Str, val pairedLuid: Str?, val createIfNotFound: Bool)
|
||||
data class CardResolveRes(val extId: Long, val isGhostCard: Bool, val isNewlyCreated: Bool, val isPairedLuidDiverged: Bool)
|
||||
@API("/card/resolve")
|
||||
fun handleCardResolve(@RH(KEY_HEADER) key: Str, @RB req: CardResolveReq): CardResolveRes = handleFedy(key) {
|
||||
var card = cardService.tryLookup(req.luid)
|
||||
var isNewlyCreated = false
|
||||
if (card != null) {
|
||||
card = card.maybeGhost()
|
||||
if (!card.isGhost) isNewlyCreated = isCardFresh(card)
|
||||
} else if (req.createIfNotFound) {
|
||||
card = cardService.registerByAccessCode(req.luid, null)
|
||||
isNewlyCreated = true
|
||||
log.info("Fedy /card/resolve : Created new card ${card.id} (${card.luid})")
|
||||
}
|
||||
var isPairedLuidDiverged = false
|
||||
if (req.pairedLuid != null) {
|
||||
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) {
|
||||
// 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) }
|
||||
else { pairedCard.aquaUser = card.aquaUser; cardRepo.save(pairedCard) }
|
||||
log.info("Fedy /card/resolve : Created paired card ${pairedCard.id} (${pairedCard.luid}) for user ${card.aquaUser?.auId} (${card.aquaUser?.username})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun maybeNotifyAsync(event: FedyEvent, game: Str, maybeExtId: Any?) = if (!props.enabled) {} else CompletableFuture.runAsync { try {
|
||||
val extId = maybeExtId?.long ?: return@runAsync
|
||||
val user = userRepo.findByGhostCardExtId(extId) ?: return@runAsync
|
||||
val userFedy = userFedyRepo.findByAquaNetUserAuId(user.auId) ?: return@runAsync
|
||||
notify(event, mapOf("auId" to user.auId, "game" to game))
|
||||
} catch (e: Exception) {
|
||||
log.error("Error handling Fedy on maybeNotifyAsync($event, $game, $maybeExtId)", e)
|
||||
} }
|
||||
CardResolveRes(
|
||||
card?.extId ?: 0,
|
||||
card?.isGhost ?: false,
|
||||
isNewlyCreated,
|
||||
isPairedLuidDiverged)
|
||||
}
|
||||
|
||||
private fun notify(event: FedyEvent, body: Any?) {
|
||||
data class CardLinkReq(val auId: Long, val luid: Str)
|
||||
@API("/card/link")
|
||||
fun handleCardLink(@RH(KEY_HEADER) key: Str, @RB req: CardLinkReq): Any = handleFedy(key) {
|
||||
val ru = us.userRepo.findByAuId(req.auId) ?: (404 - "User not found")
|
||||
var card = cardService.tryLookup(req.luid)
|
||||
if (card == null) {
|
||||
card = cardService.registerByAccessCode(req.luid, ru)
|
||||
log.info("Fedy /card/link : Linked new card ${card.id} (${card.luid}) to user ${ru.auId} (${ru.username})")
|
||||
} else {
|
||||
if (card.isGhost) 400 - "Account virtual cards cannot be unlinked"
|
||||
val cu = card.aquaUser
|
||||
if (cu != null) {
|
||||
if (cu.auId == req.auId) log.info("Fedy /card/link : Existing card ${card.id} (${card.luid}) already linked to user ${ru.auId} (${ru.username})")
|
||||
else 400 - "Card linked to another user"
|
||||
} else {
|
||||
card.aquaUser = ru
|
||||
cardRepo.save(card)
|
||||
log.info("Fedy /card/link : Linked existing card ${card.id} (${card.luid}) to user ${ru.auId} (${ru.username})")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class CardUnlinkReq(val auId: Long, val luid: Str)
|
||||
@API("/card/unlink")
|
||||
fun handleCardUnlink(@RH(KEY_HEADER) key: Str, @RB req: CardUnlinkReq): Any = handleFedy(key) {
|
||||
val card = cardService.tryLookup(req.luid)
|
||||
val cu = card?.aquaUser ?: return@handleFedy SUCCESS // Nothing to do
|
||||
|
||||
if (cu.auId != req.auId) 400 - "Card linked to another user"
|
||||
if (card.isGhost) 400 - "Account virtual cards cannot be unlinked"
|
||||
|
||||
card.aquaUser = null
|
||||
cardRepo.save(card)
|
||||
log.info("Fedy /card/unlink : Unlinked card ${card.id} (${card.luid}) from user ${cu.auId} (${cu.username})")
|
||||
}
|
||||
|
||||
fun onCardCreated(luid: Str, extId: Long) = maybeNotifyAsync(FedyEvent(cardCreated = CardCreatedEvent(luid, extId)))
|
||||
fun onCardLinked(luid: Str, oldExtId: Long?, ghostExtId: Long, migratedGames: List<Str>) = maybeNotifyAsync(FedyEvent(cardLinked = CardLinkedEvent(luid, oldExtId, ghostExtId, migratedGames)))
|
||||
fun onCardUnlinked(luid: Str) = maybeNotifyAsync(FedyEvent(cardUnlinked = CardUnlinkedEvent(luid)))
|
||||
fun onDataUpdated(extId: Long, game: Str, removeOldData: Bool) = maybeNotifyAsync({
|
||||
val card = cardRepo.findByExtId(extId).orElse(null) ?: return@maybeNotifyAsync null // Card not found, nothing to do
|
||||
FedyEvent(dataUpdated = DataUpdatedEvent(extId, card.isGhost, game, removeOldData))
|
||||
})
|
||||
|
||||
private fun maybeNotifyAsync(event: FedyEvent) = maybeNotifyAsync({ event })
|
||||
private fun maybeNotifyAsync(getEvent: () -> FedyEvent?) = if (!props.enabled && !suppressEvents.get()) {} else CompletableFuture.runAsync {
|
||||
var event: FedyEvent? = null
|
||||
try {
|
||||
event = getEvent()
|
||||
if (event == null) return@runAsync // Nothing to do
|
||||
notify(event)
|
||||
} catch (e: Exception) {
|
||||
log.error("Error handling Fedy on maybeNotifyAsync($event)", e)
|
||||
}
|
||||
}.let {}
|
||||
|
||||
private fun notify(event: FedyEvent) {
|
||||
val MAX_RETRY = 3
|
||||
val body = body?.toJson() ?: "{}"
|
||||
val body = event.toJson() ?: "{}"
|
||||
var retry = 0
|
||||
var shouldRetry = true
|
||||
while (retry < MAX_RETRY) {
|
||||
while (true) {
|
||||
try {
|
||||
val response = "${props.remote.trimEnd('/')}/notify/${event.name}".request()
|
||||
val response = "${props.remote.trimEnd('/')}/notify".request()
|
||||
.header("Content-Type" to "application/json")
|
||||
.header(KEY_HEADER to props.key)
|
||||
.post(body)
|
||||
@ -191,13 +244,32 @@ 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
|
||||
return when {
|
||||
checkForGame(mai2UserDataRepo, c) -> false
|
||||
checkForGame(chu3UserDataRepo, c) -> false
|
||||
checkForGame(ongekiUserDataRepo, c) -> false
|
||||
checkForGame(waccaUserDataRepo, c) -> false
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
|
||||
companion object
|
||||
{
|
||||
const val KEY_HEADER = "X-Fedy-Key"
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
@ -19,7 +19,8 @@ class FrontierProps {
|
||||
@API("/api/v2/frontier")
|
||||
class Frontier(
|
||||
val cardService: CardService,
|
||||
val props: FrontierProps
|
||||
val props: FrontierProps,
|
||||
val fedy: Fedy
|
||||
) {
|
||||
fun Str.checkFtk() {
|
||||
if (this != props.ftk) 403 - "Invalid FTK"
|
||||
@ -35,6 +36,9 @@ class Frontier(
|
||||
if (async { cardService.cardRepo.findByLuid(accessCode) }.isPresent) 400 - "Card already registered"
|
||||
|
||||
val card = async { cardService.registerByAccessCode(accessCode) }
|
||||
|
||||
fedy.onCardCreated(accessCode, card.extId)
|
||||
|
||||
return mapOf(
|
||||
"card" to card,
|
||||
"id" to card.extId // Expose hidden ID
|
||||
|
||||
@ -1,29 +0,0 @@
|
||||
package icu.samnyan.aqua.net.db
|
||||
|
||||
import jakarta.persistence.*
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.stereotype.Repository
|
||||
import java.io.Serializable
|
||||
import java.time.Instant
|
||||
|
||||
@Entity
|
||||
@Table(name = "aqua_net_user_fedy")
|
||||
class AquaNetUserFedy(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
var id: Long = 0,
|
||||
|
||||
@Column(nullable = false)
|
||||
var createdAt: Instant = Instant.now(),
|
||||
|
||||
// Linking to the AquaNetUser
|
||||
@OneToOne
|
||||
@JoinColumn(name = "auId", referencedColumnName = "auId")
|
||||
var aquaNetUser: AquaNetUser,
|
||||
) : Serializable
|
||||
|
||||
@Repository
|
||||
interface AquaNetUserFedyRepo : JpaRepository<AquaNetUserFedy, Long> {
|
||||
fun findByAquaNetUserAuId(auId: Long): AquaNetUserFedy?
|
||||
fun deleteByAquaNetUserAuId(auId: Long): Unit
|
||||
}
|
||||
@ -6,6 +6,7 @@ import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
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 org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.data.jpa.repository.JpaRepository
|
||||
import org.springframework.data.repository.NoRepositoryBean
|
||||
@ -81,11 +82,11 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
|
||||
val listRepos = exportRepos.filter { it.key returns List::class }
|
||||
val singleRepos = exportRepos.filter { !(it.key returns List::class) }
|
||||
|
||||
fun export(u: AquaNetUser): ExportModel = export(u, ExportOptions())
|
||||
fun export(u: AquaNetUser): ExportModel = export(u.ghostCard, ExportOptions())
|
||||
|
||||
fun export(u: AquaNetUser, options: ExportOptions) = createEmpty().apply {
|
||||
fun export(c: Card, options: ExportOptions) = createEmpty().apply {
|
||||
gameId = game
|
||||
userData = userDataRepo.findByCard(u.ghostCard) ?: (404 - "User not found")
|
||||
userData = userDataRepo.findByCard(c) ?: (404 - "User not found")
|
||||
exportRepos.forEach { (f, u) ->
|
||||
if (f returns List::class) f.set(this, u.findByUser(userData))
|
||||
else u.findSingleByUser(userData)()?.let { f.set(this, it) }
|
||||
@ -147,7 +148,7 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
|
||||
}
|
||||
}
|
||||
|
||||
Fedy.getGameName(game)?.let { fedy.onImported(it, u.ghostCard.extId) }
|
||||
Fedy.getGameName(game)?.let { fedy.onDataUpdated(u.ghostCard.extId, it, true) }
|
||||
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ package icu.samnyan.aqua.sega.aimedb
|
||||
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.BotProps
|
||||
import icu.samnyan.aqua.net.Fedy
|
||||
import icu.samnyan.aqua.net.db.AquaUserServices
|
||||
import icu.samnyan.aqua.sega.allnet.AllNetProps
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
@ -26,6 +27,7 @@ class AimeDB(
|
||||
val cardService: CardService,
|
||||
val us: AquaUserServices,
|
||||
val allNetProps: AllNetProps,
|
||||
val fedy: Fedy,
|
||||
): ChannelInboundHandlerAdapter() {
|
||||
val logger = logger()
|
||||
|
||||
@ -200,6 +202,8 @@ class AimeDB(
|
||||
|
||||
status = 1
|
||||
aimeId = card.extId
|
||||
|
||||
fedy.onCardCreated(luid, card.extId)
|
||||
}
|
||||
else logger.warn("> Duplicated Aime Card Register detected, access code: $luid")
|
||||
|
||||
|
||||
@ -95,7 +95,7 @@ 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") { fedy.onUpserted("mai2", data["userId"]) }
|
||||
if (api == "UpsertUserAllApi") { data["userId"]?.long?.let { fedy.onDataUpdated(it, "mai2", false) } }
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
@ -0,0 +1 @@
|
||||
DROP TABLE aqua_net_user_fedy;
|
||||
Loading…
x
Reference in New Issue
Block a user