Files
deckyvault/lib/api/app.ts
T

221 lines
7.3 KiB
TypeScript

import { Elysia } from "elysia"
import { openapi } from "@elysia/openapi"
import { cron, Patterns } from "@elysia/cron"
import { auth } from "@/lib/auth"
import { rateLimit } from "@/lib/auth/rate-limit"
import { taskRegistry } from "./cron"
import {
healthRoutes,
userRoutes,
gamesRoutes,
gameVersionsRoutes,
gameSyncRoutes,
hardwareRoutes,
hardwareStatsRoutes,
performanceRoutes,
performanceVerifyRoutes,
performanceSubmitRoutes,
commentsRoutes,
savedGamesRoutes,
reportRoutes,
contactRoutes,
adminReportRoutes,
adminPerformanceRoutes,
adminCommentRoutes,
} from "@/lib/api"
import { adminStorageRoutes } from "@/lib/api/admin-storage"
import { adminAnalyticsRoutes } from "@/lib/api/admin-analytics"
import { steamSearchRoutes } from "@/lib/api/steam-search"
import { steamdbVersionRoutes } from "@/lib/api/steamdb-version"
import { searchUnifiedRoutes } from "@/lib/api/search-unified"
import { gameStubRoutes } from "@/lib/api/game-stub"
import { gameStatsRoutes } from "@/lib/api/game-stats"
import { gamesPerformanceRoutes } from "@/lib/api/games-performance"
import { gamesManualRoutes } from "@/lib/api/games-manual"
import { compareRoutes } from "@/lib/api/compare"
import { playabilityRoutes } from "@/lib/api/playability"
import { steamReviewRoutes } from "@/lib/api/steam-reviews"
import { communitySuggestionRoutes } from "@/lib/api/community-suggestions"
import { gamesListingRoutes } from "@/lib/api/games-listing"
import { steamgridProxyRoutes } from "@/lib/api/steamgrid-proxy"
import { dashboardRoutes } from "@/lib/api/dashboard"
import { dashboardPublicRoutes } from "@/lib/api/dashboard-public"
import { savedFilterRoutes } from "@/lib/api/saved-filters"
import { cronRoutes } from "@/lib/api/cron"
import { profilePhotoRoutes } from "@/lib/api/profile-photo"
import { screenshotRoutes } from "@/lib/api/screenshots"
import { mobileRoutes } from "@/lib/api/mobile"
const betterAuth = new Elysia({ name: "better-auth" })
.mount(auth.handler)
.macro({
auth: {
async resolve({ status, request: { headers } }) {
const session = await auth.api.getSession({
headers,
})
if (!session) return status(401)
return {
user: session.user,
session: session.session,
}
},
},
})
export const app = new Elysia({ prefix: "/api" })
.use(
openapi({
path: "/openapi",
embedSpec: true,
documentation: {
info: {
title: "DeckyVault API",
version: "2026.0.101",
description:
"API for DeckyVault — Steam Deck game compatibility, performance reports, and community features.",
},
tags: [
{ name: "Health", description: "Health check endpoints" },
{ name: "Auth", description: "Authentication endpoints" },
{ name: "Users", description: "User management" },
{ name: "Games", description: "Games listing and details" },
{ name: "Hardware", description: "Hardware submission and stats" },
{ name: "Performance", description: "Performance reports and verification" },
{ name: "Comments", description: "Game comments" },
{ name: "Reports", description: "User reports" },
{ name: "Admin", description: "Admin-only endpoints" },
{ name: "Steam", description: "Steam proxy endpoints" },
{ name: "Search", description: "Search endpoints" },
{ name: "Mobile", description: "Mobile-optimized consolidated endpoints" },
{ name: "Contact", description: "Contact form" },
{ name: "Dashboard", description: "Dashboard data" },
{ name: "Cron", description: "Scheduled job triggers" },
],
components: {
securitySchemes: {
bearerAuth: {
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
},
},
},
},
}),
)
.use(
cron({
name: "orphan_detection",
pattern: Patterns.EVERY_DAY_AT_2AM,
async run() {
const task = taskRegistry.get("orphan_detection")
if (!task) return
try {
const result = await task()
console.log("[cron] orphan_detection:", result.status, result.details)
} catch (err) {
console.error("[cron] orphan_detection failed:", err)
}
},
}),
)
.use(
cron({
name: "storage_cleanup",
pattern: Patterns.EVERY_DAY_AT_3AM,
async run() {
const task = taskRegistry.get("storage_cleanup")
if (!task) return
try {
const result = await task()
console.log("[cron] storage_cleanup:", result.status, result.details)
} catch (err) {
console.error("[cron] storage_cleanup failed:", err)
}
},
}),
)
.onError(({ code, error, set, request }) => {
console.error(
`[API Error] ${code} ${request.url}`,
error instanceof Error ? error.message : error,
)
set.status = code === "NOT_FOUND" ? 404 : 500
return {
error: code === "NOT_FOUND" ? "Not found" : "Internal server error",
}
})
// ── Auth routes (auth rate limit + betterAuth) ──────────────
.group("", (app) =>
app
.use(rateLimit("auth"))
.use(betterAuth)
.use(userRoutes)
.use(profilePhotoRoutes)
)
// ── Read-heavy public routes (read rate limit) ───────────────
.group("", (app) =>
app
.use(rateLimit("read"))
.use(healthRoutes)
.use(gamesRoutes)
.use(gameVersionsRoutes)
.use(gameSyncRoutes)
.use(gamesListingRoutes)
.use(hardwareRoutes)
.use(hardwareStatsRoutes)
.use(performanceRoutes)
.use(gameStatsRoutes)
.use(gamesPerformanceRoutes)
.use(dashboardRoutes)
.use(dashboardPublicRoutes)
.use(playabilityRoutes)
.use(steamReviewRoutes)
.use(compareRoutes)
.use(savedGamesRoutes)
.use(savedFilterRoutes)
.use(steamSearchRoutes)
.use(searchUnifiedRoutes)
.use(gameStubRoutes)
.use(steamgridProxyRoutes)
.use(steamdbVersionRoutes)
.use(gamesManualRoutes)
.use(screenshotRoutes)
.use(mobileRoutes)
)
// ── Write routes (write rate limit + betterAuth) ─────────────
.group("", (app) =>
app
.use(rateLimit("write"))
.use(betterAuth)
.use(performanceVerifyRoutes)
.use(performanceSubmitRoutes)
.use(commentsRoutes)
.use(reportRoutes)
.use(adminReportRoutes)
.use(adminPerformanceRoutes)
.use(adminCommentRoutes)
.use(adminStorageRoutes)
.use(adminAnalyticsRoutes)
)
// ── Strict rate limit (public forms, no auth) ───────────────
.group("", (app) =>
app
.use(rateLimit("strict"))
.use(contactRoutes)
.use(communitySuggestionRoutes)
)
// ── Cron (no rate limit) ─────────────────────────────────────
.use(cronRoutes)
// ── Root (default rate limit) ────────────────────────────────
.use(rateLimit("default"))
.get("/", () => ({
name: "DeckyVault API",
version: "2026.0.101",
}))
export type App = typeof app