This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { json, isHttpError } from '@sveltejs/kit';
|
||||
import { authenticateBank } from '$lib/server/auth/bank.js';
|
||||
import { afkRequest, type AfkAction } from '$lib/server/fleet/afk.js';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
|
||||
export async function POST({ request, params }: { request: Request; params: { action: string } }) {
|
||||
const headers = { 'Cache-Control': 'no-store' };
|
||||
const authentication = authenticateBank(request);
|
||||
if (!authentication.ok)
|
||||
return json(
|
||||
{ ok: false, code: authentication.code },
|
||||
{ status: authentication.status, headers }
|
||||
);
|
||||
if (!['balance', 'event', 'purchase', 'reserve', 'start', 'settle'].includes(params.action))
|
||||
return json({ ok: false, code: 'invalid_action' }, { status: 404, headers });
|
||||
if (
|
||||
request.headers.get('content-type')?.split(';')[0].trim().toLowerCase() !==
|
||||
'application/json'
|
||||
)
|
||||
return json({ ok: false, code: 'invalid_content_type' }, { status: 415, headers });
|
||||
let value: unknown;
|
||||
try {
|
||||
value = await jsonBody(request, 4096);
|
||||
} catch (cause) {
|
||||
if (isHttpError(cause, 413)) throw cause;
|
||||
return json({ ok: false, code: 'invalid_request' }, { status: 400, headers });
|
||||
}
|
||||
try {
|
||||
const { body, status } = afkRequest(
|
||||
params.action as AfkAction,
|
||||
value,
|
||||
authentication.server,
|
||||
authentication.instanceId
|
||||
);
|
||||
return json(body, { status, headers });
|
||||
} catch {
|
||||
console.error('AFK transaction failed; request may be retried with the same identity.');
|
||||
return json({ ok: false, code: 'unavailable' }, { status: 503, headers });
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
import { jsonBody } from '$lib/server/request-body.js';
|
||||
import { afkCorrectSession } from '$lib/server/fleet/afk.js';
|
||||
|
||||
/** Human reconciliation is restricted to sessions already confirmed stopped. */
|
||||
export async function POST({ request, locals }: { request: Request; locals: App.Locals }) {
|
||||
if (!locals.user || locals.user.role !== 'admin') error(403, 'Administrator access required.');
|
||||
const result = afkCorrectSession(await jsonBody(request, 4096), locals.user.id);
|
||||
return json(result.body, { status: result.status, headers: { 'Cache-Control': 'no-store' } });
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import { error, json } from '@sveltejs/kit';
|
||||
import { authenticateAgent, objectBody } from '$lib/server/hosts/http.js';
|
||||
import { db } from '$lib/server/db/index.js';
|
||||
import { afkRequest } from '$lib/server/fleet/afk.js';
|
||||
|
||||
/** Only the assigned teardown host may reconcile a stopped runtime. */
|
||||
export async function POST({ request }: { request: Request }) {
|
||||
const host = authenticateAgent(request);
|
||||
const body = await objectBody(request, 24576);
|
||||
if (
|
||||
typeof body.operationId !== 'string' ||
|
||||
!['settle', 'event', 'purchase'].includes(String(body.action))
|
||||
)
|
||||
error(400, 'Invalid AFK recovery.');
|
||||
const scope = db()
|
||||
.prepare(
|
||||
`SELECT n.id, n.server FROM ec_host_operations o
|
||||
JOIN native_instances n ON n.id=o.instance_id
|
||||
WHERE o.id=? AND o.host_id=? AND o.kind IN ('instance.remove','instance.rebuild','instance.cancel-create')
|
||||
AND o.state IN ('pending','accepted','running','unknown')`
|
||||
)
|
||||
.get(body.operationId, host.id) as { id: string; server: string } | undefined;
|
||||
if (!scope) error(403, 'AFK recovery requires an assigned, unfinished teardown.');
|
||||
const result = afkRequest(
|
||||
body.action as 'settle' | 'event' | 'purchase',
|
||||
body.transfer,
|
||||
scope.server,
|
||||
scope.id,
|
||||
{ receiptOnly: body.action !== 'settle' }
|
||||
);
|
||||
return json(result.body, { status: result.status, headers: { 'Cache-Control': 'no-store' } });
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
import { bankProfile } from '$lib/server/fleet/bank.js';
|
||||
import { afkProfile } from '$lib/server/fleet/afk.js';
|
||||
import { error } from '@sveltejs/kit';
|
||||
import { playerReport } from '$lib/server/reports/index.js';
|
||||
import { isLeaderboardMetric } from '$lib/server/fleet/leaderboard.js';
|
||||
@@ -40,6 +41,7 @@ export async function load({ params, url }: { params: { guid: string }; url: URL
|
||||
return {
|
||||
profile,
|
||||
bank: bankProfile(params.guid),
|
||||
afk: afkProfile(params.guid),
|
||||
returnTo: leaderboardReturnHref(url.searchParams.get('returnTo')),
|
||||
scope: { game: game ?? '', map: map ?? '', players: players ?? '', metric, entry }
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script lang="ts">
|
||||
import type { bankProfile } from '$lib/server/fleet/bank.js';
|
||||
import type { afkProfile } from '$lib/server/fleet/afk.js';
|
||||
import AfkReserveCard from '$ui/components/AfkReserveCard.svelte';
|
||||
import Select from '$ui/components/primitives/Select.svelte';
|
||||
import Field from '$ui/components/primitives/Field.svelte';
|
||||
import RelativeTime from '$ui/components/RelativeTime.svelte';
|
||||
@@ -47,6 +49,7 @@
|
||||
data: {
|
||||
profile: PlayerProfile;
|
||||
bank: ReturnType<typeof bankProfile>;
|
||||
afk: ReturnType<typeof afkProfile>;
|
||||
returnTo: string;
|
||||
scope: {
|
||||
game: string;
|
||||
@@ -220,6 +223,7 @@
|
||||
</div>
|
||||
{:else}<p>No bank transfers yet.</p>{/if}
|
||||
</section>
|
||||
<AfkReserveCard account={data.afk} />
|
||||
<nav class="jump-links" aria-label="Player sections">
|
||||
<a href="#overview">Overview</a><a href="#maps-title">Maps</a><a href="#history-title"
|
||||
>Matches</a
|
||||
|
||||
Reference in New Issue
Block a user