Android build v1.1.14

This commit is contained in:
Warren H
2026-07-03 12:58:53 -04:00
parent a121b2ef74
commit a849cd2c69
9 changed files with 403 additions and 73 deletions
+78 -22
View File
@@ -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,