feat: add OpenAPI support and in-process cron scheduler
- Install @elysia/openapi to auto-generate API docs at /api/openapi - Install @elysia/cron for in-process scheduled jobs (orphan detection at 2am, storage cleanup at 3am) - Add Scalar UI docs with 14 tag groups and Bearer JWT security scheme - Add route-level detail annotations (tags, summaries) to all 30+ route files - Upgrade crud-builder to accept optional tags config - Keep HTTP cron endpoint as manual/admin fallback
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
requireAdmin,
|
||||
} from "@/lib/auth/guard"
|
||||
|
||||
export const adminCommentRoutes = new Elysia({ prefix: "/admin" })
|
||||
export const adminCommentRoutes = new Elysia({ prefix: "/admin", detail: { tags: ["Admin"] } })
|
||||
.get(
|
||||
"/comments",
|
||||
async ({ query, request, set }) => {
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
requireAdmin,
|
||||
} from "@/lib/auth/guard"
|
||||
|
||||
export const adminPerformanceRoutes = new Elysia({ prefix: "/admin" })
|
||||
export const adminPerformanceRoutes = new Elysia({ prefix: "/admin", detail: { tags: ["Admin"] } })
|
||||
.get(
|
||||
"/performance",
|
||||
async ({ query, request, set }) => {
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import { eq, desc, sql, and, inArray } from "drizzle-orm"
|
||||
import { requireContributorOrAdmin } from "@/lib/auth/guard"
|
||||
|
||||
export const adminReportRoutes = new Elysia({ prefix: "/admin" })
|
||||
export const adminReportRoutes = new Elysia({ prefix: "/admin", detail: { tags: ["Admin"] } })
|
||||
.get(
|
||||
"/reports",
|
||||
async ({ query, request, set }) => {
|
||||
|
||||
@@ -5,7 +5,7 @@ import { eq, sql, and, ilike, desc } from "drizzle-orm"
|
||||
import { requireAdmin } from "@/lib/auth/guard"
|
||||
import { deleteObject, isR2Configured } from "@/lib/storage"
|
||||
|
||||
export const adminStorageRoutes = new Elysia({ prefix: "/admin/storage" })
|
||||
export const adminStorageRoutes = new Elysia({ prefix: "/admin/storage", detail: { tags: ["Admin"] } })
|
||||
|
||||
// ── GET /stats ──────────────────────────────────────────────────
|
||||
.get(
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
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,
|
||||
@@ -59,6 +62,76 @@ const betterAuth = new Elysia({ name: "better-auth" })
|
||||
})
|
||||
|
||||
export const app = new Elysia({ prefix: "/api" })
|
||||
.use(
|
||||
openapi({
|
||||
path: "/openapi",
|
||||
documentation: {
|
||||
info: {
|
||||
title: "DeckyVault API",
|
||||
version: "2026.0.100",
|
||||
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: "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}`,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { requireRole } from "@/lib/auth/guard"
|
||||
|
||||
export const commentsRoutes = new Elysia({
|
||||
prefix: "/games/:gameId/comments",
|
||||
detail: { tags: ["Comments"] },
|
||||
})
|
||||
// LIST top-level comments for a game (paginated)
|
||||
.get(
|
||||
|
||||
@@ -58,6 +58,7 @@ const allowedFields = [
|
||||
|
||||
export const communitySuggestionRoutes = new Elysia({
|
||||
prefix: "/community-suggestions",
|
||||
detail: { tags: ["Games"] },
|
||||
})
|
||||
|
||||
// Admin list with pagination and filtering
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { db } from "@/lib/db/index"
|
||||
import { games, gameVersions, performanceEntries, hardware } from "@/lib/db/schema"
|
||||
import { eq, and, inArray } from "drizzle-orm"
|
||||
|
||||
export const compareRoutes = new Elysia({ prefix: "/compare" })
|
||||
export const compareRoutes = new Elysia({ prefix: "/compare", detail: { tags: ["Games"] } })
|
||||
.get(
|
||||
"/games",
|
||||
async ({ query, set }) => {
|
||||
|
||||
+1
-1
@@ -58,7 +58,7 @@ const CATEGORY_LABELS: Record<string, string> = {
|
||||
|
||||
const VALID_CATEGORIES = ["bug", "game_data", "user_report", "feature", "feedback", "database"]
|
||||
|
||||
export const contactRoutes = new Elysia({ prefix: "/contact" }).post(
|
||||
export const contactRoutes = new Elysia({ prefix: "/contact", detail: { tags: ["Contact"] } }).post(
|
||||
"/",
|
||||
async ({ body, request, set }) => {
|
||||
const payload = body as {
|
||||
|
||||
+2
-2
@@ -15,7 +15,7 @@ interface CronTaskResult {
|
||||
// ── Task Registry ───────────────────────────────────────────────────
|
||||
type CronTask = () => Promise<CronTaskResult>
|
||||
|
||||
const taskRegistry = new Map<string, CronTask>()
|
||||
export const taskRegistry = new Map<string, CronTask>()
|
||||
|
||||
export function registerCronTask(name: string, task: CronTask): void {
|
||||
taskRegistry.set(name, task)
|
||||
@@ -111,7 +111,7 @@ registerCronTask("orphan_detection", async () => {
|
||||
})
|
||||
|
||||
// ── Cron Route ──────────────────────────────────────────────────────
|
||||
export const cronRoutes = new Elysia({ prefix: "/cron" }).post(
|
||||
export const cronRoutes = new Elysia({ prefix: "/cron", detail: { tags: ["Cron"] } }).post(
|
||||
"/daily",
|
||||
async ({ query, set }) => {
|
||||
const cronSecret = process.env.CRON_SECRET
|
||||
|
||||
+26
-1
@@ -60,6 +60,7 @@ export function createCrudRoutes<T extends AnyPgTable>(
|
||||
primaryKey?: string
|
||||
paramName?: string
|
||||
softDelete?: boolean
|
||||
tags?: string[]
|
||||
},
|
||||
) {
|
||||
const {
|
||||
@@ -71,6 +72,7 @@ export function createCrudRoutes<T extends AnyPgTable>(
|
||||
primaryKey = "id",
|
||||
paramName = primaryKey,
|
||||
softDelete = false,
|
||||
tags,
|
||||
} = config
|
||||
|
||||
const columns = getTableColumns(table) as Record<string, PgColumn>
|
||||
@@ -80,7 +82,10 @@ export function createCrudRoutes<T extends AnyPgTable>(
|
||||
throw new Error(`Primary key column "${primaryKey}" not found on table`)
|
||||
}
|
||||
|
||||
const routes = new Elysia({ prefix })
|
||||
const routes = new Elysia({
|
||||
prefix,
|
||||
...(tags ? { detail: { tags } } : {}),
|
||||
})
|
||||
|
||||
// ── LIST ──────────────────────────────────────────────────────────
|
||||
routes.get(
|
||||
@@ -157,6 +162,10 @@ export function createCrudRoutes<T extends AnyPgTable>(
|
||||
// Dynamic filter fields are too varied for static TypeBox,
|
||||
// so we allow any string keys with filter_ prefix
|
||||
}),
|
||||
detail: {
|
||||
summary: `List ${name}s`,
|
||||
description: `Returns a paginated list of ${name}s with optional search and filtering.`,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -183,6 +192,10 @@ export function createCrudRoutes<T extends AnyPgTable>(
|
||||
params: t.Object({
|
||||
[paramName]: t.String(),
|
||||
}),
|
||||
detail: {
|
||||
summary: `Get ${name} by ID`,
|
||||
description: `Returns a single ${name} by its unique identifier.`,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -211,6 +224,10 @@ export function createCrudRoutes<T extends AnyPgTable>(
|
||||
},
|
||||
{
|
||||
body: t.Record(t.String(), t.Any()),
|
||||
detail: {
|
||||
summary: `Create ${name}`,
|
||||
description: `Creates a new ${name}. Requires authentication.`,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -256,6 +273,10 @@ export function createCrudRoutes<T extends AnyPgTable>(
|
||||
[paramName]: t.String(),
|
||||
}),
|
||||
body: t.Record(t.String(), t.Any()),
|
||||
detail: {
|
||||
summary: `Update ${name}`,
|
||||
description: `Updates an existing ${name} by ID. Requires authentication.`,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -303,6 +324,10 @@ export function createCrudRoutes<T extends AnyPgTable>(
|
||||
params: t.Object({
|
||||
[paramName]: t.String(),
|
||||
}),
|
||||
detail: {
|
||||
summary: `Delete ${name}`,
|
||||
description: `Deletes a ${name} by ID. Requires admin or contributor role.`,
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ import { sql } from "drizzle-orm"
|
||||
|
||||
const SEVEN_DAYS_AGO = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
|
||||
|
||||
export const dashboardPublicRoutes = new Elysia({ prefix: "/dashboard" })
|
||||
export const dashboardPublicRoutes = new Elysia({ prefix: "/dashboard", detail: { tags: ["Dashboard"] } })
|
||||
|
||||
// ── Trending Games (7-day activity) ────────────────────────────────
|
||||
.get(
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
import { eq, count, sql, gte, and, desc } from "drizzle-orm";
|
||||
import { requireContributorOrAdmin } from "@/lib/auth/guard";
|
||||
|
||||
export const dashboardRoutes = new Elysia({ prefix: "/dashboard" }).get(
|
||||
export const dashboardRoutes = new Elysia({ prefix: "/dashboard", detail: { tags: ["Dashboard"] } }).get(
|
||||
"/stats",
|
||||
async ({ request, set }) => {
|
||||
const guard = await requireContributorOrAdmin(request.headers);
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@/lib/db/schema"
|
||||
import { eq, and, inArray, sql } from "drizzle-orm"
|
||||
|
||||
export const gameStatsRoutes = new Elysia({ prefix: "/games" }).get(
|
||||
export const gameStatsRoutes = new Elysia({ prefix: "/games", detail: { tags: ["Games"] } }).get(
|
||||
"/:gameId/stats",
|
||||
async ({ params, set }) => {
|
||||
const { gameId } = params
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Elysia, t } from "elysia"
|
||||
import { ensureSteamGame } from "@/lib/steam/sync"
|
||||
|
||||
export const gameStubRoutes = new Elysia({ prefix: "/games" }).post(
|
||||
export const gameStubRoutes = new Elysia({ prefix: "/games", detail: { tags: ["Games"] } }).post(
|
||||
"/stub",
|
||||
async ({ body, set }) => {
|
||||
const result = await ensureSteamGame(body.steamAppId)
|
||||
|
||||
@@ -13,7 +13,7 @@ import { fuzzySearchTerm } from "@/lib/db/search"
|
||||
const MAX_OFFSET = 10000
|
||||
const PAGE_SIZE = 24
|
||||
|
||||
export const gamesListingRoutes = new Elysia({ prefix: "/games/listing" }).get(
|
||||
export const gamesListingRoutes = new Elysia({ prefix: "/games/listing", detail: { tags: ["Games"] } }).get(
|
||||
"/",
|
||||
async ({ query }) => {
|
||||
const offset = Math.min(Number(query.offset) || 0, MAX_OFFSET)
|
||||
|
||||
@@ -4,7 +4,7 @@ import { games, gameVersions, gamePlatformSupport } from "@/lib/db/schema"
|
||||
import { ilike, eq } from "drizzle-orm"
|
||||
import { requireRole } from "@/lib/auth/guard"
|
||||
|
||||
export const gamesManualRoutes = new Elysia({ prefix: "/games" })
|
||||
export const gamesManualRoutes = new Elysia({ prefix: "/games", detail: { tags: ["Games"] } })
|
||||
.post(
|
||||
"/manual",
|
||||
async ({ request, body, set }) => {
|
||||
|
||||
+9
-2
@@ -10,6 +10,7 @@ import { requireRole } from "@/lib/auth/guard"
|
||||
export const gamesRoutes = createCrudRoutes(games, {
|
||||
prefix: "/games",
|
||||
name: "Game",
|
||||
tags: ["Games"],
|
||||
auth: { read: "public", write: "contributor", delete: "admin" },
|
||||
search: { fields: ["title", "developer", "publisher"] },
|
||||
filter: { fields: ["source", "onlineMultiplayerStatus", "syncStatus"] },
|
||||
@@ -17,7 +18,10 @@ export const gamesRoutes = createCrudRoutes(games, {
|
||||
})
|
||||
|
||||
// ── Game Versions (nested under /games/:gameId/versions) ──────────
|
||||
export const gameVersionsRoutes = new Elysia({ prefix: "/games/:gameId/versions" })
|
||||
export const gameVersionsRoutes = new Elysia({
|
||||
prefix: "/games/:gameId/versions",
|
||||
detail: { tags: ["Games"] },
|
||||
})
|
||||
// LIST versions for a game
|
||||
.get(
|
||||
"/",
|
||||
@@ -192,7 +196,10 @@ export const gameVersionsRoutes = new Elysia({ prefix: "/games/:gameId/versions"
|
||||
const MAX_BULK_SYNC = 1000
|
||||
|
||||
|
||||
export const gameSyncRoutes = new Elysia({ prefix: "/games" })
|
||||
export const gameSyncRoutes = new Elysia({
|
||||
prefix: "/games",
|
||||
detail: { tags: ["Admin"] },
|
||||
})
|
||||
// Bulk sync with streaming progress (defined before /:gameId/sync to avoid route conflict)
|
||||
.post(
|
||||
"/sync/bulk",
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from "@/lib/db/schema"
|
||||
import { eq, and, sql, desc } from "drizzle-orm"
|
||||
|
||||
export const hardwareStatsRoutes = new Elysia({ prefix: "/hardware" })
|
||||
export const hardwareStatsRoutes = new Elysia({ prefix: "/hardware", detail: { tags: ["Hardware"] } })
|
||||
// ── All devices with aggregated stats ──────────────────────
|
||||
.get(
|
||||
"/stats",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { hardware } from "@/lib/db/schema"
|
||||
export const hardwareRoutes = createCrudRoutes(hardware, {
|
||||
prefix: "/hardware",
|
||||
name: "Hardware",
|
||||
tags: ["Hardware"],
|
||||
primaryKey: "slug",
|
||||
auth: { read: "public", write: "admin", delete: "admin" },
|
||||
filter: { fields: ["deviceType"] },
|
||||
|
||||
+10
-1
@@ -1,10 +1,19 @@
|
||||
import { Elysia } from "elysia"
|
||||
|
||||
export const healthRoutes = new Elysia({ prefix: "/health" }).get(
|
||||
export const healthRoutes = new Elysia({
|
||||
prefix: "/health",
|
||||
detail: { tags: ["Health"] },
|
||||
}).get(
|
||||
"/",
|
||||
() => ({
|
||||
status: "ok",
|
||||
timestamp: new Date().toISOString(),
|
||||
service: "deckyvault-api",
|
||||
}),
|
||||
{
|
||||
detail: {
|
||||
summary: "Health check",
|
||||
description: "Returns the current health status of the API.",
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
@@ -18,7 +18,7 @@ import { processScreenshot, isAllowedMimeType, validateMagicBytes } from "@/lib/
|
||||
const MAX_SCREENSHOTS_PER_ENTRY = 2
|
||||
const MAX_UPLOAD_SIZE = 10 * 1024 * 1024 // 10 MB
|
||||
|
||||
export const performanceSubmitRoutes = new Elysia({ prefix: "/performance" })
|
||||
export const performanceSubmitRoutes = new Elysia({ prefix: "/performance", detail: { tags: ["Performance"] } })
|
||||
.get(
|
||||
"/autocomplete",
|
||||
async ({ query }) => {
|
||||
|
||||
@@ -15,6 +15,7 @@ const MAX_UPLOAD_SIZE = 10 * 1024 * 1024 // 10 MB
|
||||
export const performanceRoutes = createCrudRoutes(performanceEntries, {
|
||||
prefix: "/performance",
|
||||
name: "Performance Entry",
|
||||
tags: ["Performance"],
|
||||
auth: { read: "public", write: "user", delete: "admin" },
|
||||
softDelete: true,
|
||||
search: { fields: ["userNotes"] },
|
||||
@@ -24,6 +25,7 @@ export const performanceRoutes = createCrudRoutes(performanceEntries, {
|
||||
// ── Verify endpoint (admin/mod) ───────────────────────────────────
|
||||
export const performanceVerifyRoutes = new Elysia({
|
||||
prefix: "/performance",
|
||||
detail: { tags: ["Performance"] },
|
||||
})
|
||||
.post(
|
||||
"/:id/verify",
|
||||
|
||||
@@ -141,7 +141,7 @@ export async function recalculatePlayability(gameId: string): Promise<{
|
||||
return { gamePlayability: worstStatus, deviceResults: results };
|
||||
}
|
||||
|
||||
export const playabilityRoutes = new Elysia({ prefix: "/playability" })
|
||||
export const playabilityRoutes = new Elysia({ prefix: "/playability", detail: { tags: ["Games"] } })
|
||||
// Manual trigger for recalculation (admin/contributor)
|
||||
.post("/calculate/:gameId", async ({ params, request, set }) => {
|
||||
const guard = await requireContributorOrAdmin(request.headers);
|
||||
|
||||
@@ -29,7 +29,7 @@ function validateMagicBytes(buffer: Buffer, declaredMime: string): boolean {
|
||||
return expected.every((byte, i) => buffer[i] === byte)
|
||||
}
|
||||
|
||||
export const profilePhotoRoutes = new Elysia({ prefix: "/user" })
|
||||
export const profilePhotoRoutes = new Elysia({ prefix: "/user", detail: { tags: ["Users"] } })
|
||||
|
||||
// ── Upload Profile Photo ──────────────────────────────────────────
|
||||
.post(
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ import { reports } from "@/lib/db/schema"
|
||||
import { eq, and } from "drizzle-orm"
|
||||
import { requireRole } from "@/lib/auth/guard"
|
||||
|
||||
export const reportRoutes = new Elysia({ prefix: "/performance" }).post(
|
||||
export const reportRoutes = new Elysia({ prefix: "/performance", detail: { tags: ["Reports"] } }).post(
|
||||
"/:id/report",
|
||||
async ({ params, body, request, set }) => {
|
||||
const guard = await requireRole(request.headers, [
|
||||
|
||||
@@ -4,7 +4,7 @@ import { savedFilters } from "@/lib/db/schema"
|
||||
import { eq, and, desc } from "drizzle-orm"
|
||||
import { requireAuth } from "@/lib/auth/guard"
|
||||
|
||||
export const savedFilterRoutes = new Elysia({ prefix: "/saved-filters" })
|
||||
export const savedFilterRoutes = new Elysia({ prefix: "/saved-filters", detail: { tags: ["Games"] } })
|
||||
|
||||
// Get user's saved filters
|
||||
.get("/", async ({ request, set }) => {
|
||||
|
||||
@@ -4,7 +4,7 @@ import { db } from "@/lib/db/index"
|
||||
import { savedGames, games } from "@/lib/db/schema"
|
||||
import { eq, and, sql } from "drizzle-orm"
|
||||
|
||||
export const savedGamesRoutes = new Elysia({ prefix: "/user/me/saved-games" })
|
||||
export const savedGamesRoutes = new Elysia({ prefix: "/user/me/saved-games", detail: { tags: ["Games"] } })
|
||||
.post(
|
||||
"/",
|
||||
async ({ request, body, set }) => {
|
||||
|
||||
@@ -9,7 +9,7 @@ import { processScreenshot, isAllowedMimeType } from "@/lib/image-processing"
|
||||
const MAX_SCREENSHOTS_PER_ENTRY = 2
|
||||
const MAX_UPLOAD_SIZE = 10 * 1024 * 1024 // 10 MB raw
|
||||
|
||||
export const screenshotRoutes = new Elysia({ prefix: "/performance" })
|
||||
export const screenshotRoutes = new Elysia({ prefix: "/performance", detail: { tags: ["Performance"] } })
|
||||
|
||||
// ── Upload screenshots ─────────────────────────────────────────
|
||||
.post(
|
||||
|
||||
@@ -24,7 +24,7 @@ interface SteamSearchResponse {
|
||||
total: number
|
||||
}
|
||||
|
||||
export const searchUnifiedRoutes = new Elysia({ prefix: "/search" }).get(
|
||||
export const searchUnifiedRoutes = new Elysia({ prefix: "/search", detail: { tags: ["Search"] } }).get(
|
||||
"/unified",
|
||||
async ({ query, set }) => {
|
||||
if (!query.q || query.q.length < 2) {
|
||||
|
||||
@@ -47,7 +47,7 @@ interface SteamReviewResponse {
|
||||
reviews: SteamReview[]
|
||||
}
|
||||
|
||||
export const steamReviewRoutes = new Elysia({ prefix: "/steam-reviews" })
|
||||
export const steamReviewRoutes = new Elysia({ prefix: "/steam-reviews", detail: { tags: ["Steam"] } })
|
||||
|
||||
// Get embedded Steam reviews for a game
|
||||
.get(
|
||||
|
||||
@@ -12,7 +12,7 @@ interface SteamSearchItem {
|
||||
}
|
||||
}
|
||||
|
||||
export const steamSearchRoutes = new Elysia({ prefix: "/search" })
|
||||
export const steamSearchRoutes = new Elysia({ prefix: "/search", detail: { tags: ["Search"] } })
|
||||
.get(
|
||||
"/steam",
|
||||
async ({ query, set }) => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { Elysia, t } from "elysia"
|
||||
|
||||
const STEAMGRIDDB_BASE = "https://www.steamgriddb.com/api/v2"
|
||||
|
||||
export const steamgridProxyRoutes = new Elysia({ prefix: "/steamgrid" })
|
||||
export const steamgridProxyRoutes = new Elysia({ prefix: "/steamgrid", detail: { tags: ["Steam"] } })
|
||||
.get(
|
||||
"/search",
|
||||
async ({ query, set }) => {
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import { user, performanceEntries, games, gameVersions, hardware, account, passk
|
||||
import { eq, sql, and, desc } from "drizzle-orm"
|
||||
import { hashPassword } from "better-auth/crypto"
|
||||
|
||||
export const userRoutes = new Elysia({ prefix: "/user" })
|
||||
export const userRoutes = new Elysia({ prefix: "/user", detail: { tags: ["Users"] } })
|
||||
.get(
|
||||
"/profile/:id",
|
||||
async ({ params, set }) => {
|
||||
|
||||
Reference in New Issue
Block a user