Some checks failed
Build Android APK / build-apk (push) Failing after 1h11m4s
Portal (/crush33): password-gated page (PORTAL_PASSWORD) for admins to create entry-only tickets from just name + email, with a category (Guest/Worker/Performer/Volunteer/Speaker). Creates a 1-admission ticket, emails the QR, and shows the QR on-screen. New Ticket Type column. Scanner: shows a prominent TYPE badge (🎭 PERFORMER, 🛠️ WORKER, …) on the confirm + success screens and in admin, so staff can see it's a special ticket. Added Worker + Performer personas to /test. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
76 lines
2.7 KiB
TypeScript
76 lines
2.7 KiB
TypeScript
import { existsSync } from "node:fs";
|
|
import { join } from "node:path";
|
|
import Fastify from "fastify";
|
|
import jwt from "@fastify/jwt";
|
|
import rateLimit from "@fastify/rate-limit";
|
|
import fastifyStatic from "@fastify/static";
|
|
import formbody from "@fastify/formbody";
|
|
import { loadConfig } from "./config.js";
|
|
import { buildContext } from "./context.js";
|
|
import { authRoutes } from "./routes/auth.js";
|
|
import { webhookRoutes } from "./routes/webhook.js";
|
|
import { ticketRoutes } from "./routes/tickets.js";
|
|
import { testRoutes } from "./routes/test.js";
|
|
import { installRoutes } from "./routes/install.js";
|
|
import { webhookDocRoutes } from "./routes/webhookDoc.js";
|
|
import { publicLookupRoutes } from "./routes/publicLookup.js";
|
|
import { portalRoutes } from "./routes/portal.js";
|
|
|
|
export async function build() {
|
|
const config = loadConfig();
|
|
const app = Fastify({
|
|
logger: { level: config.LOG_LEVEL },
|
|
trustProxy: true, // behind nginx
|
|
bodyLimit: 1_000_000,
|
|
});
|
|
|
|
app.decorate("ctx", buildContext(config));
|
|
|
|
await app.register(jwt, { secret: config.TOKEN_SECRET });
|
|
await app.register(rateLimit, { global: false });
|
|
await app.register(formbody); // accept application/x-www-form-urlencoded webhooks too
|
|
|
|
await app.register(authRoutes);
|
|
await app.register(webhookRoutes);
|
|
await app.register(ticketRoutes);
|
|
await app.register(testRoutes);
|
|
await app.register(installRoutes);
|
|
await app.register(webhookDocRoutes);
|
|
await app.register(publicLookupRoutes);
|
|
await app.register(portalRoutes);
|
|
|
|
// Serve the exported Expo web build (if present) with SPA fallback.
|
|
const webDir = config.WEB_DIR ?? join(process.cwd(), "web");
|
|
if (existsSync(webDir)) {
|
|
await app.register(fastifyStatic, { root: webDir, wildcard: false });
|
|
app.setNotFoundHandler((req, reply) => {
|
|
// Let unknown /api routes 404 as JSON; everything else -> SPA index.
|
|
if (req.raw.url && req.raw.url.startsWith("/api")) {
|
|
return reply.code(404).send({ error: "not_found" });
|
|
}
|
|
return reply.sendFile("index.html");
|
|
});
|
|
app.log.info({ webDir }, "serving static web build");
|
|
} else {
|
|
app.log.warn({ webDir }, "no web build found; serving API only");
|
|
}
|
|
|
|
return app;
|
|
}
|
|
|
|
// Entry point (skipped when imported by tests).
|
|
const isMain = process.argv[1] && import.meta.url === `file://${process.argv[1]}`;
|
|
if (isMain) {
|
|
const config = loadConfig();
|
|
build()
|
|
.then((app) => app.listen({ port: config.PORT, host: config.HOST }))
|
|
.then((addr) => {
|
|
// eslint-disable-next-line no-console
|
|
console.log(`camptickets backend listening on ${addr}`);
|
|
})
|
|
.catch((err) => {
|
|
// eslint-disable-next-line no-console
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|
|
}
|