All checks were successful
Deploy / deploy (push) Successful in 3m58s
- Extract world-parse + packet capture into @albion/shared (reused by server and a new standalone agent workspace). - Migrate the data layer from SQLite/better-sqlite3 to shared Postgres (async pg with a ?->$n helper; economy engine preloads the item/recipe graph so its sync recursion still works). - API-key auth (ALBION_API_KEY) on all /api routes except health; web UI sends it via a fetch wrapper + key-entry gate; server serves the built UI with a path-traversal-safe static handler (drops @fastify/static). - Windows companion agent: pushes world map + captured market orders to /api/ingest/*; runs on the gaming PC where the game traffic lives. - Docker + docker-compose (external proxy + backend networks for Nginx Proxy Manager and shared Postgres) and a Forgejo deploy workflow. - Validated end-to-end against Postgres 16 and a full compose run. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
97 lines
3 KiB
TypeScript
97 lines
3 KiB
TypeScript
import type { FastifyInstance } from 'fastify';
|
|
import { getMeta, query, setMeta } from '../db/index.js';
|
|
import { CITIES, SERVER_HOSTS } from '../market/client.js';
|
|
import {
|
|
deleteOverride,
|
|
getAlbionServer,
|
|
getPricesForItem,
|
|
getRefreshJob,
|
|
marketSummary,
|
|
setAlbionServer,
|
|
setOverride,
|
|
startRefresh,
|
|
} from '../market/service.js';
|
|
|
|
export async function marketRoutes(app: FastifyInstance): Promise<void> {
|
|
app.get('/api/market/summary', async () => marketSummary());
|
|
|
|
app.get('/api/market/cities', async () => ({ cities: CITIES }));
|
|
|
|
app.get('/api/market/refresh/status', async () => getRefreshJob());
|
|
|
|
app.post('/api/market/refresh', async (req, reply) => {
|
|
const body = (req.body ?? {}) as {
|
|
minTier?: number;
|
|
maxTier?: number;
|
|
cities?: string[];
|
|
items?: string[];
|
|
};
|
|
const result = startRefresh(body);
|
|
if (!result.started) return reply.code(409).send({ error: result.reason });
|
|
return { started: true };
|
|
});
|
|
|
|
app.get('/api/market/prices/:item', async (req) => {
|
|
const { item } = req.params as { item: string };
|
|
return { prices: await getPricesForItem(item) };
|
|
});
|
|
|
|
app.put('/api/market/override', async (req, reply) => {
|
|
const body = req.body as {
|
|
item?: string;
|
|
city?: string;
|
|
quality?: number;
|
|
sellPrice?: number | null;
|
|
buyPrice?: number | null;
|
|
note?: string | null;
|
|
};
|
|
if (!body?.item || !body?.city) return reply.code(400).send({ error: 'item and city are required' });
|
|
await setOverride({
|
|
item: body.item,
|
|
city: body.city,
|
|
quality: body.quality,
|
|
sellPrice: body.sellPrice,
|
|
buyPrice: body.buyPrice,
|
|
note: body.note,
|
|
});
|
|
return { ok: true };
|
|
});
|
|
|
|
app.delete('/api/market/override', async (req, reply) => {
|
|
const { item, city, quality } = req.query as { item?: string; city?: string; quality?: string };
|
|
if (!item || !city) return reply.code(400).send({ error: 'item and city are required' });
|
|
await deleteOverride(item, city, quality ? Number(quality) : 1);
|
|
return { ok: true };
|
|
});
|
|
|
|
app.get('/api/settings', async () => ({
|
|
albionServer: await getAlbionServer(),
|
|
availableServers: Object.keys(SERVER_HOSTS),
|
|
proxyUrl: (await getMeta('proxy_url')) ?? '',
|
|
gamePath: (await getMeta('game_path')) ?? '',
|
|
}));
|
|
|
|
app.put('/api/settings', async (req, reply) => {
|
|
const { albionServer, proxyUrl, gamePath } = (req.body ?? {}) as {
|
|
albionServer?: string;
|
|
proxyUrl?: string;
|
|
gamePath?: string;
|
|
};
|
|
if (albionServer) {
|
|
try {
|
|
await setAlbionServer(albionServer);
|
|
} catch (e) {
|
|
return reply.code(400).send({ error: String(e) });
|
|
}
|
|
}
|
|
if (proxyUrl !== undefined) {
|
|
if (proxyUrl === '') await query("DELETE FROM meta WHERE key = 'proxy_url'");
|
|
else await setMeta('proxy_url', proxyUrl);
|
|
}
|
|
if (gamePath !== undefined) {
|
|
if (gamePath === '') await query("DELETE FROM meta WHERE key = 'game_path'");
|
|
else await setMeta('game_path', gamePath);
|
|
}
|
|
return { ok: true };
|
|
});
|
|
}
|