diff --git a/src/main/java/icu/samnyan/aqua/net/CardController.kt b/src/main/java/icu/samnyan/aqua/net/CardController.kt index 68c1de10..304ede8d 100644 --- a/src/main/java/icu/samnyan/aqua/net/CardController.kt +++ b/src/main/java/icu/samnyan/aqua/net/CardController.kt @@ -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 migrateCard(repo: GenericUserDataRepo, cardRepo: CardRepository, card: Card): Bool { +suspend fun migrateCard(gameName: Str, repo: GenericUserDataRepo, 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 migrateCard(repo: GenericUserDataRepo, 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 migrateCard(repo: GenericUserDataRepo, cardRepo: return true } +suspend fun orphanData(gameName: Str, repo: GenericUserDataRepo, 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? { val data = async { repo.findByCard(card) } ?: return null return mapOf( @@ -189,18 +216,20 @@ class CardGameService( suspend fun migrate(crd: Card, games: List) = 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 { diff --git a/src/main/java/icu/samnyan/aqua/net/Fedy.kt b/src/main/java/icu/samnyan/aqua/net/Fedy.kt index b6a89470..ade7cc2b 100644 --- a/src/main/java/icu/samnyan/aqua/net/Fedy.kt +++ b/src/main/java/icu/samnyan/aqua/net/Fedy.kt @@ -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) +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)) - } - - @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 + val suppressEvents = ThreadLocal.withInitial { false } + private fun handleFedy(key: Str, block: () -> T): T { + val old = suppressEvents.get() + suppressEvents.set(true) + try { + key.checkKey() + return block() + } finally { suppressEvents.set(old) } } - 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> 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})") + } + } + } + + CardResolveRes( + card?.extId ?: 0, + card?.isGhost ?: false, + isNewlyCreated, + isPairedLuidDiverged) + } + + 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 - 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) - } } + if (cu.auId != req.auId) 400 - "Card linked to another user" + if (card.isGhost) 400 - "Account virtual cards cannot be unlinked" - private fun notify(event: FedyEvent, body: Any?) { + 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) = 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 checkForGame(repo: GenericUserDataRepo, 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 } } diff --git a/src/main/java/icu/samnyan/aqua/net/Frontier.kt b/src/main/java/icu/samnyan/aqua/net/Frontier.kt index 7947e923..6ea55c9f 100644 --- a/src/main/java/icu/samnyan/aqua/net/Frontier.kt +++ b/src/main/java/icu/samnyan/aqua/net/Frontier.kt @@ -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 diff --git a/src/main/java/icu/samnyan/aqua/net/db/AquaNetUserFedy.kt b/src/main/java/icu/samnyan/aqua/net/db/AquaNetUserFedy.kt deleted file mode 100644 index b7833f7b..00000000 --- a/src/main/java/icu/samnyan/aqua/net/db/AquaNetUserFedy.kt +++ /dev/null @@ -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 { - fun findByAquaNetUserAuId(auId: Long): AquaNetUserFedy? - fun deleteByAquaNetUserAuId(auId: Long): Unit -} diff --git a/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt b/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt index f9e822b3..892ffeb4 100644 --- a/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt +++ b/src/main/java/icu/samnyan/aqua/net/games/ImportController.kt @@ -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, 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, UserModel: } } - Fedy.getGameName(game)?.let { fedy.onImported(it, u.ghostCard.extId) } + Fedy.getGameName(game)?.let { fedy.onDataUpdated(u.ghostCard.extId, it, true) } SUCCESS } diff --git a/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDB.kt b/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDB.kt index c0971c6c..16ca534a 100644 --- a/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDB.kt +++ b/src/main/java/icu/samnyan/aqua/sega/aimedb/AimeDB.kt @@ -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") diff --git a/src/main/java/icu/samnyan/aqua/sega/maimai2/Maimai2ServletController.kt b/src/main/java/icu/samnyan/aqua/sega/maimai2/Maimai2ServletController.kt index f1dfe3d4..6eb86ef3 100644 --- a/src/main/java/icu/samnyan/aqua/sega/maimai2/Maimai2ServletController.kt +++ b/src/main/java/icu/samnyan/aqua/sega/maimai2/Maimai2ServletController.kt @@ -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) { diff --git a/src/main/resources/db/80/V1000_58__aqua_net_user_fedy_drop.sql b/src/main/resources/db/80/V1000_58__aqua_net_user_fedy_drop.sql new file mode 100644 index 00000000..2f12a609 --- /dev/null +++ b/src/main/resources/db/80/V1000_58__aqua_net_user_fedy_drop.sql @@ -0,0 +1 @@ +DROP TABLE aqua_net_user_fedy; \ No newline at end of file