mirror of
https://github.com/hykilpikonna/AquaDX.git
synced 2026-08-23 17:54:40 -05:00
[+] Delete account
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { slide, fade } from "svelte/transition";
|
||||
import type { AquaNetUser } from "../../libs/generalTypes";
|
||||
import type { AquaNetUser, ConfirmProps } from "../../libs/generalTypes";
|
||||
import { CARD, USER } from "../../libs/sdk";
|
||||
import StatusOverlays from "../../components/StatusOverlays.svelte";
|
||||
import Icon from "@iconify/svelte";
|
||||
import { pfp } from "../../libs/ui";
|
||||
import { download, pfp } from "../../libs/ui";
|
||||
import { t, ts } from "../../libs/i18n";
|
||||
import Cropper from "svelte-easy-crop";
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
let error: string;
|
||||
let submitting = ""
|
||||
let loading = false;
|
||||
let confirm: ConfirmProps | null = null;
|
||||
|
||||
const profileFields = [
|
||||
[ 'displayName', t('settings.profile.name') ],
|
||||
@@ -97,11 +98,41 @@
|
||||
location.href = "/";
|
||||
}
|
||||
|
||||
function requestAccountDeletion() {
|
||||
if (submitting) return;
|
||||
confirm = {
|
||||
title: t('settings.profile.delete-confirm-title'),
|
||||
message: t('settings.profile.delete-confirm-message'),
|
||||
dangerous: true,
|
||||
cancel: () => confirm = null,
|
||||
confirm: deleteAccount,
|
||||
};
|
||||
}
|
||||
|
||||
async function deleteAccount() {
|
||||
if (submitting) return;
|
||||
submitting = "deleteAccount";
|
||||
|
||||
try {
|
||||
const data = await USER.exportAccountData();
|
||||
download(
|
||||
JSON.stringify(data, null, 2),
|
||||
`AquaDX_account_export_${me.username}.json`,
|
||||
);
|
||||
await USER.deleteAccount();
|
||||
localStorage.removeItem("token");
|
||||
location.href = "/";
|
||||
} catch (e) {
|
||||
error = (e as Error).message;
|
||||
submitting = "";
|
||||
}
|
||||
}
|
||||
|
||||
const passwordAction = (node: HTMLInputElement, whether: boolean) => {
|
||||
if (whether) node.type = 'password'
|
||||
}
|
||||
</script>
|
||||
<StatusOverlays {error} loading={!!submitting || loading} />
|
||||
<StatusOverlays bind:confirm={confirm} {error} loading={!!submitting || loading} />
|
||||
{#if !submitting && !error && me}
|
||||
<div>
|
||||
<div class="fields">
|
||||
@@ -171,6 +202,16 @@
|
||||
<button on:click={logOut}>{ts(`settings.profile.logout`)}</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field danger-zone m-t">
|
||||
<span class="name">{t('settings.profile.delete-title')}</span>
|
||||
<span class="desc">{t('settings.profile.delete-description')}</span>
|
||||
<div>
|
||||
<button class="error" on:click={requestAccountDeletion}>
|
||||
<Icon icon="tabler:trash-x-filled" />
|
||||
{t('settings.profile.delete')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
@@ -234,6 +275,19 @@
|
||||
object-fit: cover
|
||||
aspect-ratio: 1
|
||||
|
||||
.danger-zone
|
||||
margin-top: 1.5rem
|
||||
padding-top: 1.5rem
|
||||
border-top: 1px solid vars.$ov-lighter
|
||||
|
||||
.desc
|
||||
opacity: 0.6
|
||||
|
||||
button
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 0.5rem
|
||||
|
||||
|
||||
|
||||
.cropper-container
|
||||
|
||||
@@ -274,6 +274,11 @@ export const EN_REF_SETTINGS = {
|
||||
'settings.profile.unset': 'Unset',
|
||||
'settings.profile.logout': 'Log out',
|
||||
'settings.profile.unchanged': 'Unchanged',
|
||||
'settings.profile.delete-title': 'Delete account',
|
||||
'settings.profile.delete-description': 'Download a backup, then permanently delete your account and all associated game data.',
|
||||
'settings.profile.delete': 'Delete account',
|
||||
'settings.profile.delete-confirm-title': 'Permanently delete your account?',
|
||||
'settings.profile.delete-confirm-message': 'A backup of all game data will be downloaded first. After that, your account and all associated game data will be permanently deleted. This cannot be undone.',
|
||||
'settings.export': 'Export Player Data',
|
||||
'settings.batchManualExport': "Export in Batch Manual (for Tachi)",
|
||||
'settings.cabNotice': "These settings only apply for your keychip. If you're playing on someone else's setup, please ask them to change these settings.",
|
||||
|
||||
@@ -287,6 +287,11 @@ const zhSettings: typeof EN_REF_SETTINGS = {
|
||||
'settings.profile.unset': '未设置',
|
||||
'settings.profile.logout': '登出',
|
||||
'settings.profile.unchanged': '未更改',
|
||||
'settings.profile.delete-title': '删除账户',
|
||||
'settings.profile.delete-description': '下载备份后,永久删除您的账户及所有关联游戏数据。',
|
||||
'settings.profile.delete': '删除账户',
|
||||
'settings.profile.delete-confirm-title': '永久删除您的账户?',
|
||||
'settings.profile.delete-confirm-message': '系统会先下载所有游戏数据的备份,然后永久删除您的账户及所有关联游戏数据。此操作无法撤销。',
|
||||
'settings.export': '导出玩家数据',
|
||||
'settings.batchManualExport': "导出 Batch Manual 格式(用于 Tachi)",
|
||||
'settings.cabNotice': '注意:下面这些设置只会影响你自己的机器,如果你是在其他人的机器上玩的话,请联系机主来改设置',
|
||||
|
||||
@@ -203,6 +203,10 @@ export const USER = {
|
||||
ensureLoggedIn,
|
||||
changeRegion: (regionId: number) =>
|
||||
post('/api/v2/user/change-region', { regionId }),
|
||||
exportAccountData: (): Promise<Record<string, any>> =>
|
||||
post('/api/v2/user/export-data'),
|
||||
deleteAccount: () =>
|
||||
post('/api/v2/user/delete-account'),
|
||||
}
|
||||
|
||||
export const USERBOX = {
|
||||
|
||||
312
src/main/java/icu/samnyan/aqua/net/AccountController.kt
Normal file
312
src/main/java/icu/samnyan/aqua/net/AccountController.kt
Normal file
@@ -0,0 +1,312 @@
|
||||
package icu.samnyan.aqua.net
|
||||
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.components.JWT
|
||||
import icu.samnyan.aqua.net.db.*
|
||||
import icu.samnyan.aqua.net.utils.PathProps
|
||||
import icu.samnyan.aqua.net.utils.SUCCESS
|
||||
import icu.samnyan.aqua.sega.allnet.KeychipSessionRepo
|
||||
import icu.samnyan.aqua.sega.allnet.UserKeychipRepo
|
||||
import icu.samnyan.aqua.sega.chusan.model.Chu3Repos
|
||||
import icu.samnyan.aqua.sega.diva.DivaRepos
|
||||
import icu.samnyan.aqua.sega.diva.model.db.userdata.PlayerProfile
|
||||
import icu.samnyan.aqua.sega.general.dao.CardRepository
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
|
||||
import icu.samnyan.aqua.sega.ongeki.OngekiUserRepos
|
||||
import icu.samnyan.aqua.sega.wacca.model.db.WaccaRepos
|
||||
import jakarta.transaction.Transactional
|
||||
import org.springframework.stereotype.Service
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.time.Instant
|
||||
import kotlin.io.path.Path
|
||||
import kotlin.io.path.deleteIfExists
|
||||
import kotlin.io.path.exists
|
||||
import kotlin.io.path.name
|
||||
|
||||
data class AccountGameDataExport(
|
||||
val formatVersion: Int = 1,
|
||||
val exportedAt: Instant = Instant.now(),
|
||||
val username: String,
|
||||
val gameOptions: AquaGameOptions?,
|
||||
val games: Map<String, Any>,
|
||||
)
|
||||
|
||||
data class AccountFileCleanup(
|
||||
val profilePicture: String?,
|
||||
val gameUserId: Long,
|
||||
val divaScreenshots: List<String>,
|
||||
)
|
||||
|
||||
@RestController
|
||||
@API("/api/v2/user")
|
||||
class AccountController(
|
||||
val jwt: JWT,
|
||||
val accountData: AccountDataService,
|
||||
) {
|
||||
@API("/export-data")
|
||||
@Doc("Export all game data associated with the current user's account.", "All account game data")
|
||||
fun exportData(@RP token: Str) = jwt.auth(token) { accountData.export(it) }
|
||||
|
||||
@API("/delete-account")
|
||||
@Doc("Permanently delete the current user's account and all associated game data.", "Success message")
|
||||
fun deleteAccount(@RP token: Str) = jwt.auth(token) { user ->
|
||||
val cleanup = accountData.deleteDatabaseData(user.auId)
|
||||
accountData.deleteUploadedFiles(cleanup)
|
||||
SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
@Service
|
||||
class AccountDataService(
|
||||
val userRepo: AquaNetUserRepo,
|
||||
val cardRepo: CardRepository,
|
||||
val gameOptionsRepo: AquaGameOptionsRepo,
|
||||
val sessionRepo: SessionTokenRepo,
|
||||
val confirmationRepo: EmailConfirmationRepo,
|
||||
val resetPasswordRepo: ResetPasswordRepo,
|
||||
val userKeychipRepo: UserKeychipRepo,
|
||||
val keychipSessionRepo: KeychipSessionRepo,
|
||||
val mai2: Mai2Repos,
|
||||
val chu3: Chu3Repos,
|
||||
val ongeki: OngekiUserRepos,
|
||||
val wacca: WaccaRepos,
|
||||
val diva: DivaRepos,
|
||||
val paths: PathProps,
|
||||
) {
|
||||
companion object {
|
||||
val log = logger()
|
||||
}
|
||||
|
||||
fun export(user: AquaNetUser): AccountGameDataExport {
|
||||
val card = user.ghostCard
|
||||
val games = linkedMapOf<String, Any>()
|
||||
|
||||
exportMai2(card)?.let { games["mai2"] = it }
|
||||
exportChu3(card)?.let { games["chu3"] = it }
|
||||
exportOngeki(card)?.let { games["ongeki"] = it }
|
||||
exportWacca(card)?.let { games["wacca"] = it }
|
||||
exportDiva(card)?.let { games["diva"] = it }
|
||||
|
||||
log.info("Exported all account game data for user ${user.auId}")
|
||||
return AccountGameDataExport(username = user.username, gameOptions = user.gameOptions, games = games)
|
||||
}
|
||||
|
||||
private fun exportMai2(card: Card): Map<String, Any>? {
|
||||
val user = mai2.userData.findByCard(card) ?: return null
|
||||
return linkedMapOf(
|
||||
"gameId" to "SDEZ",
|
||||
"userData" to user,
|
||||
"mapEncountNpcList" to mai2.mapEncountNpc.findByUser(user),
|
||||
"userActList" to mai2.userAct.findByUser(user),
|
||||
"userCardList" to mai2.userCard.findByUser(user),
|
||||
"userCharacterList" to mai2.userCharacter.findByUser(user),
|
||||
"userChargeList" to mai2.userCharge.findByUser(user),
|
||||
"userCourseList" to mai2.userCourse.findByUser(user),
|
||||
"userExtendList" to mai2.userExtend.findByUser(user),
|
||||
"userFavoriteList" to mai2.userFavorite.findByUser(user),
|
||||
"userFriendSeasonRankingList" to mai2.userFriendSeasonRanking.findByUser(user),
|
||||
"userGeneralDataList" to mai2.userGeneralData.findByUser(user),
|
||||
"userItemList" to mai2.userItem.findByUser(user),
|
||||
"userLoginBonusList" to mai2.userLoginBonus.findByUser(user),
|
||||
"userMapList" to mai2.userMap.findByUser(user),
|
||||
"userMusicDetailList" to mai2.userMusicDetail.findByUser(user),
|
||||
"userOptionList" to mai2.userOption.findByUser(user),
|
||||
"userPlaylogList" to mai2.userPlaylog.findByUser(user),
|
||||
"userPrintDetailList" to mai2.userPrintDetail.findByUser(user),
|
||||
"userUdemaeList" to mai2.userUdemae.findByUser(user),
|
||||
"userKaleidxScopeList" to mai2.userKaleidx.findByUser(user),
|
||||
"userIntimateList" to mai2.userIntimate.findByUser(user),
|
||||
"userRegionsList" to mai2.userRegions.findByUser(user),
|
||||
)
|
||||
}
|
||||
|
||||
private fun exportChu3(card: Card): Map<String, Any>? {
|
||||
val user = chu3.userData.findByCard(card) ?: return null
|
||||
return linkedMapOf(
|
||||
"gameId" to "SDHD",
|
||||
"userData" to user,
|
||||
"userActivityList" to chu3.userActivity.findByUser(user),
|
||||
"userCardPrintStateList" to chu3.userCardPrintState.findByUser(user),
|
||||
"userCharacterList" to chu3.userCharacter.findByUser(user),
|
||||
"userChargeList" to chu3.userCharge.findByUser(user),
|
||||
"userCourseList" to chu3.userCourse.findByUser(user),
|
||||
"userDuelList" to chu3.userDuel.findByUser(user),
|
||||
"userGachaList" to chu3.userGacha.findByUser(user),
|
||||
"userGameOptionList" to chu3.userGameOption.findByUser(user),
|
||||
"userGeneralDataList" to chu3.userGeneralData.findByUser(user),
|
||||
"userItemList" to chu3.userItem.findByUser(user),
|
||||
"userMapList" to chu3.userMap.findByUser(user),
|
||||
"userMusicDetailList" to chu3.userMusicDetail.findByUser(user),
|
||||
"userPlaylogList" to chu3.userPlaylog.findByUser(user),
|
||||
"userRegionsList" to chu3.userRegions.findByUser(user),
|
||||
"userCMissionList" to chu3.userCMission.findByUser(user),
|
||||
"userCMissionProgressList" to chu3.userCMissionProgress.findByUser(user),
|
||||
"netBattleLogList" to chu3.netBattleLog.findByUser(user),
|
||||
"userMiscList" to chu3.userMisc.findByUser(user),
|
||||
"userChallengeList" to chu3.userChallenge.findByUser(user),
|
||||
"userLinkedVerseList" to chu3.userLinkedVerse.findByUser(user),
|
||||
"userMateList" to chu3.userMate.findByUser(user),
|
||||
"userVoteList" to chu3.userVote.findByUser(user),
|
||||
"userLoginBonusList" to chu3.userLoginBonus.findByUser(card.extId.toInt()),
|
||||
)
|
||||
}
|
||||
|
||||
private fun exportOngeki(card: Card): Map<String, Any>? {
|
||||
val user = ongeki.data.findByCard(card) ?: return null
|
||||
return linkedMapOf(
|
||||
"gameId" to "SDDT",
|
||||
"userData" to user,
|
||||
"userActivityList" to ongeki.activity.findByUser(user),
|
||||
"userBossList" to ongeki.boss.findByUser(user),
|
||||
"userCardList" to ongeki.card.findByUser(user),
|
||||
"userChapterList" to ongeki.chapter.findByUser(user),
|
||||
"userCharacterList" to ongeki.character.findByUser(user),
|
||||
"userDeckList" to ongeki.deck.findByUser(user),
|
||||
"userEventMusicList" to ongeki.eventMusic.findByUser(user),
|
||||
"userEventPointList" to ongeki.eventPoint.findByUser(user),
|
||||
"userGeneralDataList" to ongeki.generalData.findByUser(user),
|
||||
"userItemList" to ongeki.item.findByUser(user),
|
||||
"userKopList" to ongeki.kop.findByUser(user),
|
||||
"userLoginBonusList" to ongeki.loginBonus.findByUser(user),
|
||||
"userMemoryChapterList" to ongeki.memoryChapter.findByUser(user),
|
||||
"userMissionPointList" to ongeki.missionPoint.findByUser(user),
|
||||
"userMusicDetailList" to ongeki.musicDetail.findByUser(user),
|
||||
"userMusicItemList" to ongeki.musicItem.findByUser(user),
|
||||
"userOptionList" to ongeki.option.findByUser(user),
|
||||
"userPlaylogList" to ongeki.playlog.findByUser(user),
|
||||
"userRivalList" to ongeki.rival.findByUser(user),
|
||||
"userScenarioList" to ongeki.scenario.findByUser(user),
|
||||
"userStoryList" to ongeki.story.findByUser(user),
|
||||
"userTechCountList" to ongeki.techCount.findByUser(user),
|
||||
"userTechEventList" to ongeki.techEvent.findByUser(user),
|
||||
"userTradeItemList" to ongeki.tradeItem.findByUser(user),
|
||||
"userTrainingRoomList" to ongeki.trainingRoom.findByUser(user),
|
||||
"userEventMapList" to ongeki.eventMap.findByUser(user),
|
||||
"userSkinList" to ongeki.skin.findByUser(user),
|
||||
"userRegionsList" to ongeki.regions.findByUser(user),
|
||||
"userGachaList" to ongeki.gacha.findByUser(user),
|
||||
)
|
||||
}
|
||||
|
||||
private fun exportWacca(card: Card): Map<String, Any>? {
|
||||
val user = wacca.user.findByCard(card) ?: return null
|
||||
return linkedMapOf(
|
||||
"gameId" to "SDFE",
|
||||
"userData" to user,
|
||||
"userOptionList" to wacca.option.findByUser(user),
|
||||
"userBingoList" to wacca.bingo.findByUser(user),
|
||||
"userFriendList" to wacca.friend.findByUser(user).map {
|
||||
mapOf("withUserId" to it.with.id, "isAccepted" to it.isAccepted)
|
||||
},
|
||||
"userGateList" to wacca.gate.findByUser(user),
|
||||
"userItemList" to wacca.item.findByUser(user),
|
||||
"userBestScoreList" to wacca.bestScore.findByUser(user),
|
||||
"userPlaylogList" to wacca.playLog.findByUser(user),
|
||||
"userStageUpList" to wacca.stageUp.findByUser(user),
|
||||
)
|
||||
}
|
||||
|
||||
private fun exportDiva(card: Card): Map<String, Any>? {
|
||||
val profile = diva.profile.findByPdId(card.extId).orElse(null) ?: return null
|
||||
return linkedMapOf(
|
||||
"gameId" to "SBZV",
|
||||
"userData" to profile,
|
||||
"gameSession" to diva.gameSession.findByPdId(profile).orElse(null),
|
||||
"playLogList" to diva.playLog.findByPdId(profile),
|
||||
"playerContestList" to diva.contest.findByPdId(profile),
|
||||
"playerCustomizeList" to diva.customize.findByPdId(profile),
|
||||
"playerInventoryList" to diva.inventory.findByPdId(profile),
|
||||
"playerModuleList" to diva.module.findByPdId(profile),
|
||||
"playerPvCustomizeList" to diva.pvCustomize.findByPdId(profile),
|
||||
"playerPvRecordList" to diva.pvRecord.findByPdId(profile),
|
||||
"playerScreenShotList" to diva.screenShot.findByPdId(profile),
|
||||
)
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun deleteDatabaseData(auId: Long): AccountFileCleanup {
|
||||
val user = userRepo.findByAuId(auId) ?: (404 - "User not found")
|
||||
val ghostCard = user.ghostCard
|
||||
val divaScreenshots = deleteDiva(ghostCard)
|
||||
|
||||
mai2.userData.findByCard(ghostCard)?.let { mai2.userData.delete(it) }
|
||||
chu3.userData.findByCard(ghostCard)?.let { chu3.userData.delete(it) }
|
||||
ongeki.data.findByCard(ghostCard)?.let { ongeki.data.delete(it) }
|
||||
wacca.user.findByCard(ghostCard)?.let { wacca.user.delete(it) }
|
||||
chu3.userLoginBonus.deleteAll(chu3.userLoginBonus.findByUser(ghostCard.extId.toInt()))
|
||||
|
||||
sessionRepo.deleteAll(sessionRepo.findByAquaNetUserAuId(auId))
|
||||
confirmationRepo.deleteAll(confirmationRepo.findByAquaNetUserAuId(auId))
|
||||
resetPasswordRepo.deleteAll(resetPasswordRepo.findByAquaNetUserAuId(auId))
|
||||
userKeychipRepo.deleteAll(userKeychipRepo.findAllByUserAuId(auId))
|
||||
keychipSessionRepo.deleteAll(keychipSessionRepo.findAllByUserAuId(auId))
|
||||
|
||||
val linkedCards = (cardRepo.findAllByAquaUserAuId(auId) + ghostCard).distinctBy { it.id }
|
||||
linkedCards.forEach { it.aquaUser = null }
|
||||
cardRepo.saveAll(linkedCards)
|
||||
cardRepo.flush()
|
||||
|
||||
user.cards.clear()
|
||||
user.keychips.clear()
|
||||
user.keychipSessions.clear()
|
||||
val gameOptions = user.gameOptions
|
||||
user.gameOptions = null
|
||||
|
||||
userRepo.delete(user)
|
||||
userRepo.flush()
|
||||
cardRepo.delete(ghostCard)
|
||||
cardRepo.flush()
|
||||
gameOptions?.let { gameOptionsRepo.delete(it) }
|
||||
|
||||
log.info("Deleted account and game data for user $auId")
|
||||
return AccountFileCleanup(user.profilePicture, ghostCard.extId, divaScreenshots)
|
||||
}
|
||||
|
||||
private fun deleteDiva(card: Card): List<String> {
|
||||
val profile: PlayerProfile = diva.profile.findByPdId(card.extId).orElse(null) ?: return emptyList()
|
||||
val screenshots = diva.screenShot.findByPdId(profile)
|
||||
|
||||
diva.gameSession.findByPdId(profile).ifPresent { diva.gameSession.delete(it) }
|
||||
diva.playLog.deleteAll(diva.playLog.findByPdId(profile))
|
||||
diva.contest.deleteAll(diva.contest.findByPdId(profile))
|
||||
diva.customize.deleteAll(diva.customize.findByPdId(profile))
|
||||
diva.inventory.deleteAll(diva.inventory.findByPdId(profile))
|
||||
diva.module.deleteAll(diva.module.findByPdId(profile))
|
||||
diva.pvCustomize.deleteAll(diva.pvCustomize.findByPdId(profile))
|
||||
diva.pvRecord.deleteAll(diva.pvRecord.findByPdId(profile))
|
||||
diva.screenShot.deleteAll(screenshots)
|
||||
diva.profile.delete(profile)
|
||||
|
||||
return screenshots.map { it.fileName }
|
||||
}
|
||||
|
||||
fun deleteUploadedFiles(cleanup: AccountFileCleanup) {
|
||||
try {
|
||||
cleanup.profilePicture
|
||||
?.takeIf { it.isNotBlank() && Path(it).name == it }
|
||||
?.let { (Path(paths.aquaNetPortrait) / it).deleteIfExists() }
|
||||
|
||||
deleteFilesStartingWith(Path(paths.mai2Portrait), "${cleanup.gameUserId}-")
|
||||
deleteFilesStartingWith(Path(paths.mai2Plays), "${cleanup.gameUserId}-")
|
||||
deleteFilesStartingWith(Path("data/tmp"), "${cleanup.gameUserId}-")
|
||||
cleanup.divaScreenshots
|
||||
.filter { Path(it).name == it }
|
||||
.forEach { (Path("data") / it).deleteIfExists() }
|
||||
} catch (e: Exception) {
|
||||
// Database deletion has already committed. Leave a useful trail for administrators
|
||||
// rather than turning a successful account deletion into a client-visible failure.
|
||||
log.error("Failed to delete one or more uploaded files for deleted user ${cleanup.gameUserId}", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteFilesStartingWith(directory: Path, prefix: String) {
|
||||
if (!directory.exists()) return
|
||||
Files.list(directory).use { files ->
|
||||
files.filter { it.fileName.toString().startsWith(prefix) }
|
||||
.forEach { it.deleteIfExists() }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,12 +69,12 @@ class AquaNetUser(
|
||||
// Email confirmation
|
||||
var emailConfirmed: Boolean = false,
|
||||
|
||||
@OneToOne(cascade = [CascadeType.ALL])
|
||||
@OneToOne(cascade = [CascadeType.PERSIST, CascadeType.MERGE])
|
||||
@JoinColumn(name = "ghostCard", unique = true, nullable = false)
|
||||
var ghostCard: Card = Card(),
|
||||
|
||||
// One user can have multiple cards
|
||||
@OneToMany(mappedBy = "aquaUser", cascade = [CascadeType.ALL])
|
||||
@OneToMany(mappedBy = "aquaUser", cascade = [CascadeType.PERSIST, CascadeType.MERGE])
|
||||
var cards: MutableList<Card> = mutableListOf(),
|
||||
|
||||
// Each user can have multiple keychips (if the user owns cabinets)
|
||||
|
||||
@@ -45,12 +45,13 @@ fun genUrlSafeToken(length: Int): String {
|
||||
}
|
||||
|
||||
@Repository("KeychipSessionRepo")
|
||||
interface KeychipSessionRepo : JpaRepository<KeychipSession, String> {
|
||||
fun findByToken(token: String): KeychipSession?
|
||||
|
||||
@Transactional
|
||||
fun deleteAllByLastUseBefore(expire: Long)
|
||||
}
|
||||
interface KeychipSessionRepo : JpaRepository<KeychipSession, String> {
|
||||
fun findByToken(token: String): KeychipSession?
|
||||
fun findAllByUserAuId(auId: Long): List<KeychipSession>
|
||||
|
||||
@Transactional
|
||||
fun deleteAllByLastUseBefore(expire: Long)
|
||||
}
|
||||
|
||||
@Service
|
||||
class KeychipSessionService(
|
||||
|
||||
@@ -27,8 +27,10 @@ interface Chu3UserLinked<T> : IUserRepo<Chu3UserData, T> {
|
||||
|
||||
// This repo cannot be generalized as UserLinked because the entity stores user as an int
|
||||
// TODO: Find a way to generalize this
|
||||
interface Chu3UserLoginBonusRepo : JpaRepository<UserLoginBonus, Long> {
|
||||
@Query(
|
||||
interface Chu3UserLoginBonusRepo : JpaRepository<UserLoginBonus, Long> {
|
||||
fun findByUser(userId: Int): List<UserLoginBonus>
|
||||
|
||||
@Query(
|
||||
value = "select * from chusan_user_login_bonus where user = ?1 and version = ?2 and is_finished = ?3 order by last_update_date desc",
|
||||
nativeQuery = true
|
||||
)
|
||||
|
||||
@@ -67,6 +67,8 @@ interface PvEntryRepository : JpaRepository<PvEntry, Int> {
|
||||
}
|
||||
|
||||
interface PlayerContestRepository : JpaRepository<PlayerContest, Long> {
|
||||
fun findByPdId(pdId: PlayerProfile): MutableList<PlayerContest>
|
||||
|
||||
fun findByPdIdAndContestId(pdId: PlayerProfile, contestId: Int): Optional<PlayerContest>
|
||||
|
||||
fun findTop4ByPdIdOrderByLastUpdateTimeDesc(pdId: PlayerProfile): MutableList<PlayerContest>
|
||||
@@ -86,6 +88,8 @@ interface PlayerCustomizeRepository : JpaRepository<PlayerCustomize, Long> {
|
||||
|
||||
@Repository
|
||||
interface PlayerInventoryRepository : JpaRepository<PlayerInventory, Long> {
|
||||
fun findByPdId(profile: PlayerProfile): MutableList<PlayerInventory>
|
||||
|
||||
fun findByPdIdAndTypeAndValue(profile: PlayerProfile, type: String, value: String): Optional<PlayerInventory>
|
||||
}
|
||||
|
||||
@@ -96,6 +100,8 @@ interface PlayerProfileRepository : JpaRepository<PlayerProfile, Long> {
|
||||
|
||||
@Repository
|
||||
interface PlayerPvCustomizeRepository : JpaRepository<PlayerPvCustomize, Long> {
|
||||
fun findByPdId(profile: PlayerProfile): MutableList<PlayerPvCustomize>
|
||||
|
||||
fun findByPdIdAndPvId(profile: PlayerProfile, pvId: Int): Optional<PlayerPvCustomize>
|
||||
}
|
||||
|
||||
@@ -139,12 +145,16 @@ interface PlayerPvRecordRepository : JpaRepository<PlayerPvRecord, Long> {
|
||||
): MutableList<PlayerPvRecord>
|
||||
}
|
||||
|
||||
interface PlayerScreenShotRepository : JpaRepository<PlayerScreenShot, Long>
|
||||
interface PlayerScreenShotRepository : JpaRepository<PlayerScreenShot, Long> {
|
||||
fun findByPdId(profile: PlayerProfile): MutableList<PlayerScreenShot>
|
||||
}
|
||||
|
||||
@Repository
|
||||
interface PlayLogRepository : JpaRepository<PlayLog, Long>
|
||||
interface PlayLogRepository : JpaRepository<PlayLog, Long> {
|
||||
fun findByPdId(profile: PlayerProfile): MutableList<PlayLog>
|
||||
}
|
||||
|
||||
@Repository
|
||||
interface PlayerModuleRepository : JpaRepository<PlayerModule, Long> {
|
||||
fun findByPdId(profile: PlayerProfile): MutableList<PlayerModule>
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,4 +11,5 @@ import org.springframework.stereotype.Repository
|
||||
interface CardRepository : JpaRepository<Card, Long> {
|
||||
fun findByExtId(extId: Long): Card?
|
||||
fun findByLuid(luid: String): Card?
|
||||
fun findAllByAquaUserAuId(auId: Long): List<Card>
|
||||
}
|
||||
|
||||
@@ -107,7 +107,9 @@ interface Mai2UserPlaylogRepo : GenericPlaylogRepo<Mai2UserPlaylog>, Mai2UserLin
|
||||
fun findByUserAndUserPlayDateAfter(user: Mai2UserDetail, userPlayDate: String): List<Mai2UserPlaylog>
|
||||
}
|
||||
|
||||
interface Mai2UserPrintDetailRepo : JpaRepository<Mai2UserPrintDetail, Long>
|
||||
interface Mai2UserPrintDetailRepo : JpaRepository<Mai2UserPrintDetail, Long> {
|
||||
fun findByUser(user: Mai2UserDetail): List<Mai2UserPrintDetail>
|
||||
}
|
||||
|
||||
interface Mai2UserUdemaeRepo : Mai2UserLinked<Mai2UserUdemae>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user