Android build v1.1.14
This commit is contained in:
+78
-22
@@ -37,6 +37,35 @@ const pvpMatches = new Map()
|
||||
const pvpQueueTtlMs = 15 * 1000
|
||||
const pvpMatchTtlMs = 60 * 60 * 1000
|
||||
|
||||
function addColumnIfMissing(database, table, column, definition) {
|
||||
const columns = database.prepare(`PRAGMA table_info(${table})`).all()
|
||||
if (!columns.some((candidate) => candidate.name === column)) {
|
||||
database.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`)
|
||||
}
|
||||
}
|
||||
|
||||
function ensureRuntimeMigrations(database) {
|
||||
addColumnIfMissing(database, 'accounts', 'last_saved_at', 'TEXT')
|
||||
database.prepare(`
|
||||
UPDATE accounts
|
||||
SET last_saved_at = COALESCE(NULLIF(last_saved_at, ''), created_at, CURRENT_TIMESTAMP)
|
||||
WHERE last_saved_at IS NULL OR last_saved_at = ''
|
||||
`).run()
|
||||
}
|
||||
|
||||
function sqliteDateFromMs(value) {
|
||||
const timestamp = Number(value)
|
||||
if (!Number.isFinite(timestamp) || timestamp <= 0) {
|
||||
return new Date().toISOString()
|
||||
}
|
||||
return new Date(timestamp).toISOString()
|
||||
}
|
||||
|
||||
function epochMsFromSqliteDate(value) {
|
||||
const timestamp = Date.parse(String(value ?? ''))
|
||||
return Number.isFinite(timestamp) ? timestamp : 0
|
||||
}
|
||||
|
||||
function sendJson(response, status, body, headers = {}) {
|
||||
response.statusCode = status
|
||||
response.setHeader('Content-Type', 'application/json')
|
||||
@@ -44,6 +73,19 @@ function sendJson(response, status, body, headers = {}) {
|
||||
response.end(JSON.stringify(body))
|
||||
}
|
||||
|
||||
function touchAccountSave(database, accountId) {
|
||||
database.prepare(`
|
||||
UPDATE accounts
|
||||
SET last_saved_at = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`).run(accountId)
|
||||
}
|
||||
|
||||
function sendMutatingResult(response, database, accountId, body) {
|
||||
touchAccountSave(database, accountId)
|
||||
sendJson(response, 200, body)
|
||||
}
|
||||
|
||||
function configuredCorsOrigins() {
|
||||
return String(process.env.CORS_ORIGINS ?? process.env.AUTH_CORS_ORIGINS ?? '')
|
||||
.split(',')
|
||||
@@ -1027,7 +1069,8 @@ function buildSyncSave(database, accountId, activeCharacterId) {
|
||||
const account = database.prepare(`
|
||||
SELECT
|
||||
completed_dungeon_parts AS completedDungeonParts,
|
||||
completed_raid_phases AS completedRaidPhases
|
||||
completed_raid_phases AS completedRaidPhases,
|
||||
last_saved_at AS lastSavedAt
|
||||
FROM accounts
|
||||
WHERE id = ?
|
||||
`).get(accountId)
|
||||
@@ -1058,6 +1101,7 @@ function buildSyncSave(database, accountId, activeCharacterId) {
|
||||
`).get(activeCharacterId)
|
||||
return {
|
||||
version: 4,
|
||||
updatedAt: epochMsFromSqliteDate(account?.lastSavedAt),
|
||||
characterName,
|
||||
activeClassId,
|
||||
completedDungeonParts: account?.completedDungeonParts ?? 0,
|
||||
@@ -1164,11 +1208,12 @@ function importSyncSave(database, accountId, activeCharacterId, payload) {
|
||||
|
||||
database.prepare(`
|
||||
UPDATE accounts
|
||||
SET completed_dungeon_parts = ?, completed_raid_phases = ?
|
||||
SET completed_dungeon_parts = ?, completed_raid_phases = ?, last_saved_at = ?
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
clampInteger(save.completedDungeonParts, 0, 0, 3),
|
||||
clampInteger(save.completedRaidPhases, 0, 0, 3),
|
||||
sqliteDateFromMs(save.updatedAt),
|
||||
accountId,
|
||||
)
|
||||
|
||||
@@ -3004,6 +3049,7 @@ export async function handleAuthApiRequest(request, response, next = null) {
|
||||
|
||||
const database = new DatabaseSync(databasePath)
|
||||
database.exec('PRAGMA foreign_keys = ON')
|
||||
ensureRuntimeMigrations(database)
|
||||
|
||||
try {
|
||||
const ip = requestIp(request)
|
||||
@@ -3060,6 +3106,7 @@ export async function handleApiRequest(request, response, next) {
|
||||
|
||||
const database = new DatabaseSync(databasePath)
|
||||
database.exec('PRAGMA foreign_keys = ON')
|
||||
ensureRuntimeMigrations(database)
|
||||
|
||||
try {
|
||||
const ip = requestIp(request)
|
||||
@@ -3098,7 +3145,7 @@ export async function handleApiRequest(request, response, next) {
|
||||
if (request.url === '/api/profile' && request.method === 'PUT') {
|
||||
const payload = await readJson(request)
|
||||
const newCharacterId = saveProfile(database, session.characterId, session.accountId, payload)
|
||||
sendJson(response, 200, getProfile(database, newCharacterId, session.accountId))
|
||||
sendMutatingResult(response, database, session.accountId, getProfile(database, newCharacterId, session.accountId))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -3148,9 +3195,10 @@ export async function handleApiRequest(request, response, next) {
|
||||
const dungeonCompletion = request.url.match(/^\/api\/dungeons\/(\d+)\/complete$/)
|
||||
if (dungeonCompletion && request.method === 'POST') {
|
||||
const payload = await readJson(request)
|
||||
sendJson(
|
||||
sendMutatingResult(
|
||||
response,
|
||||
200,
|
||||
database,
|
||||
session.accountId,
|
||||
completeDungeon(
|
||||
database,
|
||||
session.characterId,
|
||||
@@ -3165,9 +3213,10 @@ export async function handleApiRequest(request, response, next) {
|
||||
|
||||
if (request.url === '/api/roguelike/complete' && request.method === 'POST') {
|
||||
const payload = await readJson(request)
|
||||
sendJson(
|
||||
sendMutatingResult(
|
||||
response,
|
||||
200,
|
||||
database,
|
||||
session.accountId,
|
||||
completeRoguelike(database, session.characterId, session.accountId, payload),
|
||||
)
|
||||
return
|
||||
@@ -3175,24 +3224,26 @@ export async function handleApiRequest(request, response, next) {
|
||||
|
||||
const talentAllocation = request.url.match(/^\/api\/talents\/(\d+)\/allocate$/)
|
||||
if (talentAllocation && request.method === 'POST') {
|
||||
sendJson(
|
||||
sendMutatingResult(
|
||||
response,
|
||||
200,
|
||||
database,
|
||||
session.accountId,
|
||||
allocateTalent(database, session.characterId, Number(talentAllocation[1])),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (request.url === '/api/talents/reset' && request.method === 'POST') {
|
||||
sendJson(response, 200, resetTalents(database, session.characterId))
|
||||
sendMutatingResult(response, database, session.accountId, resetTalents(database, session.characterId))
|
||||
return
|
||||
}
|
||||
|
||||
const itemEquip = request.url.match(/^\/api\/equipment\/(\d+)\/equip$/)
|
||||
if (itemEquip && request.method === 'POST') {
|
||||
sendJson(
|
||||
sendMutatingResult(
|
||||
response,
|
||||
200,
|
||||
database,
|
||||
session.accountId,
|
||||
equipItem(database, session.characterId, Number(itemEquip[1])),
|
||||
)
|
||||
return
|
||||
@@ -3200,9 +3251,10 @@ export async function handleApiRequest(request, response, next) {
|
||||
|
||||
const itemDiscard = request.url.match(/^\/api\/equipment\/(\d+)\/discard-extra$/)
|
||||
if (itemDiscard && request.method === 'POST') {
|
||||
sendJson(
|
||||
sendMutatingResult(
|
||||
response,
|
||||
200,
|
||||
database,
|
||||
session.accountId,
|
||||
discardExtraItem(database, session.characterId, Number(itemDiscard[1])),
|
||||
)
|
||||
return
|
||||
@@ -3210,9 +3262,10 @@ export async function handleApiRequest(request, response, next) {
|
||||
|
||||
const itemBreakdown = request.url.match(/^\/api\/equipment\/(\d+)\/breakdown$/)
|
||||
if (itemBreakdown && request.method === 'POST') {
|
||||
sendJson(
|
||||
sendMutatingResult(
|
||||
response,
|
||||
200,
|
||||
database,
|
||||
session.accountId,
|
||||
breakdownItem(database, session.characterId, Number(itemBreakdown[1])),
|
||||
)
|
||||
return
|
||||
@@ -3220,9 +3273,10 @@ export async function handleApiRequest(request, response, next) {
|
||||
|
||||
const recipeCraft = request.url.match(/^\/api\/crafting\/recipes\/(\d+)\/craft$/)
|
||||
if (recipeCraft && request.method === 'POST') {
|
||||
sendJson(
|
||||
sendMutatingResult(
|
||||
response,
|
||||
200,
|
||||
database,
|
||||
session.accountId,
|
||||
craftItem(database, session.characterId, Number(recipeCraft[1])),
|
||||
)
|
||||
return
|
||||
@@ -3230,9 +3284,10 @@ export async function handleApiRequest(request, response, next) {
|
||||
|
||||
const itemUpgrade = request.url.match(/^\/api\/items\/(\d+)\/upgrade$/)
|
||||
if (itemUpgrade && request.method === 'POST') {
|
||||
sendJson(
|
||||
sendMutatingResult(
|
||||
response,
|
||||
200,
|
||||
database,
|
||||
session.accountId,
|
||||
upgradeItem(database, session.characterId, Number(itemUpgrade[1])),
|
||||
)
|
||||
return
|
||||
@@ -3241,9 +3296,10 @@ export async function handleApiRequest(request, response, next) {
|
||||
const encounterLootRoll = request.url.match(/^\/api\/encounters\/(\d+)\/loot-roll$/)
|
||||
if (encounterLootRoll && request.method === 'POST') {
|
||||
const payload = await readJson(request)
|
||||
sendJson(
|
||||
sendMutatingResult(
|
||||
response,
|
||||
200,
|
||||
database,
|
||||
session.accountId,
|
||||
rollEncounterLoot(
|
||||
database,
|
||||
session.characterId,
|
||||
|
||||
Reference in New Issue
Block a user