forked from Cookies_Github_mirror/AquaDX
merge upstream
Some checks failed
Build and Publish Docker Image / build-and-push (push) Has been cancelled
Some checks failed
Build and Publish Docker Image / build-and-push (push) Has been cancelled
This commit is contained in:
@@ -79,6 +79,8 @@
|
||||
|
||||
<Router {url}>
|
||||
<Route path="/" component={Welcome} />
|
||||
<Route path="/verify" component={Welcome} /> <!-- For email verification only, backwards compatibility with AquaNet2 in the future -->
|
||||
<Route path="/reset-password" component={Welcome} />
|
||||
<Route path="/home" component={Home} />
|
||||
<Route path="/ranking" component={Ranking} />
|
||||
<Route path="/ranking/:game" component={Ranking} />
|
||||
|
||||
32
AquaNet/src/components/MunetRegisterBanner.svelte
Normal file
32
AquaNet/src/components/MunetRegisterBanner.svelte
Normal file
@@ -0,0 +1,32 @@
|
||||
<script lang="ts">
|
||||
export let username: string;
|
||||
export let email: string;
|
||||
|
||||
let shouldShow = navigator.language.startsWith('zh');
|
||||
|
||||
// 会导致瞬间出现,但是不知道为什么 svelte 的 transition 动画不工作
|
||||
// if (!shouldShow) {
|
||||
// fetch('https://47.122.72.135/ip/isChina')
|
||||
// .then(it => it.json())
|
||||
// .then(it => shouldShow = it)
|
||||
// .catch(() => shouldShow = false);
|
||||
// }
|
||||
|
||||
const jump = () => {
|
||||
const params = new URLSearchParams();
|
||||
if (username) params.set('username', username);
|
||||
if (email) params.set('email', email);
|
||||
location.href = `https://portal.mumur.net/register?${params.toString()}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if shouldShow}
|
||||
<div class="cursor-pointer" on:click={jump}>
|
||||
<h2>MuNET 了解一下!</h2>
|
||||
<div>
|
||||
<p>MuNET 是 AquaDX 的继任者,提供更适合中国用户的服务器和更好的游戏体验。</p>
|
||||
<p>如果你还没有游戏数据,建议在 MuNET 上创建账号并开始游戏。点击立即前往</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
if (ubKey == 'namePlateId') ubKey = 'nameplateId'
|
||||
if (ubKey == 'systemVoiceId') ubKey = 'voiceId'
|
||||
return [{ iKey, ubKey: ubKey as keyof UserBox,
|
||||
items: profile.items.filter(x => x.itemKind === iKind)
|
||||
items: profile.items.filter(x => x.itemKind === iKind || (iKey == "trophy" && x.itemKind == 3))
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -106,6 +106,133 @@
|
||||
.finally(() => submitting = "")
|
||||
}
|
||||
|
||||
async function exportBatchManual() {
|
||||
submitting = "batchExport"
|
||||
|
||||
const DIFFICULTY_MAP: Record<number, string> = {
|
||||
0: "BASIC",
|
||||
1: "ADVANCED",
|
||||
2: "EXPERT",
|
||||
3: "MASTER",
|
||||
4: "ULTIMA"
|
||||
} as const // WORLD'S END scores not supported by Tachi
|
||||
const DAN_MAP: Record<number, string> = {
|
||||
1: "DAN_I",
|
||||
2: "DAN_II",
|
||||
3: "DAN_III",
|
||||
4: "DAN_IV",
|
||||
5: "DAN_V",
|
||||
6: "DAN_INFINITE"
|
||||
} as const
|
||||
const SKILL_IDS: Record<number, string> = {
|
||||
100009: 'CATASTROPHY',
|
||||
102009: 'CATASTROPHY',
|
||||
103007: 'CATASTROPHY',
|
||||
|
||||
100008: 'ABSOLUTE',
|
||||
101008: 'ABSOLUTE',
|
||||
102008: 'ABSOLUTE',
|
||||
103006: 'ABSOLUTE',
|
||||
|
||||
100007: 'BRAVE',
|
||||
101007: 'BRAVE',
|
||||
102007: 'BRAVE',
|
||||
103005: 'BRAVE',
|
||||
|
||||
100005: 'HARD',
|
||||
100006: 'HARD',
|
||||
101004: 'HARD',
|
||||
101005: 'HARD',
|
||||
101006: 'HARD',
|
||||
102004: 'HARD',
|
||||
102005: 'HARD',
|
||||
102006: 'HARD',
|
||||
103002: 'HARD',
|
||||
103003: 'HARD',
|
||||
103004: 'HARD'
|
||||
} as const
|
||||
// Shamelessly stolen from https://github.com/beer-psi/saekawa/commit/b3bee13e126df2f4e2a449bdf971debb8c95ba40, needs to be updated every major version :(
|
||||
|
||||
let data: any
|
||||
let output: any = {
|
||||
"meta": {
|
||||
"game": "chunithm",
|
||||
"playtype": "Single",
|
||||
"service": "AquaDX-Manual"
|
||||
},
|
||||
"scores": [],
|
||||
"classes": {}
|
||||
}
|
||||
|
||||
try {
|
||||
data = await GAME.export('chu3')
|
||||
}
|
||||
catch (e) {
|
||||
error = e.message
|
||||
submitting = ""
|
||||
return
|
||||
}
|
||||
|
||||
if (data && "userPlaylogList" in data) {
|
||||
for (let score of data.userPlaylogList) {
|
||||
let clearLamp = null
|
||||
let noteLamp = null
|
||||
|
||||
if (score.level in DIFFICULTY_MAP) {
|
||||
if (score.isClear) {
|
||||
clearLamp = score.skillId in SKILL_IDS ? SKILL_IDS[score.skillId] : "CLEAR"
|
||||
}
|
||||
else {
|
||||
clearLamp = "FAILED"
|
||||
}
|
||||
|
||||
if (score.score === 1010000) {
|
||||
noteLamp = "ALL JUSTICE CRITICAL"
|
||||
}
|
||||
else if (score.isAllJustice) {
|
||||
noteLamp = "ALL JUSTICE"
|
||||
}
|
||||
else if (score.isFullCombo) {
|
||||
noteLamp = "FULL COMBO"
|
||||
}
|
||||
else {
|
||||
noteLamp = "NONE"
|
||||
}
|
||||
|
||||
output.scores.push({
|
||||
"score": score.score,
|
||||
"clearLamp": clearLamp,
|
||||
"noteLamp": noteLamp,
|
||||
"judgements": {
|
||||
"jcrit": score.judgeHeaven + score.judgeCritical,
|
||||
"justice": score.judgeJustice,
|
||||
"attack": score.judgeAttack,
|
||||
"miss": score.judgeGuilty
|
||||
},
|
||||
"matchType": "inGameID",
|
||||
"identifier": score.musicId.toString(),
|
||||
"difficulty": DIFFICULTY_MAP[score.level],
|
||||
"timeAchieved": score.sortNumber * 1000,
|
||||
"optional": {
|
||||
"maxCombo": score.maxCombo
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (data.userData.classEmblemMedal in DAN_MAP) {
|
||||
output.classes["dan"] = DAN_MAP[data.userData.classEmblemMedal]
|
||||
}
|
||||
|
||||
if (data.userData.classEmblemBase in DAN_MAP) {
|
||||
output.classes["emblem"] = DAN_MAP[data.userData.classEmblemBase]
|
||||
}
|
||||
|
||||
download(JSON.stringify(output), `AquaDX_chu3_BatchManualExport_${userbox.userName}.json`)
|
||||
submitting = ""
|
||||
}
|
||||
|
||||
function download(data: string, filename: string) {
|
||||
const blob = new Blob([data]);
|
||||
const url = URL.createObjectURL(blob);
|
||||
@@ -301,6 +428,10 @@
|
||||
<Icon icon="bxs:file-export"/>
|
||||
{t('settings.export')}
|
||||
</button>
|
||||
<button class="exportBatchManualButton" on:click={exportBatchManual}>
|
||||
<Icon icon="bxs:file-export"/>
|
||||
{t('settings.batchManualExport')}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
import GameSettingFields from "./GameSettingFields.svelte";
|
||||
import { t, ts } from "../../libs/i18n";
|
||||
import useLocalStorage from "../../libs/hooks/useLocalStorage.svelte";
|
||||
import RegionSelector from "./RegionSelector.svelte";
|
||||
|
||||
const rounding = useLocalStorage("rounding", true);
|
||||
</script>
|
||||
@@ -22,6 +23,11 @@
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<blockquote>
|
||||
{ts("settings.regionNotice")}
|
||||
</blockquote>
|
||||
<RegionSelector/>
|
||||
</div>
|
||||
|
||||
<style lang="sass">
|
||||
@@ -44,19 +50,10 @@
|
||||
.desc
|
||||
opacity: 0.6
|
||||
|
||||
.field
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
label
|
||||
max-width: max-content
|
||||
|
||||
> div:not(.bool)
|
||||
display: flex
|
||||
align-items: center
|
||||
gap: 1rem
|
||||
margin-top: 0.5rem
|
||||
|
||||
> input
|
||||
flex: 1
|
||||
.divider
|
||||
width: 100%
|
||||
height: 0.5px
|
||||
background: white
|
||||
opacity: 0.2
|
||||
margin: 0.4rem 0
|
||||
</style>
|
||||
|
||||
59
AquaNet/src/components/settings/RegionSelector.svelte
Normal file
59
AquaNet/src/components/settings/RegionSelector.svelte
Normal file
@@ -0,0 +1,59 @@
|
||||
<script lang="ts">
|
||||
import { USER} from "../../libs/sdk";
|
||||
import { ts } from "../../libs/i18n";
|
||||
import StatusOverlays from "../StatusOverlays.svelte";
|
||||
let regionId = $state(0);
|
||||
let submitting = ""
|
||||
let error: string;
|
||||
|
||||
const prefectures = ["None","Aichi","Aomori","Akita","Ishikawa","Ibaraki","Iwate","Ehime","Oita","Osaka","Okayama","Okinawa","Kagawa","Kagoshima","Kanagawa","Gifu","Kyoto","Kumamoto","Gunma","Kochi","Saitama","Saga","Shiga","Shizuoka","Shimane","Chiba","Tokyo","Tokushima","Tochigi","Tottori","Toyama","Nagasaki","Nagano","Nara","Niigata","Hyogo","Hiroshima","Fukui","Fukuoka","Fukushima","Hokkaido","Mie","Miyagi","Miyazaki","Yamagata","Yamaguchi","Yamanashi","Wakayama"]
|
||||
|
||||
USER.me().then(user => {
|
||||
const parsedRegion = parseInt(user.region);
|
||||
if (!isNaN(parsedRegion) && parsedRegion > 0) {
|
||||
regionId = parsedRegion - 1;
|
||||
} else {
|
||||
regionId = 0;
|
||||
}
|
||||
})
|
||||
|
||||
async function saveNewRegion() {
|
||||
if (submitting) return false
|
||||
submitting = "region"
|
||||
|
||||
await USER.changeRegion(regionId+1).catch(e => error = e.message).finally(() => submitting = "")
|
||||
return true
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="fields">
|
||||
<label for="rounding">
|
||||
<span class="name">{ts(`settings.regionSelector.title`)}</span>
|
||||
<span class="desc">{ts(`settings.regionSelector.desc`)}</span>
|
||||
</label>
|
||||
<select bind:value={regionId} on:change={saveNewRegion}>
|
||||
<option value={0} disabled selected>{ts("settings.regionSelector.select")}</option>
|
||||
{#each prefectures.slice(1) as prefecture, index}
|
||||
<option value={index}>{prefecture}</option>
|
||||
{/each}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<StatusOverlays {error} loading={!!submitting}/>
|
||||
|
||||
<style lang="sass">
|
||||
@use "../../vars"
|
||||
|
||||
.fields
|
||||
display: flex
|
||||
flex-direction: column
|
||||
gap: 12px
|
||||
|
||||
label
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.desc
|
||||
opacity: 0.6
|
||||
|
||||
</style>
|
||||
@@ -19,6 +19,7 @@ export interface AquaNetUser {
|
||||
email: string
|
||||
displayName: string
|
||||
country: string
|
||||
region:string
|
||||
lastLogin: number
|
||||
regTime: number
|
||||
profileLocation: string
|
||||
|
||||
@@ -34,21 +34,31 @@ export const EN_REF_Welcome = {
|
||||
'back': 'Back',
|
||||
'email': 'Email',
|
||||
'password': 'Password',
|
||||
'new-password': 'New password',
|
||||
'username': 'Username',
|
||||
'welcome.btn-login': 'Log in',
|
||||
'welcome.btn-signup': 'Sign up',
|
||||
'welcome.email-password-missing': 'Email and password are required',
|
||||
'welcome.btn-reset-password': 'Forgot password?',
|
||||
'welcome.btn-submit-reset-password': 'Send reset link',
|
||||
'welcome.btn-submit-new-password': 'Change password',
|
||||
'welcome.email-missing': 'Email is required',
|
||||
'welcome.password-missing': 'Password is required',
|
||||
'welcome.username-missing': 'Username/email is required',
|
||||
'welcome.email-password-missing': 'Email and password are required',
|
||||
'welcome.waiting-turnstile': 'Waiting for Turnstile to verify your network environment...',
|
||||
'welcome.turnstile-error': 'Error verifying your network environment. Please turn off your VPN and try again.',
|
||||
'welcome.turnstile-timeout': 'Network verification timed out. Please try again.',
|
||||
'welcome.verification-sent': 'A verification email has been sent to ${email}. Please check your inbox!',
|
||||
'welcome.verify-state-0': 'You haven\'t verified your email. A verification email had been sent to your inbox less than a minute ago. Please check your inbox!',
|
||||
'welcome.verify-state-1': 'You haven\'t verified your email. We\'ve already sent 3 emails over the last 24 hours so we\'ll not send another one. Please check your inbox!',
|
||||
'welcome.reset-password-sent': 'A password reset email has been sent to ${email}. Please check your inbox!',
|
||||
'welcome.verify-state-0': 'You haven\'t verified your email. A verification email has been sent to your inbox just now. Please check your inbox!',
|
||||
'welcome.verify-state-1': 'You haven\'t verified your email. You have requested too many emails, please try again later.',
|
||||
'welcome.verify-state-2': 'You haven\'t verified your email. We just sent you another verification email. Please check your inbox!',
|
||||
'welcome.reset-state-0': 'A reset email has been sent to your inbox just now. Please check your inbox!',
|
||||
'welcome.reset-state-1': 'Too many emails have been sent. Another will not be sent.',
|
||||
'welcome.verifying': 'Verifying your email... please wait.',
|
||||
'welcome.verified': 'Your email has been verified! You can now log in now.',
|
||||
'welcome.verification-failed': 'Verification failed: ${message}. Please try again.',
|
||||
'welcome.password-reset-done': 'Your password has been updated! Please log back in.',
|
||||
}
|
||||
|
||||
export const EN_REF_LEADERBOARD = {
|
||||
@@ -183,8 +193,13 @@ export const EN_REF_SETTINGS = {
|
||||
'settings.profile.logout': 'Log out',
|
||||
'settings.profile.unchanged': 'Unchanged',
|
||||
'settings.export': 'Export Player Data',
|
||||
'settings.batchManualExport': "Export in Batch Manual (for Tachi)",
|
||||
'settings.cabNotice': "Note: These settings will only affect your own cab/setup. If you're playing on someone else's setup, please contact them to change these settings.",
|
||||
'settings.gameNotice': "These only apply to Mai and Wacca."
|
||||
'settings.gameNotice': "These only apply to Mai and Wacca.",
|
||||
'settings.regionNotice': "These only apply to Mai, Ongeki and Chuni.",
|
||||
'settings.regionSelector.title': "Prefecture Selector",
|
||||
'settings.regionSelector.desc': "Select the region where you want the game to think you are playing",
|
||||
'settings.regionSelector.select': "Select Prefecture",
|
||||
}
|
||||
|
||||
export const EN_REF_USERBOX = {
|
||||
|
||||
@@ -46,21 +46,31 @@ const zhWelcome: typeof EN_REF_Welcome = {
|
||||
'back': '返回',
|
||||
'email': '邮箱',
|
||||
'password': '密码',
|
||||
'new-password': '新密码',
|
||||
'username': '用户名',
|
||||
'welcome.btn-login': '登录',
|
||||
'welcome.btn-signup': '注册',
|
||||
'welcome.email-password-missing': '邮箱和密码必须填哦',
|
||||
'welcome.btn-reset-password': '忘记密码?',
|
||||
'welcome.btn-submit-reset-password': '发送重置链接',
|
||||
'welcome.btn-submit-new-password': '修改密码',
|
||||
'welcome.email-missing': '邮箱必须填哦',
|
||||
'welcome.password-missing': '密码必须填哦',
|
||||
'welcome.username-missing': '用户名/邮箱必须填哦',
|
||||
'welcome.email-password-missing': '邮箱和密码必须填哦',
|
||||
'welcome.waiting-turnstile': '正在验证网络环境…',
|
||||
'welcome.turnstile-error': '验证网络环境出错了,请关闭 VPN 后重试',
|
||||
'welcome.turnstile-timeout': '验证网络环境超时了,请重试',
|
||||
'welcome.verification-sent': '验证邮件已发送至 ${email},请翻翻收件箱',
|
||||
'welcome.reset-password-sent': '重置邮件已发送至 ${email},请翻翻收件箱',
|
||||
'welcome.verify-state-0': '您还没有验证邮箱哦!验证邮件一分钟内刚刚发到您的邮箱,请翻翻收件箱',
|
||||
'welcome.verify-state-1': '您还没有验证邮箱哦!我们在过去的 24 小时内已经发送了 3 封验证邮件,所以我们不会再发送了,请翻翻收件箱',
|
||||
'welcome.verify-state-2': '您还没有验证邮箱哦!我们刚刚又发送了一封验证邮件,请翻翻收件箱',
|
||||
'welcome.reset-state-0': '重置邮件刚刚发送到你的邮箱啦,请翻翻收件箱!',
|
||||
'welcome.reset-state-1': '邮件发送次数过多,暂时不会再发送新的重置邮件了',
|
||||
'welcome.verifying': '正在验证邮箱…请稍等',
|
||||
'welcome.verified': '您的邮箱已经验证成功!您现在可以登录了',
|
||||
'welcome.verification-failed': '验证失败:${message}。请重试',
|
||||
'welcome.password-reset-done': '您的密码已更新!请重新登录',
|
||||
}
|
||||
|
||||
const zhLeaderboard: typeof EN_REF_LEADERBOARD = {
|
||||
@@ -195,8 +205,17 @@ const zhSettings: typeof EN_REF_SETTINGS = {
|
||||
'settings.profile.logout': '登出',
|
||||
'settings.profile.unchanged': '未更改',
|
||||
'settings.export': '导出玩家数据',
|
||||
'settings.batchManualExport': "导出 Batch Manual 格式(用于 Tachi)",
|
||||
'settings.cabNotice': '注意:下面这些设置只会影响你自己的机器,如果你是在其他人的机器上玩的话,请联系机主来改设置',
|
||||
'settings.gameNotice': "这些设置仅对舞萌和华卡生效。",
|
||||
// AI
|
||||
'settings.regionNotice': "这些设置仅适用于舞萌、音击和中二。",
|
||||
// AI
|
||||
'settings.regionSelector.title': "地区选择器",
|
||||
// AI
|
||||
'settings.regionSelector.desc': "选择游戏中显示的地区",
|
||||
// AI
|
||||
'settings.regionSelector.select': "选择地区",
|
||||
}
|
||||
|
||||
export const zhUserbox: typeof EN_REF_USERBOX = {
|
||||
|
||||
@@ -163,12 +163,22 @@ async function login(user: { email: string, password: string, turnstile: string
|
||||
localStorage.setItem('token', data.token)
|
||||
}
|
||||
|
||||
async function resetPassword(user: { email: string, turnstile: string }) {
|
||||
return await post('/api/v2/user/reset-password', user)
|
||||
}
|
||||
|
||||
async function changePassword(user: { token: string, password: string }) {
|
||||
return await post('/api/v2/user/change-password', user)
|
||||
}
|
||||
|
||||
const isLoggedIn = () => !!localStorage.getItem('token')
|
||||
const ensureLoggedIn = () => !isLoggedIn() && (window.location.href = '/')
|
||||
|
||||
export const USER = {
|
||||
register,
|
||||
login,
|
||||
resetPassword,
|
||||
changePassword,
|
||||
confirmEmail: (token: string) =>
|
||||
post('/api/v2/user/confirm-email', { token }),
|
||||
me: (): Promise<AquaNetUser> => {
|
||||
@@ -186,6 +196,8 @@ export const USER = {
|
||||
},
|
||||
isLoggedIn,
|
||||
ensureLoggedIn,
|
||||
changeRegion: (regionId: number) =>
|
||||
post('/api/v2/user/change-region', { regionId }),
|
||||
}
|
||||
|
||||
export const USERBOX = {
|
||||
@@ -254,5 +266,14 @@ export const TRANSFER = {
|
||||
post('/api/v2/transfer/push', {}, { json: { client: d, data } }),
|
||||
}
|
||||
|
||||
export const FEDY = {
|
||||
status: (): Promise<{ linkedAt: number }> =>
|
||||
post('/api/v2/fedy/status'),
|
||||
link: (nonce: string): Promise<{ linkedAt: number }> =>
|
||||
post('/api/v2/fedy/link', { nonce }),
|
||||
unlink: () =>
|
||||
post('/api/v2/fedy/unlink'),
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
window.sdk = { USER, USERBOX, CARD, GAME, DATA, SETTING, TRANSFER }
|
||||
window.sdk = { USER, USERBOX, CARD, GAME, DATA, SETTING, TRANSFER, FEDY }
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
import { t } from "../libs/i18n";
|
||||
import ImportDataAction from "./Home/ImportDataAction.svelte";
|
||||
import Communities from "./Home/Communities.svelte";
|
||||
import MigrateAction from "./Home/MigrateAction.svelte";
|
||||
|
||||
USER.ensureLoggedIn();
|
||||
|
||||
@@ -58,6 +59,9 @@
|
||||
</ActionCard>
|
||||
|
||||
<ImportDataAction/>
|
||||
{#if me}
|
||||
<MigrateAction username={me.username}/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if tab === 1}
|
||||
<div out:fade={FADE_OUT} in:fade={FADE_IN}>
|
||||
|
||||
69
AquaNet/src/pages/Home/MigrateAction.svelte
Normal file
69
AquaNet/src/pages/Home/MigrateAction.svelte
Normal file
@@ -0,0 +1,69 @@
|
||||
<script lang="ts">
|
||||
import { fade } from "svelte/transition"
|
||||
import { t } from "../../libs/i18n";
|
||||
import ActionCard from "../../components/ActionCard.svelte";
|
||||
import { CARD, GAME, USER } from "../../libs/sdk";
|
||||
|
||||
export let username: string;
|
||||
let shouldShow = navigator.language.startsWith('zh');
|
||||
let showWarning = false;
|
||||
let isCardBindIssue = false;
|
||||
|
||||
if (!shouldShow) {
|
||||
fetch('https://47.122.72.135/ip/isChina')
|
||||
.then(it => it.json())
|
||||
.then(it => shouldShow = it)
|
||||
.catch(() => shouldShow = false);
|
||||
}
|
||||
|
||||
CARD.userGames(username).then(games => {
|
||||
if (!Object.values(games).some(it => it)) {
|
||||
isCardBindIssue = true;
|
||||
}
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
if (isCardBindIssue) {
|
||||
showWarning = true;
|
||||
return
|
||||
}
|
||||
jump()
|
||||
}
|
||||
|
||||
const jump = () => {
|
||||
const token = localStorage.getItem('token')
|
||||
location.href = `https://portal.mumur.net/migrateFromAquaDx/${token}`
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if shouldShow}
|
||||
<ActionCard color="190, 149, 255" icon="system-uicons:jump-up" on:click={handleClick}>
|
||||
<h3>迁移到 MuNET</h3>
|
||||
<span>更适合中国宝宝体质的服务器,AquaDX 的继任者。点击查看详情</span>
|
||||
</ActionCard>
|
||||
{/if}
|
||||
|
||||
{#if showWarning}
|
||||
<div class="overlay" transition:fade>
|
||||
<div>
|
||||
<h2>提示</h2>
|
||||
<p>看起来你在 AquaDX 还没有游戏数据,也许是因为没有绑卡或者绑定的卡不是在游戏中点击“查看卡号”获取的…</p>
|
||||
<p>现在迁移的话,大概会导致你的游戏数据无法被正确的迁移。建议你先去检查一下吧</p>
|
||||
<div class="buttons">
|
||||
<button on:click={() => showWarning = false}>{t('action.cancel')}</button>
|
||||
<button on:click={jump}>继续</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style lang="sass">
|
||||
h3
|
||||
font-size: 1.3rem
|
||||
margin: 0
|
||||
|
||||
.buttons
|
||||
display: grid
|
||||
grid-template-columns: 1fr 1fr
|
||||
gap: 1rem
|
||||
</style>
|
||||
@@ -84,7 +84,7 @@
|
||||
}).catch(err => error = err.message).finally(() => loading = false)
|
||||
}
|
||||
|
||||
$: isBlacklist = !!blacklist.filter(x => src.dns.includes(x))
|
||||
$: isBlacklist = blacklist.filter(x => src.dns.includes(x)).length > 0
|
||||
</script>
|
||||
|
||||
<StatusOverlays {loading} />
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import Icon from "@iconify/svelte";
|
||||
import { USER } from "../libs/sdk";
|
||||
import { t } from "../libs/i18n"
|
||||
import MunetRegisterBanner from "../components/MunetRegisterBanner.svelte";
|
||||
|
||||
let params = new URLSearchParams(window.location.search)
|
||||
|
||||
@@ -20,28 +21,33 @@
|
||||
|
||||
let error = ""
|
||||
let verifyMsg = ""
|
||||
let token = ""
|
||||
|
||||
if (USER.isLoggedIn()) {
|
||||
window.location.href = "/home"
|
||||
}
|
||||
if (params.get('code')) {
|
||||
token = params.get('code')!
|
||||
if (location.pathname === '/verify') {
|
||||
state = 'verify'
|
||||
verifyMsg = t("welcome.verifying")
|
||||
submitting = true
|
||||
|
||||
if (params.get('confirm-email')) {
|
||||
state = 'verify'
|
||||
verifyMsg = t("welcome.verifying")
|
||||
submitting = true
|
||||
|
||||
// Send request to server
|
||||
USER.confirmEmail(params.get('confirm-email')!)
|
||||
.then(() => {
|
||||
verifyMsg = t('welcome.verified')
|
||||
submitting = false
|
||||
// Send request to server
|
||||
USER.confirmEmail(token)
|
||||
.then(() => {
|
||||
verifyMsg = t('welcome.verified')
|
||||
submitting = false
|
||||
|
||||
// Clear the query param
|
||||
window.history.replaceState({}, document.title, window.location.pathname)
|
||||
})
|
||||
.catch(e => verifyMsg = t('welcome.verification-failed', { message: e.message }))
|
||||
}
|
||||
else if (location.pathname === '/reset-password') {
|
||||
state = 'reset'
|
||||
}
|
||||
}
|
||||
|
||||
async function submit(): Promise<any> {
|
||||
submitting = true
|
||||
|
||||
@@ -94,14 +100,80 @@
|
||||
state = 'verify'
|
||||
verifyMsg = t("welcome.verify-state-2")
|
||||
}
|
||||
else if (e.message === 'Login not allowed: Card has been migrated to Minato.') {
|
||||
location.href = `https://portal.mumur.net/login?username=${encodeURIComponent(email)}`
|
||||
}
|
||||
else {
|
||||
error = e.message
|
||||
submitting = false // unnecessary? see line 113, same for both reset functions
|
||||
turnstileReset()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
submitting = false
|
||||
}
|
||||
|
||||
async function resetPassword(): Promise<any> {
|
||||
submitting = true;
|
||||
|
||||
if (email === "") {
|
||||
error = t("welcome.email-missing")
|
||||
return submitting = false
|
||||
}
|
||||
|
||||
if (TURNSTILE_SITE_KEY && turnstile === "") {
|
||||
// Sleep for 100ms to allow Turnstile to finish
|
||||
error = t("welcome.waiting-turnstile")
|
||||
return setTimeout(resetPassword, 100)
|
||||
}
|
||||
|
||||
// Send request to server
|
||||
await USER.resetPassword({ email, turnstile })
|
||||
.then(() => {
|
||||
// Show email sent message, reusing email verify page
|
||||
state = 'verify'
|
||||
verifyMsg = t("welcome.reset-password-sent", { email })
|
||||
})
|
||||
.catch(e => {
|
||||
if (e.message === "Reset request rejected - STATE_0") {
|
||||
state = 'verify'
|
||||
verifyMsg = t("welcome.reset-state-0")
|
||||
}
|
||||
else if (e.message === "Reset request rejected - STATE_1") {
|
||||
state = 'verify'
|
||||
verifyMsg = t("welcome.reset-state-1")
|
||||
}
|
||||
else {
|
||||
error = e.message
|
||||
submitting = false
|
||||
turnstileReset()
|
||||
}
|
||||
})
|
||||
|
||||
submitting = false
|
||||
}
|
||||
|
||||
async function changePassword(): Promise<any> {
|
||||
submitting = true
|
||||
|
||||
if (password === "") {
|
||||
error = t("welcome.password-missing")
|
||||
return submitting = false
|
||||
}
|
||||
|
||||
// Send request to server
|
||||
await USER.changePassword({ token, password })
|
||||
.then(() => {
|
||||
state = 'verify'
|
||||
verifyMsg = t("welcome.password-reset-done")
|
||||
})
|
||||
.catch(e => {
|
||||
error = e.message
|
||||
submitting = false
|
||||
turnstileReset()
|
||||
})
|
||||
|
||||
submitting = false
|
||||
}
|
||||
|
||||
@@ -120,11 +192,13 @@
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
<div on:click={() => state = 'home'} on:keypress={() => state = 'home'}
|
||||
role="button" tabindex="0" class="clickable">
|
||||
<Icon icon="line-md:chevron-small-left" />
|
||||
<span>{t('back')}</span>
|
||||
</div>
|
||||
{#if error != t("welcome.waiting-turnstile")}
|
||||
<div on:click={() => state = 'home'} on:keypress={() => state = 'home'}
|
||||
role="button" tabindex="0" class="clickable">
|
||||
<Icon icon="line-md:chevron-small-left" />
|
||||
<span>{t('back')}</span>
|
||||
</div>
|
||||
{/if}
|
||||
{#if isSignup}
|
||||
<input type="text" placeholder={t('username')} bind:value={username}>
|
||||
{/if}
|
||||
@@ -137,6 +211,9 @@
|
||||
{isSignup ? t('welcome.btn-signup') : t('welcome.btn-login')}
|
||||
{/if}
|
||||
</button>
|
||||
{#if state === "login" && !submitting}
|
||||
<button on:click={() => state = 'submitreset'}>{t('welcome.btn-reset-password')}</button>
|
||||
{/if}
|
||||
{#if TURNSTILE_SITE_KEY}
|
||||
<Turnstile siteKey={TURNSTILE_SITE_KEY} bind:reset={turnstileReset}
|
||||
on:turnstile-callback={e => console.log(turnstile = e.detail.token)}
|
||||
@@ -144,6 +221,37 @@
|
||||
on:turnstile-expired={_ => window.location.reload()}
|
||||
on:turnstile-timeout={_ => console.log(error = t('welcome.turnstile-timeout'))} />
|
||||
{/if}
|
||||
{#if isSignup}
|
||||
<MunetRegisterBanner username={username} email={email}/>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if state === "submitreset"}
|
||||
<div class="login-form" transition:slide>
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
{#if error != t("welcome.waiting-turnstile")}
|
||||
<div on:click={() => state = 'login'} on:keypress={() => state = 'login'}
|
||||
role="button" tabindex="0" class="clickable">
|
||||
<Icon icon="line-md:chevron-small-left" />
|
||||
<span>{t('back')}</span>
|
||||
</div>
|
||||
{/if}
|
||||
<input type="email" placeholder={t('email')} bind:value={email}>
|
||||
<button on:click={resetPassword}>
|
||||
{#if submitting}
|
||||
<Icon icon="line-md:loading-twotone-loop"/>
|
||||
{:else}
|
||||
{t('welcome.btn-submit-reset-password')}
|
||||
{/if}
|
||||
</button>
|
||||
{#if TURNSTILE_SITE_KEY}
|
||||
<Turnstile siteKey={TURNSTILE_SITE_KEY} bind:reset={turnstileReset}
|
||||
on:turnstile-callback={e => console.log(turnstile = e.detail.token)}
|
||||
on:turnstile-error={_ => console.log(error = t("welcome.turnstile-error"))}
|
||||
on:turnstile-expired={_ => window.location.reload()}
|
||||
on:turnstile-timeout={_ => console.log(error = t('welcome.turnstile-timeout'))} />
|
||||
{/if}
|
||||
</div>
|
||||
{:else if state === "verify"}
|
||||
<div class="login-form" transition:slide>
|
||||
@@ -152,6 +260,20 @@
|
||||
<button on:click={() => state = 'home'} transition:slide>{t('back')}</button>
|
||||
{/if}
|
||||
</div>
|
||||
{:else if state === "reset"}
|
||||
{#if error}
|
||||
<span class="error">{error}</span>
|
||||
{/if}
|
||||
<div class="login-form" transition:slide>
|
||||
<input type="password" placeholder={t('new-password')} bind:value={password}>
|
||||
<button on:click={changePassword}>
|
||||
{#if submitting}
|
||||
<Icon icon="line-md:loading-twotone-loop"/>
|
||||
{:else}
|
||||
{t('welcome.btn-submit-new-password')}
|
||||
{/if}
|
||||
</button>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -171,3 +171,21 @@ sourceSets {
|
||||
java.srcDir("${layout.buildDirectory.get()}/generated/source/kapt/main")
|
||||
}
|
||||
}
|
||||
|
||||
val copyDependencies by tasks.registering(Copy::class) {
|
||||
from(configurations.runtimeClasspath)
|
||||
into("${layout.buildDirectory.get()}/libs/lib")
|
||||
}
|
||||
|
||||
val packageThin by tasks.registering(Jar::class) {
|
||||
group = "build"
|
||||
from(sourceSets.main.get().output)
|
||||
manifest {
|
||||
attributes(
|
||||
"Main-Class" to "icu.samnyan.aqua.EntryKt",
|
||||
"Class-Path" to configurations.runtimeClasspath.get().files.joinToString(" ") { "lib/${it.name}" }
|
||||
)
|
||||
}
|
||||
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
|
||||
dependsOn(copyDependencies)
|
||||
}
|
||||
|
||||
@@ -131,6 +131,11 @@ server.error.whitelabel.enabled=false
|
||||
aqua-net.frontier.enabled=false
|
||||
aqua-net.frontier.ftk=0x00
|
||||
|
||||
## Fedy Settings
|
||||
aqua-net.fedy.enabled=false
|
||||
aqua-net.fedy.key=maigo
|
||||
aqua-net.fedy.remote=http://localhost:2528/api/fedy
|
||||
|
||||
## APIs for bot management
|
||||
aqua-net.bot.enabled=true
|
||||
aqua-net.bot.secret=hunter2
|
||||
|
||||
@@ -59,7 +59,7 @@ Located at: [icu.samnyan.aqua.net.UserRegistrar](icu/samnyan/aqua/net/UserRegist
|
||||
* token: String
|
||||
* **Returns**: User information
|
||||
|
||||
**/user/login** : Login with email/username and password. This will also check if the email is verified and send another confirmation
|
||||
**/user/login** : Login with email/username and password. This will also check if the email is verified and send another confirmation.
|
||||
|
||||
* email: String
|
||||
* password: String
|
||||
@@ -74,6 +74,18 @@ Located at: [icu.samnyan.aqua.net.UserRegistrar](icu/samnyan/aqua/net/UserRegist
|
||||
* turnstile: String
|
||||
* **Returns**: Success message
|
||||
|
||||
**/user/reset-password** : Send the user a reset password email. This will also check if the email is verified or if many requests were sent recently.
|
||||
|
||||
* email: String
|
||||
* turnstile: String
|
||||
* **Returns** Success message
|
||||
|
||||
**/user/change-password** : Reset a user's password with a token sent through email to the user.
|
||||
|
||||
* token: String
|
||||
* password: String
|
||||
* **Returns** Success message
|
||||
|
||||
**/user/setting** : Validate and set a user setting field.
|
||||
|
||||
* token: String
|
||||
|
||||
@@ -8,6 +8,8 @@ import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper
|
||||
import kotlinx.serialization.ExperimentalSerializationApi
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.JsonNamingStrategy
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
// Jackson
|
||||
val ACCEPTABLE_FALSE = setOf("0", "false", "no", "off", "False", "None", "null")
|
||||
@@ -21,7 +23,13 @@ val JSON_FUZZY_BOOLEAN = SimpleModule().addDeserializer(Boolean::class.java, obj
|
||||
})
|
||||
val JSON_DATETIME = SimpleModule().addDeserializer(java.time.LocalDateTime::class.java, object : JsonDeserializer<java.time.LocalDateTime>() {
|
||||
override fun deserialize(parser: JsonParser, context: DeserializationContext) =
|
||||
parser.text.asDateTime() ?: (400 - "Invalid date time value ${parser.text}")
|
||||
// First try standard formats via asDateTime() method
|
||||
parser.text.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)
|
||||
@@ -73,4 +81,4 @@ val JSON = Json {
|
||||
// fun objectMapper(): ObjectMapper {
|
||||
// return JACKSON
|
||||
// }
|
||||
//}
|
||||
//}
|
||||
|
||||
204
src/main/java/icu/samnyan/aqua/net/Fedy.kt
Normal file
204
src/main/java/icu/samnyan/aqua/net/Fedy.kt
Normal file
@@ -0,0 +1,204 @@
|
||||
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.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 java.util.concurrent.CompletableFuture
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "aqua-net.fedy")
|
||||
class FedyProps {
|
||||
var enabled: Boolean = false
|
||||
var key: String = ""
|
||||
var remote: String = ""
|
||||
}
|
||||
|
||||
enum class FedyEvent {
|
||||
Linked,
|
||||
Unlinked,
|
||||
Upserted,
|
||||
Imported,
|
||||
}
|
||||
|
||||
@RestController
|
||||
@API("/api/v2/fedy")
|
||||
class Fedy(
|
||||
val jwt: JWT,
|
||||
val userRepo: AquaNetUserRepo,
|
||||
val userFedyRepo: AquaNetUserFedyRepo,
|
||||
val mai2Import: Mai2Import,
|
||||
val mai2UserDataRepo: Mai2UserDataRepo,
|
||||
val mai2UploadUserPlaylog: Mai2UploadUserPlaylogHandler,
|
||||
val mai2UpsertUserAll: Mai2UpsertUserAllHandler,
|
||||
val props: FedyProps,
|
||||
val transactionManager: PlatformTransactionManager
|
||||
) {
|
||||
val transaction by lazy { TransactionTemplate(transactionManager) }
|
||||
|
||||
private fun Str.checkKey() {
|
||||
if (!props.enabled) 403 - "Fedy is disabled"
|
||||
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
|
||||
}
|
||||
|
||||
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) }
|
||||
else -> 406 - "Unsupported game"
|
||||
}
|
||||
}
|
||||
|
||||
data class PushReq(val auId: 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
|
||||
fun<UserData : IUserData, UserRepo : GenericUserDataRepo<UserData>> removeOldData(repo: UserRepo) {
|
||||
val oldData = repo.findByCard_ExtId(extId)
|
||||
if (oldData.isPresent) {
|
||||
log.info("Fedy: Deleting old data for $extId (${req.game})")
|
||||
repo.delete(oldData.get());
|
||||
repo.flush()
|
||||
}
|
||||
}
|
||||
transaction.execute { when (req.game) {
|
||||
"mai2" -> {
|
||||
if (req.removeOldData) { removeOldData(mai2UserDataRepo) }
|
||||
val userAll = req.data["upsertUserAll"] as JDict // UserAll first, prevent using backlog
|
||||
mai2UpsertUserAll.handle(mapOf("userId" to extId, "upsertUserAll" to userAll))
|
||||
val playlogs = req.data["userPlaylogList"] as List<JDict>
|
||||
playlogs.forEach { mai2UploadUserPlaylog.handle(mapOf("userId" to extId, "userPlaylog" to it)) }
|
||||
}
|
||||
else -> 406 - "Unsupported game"
|
||||
} }
|
||||
|
||||
return SUCCESS
|
||||
}
|
||||
|
||||
fun onUpserted(game: Str, maybeExtId: Any?) = maybeNotifyAsync(FedyEvent.Upserted, game, maybeExtId)
|
||||
fun onImported(game: Str, maybeExtId: Any?) = maybeNotifyAsync(FedyEvent.Imported, game, maybeExtId)
|
||||
|
||||
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)
|
||||
} }
|
||||
|
||||
private fun notify(event: FedyEvent, body: Any?) {
|
||||
val MAX_RETRY = 3
|
||||
val body = body?.toJson() ?: "{}"
|
||||
var retry = 0
|
||||
var shouldRetry = true
|
||||
while (retry < MAX_RETRY) {
|
||||
try {
|
||||
val response = "${props.remote.trimEnd('/')}/notify/${event.name}".request()
|
||||
.header("Content-Type" to "application/json")
|
||||
.header(KEY_HEADER to props.key)
|
||||
.post(body)
|
||||
val statusCodeStr = response.statusCode().toString()
|
||||
val hasError = !statusCodeStr.startsWith("2")
|
||||
// Check for non-transient errors
|
||||
if (hasError) {
|
||||
if (!statusCodeStr.startsWith("5")) { shouldRetry = false }
|
||||
throw Exception("Failed to notify Fedy event $event with body $body, status code $statusCodeStr")
|
||||
}
|
||||
return
|
||||
} catch (e: Exception) {
|
||||
retry++
|
||||
if (retry >= MAX_RETRY || !shouldRetry) throw e
|
||||
log.error("Error notifying Fedy event $event with body $body, retrying ($retry/$MAX_RETRY)", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object
|
||||
{
|
||||
const val KEY_HEADER = "X-Fedy-Key"
|
||||
val log = logger()
|
||||
|
||||
fun getGameName(gameId: Str) = when (gameId) {
|
||||
"SDEZ" -> "mai2"
|
||||
else -> null // Not supported
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,10 +29,12 @@ class UserRegistrar(
|
||||
val geoIP: GeoIP,
|
||||
val jwt: JWT,
|
||||
val confirmationRepo: EmailConfirmationRepo,
|
||||
val resetPasswordRepo: ResetPasswordRepo,
|
||||
val cardRepo: CardRepository,
|
||||
val cardService: CardService,
|
||||
val validator: AquaUserServices,
|
||||
val emailProps: EmailProperties,
|
||||
val sessionRepo: SessionTokenRepo,
|
||||
final val paths: PathProps
|
||||
) {
|
||||
val portraitPath = paths.aquaNetPortrait.path()
|
||||
@@ -144,6 +146,73 @@ class UserRegistrar(
|
||||
return mapOf("token" to token)
|
||||
}
|
||||
|
||||
@API("/reset-password")
|
||||
@Doc("Reset password with a token sent through email to the user, if it exists.", "Success message")
|
||||
suspend fun resetPassword(
|
||||
@RP email: Str, @RP turnstile: Str,
|
||||
request: HttpServletRequest
|
||||
) : Any {
|
||||
|
||||
// Check captcha
|
||||
val ip = geoIP.getIP(request)
|
||||
log.info("Net: /user/reset-password from $ip : $email")
|
||||
if (!turnstileService.validate(turnstile, ip)) 400 - "Invalid captcha"
|
||||
|
||||
// Check if user exists, treat as email / username
|
||||
val user = async { userRepo.findByEmailIgnoreCase(email) ?: userRepo.findByUsernameIgnoreCase(email) }
|
||||
?: return SUCCESS // obviously dont tell them if the email exists or not
|
||||
|
||||
// Check if email is verified
|
||||
if (!user.emailConfirmed && emailProps.enable) 400 - "Email not verified"
|
||||
|
||||
val resets = async { resetPasswordRepo.findByAquaNetUserAuId(user.auId) }
|
||||
val lastReset = resets.maxByOrNull { it.createdAt }
|
||||
|
||||
if (lastReset?.createdAt?.plusSeconds(60)?.isAfter(Instant.now()) == true) {
|
||||
400 - "Reset request rejected - STATE_0"
|
||||
}
|
||||
|
||||
// Check if we have sent more than 3 confirmation emails in the last 24 hours
|
||||
if (resets.count { it.createdAt.plusSeconds(60 * 60 * 24).isAfter(Instant.now()) } > 3) {
|
||||
400 - "Reset request rejected - STATE_1"
|
||||
}
|
||||
|
||||
// Send a password reset email
|
||||
emailService.sendPasswordReset(user)
|
||||
|
||||
return SUCCESS
|
||||
}
|
||||
|
||||
@API("/change-password")
|
||||
@Doc("Change a user's password given a reset code", "Success message")
|
||||
suspend fun changePassword(
|
||||
@RP token: Str, @RP password: Str,
|
||||
request: HttpServletRequest
|
||||
) : Any {
|
||||
|
||||
// Find the reset token
|
||||
val reset = async { resetPasswordRepo.findByToken(token) }
|
||||
|
||||
// Check if the token is valid
|
||||
if (reset == null) 400 - "Invalid token"
|
||||
|
||||
// Check if the token is expired
|
||||
if (reset.createdAt.plusSeconds(60 * 60 * 24).isBefore(Instant.now())) 400 - "Token expired"
|
||||
|
||||
// Change the password
|
||||
async { userRepo.save(reset.aquaNetUser.apply { pwHash = validator.checkPwHash(password) }) }
|
||||
|
||||
// Remove the token from the list
|
||||
resetPasswordRepo.delete(reset)
|
||||
|
||||
// Clear all sessions
|
||||
sessionRepo.deleteAll(
|
||||
sessionRepo.findByAquaNetUserAuId(reset.aquaNetUser.auId)
|
||||
)
|
||||
|
||||
return SUCCESS
|
||||
}
|
||||
|
||||
@API("/confirm-email")
|
||||
@Doc("Confirm email address with a token sent through email to the user.", "Success message")
|
||||
suspend fun confirmEmail(@RP token: Str): Any {
|
||||
@@ -185,6 +254,12 @@ class UserRegistrar(
|
||||
|
||||
// Save the user
|
||||
userRepo.save(u)
|
||||
|
||||
// Clear all tokens if changing password
|
||||
if (key == "pwHash")
|
||||
sessionRepo.deleteAll(
|
||||
sessionRepo.findByAquaNetUserAuId(u.auId)
|
||||
)
|
||||
}
|
||||
|
||||
SUCCESS
|
||||
@@ -227,4 +302,17 @@ class UserRegistrar(
|
||||
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
@API("/change-region")
|
||||
@Doc("Change the region of the user.", "Success message")
|
||||
suspend fun changeRegion(@RP token: Str, @RP regionId: Str) = jwt.auth(token) { u ->
|
||||
// Check if the region is valid (between 1 and 47)
|
||||
val r = regionId.toIntOrNull() ?: (400 - "Invalid region")
|
||||
if (r !in 1..47) 400 - "Invalid region"
|
||||
async {
|
||||
userRepo.save(u.apply { region = r.toString() })
|
||||
}
|
||||
|
||||
SUCCESS
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ import ext.logger
|
||||
import icu.samnyan.aqua.net.db.AquaNetUser
|
||||
import icu.samnyan.aqua.net.db.EmailConfirmation
|
||||
import icu.samnyan.aqua.net.db.EmailConfirmationRepo
|
||||
import icu.samnyan.aqua.net.db.ResetPassword
|
||||
import icu.samnyan.aqua.net.db.ResetPasswordRepo
|
||||
import org.simplejavamail.api.mailer.Mailer
|
||||
import org.simplejavamail.email.EmailBuilder
|
||||
import org.simplejavamail.springsupport.SimpleJavaMailSpringSupport
|
||||
@@ -38,10 +40,13 @@ class EmailService(
|
||||
val mailer: Mailer,
|
||||
val props: EmailProperties,
|
||||
val confirmationRepo: EmailConfirmationRepo,
|
||||
val resetPasswordRepo: ResetPasswordRepo,
|
||||
) {
|
||||
val log = logger()
|
||||
val confirmTemplate: Str = this::class.java.getResource("/email/confirm.html")?.readText()
|
||||
?: throw Exception("Email Template Not Found")
|
||||
?: throw Exception("Email Confirm Template Not Found")
|
||||
val resetTemplate: Str = this::class.java.getResource("/email/reset.html")?.readText()
|
||||
?: throw Exception("Password Reset Template Not Found")
|
||||
|
||||
@Async
|
||||
@EventListener(ApplicationStartedEvent::class)
|
||||
@@ -69,15 +74,38 @@ class EmailService(
|
||||
confirmationRepo.save(confirmation)
|
||||
|
||||
// Send email
|
||||
log.info("Sending confirmation email to ${user.email}")
|
||||
log.info("Sending verification email to ${user.email}")
|
||||
mailer.sendMail(EmailBuilder.startingBlank()
|
||||
.from(props.senderName, props.senderAddr)
|
||||
.to(user.computedName, user.email)
|
||||
.withSubject("Confirm Your Email Address for AquaNet")
|
||||
.withSubject("Verify Your Email Address for AquaNet")
|
||||
.withHTMLText(confirmTemplate
|
||||
.replace("{{name}}", user.computedName)
|
||||
.replace("{{url}}", "https://${props.webHost}?confirm-email=$token"))
|
||||
.buildEmail()).thenRun { log.info("Confirmation email sent to ${user.email}") }
|
||||
.replace("{{url}}", "https://${props.webHost}/verify?code=$token"))
|
||||
.buildEmail()).thenRun { log.info("Verification email sent to ${user.email}") }
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a reset password email to the user
|
||||
*/
|
||||
fun sendPasswordReset (user: AquaNetUser) {
|
||||
if (!props.enable) return
|
||||
|
||||
// Generate token (UUID4)
|
||||
val token = UUID.randomUUID().toString()
|
||||
val reset = ResetPassword(token = token, aquaNetUser = user, createdAt = Date().toInstant())
|
||||
resetPasswordRepo.save(reset)
|
||||
|
||||
// Send email
|
||||
log.info("Sending reset password email to ${user.email}")
|
||||
mailer.sendMail(EmailBuilder.startingBlank()
|
||||
.from(props.senderName, props.senderAddr)
|
||||
.to(user.computedName, user.email)
|
||||
.withSubject("Reset Your Password for AquaNet")
|
||||
.withHTMLText(resetTemplate
|
||||
.replace("{{name}}", user.computedName)
|
||||
.replace("{{url}}", "https://${props.webHost}/reset-password?code=$token"))
|
||||
.buildEmail()).thenRun { log.info("Reset password email sent to ${user.email}") }
|
||||
}
|
||||
|
||||
fun testEmail(addr: Str, name: Str) {
|
||||
@@ -93,4 +121,4 @@ class EmailService(
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,77 +1,116 @@
|
||||
package icu.samnyan.aqua.net.components
|
||||
|
||||
import ext.Str
|
||||
import ext.minus
|
||||
import icu.samnyan.aqua.net.db.AquaNetUser
|
||||
import icu.samnyan.aqua.net.db.AquaNetUserRepo
|
||||
import io.jsonwebtoken.JwtParser
|
||||
import io.jsonwebtoken.Jwts
|
||||
import io.jsonwebtoken.security.Keys
|
||||
import jakarta.annotation.PostConstruct
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.stereotype.Service
|
||||
import java.util.*
|
||||
import javax.crypto.SecretKey
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "aqua-net.jwt")
|
||||
class JWTProperties {
|
||||
var secret: Str = "Open Sesame!"
|
||||
}
|
||||
|
||||
@Service
|
||||
class JWT(
|
||||
val props: JWTProperties,
|
||||
val userRepo: AquaNetUserRepo
|
||||
) {
|
||||
val log = LoggerFactory.getLogger(JWT::class.java)!!
|
||||
lateinit var key: SecretKey
|
||||
lateinit var parser: JwtParser
|
||||
|
||||
@PostConstruct
|
||||
fun onLoad() {
|
||||
// Check secret
|
||||
if (props.secret == "Open Sesame!") {
|
||||
log.warn("USING DEFAULT JWT SECRET, PLEASE SET aqua-net.jwt IN CONFIGURATION")
|
||||
}
|
||||
|
||||
// Pad byte array to 256 bits
|
||||
var ba = props.secret.toByteArray()
|
||||
if (ba.size < 32) {
|
||||
log.warn("JWT Secret is less than 256 bits, padding with 0. PLEASE USE A STRONGER SECRET!")
|
||||
ba = ByteArray(32).also { ba.copyInto(it) }
|
||||
}
|
||||
|
||||
// Initialize key
|
||||
key = Keys.hmacShaKeyFor(ba)
|
||||
|
||||
// Create parser
|
||||
parser = Jwts.parser()
|
||||
.verifyWith(key)
|
||||
.build()
|
||||
|
||||
log.info("JWT Service Enabled")
|
||||
}
|
||||
|
||||
|
||||
fun gen(user: AquaNetUser): Str = Jwts.builder().header()
|
||||
.keyId("aqua-net")
|
||||
.and()
|
||||
.subject(user.auId.toString())
|
||||
.issuedAt(Date())
|
||||
.signWith(key)
|
||||
.compact()
|
||||
|
||||
fun parse(token: Str): AquaNetUser? = try {
|
||||
userRepo.findByAuId(parser.parseSignedClaims(token).payload.subject.toLong())
|
||||
} catch (e: Exception) {
|
||||
log.debug("Failed to parse JWT", e)
|
||||
null
|
||||
}
|
||||
|
||||
fun auth(token: Str) = parse(token) ?: (400 - "Invalid token")
|
||||
|
||||
final inline fun <T> auth(token: Str, block: (AquaNetUser) -> T) = block(auth(token))
|
||||
package icu.samnyan.aqua.net.components
|
||||
|
||||
import ext.Str
|
||||
import ext.minus
|
||||
import icu.samnyan.aqua.net.db.AquaNetUser
|
||||
import icu.samnyan.aqua.net.db.AquaNetUserRepo
|
||||
import icu.samnyan.aqua.net.db.SessionToken
|
||||
import icu.samnyan.aqua.net.db.SessionTokenRepo
|
||||
import icu.samnyan.aqua.net.db.getTokenExpiry
|
||||
import io.jsonwebtoken.JwtParser
|
||||
import io.jsonwebtoken.Jwts
|
||||
import io.jsonwebtoken.security.Keys
|
||||
import jakarta.annotation.PostConstruct
|
||||
import jakarta.transaction.Transactional
|
||||
import org.slf4j.LoggerFactory
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties
|
||||
import org.springframework.context.annotation.Configuration
|
||||
import org.springframework.stereotype.Service
|
||||
import java.time.Instant
|
||||
import java.util.*
|
||||
import javax.crypto.SecretKey
|
||||
|
||||
@Configuration
|
||||
@ConfigurationProperties(prefix = "aqua-net.jwt")
|
||||
class JWTProperties {
|
||||
var secret: Str = "Open Sesame!"
|
||||
}
|
||||
|
||||
@Service
|
||||
class JWT(
|
||||
val props: JWTProperties,
|
||||
val userRepo: AquaNetUserRepo,
|
||||
val sessionRepo: SessionTokenRepo
|
||||
) {
|
||||
val log = LoggerFactory.getLogger(JWT::class.java)!!
|
||||
lateinit var key: SecretKey
|
||||
lateinit var parser: JwtParser
|
||||
|
||||
@PostConstruct
|
||||
fun onLoad() {
|
||||
// Check secret
|
||||
if (props.secret == "Open Sesame!") {
|
||||
log.warn("USING DEFAULT JWT SECRET, PLEASE SET aqua-net.jwt IN CONFIGURATION")
|
||||
}
|
||||
|
||||
// Pad byte array to 256 bits
|
||||
var ba = props.secret.toByteArray()
|
||||
if (ba.size < 32) {
|
||||
log.warn("JWT Secret is less than 256 bits, padding with 0. PLEASE USE A STRONGER SECRET!")
|
||||
ba = ByteArray(32).also { ba.copyInto(it) }
|
||||
}
|
||||
|
||||
// Initialize key
|
||||
key = Keys.hmacShaKeyFor(ba)
|
||||
|
||||
// Create parser
|
||||
parser = Jwts.parser()
|
||||
.verifyWith(key)
|
||||
.build()
|
||||
|
||||
log.info("JWT Service Enabled")
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun gen(user: AquaNetUser): Str {
|
||||
val activeTokens = sessionRepo.findByAquaNetUserAuId(user.auId)
|
||||
.sortedByDescending { it.expiry }.drop(9) // the cap is 10, but we append a new token after the fact
|
||||
if (activeTokens.isNotEmpty()) {
|
||||
sessionRepo.deleteAll(activeTokens)
|
||||
}
|
||||
val token = SessionToken().apply {
|
||||
aquaNetUser = user
|
||||
}
|
||||
sessionRepo.save(token)
|
||||
|
||||
return Jwts.builder().header()
|
||||
.keyId("aqua-net")
|
||||
.and()
|
||||
.subject(token.token)
|
||||
.issuedAt(Date())
|
||||
.signWith(key)
|
||||
.compact()
|
||||
}
|
||||
|
||||
@Transactional
|
||||
fun parse(token: Str): AquaNetUser? {
|
||||
try {
|
||||
val uuid = parser.parseSignedClaims(token).payload.subject.toString()
|
||||
val token = sessionRepo.findByToken(uuid)
|
||||
|
||||
if (token != null) {
|
||||
val toBeRemoved = sessionRepo.findByAquaNetUserAuId(token.aquaNetUser.auId)
|
||||
.filter { it.expiry < Instant.now() }
|
||||
if (toBeRemoved.isNotEmpty())
|
||||
sessionRepo.deleteAll(toBeRemoved)
|
||||
if (token.expiry < Instant.now()) {
|
||||
sessionRepo.delete(token)
|
||||
return null
|
||||
}
|
||||
|
||||
sessionRepo.save(token.apply{
|
||||
expiry = getTokenExpiry()
|
||||
})
|
||||
}
|
||||
|
||||
return token?.aquaNetUser
|
||||
} catch (e: Exception) {
|
||||
log.debug("Failed to parse JWT", e)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
fun auth(token: Str) = parse(token) ?: (400 - "Invalid token")
|
||||
|
||||
final inline fun <T> auth(token: Str, block: (AquaNetUser) -> T) = block(auth(token))
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
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_email_reset_password")
|
||||
class ResetPassword(
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
var id: Long = 0,
|
||||
|
||||
@Column(nullable = false)
|
||||
var token: String = "",
|
||||
|
||||
// Token creation time
|
||||
@Column(nullable = false)
|
||||
var createdAt: Instant = Instant.now(),
|
||||
|
||||
// Linking to the AquaNetUser
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "auId", referencedColumnName = "auId")
|
||||
var aquaNetUser: AquaNetUser = AquaNetUser()
|
||||
) : Serializable
|
||||
|
||||
@Repository
|
||||
interface ResetPasswordRepo : JpaRepository<ResetPassword, Long> {
|
||||
fun findByToken(token: String): ResetPassword?
|
||||
fun findByAquaNetUserAuId(auId: Long): List<ResetPassword>
|
||||
}
|
||||
33
src/main/java/icu/samnyan/aqua/net/db/AquaNetSession.kt
Normal file
33
src/main/java/icu/samnyan/aqua/net/db/AquaNetSession.kt
Normal file
@@ -0,0 +1,33 @@
|
||||
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
|
||||
import java.util.UUID
|
||||
|
||||
fun getTokenExpiry() = Instant.now().plusSeconds(7 * 86400)
|
||||
|
||||
@Entity
|
||||
@Table(name = "aqua_net_session")
|
||||
class SessionToken(
|
||||
@Id
|
||||
@Column(nullable = false)
|
||||
var token: String = UUID.randomUUID().toString(),
|
||||
|
||||
// Token creation time
|
||||
@Column(nullable = false)
|
||||
var expiry: Instant = getTokenExpiry(),
|
||||
|
||||
// Linking to the AquaNetUser
|
||||
@ManyToOne
|
||||
@JoinColumn(name = "auId", referencedColumnName = "auId")
|
||||
var aquaNetUser: AquaNetUser = AquaNetUser()
|
||||
) : Serializable
|
||||
|
||||
@Repository
|
||||
interface SessionTokenRepo : JpaRepository<SessionToken, String> {
|
||||
fun findByToken(token: String): SessionToken?
|
||||
fun findByAquaNetUserAuId(auId: Long): List<SessionToken>
|
||||
}
|
||||
@@ -43,6 +43,10 @@ class AquaNetUser(
|
||||
@Column(length = 3)
|
||||
var country: String = "",
|
||||
|
||||
// Region code at most 2 characters
|
||||
@Column(length = 2)
|
||||
var region: String = "",
|
||||
|
||||
// Last login time
|
||||
var lastLogin: Long = 0L,
|
||||
|
||||
@@ -98,6 +102,7 @@ interface AquaNetUserRepo : JpaRepository<AquaNetUser, Long> {
|
||||
fun findByEmailIgnoreCase(email: String): AquaNetUser?
|
||||
fun findByUsernameIgnoreCase(username: String): AquaNetUser?
|
||||
fun findByKeychip(keychip: String): AquaNetUser?
|
||||
fun findByGhostCardExtId(extId: Long): AquaNetUser?
|
||||
}
|
||||
|
||||
data class SettingField(
|
||||
|
||||
29
src/main/java/icu/samnyan/aqua/net/db/AquaNetUserFedy.kt
Normal file
29
src/main/java/icu/samnyan/aqua/net/db/AquaNetUserFedy.kt
Normal file
@@ -0,0 +1,29 @@
|
||||
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
|
||||
}
|
||||
@@ -92,7 +92,7 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
|
||||
AVG(p.achievement) / 10000.0 AS acc,
|
||||
SUM(p.is_full_combo) AS fc,
|
||||
SUM(p.is_all_perfect) AS ap,
|
||||
c.ranking_banned or a.opt_out_of_leaderboard AS hide,
|
||||
c.ranking_banned or a.opt_out_of_leaderboard or c.status = 12 AS hide,
|
||||
a.username
|
||||
FROM ${tableName}_user_playlog_view p
|
||||
JOIN ${tableName}_user_data_view u ON p.user_id = u.id
|
||||
|
||||
@@ -3,6 +3,7 @@ package icu.samnyan.aqua.net.games
|
||||
import ext.*
|
||||
import icu.samnyan.aqua.net.db.AquaNetUser
|
||||
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 org.springframework.beans.factory.annotation.Autowired
|
||||
@@ -15,6 +16,11 @@ 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
|
||||
)
|
||||
|
||||
// Import class with renaming
|
||||
data class ImportClass<T : Any>(
|
||||
@@ -54,6 +60,8 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
|
||||
val exportFields: Map<String, Var<ExportModel, Any>>,
|
||||
val exportRepos: Map<Var<ExportModel, Any>, IUserRepo<UserModel, *>>,
|
||||
val artemisRenames: Map<String, ImportClass<*>>,
|
||||
val customExporters: Map<Var<ExportModel, Any>, (UserModel, ExportOptions) -> Any?> = emptyMap(),
|
||||
val customImporters: Map<Var<ExportModel, Any>, (ExportModel, UserModel) -> Unit> = emptyMap()
|
||||
) {
|
||||
abstract fun createEmpty(): ExportModel
|
||||
abstract val userDataRepo: GenericUserDataRepo<UserModel>
|
||||
@@ -62,6 +70,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
|
||||
|
||||
init {
|
||||
artemisRenames.values.forEach {
|
||||
@@ -72,13 +81,18 @@ 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) = createEmpty().apply {
|
||||
fun export(u: AquaNetUser): ExportModel = export(u, ExportOptions())
|
||||
|
||||
fun export(u: AquaNetUser, options: ExportOptions) = createEmpty().apply {
|
||||
gameId = game
|
||||
userData = userDataRepo.findByCard(u.ghostCard) ?: (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) }
|
||||
}
|
||||
customExporters.forEach { (f, exporter) ->
|
||||
exporter(userData, options)?.let { f.set(this, it) }
|
||||
}
|
||||
}
|
||||
|
||||
@API("export")
|
||||
@@ -95,6 +109,7 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
|
||||
|
||||
val lists = listRepos.toList().associate { (f, r) -> r to f.get(export) as List<IUserEntity<UserModel>> }.vNotNull()
|
||||
val singles = singleRepos.toList().associate { (f, r) -> r to f.get(export) as IUserEntity<UserModel> }.vNotNull()
|
||||
var repoFieldMap = exportRepos.toList().associate { (f, r) -> r to f }
|
||||
|
||||
// Validate new user data
|
||||
// Check that all ids are 0 (this should be true since all ids are @JsonIgnore)
|
||||
@@ -126,8 +141,14 @@ abstract class ImportController<ExportModel: IExportClass<UserModel>, UserModel:
|
||||
// Save new data
|
||||
singles.forEach { (repo, single) -> (repo as IUserRepo<UserModel, Any>).save(single) }
|
||||
lists.forEach { (repo, list) -> (repo as IUserRepo<UserModel, Any>).saveAll(list) }
|
||||
// Handle custom importers
|
||||
customImporters.forEach { (field, importer) ->
|
||||
importer(export, nu)
|
||||
}
|
||||
}
|
||||
|
||||
Fedy.getGameName(game)?.let { fedy.onImported(it, u.ghostCard.extId) }
|
||||
|
||||
SUCCESS
|
||||
}
|
||||
|
||||
|
||||
@@ -3,11 +3,13 @@ package icu.samnyan.aqua.net.games.mai2
|
||||
import ext.API
|
||||
import ext.returns
|
||||
import ext.vars
|
||||
import icu.samnyan.aqua.net.games.ExportOptions
|
||||
import icu.samnyan.aqua.net.games.IExportClass
|
||||
import icu.samnyan.aqua.net.games.ImportClass
|
||||
import icu.samnyan.aqua.net.games.ImportController
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2Repos
|
||||
import icu.samnyan.aqua.sega.maimai2.model.Mai2UserLinked
|
||||
import icu.samnyan.aqua.sega.maimai2.model.request.Mai2UserFavoriteItem
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.*
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import kotlin.reflect.full.declaredMembers
|
||||
@@ -22,11 +24,16 @@ class Mai2Import(
|
||||
it.name.replace("List", "").lowercase()
|
||||
},
|
||||
exportRepos = Maimai2DataExport::class.vars()
|
||||
.filter { f -> f.name !in setOf("gameId", "userData") }
|
||||
.associateWith { Mai2Repos::class.declaredMembers
|
||||
.filter { f -> f returns Mai2UserLinked::class }
|
||||
.firstOrNull { f -> f.name == it.name || f.name == it.name.replace("List", "") }
|
||||
?.call(repos) as Mai2UserLinked<*>? ?: error("No matching field found for ${it.name}")
|
||||
.filter { f -> f.name !in setOf("gameId", "userData", "userPlaylogList", "userFavoriteMusicList") }
|
||||
.associateWith { field ->
|
||||
val repoName = when (field.name) {
|
||||
"userKaleidxScopeList" -> "userKaleidx"
|
||||
else -> field.name.replace("List", "")
|
||||
}
|
||||
Mai2Repos::class.declaredMembers
|
||||
.filter { f -> f returns Mai2UserLinked::class }
|
||||
.firstOrNull { f -> f.name == repoName }
|
||||
?.call(repos) as Mai2UserLinked<*>? ?: error("No matching field found for ${field.name}")
|
||||
},
|
||||
artemisRenames = mapOf(
|
||||
"mai2_item_character" to ImportClass(Mai2UserCharacter::class),
|
||||
@@ -44,34 +51,71 @@ class Mai2Import(
|
||||
"mai2_profile_option" to ImportClass(Mai2UserOption::class, mapOf("version" to null)),
|
||||
"mai2_score_best" to ImportClass(Mai2UserMusicDetail::class),
|
||||
"mai2_score_course" to ImportClass(Mai2UserCourse::class),
|
||||
)
|
||||
),
|
||||
customExporters = mapOf(
|
||||
Maimai2DataExport::userPlaylogList to { user: Mai2UserDetail, options: ExportOptions ->
|
||||
if (options.playlogAfter != null) {
|
||||
repos.userPlaylog.findByUserAndUserPlayDateAfter(user, options.playlogAfter)
|
||||
} else {
|
||||
repos.userPlaylog.findByUser(user)
|
||||
}
|
||||
},
|
||||
Maimai2DataExport::userFavoriteMusicList to { user: Mai2UserDetail, _: ExportOptions ->
|
||||
repos.userGeneralData.findByUserAndPropertyKey(user, "favorite_music").orElse(null)
|
||||
?.propertyValue
|
||||
?.takeIf { it.isNotEmpty() }
|
||||
?.split(",")
|
||||
?.mapIndexed { index, id -> Mai2UserFavoriteItem().apply { orderId = index; this.id = id.toInt() } }
|
||||
?: emptyList()
|
||||
}
|
||||
) as Map<kotlin.reflect.KMutableProperty1<Maimai2DataExport, Any>, (Mai2UserDetail, ExportOptions) -> Any?>,
|
||||
customImporters = mapOf(
|
||||
Maimai2DataExport::userPlaylogList to { export: Maimai2DataExport, user: Mai2UserDetail ->
|
||||
repos.userPlaylog.saveAll(export.userPlaylogList.map { it.apply { it.user = user } })
|
||||
},
|
||||
Maimai2DataExport::userFavoriteMusicList to { export: Maimai2DataExport, user: Mai2UserDetail ->
|
||||
val favoriteMusicList = export.userFavoriteMusicList
|
||||
if (favoriteMusicList.isNotEmpty()) {
|
||||
val key = "favorite_music"
|
||||
// This field always imports as incremental, since the userGeneralData field (for backwards compatibility) is processed before this
|
||||
val data = repos.userGeneralData.findByUserAndPropertyKey(user, key).orElse(null)
|
||||
?: Mai2UserGeneralData().apply { this.user = user; propertyKey = key }
|
||||
repos.userGeneralData.save(data.apply {
|
||||
propertyValue = favoriteMusicList.sortedBy { it.orderId }.map { it.id }.joinToString(",")
|
||||
})
|
||||
}
|
||||
}
|
||||
) as Map<kotlin.reflect.KMutableProperty1<Maimai2DataExport, Any>, (Maimai2DataExport, Mai2UserDetail) -> Unit>
|
||||
) {
|
||||
override fun createEmpty() = Maimai2DataExport()
|
||||
override val userDataRepo = repos.userData
|
||||
}
|
||||
|
||||
data class Maimai2DataExport(
|
||||
override var userData: Mai2UserDetail,
|
||||
var userExtend: Mai2UserExtend,
|
||||
var userOption: Mai2UserOption,
|
||||
var userUdemae: Mai2UserUdemae,
|
||||
var mapEncountNpcList: List<Mai2MapEncountNpc>,
|
||||
var userActList: List<Mai2UserAct>,
|
||||
var userCharacterList: List<Mai2UserCharacter>,
|
||||
var userChargeList: List<Mai2UserCharge>,
|
||||
var userCourseList: List<Mai2UserCourse>,
|
||||
var userFavoriteList: List<Mai2UserFavorite>,
|
||||
var userFriendSeasonRankingList: List<Mai2UserFriendSeasonRanking>,
|
||||
var userGeneralDataList: List<Mai2UserGeneralData>,
|
||||
var userItemList: List<Mai2UserItem>,
|
||||
var userLoginBonusList: List<Mai2UserLoginBonus>,
|
||||
var userMapList: List<Mai2UserMap>,
|
||||
var userMusicDetailList: List<Mai2UserMusicDetail>,
|
||||
var userPlaylogList: List<Mai2UserPlaylog>,
|
||||
override var userData: Mai2UserDetail = Mai2UserDetail(),
|
||||
var userExtend: Mai2UserExtend = Mai2UserExtend(),
|
||||
var userOption: Mai2UserOption = Mai2UserOption(),
|
||||
var userUdemae: Mai2UserUdemae = Mai2UserUdemae(),
|
||||
var mapEncountNpcList: List<Mai2MapEncountNpc> = mutableListOf(),
|
||||
var userActList: List<Mai2UserAct> = mutableListOf(),
|
||||
var userCharacterList: List<Mai2UserCharacter> = mutableListOf(),
|
||||
var userChargeList: List<Mai2UserCharge> = mutableListOf(),
|
||||
var userCourseList: List<Mai2UserCourse> = mutableListOf(),
|
||||
var userFavoriteList: List<Mai2UserFavorite> = mutableListOf(),
|
||||
var userFriendSeasonRankingList: List<Mai2UserFriendSeasonRanking> = mutableListOf(),
|
||||
var userGeneralDataList: List<Mai2UserGeneralData> = mutableListOf(),
|
||||
var userItemList: List<Mai2UserItem> = mutableListOf(),
|
||||
var userLoginBonusList: List<Mai2UserLoginBonus> = mutableListOf(),
|
||||
var userMapList: List<Mai2UserMap> = mutableListOf(),
|
||||
var userMusicDetailList: List<Mai2UserMusicDetail> = mutableListOf(),
|
||||
var userIntimateList: List<Mai2UserIntimate> = mutableListOf(),
|
||||
var userFavoriteMusicList: List<Mai2UserFavoriteItem> = mutableListOf(),
|
||||
var userKaleidxScopeList: List<Mai2UserKaleidx> = mutableListOf(),
|
||||
var userPlaylogList: List<Mai2UserPlaylog> = mutableListOf(),
|
||||
// Not supported yet:
|
||||
// var userWeeklyData
|
||||
// var userMissionDataList
|
||||
// var userShopStockList
|
||||
// var userTradeItemList
|
||||
override var gameId: String = "SDEZ",
|
||||
): IExportClass<Mai2UserDetail> {
|
||||
constructor() : this(Mai2UserDetail(), Mai2UserExtend(), Mai2UserOption(), Mai2UserUdemae(),
|
||||
mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(),
|
||||
mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(), mutableListOf(),
|
||||
mutableListOf())
|
||||
}
|
||||
): IExportClass<Mai2UserDetail>
|
||||
|
||||
@@ -103,6 +103,7 @@ class AllNet(
|
||||
// encode UTF-8, format_ver 3, hops 1, token 2010451813
|
||||
val reqMap = decodeAllNet(dataStream.readAllBytes())
|
||||
val serial = reqMap["serial"] ?: ""
|
||||
var region = props.map.mut["region0"] ?: "1"
|
||||
logger.info("AllNet /PowerOn : $reqMap")
|
||||
|
||||
var session: String? = null
|
||||
@@ -114,6 +115,10 @@ class AllNet(
|
||||
if (u != null) {
|
||||
// Create a new session for the user
|
||||
logger.info("> Keychip authenticated: ${u.auId} ${u.computedName}")
|
||||
// If the user defined its own region apply it
|
||||
if (u.region.isNotBlank()) {
|
||||
region = u.region
|
||||
}
|
||||
session = keychipSessionService.new(u, reqMap["game_id"] ?: "").token
|
||||
}
|
||||
|
||||
@@ -140,6 +145,7 @@ class AllNet(
|
||||
val resp = props.map.mut + mapOf(
|
||||
"uri" to switchUri(here, localPort, gameId, ver, session),
|
||||
"host" to props.host.ifBlank { here },
|
||||
"region0" to region
|
||||
)
|
||||
|
||||
// Different responses for different versions
|
||||
|
||||
@@ -83,7 +83,6 @@ fun ChusanController.chusanInit() {
|
||||
"GetUserCtoCPlay" { """{"userId":"${data["userId"]}","orderBy":"0","count":"0","userCtoCPlayList":[]}""" }
|
||||
"GetUserRivalMusic" { """{"userId":"${data["userId"]}","rivalId":"0","length":"0","nextIndex":"0","userRivalMusicList":[]}""" }
|
||||
"GetUserRivalData" { """{"userId":"${data["userId"]}","length":"0","userRivalData":[]}""" }
|
||||
"GetUserRegion" { """{"userId":"${data["userId"]}","length":"0","userRegionList":[]}""" }
|
||||
"GetUserPrintedCard" { """{"userId":"${data["userId"]}","length":0,"nextIndex":-1,"userPrintedCardList":[]}""" }
|
||||
|
||||
// Net battle data
|
||||
@@ -237,7 +236,7 @@ fun ChusanController.chusanInit() {
|
||||
) + userDict
|
||||
|
||||
if (user.card?.status == CardStatus.MIGRATED_TO_MINATO) {
|
||||
res["userName"] = "Migrated"
|
||||
res["userName"] = "JiaQQqun / CardMigrated"
|
||||
res["rating"] = 0
|
||||
res["playerLevel"] = 0
|
||||
}
|
||||
@@ -288,6 +287,12 @@ fun ChusanController.chusanInit() {
|
||||
)
|
||||
}
|
||||
|
||||
"GetUserRegion" {
|
||||
db.userRegions.findByUser_Card_ExtId(uid)
|
||||
.map { mapOf("regionId" to it.regionId, "playCount" to it.playCount) }
|
||||
.let { mapOf("userId" to uid, "userRegionList" to it) }
|
||||
}
|
||||
|
||||
// Game settings
|
||||
"GetGameSetting" {
|
||||
val version = data["version"].toString()
|
||||
|
||||
@@ -29,6 +29,17 @@ fun ChusanController.upsertApiInit() {
|
||||
userNameEx = ""
|
||||
}.also { db.userData.saveAndFlush(it) }
|
||||
|
||||
// Only save if it is a valid region and the user has played at least a song
|
||||
req.userPlaylogList?.firstOrNull()?.regionId?.let { rid ->
|
||||
val region = db.userRegions.findByUserAndRegionId(u, rid)?.apply {
|
||||
playCount += 1
|
||||
} ?: UserRegions().apply {
|
||||
user = u
|
||||
regionId = rid
|
||||
}
|
||||
db.userRegions.save(region)
|
||||
}
|
||||
|
||||
versionHelper[u.lastClientId] = u.lastDataVersion
|
||||
|
||||
// Set users
|
||||
|
||||
@@ -174,6 +174,10 @@ interface Chu3GameLoginBonusRepo : JpaRepository<GameLoginBonus, Int> {
|
||||
fun findByRequiredDays(version: Int, presetId: Int, requiredDays: Int): Optional<GameLoginBonus>
|
||||
}
|
||||
|
||||
interface Chu3UserRegionsRepo: Chu3UserLinked<UserRegions> {
|
||||
fun findByUserAndRegionId(user: Chu3UserData, regionId: Int): UserRegions?
|
||||
}
|
||||
|
||||
@Component
|
||||
class Chu3Repos(
|
||||
val userLoginBonus: Chu3UserLoginBonusRepo,
|
||||
@@ -191,6 +195,7 @@ class Chu3Repos(
|
||||
val userMap: Chu3UserMapRepo,
|
||||
val userMusicDetail: Chu3UserMusicDetailRepo,
|
||||
val userPlaylog: Chu3UserPlaylogRepo,
|
||||
val userRegions: Chu3UserRegionsRepo,
|
||||
val userCMission: Chu3UserCMissionRepo,
|
||||
val userCMissionProgress: Chu3UserCMissionProgressRepo,
|
||||
val netBattleLog: Chu3NetBattleLogRepo,
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package icu.samnyan.aqua.sega.chusan.model.userdata
|
||||
|
||||
import jakarta.persistence.Entity
|
||||
import jakarta.persistence.Table
|
||||
import jakarta.persistence.UniqueConstraint
|
||||
import java.time.LocalDate
|
||||
|
||||
@Entity(name = "ChusanUserRegions")
|
||||
@Table(name = "chusan_user_regions", uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "region_id"])])
|
||||
class UserRegions : Chu3UserEntity() {
|
||||
var regionId = 0
|
||||
var playCount = 1
|
||||
var created: String = LocalDate.now().toString()
|
||||
}
|
||||
@@ -33,7 +33,10 @@ data class PagedProcessor(val add: JDict?, val fn: PagedHandler, var post: PageP
|
||||
// A very :3 way of declaring APIs
|
||||
abstract class MeowApi(val serialize: (String, Any) -> String) {
|
||||
val initH = mutableMapOf<String, SpecialHandler>()
|
||||
infix operator fun String.invoke(fn: SpecialHandler) = initH.set("${this}Api", fn)
|
||||
infix operator fun String.invoke(fn: SpecialHandler) {
|
||||
if (initH.containsKey("${this}Api")) error("Duplicate API $this found! Someone is not smart 👀")
|
||||
initH["${this}Api"] = fn
|
||||
}
|
||||
infix fun String.static(fn: () -> Any) = serialize(this, fn()).let { resp -> this { resp } }
|
||||
|
||||
// Page Cache: {cache key: (timestamp, full list)}
|
||||
|
||||
@@ -7,9 +7,12 @@ import icu.samnyan.aqua.sega.general.model.CardStatus
|
||||
import icu.samnyan.aqua.sega.maimai2.model.UserRivalMusic
|
||||
import icu.samnyan.aqua.sega.maimai2.model.UserRivalMusicDetail
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.Mai2UserKaleidx
|
||||
import icu.samnyan.aqua.sega.maimai2.model.userdata.UserRegions
|
||||
import java.time.LocalDate
|
||||
|
||||
fun Maimai2ServletController.initApis() {
|
||||
val log = logger()
|
||||
|
||||
"GetUserExtend" { mapOf(
|
||||
"userId" to uid,
|
||||
"userExtend" to (db.userExtend.findSingleByUser_Card_ExtId(uid)() ?: (404 - "User not found"))
|
||||
@@ -111,7 +114,7 @@ fun Maimai2ServletController.initApis() {
|
||||
)
|
||||
|
||||
if (d.card?.status == CardStatus.MIGRATED_TO_MINATO) {
|
||||
res["userName"] = "Migrated"
|
||||
res["userName"] = "JiaQQqun / CardMigrated"
|
||||
res["dispRate"] = 1
|
||||
res["playerRating"] = 66564
|
||||
res["totalAwake"] = 7114
|
||||
@@ -134,6 +137,20 @@ fun Maimai2ServletController.initApis() {
|
||||
res["returnCode"] = 0
|
||||
}
|
||||
|
||||
// Get regionId from request
|
||||
val region = data["regionId"] as? Int
|
||||
|
||||
// Only save if it is a valid region and the user has played at least a song
|
||||
if (region != null && region > 0 && d != null) {
|
||||
val region = db.userRegions.findByUserAndRegionId(d, region)?.apply {
|
||||
playCount += 1
|
||||
} ?: UserRegions().apply {
|
||||
user = d
|
||||
regionId = region
|
||||
}
|
||||
db.userRegions.save(region)
|
||||
}
|
||||
|
||||
res
|
||||
}
|
||||
|
||||
@@ -178,13 +195,19 @@ fun Maimai2ServletController.initApis() {
|
||||
mapOf("userId" to uid, "rivalId" to rivalId, "nextIndex" to 0, "userRivalMusicList" to res.values)
|
||||
}
|
||||
|
||||
"GetUserRegion" {
|
||||
logger().info("Getting user regions for user $uid")
|
||||
db.userRegions.findByUser_Card_ExtId(uid)
|
||||
.map { mapOf("regionId" to it.regionId, "playCount" to it.playCount) }
|
||||
.let { mapOf("userId" to uid, "length" to it.size, "userRegionList" to it) }
|
||||
}
|
||||
|
||||
"GetUserIntimate".unpaged {
|
||||
val u = db.userData.findByCardExtId(uid)() ?: (404 - "User not found")
|
||||
db.userIntimate.findByUser(u)
|
||||
}
|
||||
|
||||
// Empty List Handlers
|
||||
"GetUserRegion".unpaged { empty }
|
||||
"GetUserGhost".unpaged { empty }
|
||||
"GetUserFriendBonus" { mapOf("userId" to uid, "returnCode" to 0, "getMiles" to 0) }
|
||||
"GetTransferFriend" { mapOf("userId" to uid, "transferFriendList" to empty) }
|
||||
@@ -339,4 +362,4 @@ fun Maimai2ServletController.initApis() {
|
||||
"userRecommendSelectionMusicIdList" to (net.recommendedMusic[user.id] ?: empty)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,10 @@ import jakarta.servlet.http.HttpServletRequest
|
||||
import org.springframework.web.bind.annotation.*
|
||||
import java.time.format.DateTimeFormatter
|
||||
import kotlin.reflect.full.declaredMemberProperties
|
||||
import icu.samnyan.aqua.net.Fedy
|
||||
import org.springframework.beans.factory.annotation.Autowired
|
||||
import org.springframework.context.annotation.Lazy
|
||||
import org.springframework.beans.factory.ObjectProvider
|
||||
|
||||
/**
|
||||
* @author samnyan (privateamusement@protonmail.com)
|
||||
@@ -37,6 +41,8 @@ class Maimai2ServletController(
|
||||
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>()
|
||||
@@ -89,6 +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"]) }
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
||||
@@ -57,8 +57,6 @@ interface Mai2UserExtendRepo : Mai2UserLinked<Mai2UserExtend>
|
||||
interface Mai2UserFavoriteRepo : Mai2UserLinked<Mai2UserFavorite> {
|
||||
fun findByUserAndItemKind(user: Mai2UserDetail, kind: Int): Optional<Mai2UserFavorite>
|
||||
|
||||
fun findByUserIdAndItemKind(userId: Long, kind: Int): List<Mai2UserFavorite>
|
||||
|
||||
fun findByUser_Card_ExtIdAndItemKind(userId: Long, kind: Int): Optional<Mai2UserFavorite>
|
||||
}
|
||||
|
||||
@@ -104,6 +102,7 @@ interface Mai2UserPlaylogRepo : GenericPlaylogRepo<Mai2UserPlaylog>, Mai2UserLin
|
||||
musicId: Int,
|
||||
userPlayDate: String
|
||||
): MutableList<Mai2UserPlaylog>
|
||||
fun findByUserAndUserPlayDateAfter(user: Mai2UserDetail, userPlayDate: String): List<Mai2UserPlaylog>
|
||||
}
|
||||
|
||||
interface Mai2UserPrintDetailRepo : JpaRepository<Mai2UserPrintDetail, Long>
|
||||
@@ -126,6 +125,10 @@ interface Mai2GameEventRepo : JpaRepository<Mai2GameEvent, Int> {
|
||||
|
||||
interface Mai2GameSellingCardRepo : JpaRepository<Mai2GameSellingCard, Long>
|
||||
|
||||
interface Mai2UserRegionsRepo: Mai2UserLinked<UserRegions> {
|
||||
fun findByUserAndRegionId(user: Mai2UserDetail, regionId: Int): UserRegions?
|
||||
}
|
||||
|
||||
@Component
|
||||
class Mai2Repos(
|
||||
val mapEncountNpc: Mai2MapEncountNpcRepo,
|
||||
@@ -151,5 +154,6 @@ class Mai2Repos(
|
||||
val userIntimate: MAi2UserIntimateRepo,
|
||||
val gameCharge: Mai2GameChargeRepo,
|
||||
val gameEvent: Mai2GameEventRepo,
|
||||
val gameSellingCard: Mai2GameSellingCardRepo
|
||||
val gameSellingCard: Mai2GameSellingCardRepo,
|
||||
val userRegions: Mai2UserRegionsRepo,
|
||||
)
|
||||
|
||||
@@ -16,6 +16,12 @@ import lombok.AllArgsConstructor
|
||||
import lombok.Data
|
||||
import lombok.NoArgsConstructor
|
||||
import java.time.LocalDateTime
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize
|
||||
import java.time.format.DateTimeFormatter
|
||||
import com.fasterxml.jackson.databind.JsonSerializer
|
||||
import com.fasterxml.jackson.databind.SerializerProvider
|
||||
import com.fasterxml.jackson.core.JsonGenerator
|
||||
import java.time.LocalDate
|
||||
|
||||
@MappedSuperclass
|
||||
open class Mai2UserEntity : BaseEntity(), IUserEntity<Mai2UserDetail> {
|
||||
@@ -446,9 +452,9 @@ class Mai2UserPlaylog : Mai2UserEntity(), IGenericGamePlaylog {
|
||||
get() = maxCombo == totalCombo
|
||||
|
||||
override val isAllPerfect: Boolean
|
||||
get() = tapMiss + tapGood + tapGreat == 0 &&
|
||||
holdMiss + holdGood + holdGreat == 0 &&
|
||||
slideMiss + slideGood + slideGreat == 0 &&
|
||||
get() = tapMiss + tapGood + tapGreat == 0 &&
|
||||
holdMiss + holdGood + holdGreat == 0 &&
|
||||
slideMiss + slideGood + slideGreat == 0 &&
|
||||
touchMiss + touchGood + touchGreat == 0 &&
|
||||
breakMiss + breakGood + breakGreat == 0
|
||||
}
|
||||
@@ -526,10 +532,14 @@ class Mai2UserKaleidx : Mai2UserEntity() {
|
||||
var totalDeluxscore = 0
|
||||
var bestAchievement = 0
|
||||
var bestDeluxscore = 0
|
||||
@JsonSerialize(using = MaimaiDateSerializer::class)
|
||||
var bestAchievementDate: LocalDateTime? = null
|
||||
@JsonSerialize(using = MaimaiDateSerializer::class)
|
||||
var bestDeluxscoreDate: LocalDateTime? = null
|
||||
var playCount = 0
|
||||
@JsonSerialize(using = MaimaiDateSerializer::class)
|
||||
var clearDate: LocalDateTime? = null
|
||||
@JsonSerialize(using = MaimaiDateSerializer::class)
|
||||
var lastPlayDate: LocalDateTime? = null
|
||||
var isInfoWatched = false
|
||||
}
|
||||
@@ -541,3 +551,21 @@ class Mai2UserIntimate : Mai2UserEntity() {
|
||||
var intimateLevel = 0;
|
||||
var intimateCountRewarded = 0;
|
||||
}
|
||||
|
||||
@Entity(name = "Maimai2UserRegions")
|
||||
@Table(
|
||||
name = "maimai2_user_regions",
|
||||
uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "region_id"])]
|
||||
)
|
||||
class UserRegions : Mai2UserEntity() {
|
||||
var regionId = 0
|
||||
var playCount = 1
|
||||
var created: String = LocalDate.now().toString()
|
||||
}
|
||||
|
||||
val MAIMAI_DATETIME = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.0")
|
||||
class MaimaiDateSerializer : JsonSerializer<LocalDateTime>() {
|
||||
override fun serialize(v: LocalDateTime, j: JsonGenerator, s: SerializerProvider) {
|
||||
j.writeString(v.format(MAIMAI_DATETIME))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,6 +147,10 @@ interface OgkUserTrainingRoomRepo : OngekiUserLinked<UserTrainingRoom> {
|
||||
fun findByUserAndRoomId(user: UserData, roomId: Int): Optional<UserTrainingRoom>
|
||||
}
|
||||
|
||||
interface OgkUserRegionsRepo: OngekiUserLinked<UserRegions> {
|
||||
fun findByUserAndRegionId(user: UserData, regionId: Int): UserRegions?
|
||||
}
|
||||
|
||||
// Re:Fresh
|
||||
interface OgkUserEventMapRepo : OngekiUserLinked<UserEventMap>
|
||||
interface OgkUserSkinRepo : OngekiUserLinked<UserSkin>
|
||||
@@ -190,6 +194,7 @@ class OngekiUserRepos(
|
||||
val trainingRoom: OgkUserTrainingRoomRepo,
|
||||
val eventMap: OgkUserEventMapRepo,
|
||||
val skin: OgkUserSkinRepo,
|
||||
val regions: OgkUserRegionsRepo,
|
||||
)
|
||||
|
||||
@Component
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
package icu.samnyan.aqua.sega.ongeki
|
||||
|
||||
import ext.int
|
||||
import ext.invoke
|
||||
import ext.mapApply
|
||||
import ext.minus
|
||||
import icu.samnyan.aqua.sega.ongeki.model.OngekiUpsertUserAll
|
||||
import icu.samnyan.aqua.sega.ongeki.model.UserData
|
||||
import icu.samnyan.aqua.sega.ongeki.model.UserGeneralData
|
||||
import icu.samnyan.aqua.sega.ongeki.model.UserRegions
|
||||
|
||||
|
||||
fun OngekiController.initUpsertAll() {
|
||||
@@ -33,6 +35,20 @@ fun OngekiController.initUpsertAll() {
|
||||
db.data.save(this)
|
||||
} ?: oldUser ?: return@api null
|
||||
|
||||
// User region
|
||||
val region = data["regionId"]?.int ?: 0
|
||||
|
||||
// Only save if it is a valid region and the user has played at least a song
|
||||
if (region > 0 && all.userPlaylogList?.isNotEmpty() == true) {
|
||||
val region = db.regions.findByUserAndRegionId(u, region)?.apply {
|
||||
playCount += 1
|
||||
} ?:UserRegions().apply {
|
||||
user = u
|
||||
regionId = region
|
||||
}
|
||||
db.regions.save(region)
|
||||
}
|
||||
|
||||
all.run {
|
||||
// Set users
|
||||
listOfNotNull(
|
||||
|
||||
@@ -41,7 +41,10 @@ fun OngekiController.initUser() {
|
||||
|
||||
"GetUserBpBase".unpaged { empty }
|
||||
"GetUserRatinglog".unpaged { empty }
|
||||
"GetUserRegion".unpaged { empty }
|
||||
"GetUserRegion".unpaged {
|
||||
db.regions.findByUser_Card_ExtId(uid)
|
||||
.map { mapOf("regionId" to it.regionId, "playCount" to it.playCount) }
|
||||
}
|
||||
|
||||
"GetUserTradeItem".unpaged {
|
||||
val start = parsing { data["startChapterId"]!!.int }
|
||||
@@ -112,7 +115,29 @@ fun OngekiController.initUser() {
|
||||
}
|
||||
|
||||
"GetUserPreview" api@ {
|
||||
val u = db.data.findByCard_ExtId(uid)() ?: return@api mapOf("userId" to uid, "lastPlayDate" to null)
|
||||
val u = db.data.findByCard_ExtId(uid)() ?: return@api mapOf(
|
||||
"userId" to uid,
|
||||
"isLogin" to false,
|
||||
"lastLoginDate" to "0000-00-00 00:00:00",
|
||||
"userName" to "",
|
||||
"reincarnationNum" to 0,
|
||||
"level" to 0,
|
||||
"exp" to 0,
|
||||
"playerRating" to 0,
|
||||
"lastGameId" to "",
|
||||
"lastRomVersion" to "",
|
||||
"lastDataVersion" to "",
|
||||
"lastPlayDate" to "",
|
||||
"nameplateId" to 0,
|
||||
"trophyId" to 0,
|
||||
"cardId" to 0,
|
||||
"dispPlayerLv" to 0,
|
||||
"dispRating" to 0,
|
||||
"dispBP" to 0,
|
||||
"headphone" to 0,
|
||||
"banStatus" to 0,
|
||||
"isWarningConfirmed" to true
|
||||
)
|
||||
val o = db.option.findSingleByUser(u)()
|
||||
|
||||
val res = mutableMapOf(
|
||||
@@ -137,7 +162,7 @@ fun OngekiController.initUser() {
|
||||
)
|
||||
|
||||
if (u.card?.status == CardStatus.MIGRATED_TO_MINATO) {
|
||||
res["userName"] = "Migrated"
|
||||
res["userName"] = "JiaQQqun / CardMigrated"
|
||||
res["level"] = 0
|
||||
res["exp"] = 0
|
||||
res["playerRating"] = 0
|
||||
@@ -186,4 +211,4 @@ fun OngekiController.initUser() {
|
||||
|
||||
l to mapOf("rivalUserId" to rivalUserId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import icu.samnyan.aqua.net.games.*
|
||||
import icu.samnyan.aqua.sega.general.model.Card
|
||||
import icu.samnyan.aqua.sega.util.jackson.AccessCodeSerializer
|
||||
import jakarta.persistence.*
|
||||
import java.time.LocalDate
|
||||
|
||||
@MappedSuperclass
|
||||
class OngekiUserEntity : BaseEntity(), IUserEntity<UserData> {
|
||||
@@ -511,4 +512,15 @@ class UserSkin : OngekiUserEntity() {
|
||||
var cardId1 = 0
|
||||
var cardId2 = 0
|
||||
var cardId3 = 0
|
||||
}
|
||||
|
||||
@Entity(name = "OngekiUserRegions")
|
||||
@Table(
|
||||
name = "ongeki_user_regions",
|
||||
uniqueConstraints = [UniqueConstraint(columnNames = ["user_id", "regionId"])]
|
||||
)
|
||||
class UserRegions : OngekiUserEntity() {
|
||||
var regionId = 0
|
||||
var playCount = 1
|
||||
var created: String = LocalDate.now().toString()
|
||||
}
|
||||
@@ -225,7 +225,7 @@ fun WaccaServer.init() {
|
||||
val status = u.lStatus().toMutableList()
|
||||
|
||||
if (u.card?.status == CardStatus.MIGRATED_TO_MINATO) {
|
||||
status[1] = "Migrated"
|
||||
status[1] = "JiaQQqun / CardMigrated"
|
||||
}
|
||||
|
||||
u.run { ls(
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE aqua_net_user_fedy
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
au_id BIGINT NOT NULL,
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT fk_fedy_on_aqua_net_user FOREIGN KEY (au_id) REFERENCES aqua_net_user (au_id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT unq_fedy_on_aqua_net_user UNIQUE (au_id)
|
||||
);
|
||||
@@ -1,157 +1,157 @@
|
||||
INSERT INTO chusan_game_event (id, type, end_date, start_date, enable)
|
||||
VALUES
|
||||
(51,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(52,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(53,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(1021,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3027,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3217,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3309,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3412,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3514,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3623,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3726,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3808,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3912,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4010,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4111,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4210,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4323,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4513,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4614,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4710,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4808,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4909,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4911,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5026,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5112,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5216,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5311,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5360,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5410,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5513,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5630,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5708,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5819,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5920,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(6020,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(6130,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(6221,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(6319,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(6409,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(6511,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(11159,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12580,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12582,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12584,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12586,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12587,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12602,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12611,12,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12613,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13060,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13451,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13453,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13504,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13506,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13507,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13513,12,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13552,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13553,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13616,7,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13617,7,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13651,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15150,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15151,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15152,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15156,14,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15157,7,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15158,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15200,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15201,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15202,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15203,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15204,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15205,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15206,11,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15207,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15208,14,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15209,12,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15210,10,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15211,4,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15212,5,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15213,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15250,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15251,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15252,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15253,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15254,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15255,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15256,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15480,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15481,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15482,7,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15483,7,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15560,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16100,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16101,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16102,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16103,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16104,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16105,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16106,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16107,16,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16108,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16109,4,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16110,5,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16111,11,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16150,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16151,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16152,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16153,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16154,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16155,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16156,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16157,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16158,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16159,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16160,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16161,14,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16162,10,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16163,11,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16164,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16165,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16200,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16201,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16202,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16203,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16204,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16205,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16206,14,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16207,5,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16208,4,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16209,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16250,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16251,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16252,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16253,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16254,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16255,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16256,12,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16257,11,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16258,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16300,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16301,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16302,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16303,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16304,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16305,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16306,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16307,16,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16308,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16309,10,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16310,5,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16311,4,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16312,11,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(99000,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
INSERT INTO chusan_game_event (id, type, end_date, start_date, enable)
|
||||
VALUES
|
||||
(51,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(52,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(53,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(1021,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3027,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3217,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3309,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3412,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3514,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3623,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3726,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3808,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(3912,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4010,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4111,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4210,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4323,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4513,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4614,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4710,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4808,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4909,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(4911,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5026,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5112,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5216,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5311,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5360,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5410,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5513,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5630,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5708,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5819,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(5920,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(6020,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(6130,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(6221,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(6319,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(6409,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(6511,9,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(11159,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12580,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12582,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12584,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12586,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12587,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12602,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12611,12,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(12613,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13060,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13451,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13453,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13504,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13506,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13507,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13513,12,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13552,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13553,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13616,7,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13617,7,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(13651,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15150,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15151,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15152,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15156,14,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15157,7,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15158,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15200,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15201,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15202,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15203,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15204,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15205,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15206,11,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15207,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15208,14,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15209,12,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15210,10,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15211,4,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15212,5,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15213,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15250,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15251,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15252,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15253,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15254,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15255,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15256,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15480,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15481,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15482,7,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15483,7,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(15560,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16100,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16101,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16102,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16103,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16104,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16105,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16106,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16107,16,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16108,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16109,4,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16110,5,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16111,11,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16150,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16151,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16152,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16153,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16154,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16155,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16156,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16157,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16158,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16159,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16160,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16161,14,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16162,10,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16163,11,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16164,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16165,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16200,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16201,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16202,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16203,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16204,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16205,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16206,14,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16207,5,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16208,4,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16209,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16250,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16251,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16252,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16253,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16254,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16255,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16256,12,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16257,11,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16258,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16300,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16301,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16302,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16303,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16304,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16305,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16306,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16307,16,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16308,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16309,10,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16310,5,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16311,4,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16312,11,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(99000,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(99001,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true);
|
||||
21
src/main/resources/db/80/V1000_52__cardmaker_event.sql
Normal file
21
src/main/resources/db/80/V1000_52__cardmaker_event.sql
Normal file
@@ -0,0 +1,21 @@
|
||||
INSERT INTO `maimai2_game_selling_card` (`card_id`,`start_date`, `end_date`, `notice_start_date`, `notice_end_date`) VALUES
|
||||
(5504014, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5504016, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5503014, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5503016, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5502014, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5502016, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5501014, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5501016, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5500014, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5500016, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5003014, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5003016, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5002014, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5002016, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5001014, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5001016, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5000014, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5000016, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5505014, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000'),
|
||||
(5505016, '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000', '2019-01-01 00:00:00.000000', '2029-01-01 00:00:00.000000');
|
||||
@@ -0,0 +1,27 @@
|
||||
INSERT INTO chusan_game_event (id, type, end_date, start_date, enable)
|
||||
VALUES
|
||||
(16600,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16601,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16602,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16603,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16604,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16605,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16606,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16607,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16608,16,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16609,5,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16610,4,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16611,11,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16612,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16650,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16651,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16652,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16653,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16654,8,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16655,11,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16700,3,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16701,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16702,1,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16703,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16704,2,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16705,5,'2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true);
|
||||
@@ -0,0 +1,16 @@
|
||||
INSERT INTO chusan_game_event (id, type, end_date, start_date, enable)
|
||||
VALUES
|
||||
(16550, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16551, 3, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16552, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16553, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16554, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16555, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16556, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16557, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16558, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16559, 2, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16560, 8, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16561, 1, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16562, 7, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true),
|
||||
(16563, 10, '2029-01-01 00:00:00.000000','2019-01-01 00:00:00.000000',true);
|
||||
22
src/main/resources/db/80/V1000_55__net_session.sql
Normal file
22
src/main/resources/db/80/V1000_55__net_session.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
CREATE TABLE aqua_net_session
|
||||
(
|
||||
token VARCHAR(36) NOT NULL,
|
||||
expiry datetime NOT NULL,
|
||||
au_id BIGINT NULL,
|
||||
CONSTRAINT pk_session PRIMARY KEY (token)
|
||||
);
|
||||
|
||||
ALTER TABLE aqua_net_session
|
||||
ADD CONSTRAINT FK_SESSION FOREIGN KEY (au_id) REFERENCES aqua_net_user (au_id);
|
||||
|
||||
CREATE TABLE aqua_net_email_reset_password
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
token VARCHAR(255) NOT NULL,
|
||||
created_at datetime NOT NULL,
|
||||
au_id BIGINT NULL,
|
||||
CONSTRAINT pk_email_reset_password PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
ALTER TABLE aqua_net_email_reset_password
|
||||
ADD CONSTRAINT FK_EMAIL_RESET_PASSWORD_ON_AQUA_USER FOREIGN KEY (au_id) REFERENCES aqua_net_user (au_id);
|
||||
38
src/main/resources/db/80/V1000_56__prefectures.sql
Normal file
38
src/main/resources/db/80/V1000_56__prefectures.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
CREATE TABLE chusan_user_regions
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
user_id BIGINT NULL,
|
||||
region_id INT NOT NULL,
|
||||
play_count INT NOT NULL DEFAULT 1,
|
||||
created VARCHAR(355),
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT fk_chusanregions_on_chusan_user_Data FOREIGN KEY (user_id) REFERENCES chusan_user_data (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT unq_chusanregions_on_region_user UNIQUE (user_id, region_id)
|
||||
);
|
||||
|
||||
CREATE TABLE ongeki_user_regions
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
user_id BIGINT NULL,
|
||||
region_id INT NOT NULL,
|
||||
play_count INT NOT NULL DEFAULT 1,
|
||||
created VARCHAR(355),
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT fk_ongekiregions_on_aqua_net_user FOREIGN KEY (user_id) REFERENCES aqua_net_user (au_id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT unq_ongekiregions_on_region_user UNIQUE (user_id, region_id)
|
||||
);
|
||||
|
||||
CREATE TABLE maimai2_user_regions
|
||||
(
|
||||
id BIGINT AUTO_INCREMENT NOT NULL,
|
||||
user_id BIGINT NULL,
|
||||
region_id INT NOT NULL,
|
||||
play_count INT NOT NULL DEFAULT 1,
|
||||
created VARCHAR(355),
|
||||
PRIMARY KEY (id),
|
||||
CONSTRAINT fk_maimai2regions_on_user_Details FOREIGN KEY (user_id) REFERENCES maimai2_user_detail (id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
CONSTRAINT unq_maimai2regions_on_region_user UNIQUE (user_id, region_id)
|
||||
);
|
||||
|
||||
ALTER TABLE aqua_net_user
|
||||
ADD COLUMN region VARCHAR(2) NOT NULL DEFAULT '1';
|
||||
@@ -212,7 +212,7 @@
|
||||
<div style="color:#101112;direction:ltr;font-family:'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif;font-size:16px;font-weight:400;letter-spacing:0px;line-height:120%;text-align:left;mso-line-height-alt:19.2px;">
|
||||
<p style="margin: 0; margin-bottom: 16px;">Dear {{name}},</p>
|
||||
<p style="margin: 0; margin-bottom: 16px;">Thank you for registering with AquaDX! We're excited to have you on board. To complete your registration and verify your email address, please click the link below.</p>
|
||||
<p style="margin: 0;">This link will confirm your email address, and it is valid for 24 hours. If you did not initiate this request, please ignore this email.</p>
|
||||
<p style="margin: 0;">This link will verify your email address, and it is valid for 24 hours. If you did not initiate this request, please ignore this email.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -225,7 +225,7 @@
|
||||
<w:anchorlock/>
|
||||
<v:textbox inset="0px,0px,0px,0px">
|
||||
<center style="color:#ffffff; font-family:'Trebuchet MS', Tahoma, sans-serif; font-size:16px">
|
||||
<![endif]--><a href="{{url}}" style="text-decoration:none;display:inline-block;color:#ffffff;background-color:#646cff;border-radius:8px;width:auto;border-top:0px solid transparent;font-weight:400;border-right:0px solid transparent;border-bottom:0px solid transparent;border-left:0px solid transparent;padding-top:8px;padding-bottom:8px;font-family:'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif;font-size:16px;text-align:center;mso-border-alt:none;word-break:keep-all;" target="_blank"><span style="padding-left:16px;padding-right:16px;font-size:16px;display:inline-block;letter-spacing:normal;"><span style="word-break: break-word; line-height: 32px;">Confirm email</span></span></a><!--[if mso]></center></v:textbox></v:roundrect><![endif]--></div>
|
||||
<![endif]--><a href="{{url}}" style="text-decoration:none;display:inline-block;color:#ffffff;background-color:#646cff;border-radius:8px;width:auto;border-top:0px solid transparent;font-weight:400;border-right:0px solid transparent;border-bottom:0px solid transparent;border-left:0px solid transparent;padding-top:8px;padding-bottom:8px;font-family:'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif;font-size:16px;text-align:center;mso-border-alt:none;word-break:keep-all;" target="_blank"><span style="padding-left:16px;padding-right:16px;font-size:16px;display:inline-block;letter-spacing:normal;"><span style="word-break: break-word; line-height: 32px;">Verify email</span></span></a><!--[if mso]></center></v:textbox></v:roundrect><![endif]--></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
272
src/main/resources/email/reset.html
Normal file
272
src/main/resources/email/reset.html
Normal file
@@ -0,0 +1,272 @@
|
||||
<!DOCTYPE html>
|
||||
|
||||
<html lang="en" xmlns:o="urn:schemas-microsoft-com:office:office" xmlns:v="urn:schemas-microsoft-com:vml">
|
||||
<head>
|
||||
<title></title>
|
||||
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/><!--[if mso]><xml><o:OfficeDocumentSettings><o:PixelsPerInch>96</o:PixelsPerInch><o:AllowPNG/></o:OfficeDocumentSettings></xml><![endif]--><!--[if !mso]><!-->
|
||||
<link href="https://fonts.googleapis.com/css2?family=Montserrat:wght@100;200;300;400;500;600;700;800;900" rel="stylesheet" type="text/css"/><!--<![endif]-->
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
a[x-apple-data-detectors] {
|
||||
color: inherit !important;
|
||||
text-decoration: inherit !important;
|
||||
}
|
||||
|
||||
#MessageViewBody a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
p {
|
||||
line-height: inherit
|
||||
}
|
||||
|
||||
.desktop_hide,
|
||||
.desktop_hide table {
|
||||
mso-hide: all;
|
||||
display: none;
|
||||
max-height: 0px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.image_block img+div {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@media (max-width:700px) {
|
||||
|
||||
.desktop_hide table.icons-inner,
|
||||
.row-3 .column-1 .block-3.button_block .alignment a,
|
||||
.row-3 .column-1 .block-3.button_block .alignment div {
|
||||
display: inline-block !important;
|
||||
}
|
||||
|
||||
.icons-inner {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.icons-inner td {
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.image_block div.fullWidth {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
|
||||
.mobile_hide {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.row-content {
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.stack .column {
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.mobile_hide {
|
||||
min-height: 0;
|
||||
max-height: 0;
|
||||
max-width: 0;
|
||||
overflow: hidden;
|
||||
font-size: 0px;
|
||||
}
|
||||
|
||||
.desktop_hide,
|
||||
.desktop_hide table {
|
||||
display: table !important;
|
||||
max-height: none !important;
|
||||
}
|
||||
|
||||
.row-1 .column-1 .block-1.icons_block .alignment,
|
||||
.row-3 .column-1 .block-3.button_block .alignment {
|
||||
text-align: center !important;
|
||||
}
|
||||
|
||||
.row-3 .column-1 .block-2.paragraph_block td.pad>div {
|
||||
text-align: left !important;
|
||||
font-size: 14px !important;
|
||||
}
|
||||
|
||||
.row-3 .column-1 .block-1.heading_block h1 {
|
||||
text-align: center !important;
|
||||
font-size: 24px !important;
|
||||
}
|
||||
|
||||
.row-3 .column-1 .block-1.heading_block td.pad {
|
||||
padding: 15px 0 !important;
|
||||
}
|
||||
|
||||
.row-3 .column-1 .block-4.paragraph_block td.pad>div {
|
||||
text-align: justify !important;
|
||||
font-size: 10px !important;
|
||||
}
|
||||
|
||||
.row-3 .column-1 .block-3.button_block a,
|
||||
.row-3 .column-1 .block-3.button_block div,
|
||||
.row-3 .column-1 .block-3.button_block span {
|
||||
font-size: 14px !important;
|
||||
line-height: 28px !important;
|
||||
}
|
||||
|
||||
.row-3 .column-1 {
|
||||
padding: 0 24px 48px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body style="background-color: #f8f6ff; margin: 0; padding: 0; -webkit-text-size-adjust: none; text-size-adjust: none;">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="nl-container" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #f8f6ff; background-image: none; background-position: top left; background-size: auto; background-repeat: no-repeat;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" class="row row-1" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" class="row-content stack" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #99b2ff; color: #000000; width: 680px; margin: 0 auto;" width="680">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" style="font-weight: 400; text-align: left; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding-bottom: 32px; padding-left: 48px; padding-right: 48px; padding-top: 32px; vertical-align: top; border-top: 0px; border-right: 0px; border-bottom: 0px; border-left: 0px;" width="100%">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="icons_block block-1" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;" width="100%">
|
||||
<tr>
|
||||
<td class="pad" style="vertical-align: middle; color: white; font-family: inherit; font-size: 24px; font-weight: 400; letter-spacing: 6px; text-align: left;">
|
||||
<table cellpadding="0" cellspacing="0" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;" width="100%">
|
||||
<tr>
|
||||
<td class="alignment" style="vertical-align: middle; text-align: left;"><!--[if vml]><table align="left" cellpadding="0" cellspacing="0" role="presentation" style="display:inline-block;padding-left:0px;padding-right:0px;mso-table-lspace: 0pt;mso-table-rspace: 0pt;"><![endif]-->
|
||||
<!--[if !vml]><!-->
|
||||
<table cellpadding="0" cellspacing="0" class="icons-inner" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; display: inline-block; margin-right: -4px; padding-left: 0px; padding-right: 0px;"><!--<![endif]-->
|
||||
<tr>
|
||||
<td style="vertical-align: middle; text-align: center; padding-top: 0px; padding-bottom: 0px; padding-left: 0px; padding-right: 15px;"><img align="center" class="icon" height="32" src="https://aquadx.net/assets/icons/android-chrome-192x192.png" style="display: block; height: auto; margin: 0 auto; border: 0;" width="32"/></td>
|
||||
<td style="font-family: 'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif; font-size: 24px; font-weight: 400; color: white; vertical-align: middle; letter-spacing: 6px; text-align: left;">AquaDX</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" class="row row-2" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" class="row-content stack" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #99b2ff; color: #000000; border-radius: 0; width: 680px; margin: 0 auto;" width="680">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" style="font-weight: 400; text-align: left; mso-table-lspace: 0pt; mso-table-rspace: 0pt; vertical-align: top; border-top: 0px; border-right: 0px; border-bottom: 0px; border-left: 0px;" width="100%">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="image_block block-1" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;" width="100%">
|
||||
<tr>
|
||||
<td class="pad" style="width:100%;padding-right:0px;padding-left:0px;">
|
||||
<div align="center" class="alignment" style="line-height:10px">
|
||||
<div class="fullWidth" style="max-width: 646px;"><img alt="" src="https://aquadx.net/assets/email/border.png" style="display: block; height: auto; border: 0; width: 100%;" title="An open email illustration" width="646"/></div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" class="row row-3" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" class="row-content stack" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: white; border-radius: 0; color: #000000; width: 680px; margin: 0 auto;" width="680">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" style="font-weight: 400; text-align: left; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding-bottom: 48px; padding-left: 48px; padding-right: 48px; vertical-align: top; border-top: 0px; border-right: 0px; border-bottom: 0px; border-left: 0px;" width="100%">
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="heading_block block-1" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;" width="100%">
|
||||
<tr>
|
||||
<td class="pad" style="padding-bottom:12px;text-align:center;width:100%;">
|
||||
<h1 style="margin: 0; color: #292929; direction: ltr; font-family: 'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif; font-size: 32px; font-weight: 700; letter-spacing: normal; line-height: 120%; text-align: left; margin-top: 0; margin-bottom: 0; mso-line-height-alt: 38.4px;"><span class="tinyMce-placeholder">Reset your password!</span></h1>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="paragraph_block block-2" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;" width="100%">
|
||||
<tr>
|
||||
<td class="pad" style="padding-bottom:10px;padding-top:10px;">
|
||||
<div style="color:#101112;direction:ltr;font-family:'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif;font-size:16px;font-weight:400;letter-spacing:0px;line-height:120%;text-align:left;mso-line-height-alt:19.2px;">
|
||||
<p style="margin: 0; margin-bottom: 16px;">Dear {{name}},</p>
|
||||
<p style="margin: 0; margin-bottom: 16px;">You recently requested to reset your AquaDX password. To reset your password, please click the link below.</p>
|
||||
<p style="margin: 0;">This link will allow you to reset your password, and it is valid for 24 hours. If you did not initiate this request, please ignore this email.</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="button_block block-3" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;" width="100%">
|
||||
<tr>
|
||||
<td class="pad" style="padding-bottom:15px;padding-top:15px;text-align:left;">
|
||||
<div align="left" class="alignment"><!--[if mso]>
|
||||
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" href="{{url}}" style="height:48px;width:168px;v-text-anchor:middle;" arcsize="17%" stroke="false" fillcolor="#646cff">
|
||||
<w:anchorlock/>
|
||||
<v:textbox inset="0px,0px,0px,0px">
|
||||
<center style="color:#ffffff; font-family:'Trebuchet MS', Tahoma, sans-serif; font-size:16px">
|
||||
<![endif]--><a href="{{url}}" style="text-decoration:none;display:inline-block;color:#ffffff;background-color:#646cff;border-radius:8px;width:auto;border-top:0px solid transparent;font-weight:400;border-right:0px solid transparent;border-bottom:0px solid transparent;border-left:0px solid transparent;padding-top:8px;padding-bottom:8px;font-family:'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif;font-size:16px;text-align:center;mso-border-alt:none;word-break:keep-all;" target="_blank"><span style="padding-left:16px;padding-right:16px;font-size:16px;display:inline-block;letter-spacing:normal;"><span style="word-break: break-word; line-height: 32px;">Reset password</span></span></a><!--[if mso]></center></v:textbox></v:roundrect><![endif]--></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<table border="0" cellpadding="0" cellspacing="0" class="paragraph_block block-4" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; word-break: break-word;" width="100%">
|
||||
<tr>
|
||||
<td class="pad" style="padding-top:16px;">
|
||||
<div style="color:#666666;direction:ltr;font-family:'Montserrat', 'Trebuchet MS', 'Lucida Grande', 'Lucida Sans Unicode', 'Lucida Sans', Tahoma, sans-serif;font-size:12px;font-weight:400;letter-spacing:0px;line-height:120%;text-align:left;mso-line-height-alt:14.399999999999999px;">
|
||||
<p style="margin: 0; margin-bottom: 12px;">If you're having trouble clicking the link, you can also copy and paste the URL below into your web browser:</p>
|
||||
<p style="margin: 0;">{{url}}</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" class="row row-4" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt;" width="100%">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" class="row-content stack" role="presentation" style="mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: #99b2ff; color: #000000; width: 680px; margin: 0 auto;" width="680">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="column column-1" style="font-weight: 400; text-align: left; mso-table-lspace: 0pt; mso-table-rspace: 0pt; padding-bottom: 32px; padding-left: 48px; padding-right: 48px; padding-top: 32px; vertical-align: top; border-top: 0px; border-right: 0px; border-bottom: 0px; border-left: 0px;" width="100%">
|
||||
<div class="spacer_block block-1" style="height:60px;line-height:60px;font-size:1px;"> </div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table><!-- End -->
|
||||
</body>
|
||||
</html>
|
||||
@@ -22,7 +22,7 @@
|
||||
<file>${LOG_FILE}</file>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<fileNamePattern>${LOG_FILE}.%d{yyyy-MM-dd}.gz</fileNamePattern>
|
||||
<maxHistory>7</maxHistory>
|
||||
<maxHistory>90</maxHistory>
|
||||
</rollingPolicy>
|
||||
<encoder class="net.logstash.logback.encoder.LogstashEncoder"/>
|
||||
</appender>
|
||||
|
||||
Reference in New Issue
Block a user