diff --git a/src/app/admin/ads/[id]/page.tsx b/src/app/admin/ads/[id]/page.tsx index a9c027a666..70c0f4dc3d 100644 --- a/src/app/admin/ads/[id]/page.tsx +++ b/src/app/admin/ads/[id]/page.tsx @@ -8,8 +8,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { EditAdDeleteButton } from "../ads-table"; -export const dynamic = "force-dynamic"; - export default async function EditAd({ params, }: { diff --git a/src/app/admin/ads/page.tsx b/src/app/admin/ads/page.tsx index 3dfd659b41..aa6b29dd0c 100644 --- a/src/app/admin/ads/page.tsx +++ b/src/app/admin/ads/page.tsx @@ -8,8 +8,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { AdsTable } from "./ads-table"; -export const dynamic = "force-dynamic"; - export default async function AdminAds() { const { session, permissions } = await getAdminContext(); if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) { diff --git a/src/app/admin/applications/page.tsx b/src/app/admin/applications/page.tsx index 3610701fde..e58f6cc174 100644 --- a/src/app/admin/applications/page.tsx +++ b/src/app/admin/applications/page.tsx @@ -8,8 +8,6 @@ import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - type ApplicationRow = { id: bigint; userId: number; diff --git a/src/app/admin/articles/[id]/page.tsx b/src/app/admin/articles/[id]/page.tsx index 4e3f622815..b3c9b24843 100644 --- a/src/app/admin/articles/[id]/page.tsx +++ b/src/app/admin/articles/[id]/page.tsx @@ -8,8 +8,6 @@ import { Button } from "@/components/ui/button"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function EditArticle({ params, searchParams, diff --git a/src/app/admin/articles/page.tsx b/src/app/admin/articles/page.tsx index 8496564e8a..142da4b426 100644 --- a/src/app/admin/articles/page.tsx +++ b/src/app/admin/articles/page.tsx @@ -10,8 +10,6 @@ import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function AdminArticles({ searchParams, }: { diff --git a/src/app/admin/badges/page.tsx b/src/app/admin/badges/page.tsx index bf4f8d62ef..d46eb338ca 100644 --- a/src/app/admin/badges/page.tsx +++ b/src/app/admin/badges/page.tsx @@ -6,8 +6,6 @@ import { Button } from "@/components/ui/button"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export const metadata = { title: "Badges" }; export default async function AdminBadges({ diff --git a/src/app/admin/bans/page.tsx b/src/app/admin/bans/page.tsx index 0e55e48d89..ade09696bd 100644 --- a/src/app/admin/bans/page.tsx +++ b/src/app/admin/bans/page.tsx @@ -10,8 +10,6 @@ import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function AdminBans() { const { session, permissions } = await getAdminContext(); if (!canAccess(permissions, PERMS.BANS_VIEW, session.user.rank)) { diff --git a/src/app/admin/calendar/[id]/page.tsx b/src/app/admin/calendar/[id]/page.tsx index a09ca4380c..ffc65a9c80 100644 --- a/src/app/admin/calendar/[id]/page.tsx +++ b/src/app/admin/calendar/[id]/page.tsx @@ -5,8 +5,6 @@ import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - type Campaign = { id: number; name: string; diff --git a/src/app/admin/calendar/page.tsx b/src/app/admin/calendar/page.tsx index 4cd1c5e0b0..41cdc87f5e 100644 --- a/src/app/admin/calendar/page.tsx +++ b/src/app/admin/calendar/page.tsx @@ -6,8 +6,6 @@ import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - type Campaign = { id: number; name: string; diff --git a/src/app/admin/catalog/[id]/page.tsx b/src/app/admin/catalog/[id]/page.tsx index 0788aae97a..fb8dc80548 100644 --- a/src/app/admin/catalog/[id]/page.tsx +++ b/src/app/admin/catalog/[id]/page.tsx @@ -8,8 +8,6 @@ import { loadCatalogItemsData } from "@/lib/services/catalog-items-loader"; import { getAncestors } from "@/lib/services/catalog-tree"; import { CatalogDetailTabs } from "./catalog-detail-tabs"; -export const dynamic = "force-dynamic"; - export default async function CatalogEditPage({ params, }: { diff --git a/src/app/admin/commandocentrum/page.tsx b/src/app/admin/commandocentrum/page.tsx index c4672edf91..d6b3442ec5 100644 --- a/src/app/admin/commandocentrum/page.tsx +++ b/src/app/admin/commandocentrum/page.tsx @@ -17,8 +17,6 @@ import { prisma } from "@/lib/prisma"; import { siteSettings } from "@/lib/services/site-settings"; import { CommandocentrumControls } from "./commandocentrum-controls"; -export const dynamic = "force-dynamic"; - function decodeStacktrace(raw: unknown): string { if (raw == null) return ""; try { @@ -66,6 +64,7 @@ export default async function CommandoCentrum() { ]); const dbOk = health.dbOk; + const redisOk = health.redisOk; const emulatorOnline = health.emulatorOk; const onlineCount = onlineRoster.count; const onlineUsers = onlineRoster.users; @@ -175,6 +174,23 @@ export default async function CommandoCentrum() { detail={dbOk ? t("connectionHealthy") : t("unreachable")} state={dbOk ? "ok" : "danger"} /> + {/* Status cards */} - + Database @@ -67,6 +68,33 @@ export default async function DevOpsPage() { + + + Redis + + + + + {data.redisOk === true + ? "Healthy" + : data.redisOk === false + ? "Error" + : "Not configured"} + + + Cache / rate limits / sessions + + + + Emulator diff --git a/src/app/admin/emulator/page.tsx b/src/app/admin/emulator/page.tsx index 3a74e0aa05..b1e8146b34 100644 --- a/src/app/admin/emulator/page.tsx +++ b/src/app/admin/emulator/page.tsx @@ -9,8 +9,6 @@ import { Button } from "@/components/ui/button"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - const TEXTS_LIMIT = 300; export default async function AdminEmulator() { diff --git a/src/app/admin/guilds/[id]/page.tsx b/src/app/admin/guilds/[id]/page.tsx index 7813ae97aa..e1c93934c3 100644 --- a/src/app/admin/guilds/[id]/page.tsx +++ b/src/app/admin/guilds/[id]/page.tsx @@ -7,8 +7,6 @@ import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function AdminGuildDetailPage({ params, }: { diff --git a/src/app/admin/guilds/page.tsx b/src/app/admin/guilds/page.tsx index eedd751505..b6c5d676cb 100644 --- a/src/app/admin/guilds/page.tsx +++ b/src/app/admin/guilds/page.tsx @@ -11,8 +11,6 @@ import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function AdminGuildsPage({ searchParams, }: { diff --git a/src/app/admin/help-questions/[id]/page.tsx b/src/app/admin/help-questions/[id]/page.tsx index 9a8d1c069c..306945e403 100644 --- a/src/app/admin/help-questions/[id]/page.tsx +++ b/src/app/admin/help-questions/[id]/page.tsx @@ -6,8 +6,6 @@ import { Button } from "@/components/ui/button"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function EditHelpQuestion({ params, searchParams, diff --git a/src/app/admin/help-questions/page.tsx b/src/app/admin/help-questions/page.tsx index 2d55b4625a..381e6c1d94 100644 --- a/src/app/admin/help-questions/page.tsx +++ b/src/app/admin/help-questions/page.tsx @@ -6,8 +6,6 @@ import { Button } from "@/components/ui/button"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function AdminHelpQuestions() { const { session, permissions } = await getAdminContext(); if (!canAccess(permissions, PERMS.PAGES_VIEW, session.user.rank)) { diff --git a/src/app/admin/help-tickets/[id]/page.tsx b/src/app/admin/help-tickets/[id]/page.tsx index 4a386b1f2e..7c8551e8c1 100644 --- a/src/app/admin/help-tickets/[id]/page.tsx +++ b/src/app/admin/help-tickets/[id]/page.tsx @@ -8,8 +8,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { AdminHelpTicketDetail } from "./admin-help-ticket-detail"; -export const dynamic = "force-dynamic"; - export default async function AdminHelpTicketDetailPage({ params, }: { diff --git a/src/app/admin/help-tickets/page.tsx b/src/app/admin/help-tickets/page.tsx index f2b6444f5e..f1b24c0a67 100644 --- a/src/app/admin/help-tickets/page.tsx +++ b/src/app/admin/help-tickets/page.tsx @@ -10,8 +10,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { type HelpTicketRow, HelpTicketsTable } from "./help-tickets-table"; -export const dynamic = "force-dynamic"; - export default async function AdminHelpTicketsPage({ searchParams, }: { diff --git a/src/app/admin/housekeeping/page.tsx b/src/app/admin/housekeeping/page.tsx index 3456c3a2f8..91ea6853af 100644 --- a/src/app/admin/housekeeping/page.tsx +++ b/src/app/admin/housekeeping/page.tsx @@ -6,8 +6,6 @@ import { prisma } from "@/lib/prisma"; import { getAuditLogs } from "@/lib/services/audit"; import { AuditSection, ExportSection } from "./client"; -export const dynamic = "force-dynamic"; - export default async function AdminHousekeeping({ searchParams, }: { diff --git a/src/app/admin/ip/page.tsx b/src/app/admin/ip/page.tsx index 22bef8e3e1..8c32a6102e 100644 --- a/src/app/admin/ip/page.tsx +++ b/src/app/admin/ip/page.tsx @@ -12,8 +12,6 @@ import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - type IpRow = { id: bigint; ipAddress: string; diff --git a/src/app/admin/items/[id]/page.tsx b/src/app/admin/items/[id]/page.tsx index 5bd24ead5b..0370e72168 100644 --- a/src/app/admin/items/[id]/page.tsx +++ b/src/app/admin/items/[id]/page.tsx @@ -6,8 +6,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { ItemsBaseEditForm } from "./items-base-edit-form"; -export const dynamic = "force-dynamic"; - export default async function ItemsBaseDetailPage({ params, }: { diff --git a/src/app/admin/logs/page.tsx b/src/app/admin/logs/page.tsx index 7c938e1d1c..9209124f60 100644 --- a/src/app/admin/logs/page.tsx +++ b/src/app/admin/logs/page.tsx @@ -10,8 +10,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { StaffActivitiesTable } from "./staff-activities-table"; -export const dynamic = "force-dynamic"; - const SORT_MAP: Record = { when: "createdAt", staff: "userId", diff --git a/src/app/admin/maintenance/page.tsx b/src/app/admin/maintenance/page.tsx index a638e69d62..c0db56357f 100644 --- a/src/app/admin/maintenance/page.tsx +++ b/src/app/admin/maintenance/page.tsx @@ -6,8 +6,6 @@ import { Button } from "@/components/ui/button"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - const KEYS = [ "maintenance_enabled", "maintenance_message", diff --git a/src/app/admin/marketplace/page.tsx b/src/app/admin/marketplace/page.tsx index 243a854f65..a47abc712e 100644 --- a/src/app/admin/marketplace/page.tsx +++ b/src/app/admin/marketplace/page.tsx @@ -12,8 +12,6 @@ import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function AdminMarketplacePage({ searchParams, }: { diff --git a/src/app/admin/moderation/cfh/page.tsx b/src/app/admin/moderation/cfh/page.tsx index d3c24df67a..89ed8cb361 100644 --- a/src/app/admin/moderation/cfh/page.tsx +++ b/src/app/admin/moderation/cfh/page.tsx @@ -7,8 +7,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { type CfhRow, CfhTable } from "./cfh-table"; -export const dynamic = "force-dynamic"; - export default async function CfhListPage({ searchParams, }: { diff --git a/src/app/admin/page.tsx b/src/app/admin/page.tsx index 90d047763b..cc7e324c97 100644 --- a/src/app/admin/page.tsx +++ b/src/app/admin/page.tsx @@ -13,8 +13,6 @@ import { activeBanWhere } from "@/lib/bans"; import { formatDate } from "@/lib/format-date"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - type RankCount = { rank: number; count: number }; export default async function AdminDashboard() { diff --git a/src/app/admin/photos/page.tsx b/src/app/admin/photos/page.tsx index d41ae9049c..50389bf8e5 100644 --- a/src/app/admin/photos/page.tsx +++ b/src/app/admin/photos/page.tsx @@ -10,8 +10,6 @@ import { calcPagination, parseListParams } from "@/lib/admin-helpers"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function AdminPhotos({ searchParams, }: { diff --git a/src/app/admin/radio/api-keys/page.tsx b/src/app/admin/radio/api-keys/page.tsx index 73ce05a5da..876b8b9fc6 100644 --- a/src/app/admin/radio/api-keys/page.tsx +++ b/src/app/admin/radio/api-keys/page.tsx @@ -9,8 +9,6 @@ import { Button } from "@/components/ui/button"; import { formatDate } from "@/lib/format-date"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export const metadata = { title: "Radio API Keys" }; type ApiKey = { diff --git a/src/app/admin/radio/autodj/page.tsx b/src/app/admin/radio/autodj/page.tsx index f92ea509ce..c870e7f964 100644 --- a/src/app/admin/radio/autodj/page.tsx +++ b/src/app/admin/radio/autodj/page.tsx @@ -10,8 +10,6 @@ import { Button } from "@/components/ui/button"; import { formatDate } from "@/lib/format-date"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export const metadata = { title: "Radio AutoDJ" }; type Track = { diff --git a/src/app/admin/radio/banners/page.tsx b/src/app/admin/radio/banners/page.tsx index 5544ad5082..94b03b2908 100644 --- a/src/app/admin/radio/banners/page.tsx +++ b/src/app/admin/radio/banners/page.tsx @@ -10,8 +10,6 @@ import { Button } from "@/components/ui/button"; import { formatDate } from "@/lib/format-date"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - type Banner = { id: bigint; userId: bigint; diff --git a/src/app/admin/radio/embed/page.tsx b/src/app/admin/radio/embed/page.tsx index 293691ac0c..105b84edf1 100644 --- a/src/app/admin/radio/embed/page.tsx +++ b/src/app/admin/radio/embed/page.tsx @@ -6,8 +6,6 @@ import { resolveHotelName } from "@/lib/hotel-name"; import { siteSettings } from "@/lib/services/site-settings"; import { EmbedSnippet } from "./embed-snippet"; -export const dynamic = "force-dynamic"; - export const metadata = { title: "Radio Embed" }; export default async function AdminRadioEmbedPage() { diff --git a/src/app/admin/radio/history/page.tsx b/src/app/admin/radio/history/page.tsx index b81a464c16..e3ef68a52e 100644 --- a/src/app/admin/radio/history/page.tsx +++ b/src/app/admin/radio/history/page.tsx @@ -3,8 +3,6 @@ import { StatusCard } from "@/components/admin/dashboard"; import { formatDate } from "@/lib/format-date"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - type History = { id: bigint; userId: bigint; diff --git a/src/app/admin/radio/moderation/page.tsx b/src/app/admin/radio/moderation/page.tsx index 613ba6797c..150a8d3e85 100644 --- a/src/app/admin/radio/moderation/page.tsx +++ b/src/app/admin/radio/moderation/page.tsx @@ -5,8 +5,6 @@ import { Button } from "@/components/ui/button"; import { formatDate } from "@/lib/format-date"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - type Shout = { id: bigint; userId: bigint; diff --git a/src/app/admin/radio/monitoring/page.tsx b/src/app/admin/radio/monitoring/page.tsx index 19f4bfcfda..8b6b316e33 100644 --- a/src/app/admin/radio/monitoring/page.tsx +++ b/src/app/admin/radio/monitoring/page.tsx @@ -2,8 +2,6 @@ import { getTranslations } from "next-intl/server"; import { DiagnosticRow, StatusCard } from "@/components/admin/dashboard"; import { siteSettings } from "@/lib/services/site-settings"; -export const dynamic = "force-dynamic"; - export const metadata = { title: "Radio Monitoring" }; const FETCH_TIMEOUT_MS = 4000; diff --git a/src/app/admin/radio/page.tsx b/src/app/admin/radio/page.tsx index e83dd805ec..652c0819c1 100644 --- a/src/app/admin/radio/page.tsx +++ b/src/app/admin/radio/page.tsx @@ -5,8 +5,6 @@ import { Button } from "@/components/ui/button"; import { formatDate } from "@/lib/format-date"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - type Shout = { id: bigint; userId: bigint; diff --git a/src/app/admin/radio/points/page.tsx b/src/app/admin/radio/points/page.tsx index 6022197f4c..8247f2a997 100644 --- a/src/app/admin/radio/points/page.tsx +++ b/src/app/admin/radio/points/page.tsx @@ -4,8 +4,6 @@ import { StatusCard } from "@/components/admin/dashboard"; import { Button } from "@/components/ui/button"; import { siteSettings } from "@/lib/services/site-settings"; -export const dynamic = "force-dynamic"; - export const metadata = { title: "Radio Points" }; const DEFAULTS = { diff --git a/src/app/admin/radio/ranks/page.tsx b/src/app/admin/radio/ranks/page.tsx index efbccab1d6..757a7f5bce 100644 --- a/src/app/admin/radio/ranks/page.tsx +++ b/src/app/admin/radio/ranks/page.tsx @@ -8,8 +8,6 @@ import { StatusCard } from "@/components/admin/dashboard"; import { Button } from "@/components/ui/button"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - type Rank = { id: bigint; name: string; diff --git a/src/app/admin/radio/settings/page.tsx b/src/app/admin/radio/settings/page.tsx index 13f92d6ae3..dd2e700112 100644 --- a/src/app/admin/radio/settings/page.tsx +++ b/src/app/admin/radio/settings/page.tsx @@ -7,8 +7,6 @@ import { StatusCard } from "@/components/admin/dashboard"; import { Button } from "@/components/ui/button"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - type Field = { key: string; comment: string }; type Group = { title: string; fields: Field[] }; diff --git a/src/app/admin/rare-values/page.tsx b/src/app/admin/rare-values/page.tsx index 4a63fbd2b3..e0f13e81ec 100644 --- a/src/app/admin/rare-values/page.tsx +++ b/src/app/admin/rare-values/page.tsx @@ -11,8 +11,6 @@ import { Button } from "@/components/ui/button"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - const CURRENCY_TYPES = ["diamonds", "credits", "duckets", "points"]; export default async function AdminRareValues() { diff --git a/src/app/admin/settings/page.tsx b/src/app/admin/settings/page.tsx index 8bc9dc72d2..fabb356134 100644 --- a/src/app/admin/settings/page.tsx +++ b/src/app/admin/settings/page.tsx @@ -8,7 +8,6 @@ import { AdvancedSettingsPanel } from "./advanced-settings-panel"; import { FIELD_DEFAULTS, MANAGED_SETTING_KEYS } from "./cms-settings-config"; import { CmsSettingsForm } from "./cms-settings-form"; -export const dynamic = "force-dynamic"; export const metadata = { title: "CMS Settings" }; export default async function AdminSettings() { diff --git a/src/app/admin/shop/[id]/page.tsx b/src/app/admin/shop/[id]/page.tsx index dcb32617ac..c578216a88 100644 --- a/src/app/admin/shop/[id]/page.tsx +++ b/src/app/admin/shop/[id]/page.tsx @@ -7,8 +7,6 @@ import { Button } from "@/components/ui/button"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function EditShopArticle({ params, }: { diff --git a/src/app/admin/shop/page.tsx b/src/app/admin/shop/page.tsx index 1365e0a1ff..97521be83b 100644 --- a/src/app/admin/shop/page.tsx +++ b/src/app/admin/shop/page.tsx @@ -12,8 +12,6 @@ import { formatDate } from "@/lib/format-date"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - const PAID_STATUS = new Set(["completed", "paid", "success", "approved"]); const RECENT_ORDERS = 25; diff --git a/src/app/admin/teams/page.tsx b/src/app/admin/teams/page.tsx index 93101e54e3..44f624fba7 100644 --- a/src/app/admin/teams/page.tsx +++ b/src/app/admin/teams/page.tsx @@ -6,8 +6,6 @@ import { Button } from "@/components/ui/button"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - type TeamRow = { id: bigint; rankName: string; diff --git a/src/app/admin/theme/page.tsx b/src/app/admin/theme/page.tsx index 159232d31d..b19d71048d 100644 --- a/src/app/admin/theme/page.tsx +++ b/src/app/admin/theme/page.tsx @@ -15,7 +15,6 @@ import { listCustomThemes } from "@/lib/theme-custom-store"; import { FONTS, PRESETS } from "@/lib/theme-presets"; import { ColorField } from "./color-field"; -export const dynamic = "force-dynamic"; export const metadata = { title: "Theme" }; const COLOR_FIELDS: { diff --git a/src/app/admin/tickets/page.tsx b/src/app/admin/tickets/page.tsx index 32a72de6da..2e52f6f826 100644 --- a/src/app/admin/tickets/page.tsx +++ b/src/app/admin/tickets/page.tsx @@ -10,8 +10,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { type TicketRow, TicketsTable } from "./tickets-table"; -export const dynamic = "force-dynamic"; - export default async function AdminTicketsPage({ searchParams, }: { diff --git a/src/app/admin/transactions/page.tsx b/src/app/admin/transactions/page.tsx index 0744789f47..0d0fa32577 100644 --- a/src/app/admin/transactions/page.tsx +++ b/src/app/admin/transactions/page.tsx @@ -8,8 +8,6 @@ import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import { type TransactionRow, TransactionsTable } from "./transactions-table"; -export const dynamic = "force-dynamic"; - const PAID_STATUS = ["completed", "paid", "success", "approved"] as const; const FAILED_STATUS = [ "failed", diff --git a/src/app/admin/users/[id]/edit/page.tsx b/src/app/admin/users/[id]/edit/page.tsx index 5fb7b2e54f..3a1a63636c 100644 --- a/src/app/admin/users/[id]/edit/page.tsx +++ b/src/app/admin/users/[id]/edit/page.tsx @@ -1,7 +1,5 @@ import { redirect } from "next/navigation"; -export const dynamic = "force-dynamic"; - /** * Legacy URL — the canonical edit UI lives at /admin/users/edit/[id] * and is gated by PERMS.USERS_EDIT. diff --git a/src/app/admin/vpn/page.tsx b/src/app/admin/vpn/page.tsx index 783d3939ab..eee72e2260 100644 --- a/src/app/admin/vpn/page.tsx +++ b/src/app/admin/vpn/page.tsx @@ -7,7 +7,6 @@ import { Button } from "@/components/ui/button"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { siteSettings } from "@/lib/services/site-settings"; -export const dynamic = "force-dynamic"; export const metadata = { title: "VPN" }; const PROVIDERS: { value: string; label: string }[] = [ diff --git a/src/app/admin/writeable-boxes/page.tsx b/src/app/admin/writeable-boxes/page.tsx index 2022846a84..2d5d44a606 100644 --- a/src/app/admin/writeable-boxes/page.tsx +++ b/src/app/admin/writeable-boxes/page.tsx @@ -11,8 +11,6 @@ import { Button } from "@/components/ui/button"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export const metadata = { title: "Writeable boxes" }; export default async function AdminWriteableBoxes() { diff --git a/src/app/api/admin/devops/health/route.ts b/src/app/api/admin/devops/health/route.ts index 93ed299692..f9ac148672 100644 --- a/src/app/api/admin/devops/health/route.ts +++ b/src/app/api/admin/devops/health/route.ts @@ -9,6 +9,7 @@ export const GET = withAdmin({ permission: PERMS.DEVOPS_VIEW }, async () => { return NextResponse.json({ database: health.dbOk, dbLatency: health.dbLatencyMs, + redis: health.redisOk, emulator: health.emulatorOk, onlineUsers: health.onlineUsers, timestamp: new Date().toISOString(), diff --git a/src/app/mod/bans/page.tsx b/src/app/mod/bans/page.tsx index 26b77e2d74..d11a080f48 100644 --- a/src/app/mod/bans/page.tsx +++ b/src/app/mod/bans/page.tsx @@ -6,8 +6,6 @@ import { formatDate } from "@/lib/format-date"; import { PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function ModBansPage() { await requireModPermission([PERMS.MOD_BANS_VIEW, PERMS.BANS_VIEW]); diff --git a/src/app/mod/cfh/page.tsx b/src/app/mod/cfh/page.tsx index 73a54217a5..d5fdb4c88a 100644 --- a/src/app/mod/cfh/page.tsx +++ b/src/app/mod/cfh/page.tsx @@ -6,8 +6,6 @@ import { calcPagination, parseListParams } from "@/lib/admin-helpers"; import { PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function ModCfhListPage({ searchParams, }: { diff --git a/src/app/mod/help-tickets/[id]/page.tsx b/src/app/mod/help-tickets/[id]/page.tsx index a6dbe421c2..170fd9209b 100644 --- a/src/app/mod/help-tickets/[id]/page.tsx +++ b/src/app/mod/help-tickets/[id]/page.tsx @@ -6,8 +6,6 @@ import { positiveBigInt } from "@/lib/api"; import { canAccess, getAdminContext, PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function ModHelpTicketDetailPage({ params, }: { diff --git a/src/app/mod/help-tickets/page.tsx b/src/app/mod/help-tickets/page.tsx index 4cbf4a6e78..6f1dd12c16 100644 --- a/src/app/mod/help-tickets/page.tsx +++ b/src/app/mod/help-tickets/page.tsx @@ -13,8 +13,6 @@ import { formatDate } from "@/lib/format-date"; import { PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function ModHelpTicketsPage({ searchParams, }: { diff --git a/src/app/mod/tickets/page.tsx b/src/app/mod/tickets/page.tsx index 1b1305b43b..f8f844f417 100644 --- a/src/app/mod/tickets/page.tsx +++ b/src/app/mod/tickets/page.tsx @@ -13,8 +13,6 @@ import { formatDate } from "@/lib/format-date"; import { PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function ModTicketsPage({ searchParams, }: { diff --git a/src/app/mod/users/[id]/page.tsx b/src/app/mod/users/[id]/page.tsx index 0c9cc06d36..a5c2aab84b 100644 --- a/src/app/mod/users/[id]/page.tsx +++ b/src/app/mod/users/[id]/page.tsx @@ -8,8 +8,6 @@ import { getAvatarUrl } from "@/lib/imager"; import { PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; -export const dynamic = "force-dynamic"; - export default async function ModUserDetailPage({ params, }: { diff --git a/src/app/mod/users/page.tsx b/src/app/mod/users/page.tsx index e358fb35cc..ea30bff03b 100644 --- a/src/app/mod/users/page.tsx +++ b/src/app/mod/users/page.tsx @@ -9,8 +9,6 @@ import { PERMS } from "@/lib/permissions"; import { prisma } from "@/lib/prisma"; import type { DataTableColumn } from "@/types/common"; -export const dynamic = "force-dynamic"; - type ModUserRow = { id: number; username: string; diff --git a/src/lib/admin/ops-health.ts b/src/lib/admin/ops-health.ts index 8a5a4506d5..3381ba6865 100644 --- a/src/lib/admin/ops-health.ts +++ b/src/lib/admin/ops-health.ts @@ -1,21 +1,25 @@ import "server-only"; +import { env } from "@/env"; import { prisma } from "@/lib/prisma"; +import { redis } from "@/lib/redis"; import { rcon } from "@/lib/services/rcon"; export type OpsHealth = { dbOk: boolean; dbLatencyMs: number; + /** null = REDIS_URL unset (optional); true/false = probe result */ + redisOk: boolean | null; emulatorOk: boolean; onlineUsers: number; }; /** - * Shared DB + emulator health probe for CommandoCentrum, DevOps, and APIs. - * Emulator = real RCON `ping` (same semantics as CommandoCentrum /public health). + * Shared DB + Redis + emulator health probe for CommandoCentrum, DevOps, and APIs. + * Semantics match `/api/health` for Redis (null when not configured). */ export async function fetchOpsHealth(): Promise { - const [db, emulatorOk, onlineUsers] = await Promise.all([ + const [db, redisOk, emulatorOk, onlineUsers] = await Promise.all([ (async () => { try { const start = Date.now(); @@ -25,6 +29,16 @@ export async function fetchOpsHealth(): Promise { return { dbOk: false, dbLatencyMs: 0 }; } })(), + (async (): Promise => { + if (!env.REDIS_URL) return null; + if (!redis) return false; + try { + const pong = await redis.ping(); + return pong === "PONG"; + } catch { + return false; + } + })(), rcon.send("ping", null).catch(() => false), prisma.user.count({ where: { online: "1" } }).catch(() => 0), ]); @@ -32,6 +46,7 @@ export async function fetchOpsHealth(): Promise { return { dbOk: db.dbOk, dbLatencyMs: db.dbLatencyMs, + redisOk, emulatorOk: Boolean(emulatorOk), onlineUsers, }; diff --git a/src/lib/staff-smoke-contract.test.ts b/src/lib/staff-smoke-contract.test.ts index 70bc7967d6..43020166ad 100644 --- a/src/lib/staff-smoke-contract.test.ts +++ b/src/lib/staff-smoke-contract.test.ts @@ -1,4 +1,5 @@ -import { existsSync, readFileSync } from "node:fs"; +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; +import { join } from "node:path"; import { describe, expect, it } from "vitest"; describe("staff smoke contract", () => { @@ -50,10 +51,37 @@ describe("staff smoke contract", () => { it("ships shared ops + ticket queue helpers", () => { expect(existsSync("src/lib/admin/ops-health.ts")).toBe(true); + const ops = readFileSync("src/lib/admin/ops-health.ts", "utf8"); + expect(ops).toContain("redisOk"); + expect(ops).toContain("redis.ping"); expect(existsSync("src/lib/admin/ticket-queue-counts.ts")).toBe(true); expect(existsSync("src/lib/admin/ops-online-users.ts")).toBe(true); }); + it("keeps force-dynamic only on admin/mod layouts", () => { + const hits: string[] = []; + function walk(dir: string) { + for (const name of readdirSync(dir)) { + const p = join(dir, name); + if (statSync(p).isDirectory()) walk(p); + else if (/\.(tsx?|jsx?)$/.test(name)) { + const src = readFileSync(p, "utf8"); + if ( + /export\s+const\s+dynamic\s*=\s*["']force-dynamic["']/.test(src) + ) { + hits.push(p.replace(/\\/g, "/")); + } + } + } + } + walk("src/app/admin"); + walk("src/app/mod"); + expect(hits.sort()).toEqual([ + "src/app/admin/layout.tsx", + "src/app/mod/layout.tsx", + ]); + }); + it("caches analytics via redisCache", () => { for (const path of [ "src/app/admin/analytics/page.tsx",
+ Cache / rate limits / sessions +