feat(docker): prepare portable images with runtime hotel configuration
This commit is contained in:
1 parent
745d0b7247
commit
c4496e710b
28 files changed
+622
-104
No files matched your search
@@ -61,11 +61,7 @@ export function UserBadgesSection({ userId, badges }: UserBadgesSectionProps) {
|
||||
className="group flex items-center gap-1.5 rounded-md border bg-card px-2 py-1 text-sm hover:border-destructive/50 transition-colors"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
process.env.NEXT_PUBLIC_BADGE_URL
|
||||
? `${process.env.NEXT_PUBLIC_BADGE_URL}/${badge.badgeCode}.gif`
|
||||
: `/swf/c_images/album1584/${badge.badgeCode}.gif`
|
||||
}
|
||||
src={`/api/imaging/badge?source=local&code=${encodeURIComponent(badge.badgeCode)}`}
|
||||
alt={badge.badgeCode}
|
||||
className="h-5 w-5"
|
||||
onError={(e) => {
|
||||
|
||||
@@ -18,11 +18,7 @@ export function UserBadgesReadonly({ badges }: { badges: Badge[] }) {
|
||||
className="flex items-center gap-1.5 rounded-md border bg-card px-2 py-1 text-sm"
|
||||
>
|
||||
<img
|
||||
src={
|
||||
process.env.NEXT_PUBLIC_BADGE_URL
|
||||
? `${process.env.NEXT_PUBLIC_BADGE_URL}/${badge.badgeCode}.gif`
|
||||
: `/swf/c_images/album1584/${badge.badgeCode}.gif`
|
||||
}
|
||||
src={`/api/imaging/badge?source=local&code=${encodeURIComponent(badge.badgeCode)}`}
|
||||
alt={badge.badgeCode}
|
||||
className="h-5 w-5"
|
||||
onError={(e) => {
|
||||
|
||||
@@ -19,9 +19,7 @@ const schema = z.object({
|
||||
export async function POST(request: Request) {
|
||||
if (
|
||||
request.headers.get("origin") !==
|
||||
new URL(
|
||||
process.env.APP_URL || process.env.NEXT_PUBLIC_APP_URL || request.url,
|
||||
).origin ||
|
||||
new URL(process.env.APP_URL || request.url).origin ||
|
||||
!request.headers.get("content-type")?.startsWith("application/json")
|
||||
)
|
||||
return new Response(null, { status: 403 });
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { resolveImagerBase } from "@/lib/imager";
|
||||
import { resolveImagerBase } from "@/lib/runtime-asset-config";
|
||||
|
||||
const FIGURE_RE = /^[a-z]{2}-\d+(-\d+)?(\.[a-z]{2}-\d+(-\d+)?)*$/i;
|
||||
const FIGURE_RE = /^[a-z]{2}-\d+(?:-\d+)*(?:\.[a-z]{2}-\d+(?:-\d+)*)*$/i;
|
||||
const FIGURE_MAX_LEN = 512;
|
||||
const FIGURE_MAX_PARTS = 24;
|
||||
const UPSTREAM_TIMEOUT_MS = 10_000;
|
||||
@@ -66,8 +66,9 @@ export async function GET(request: NextRequest) {
|
||||
const imgFormat = searchParams.get("img_format");
|
||||
if (imgFormat) params.set("img_format", imgFormat);
|
||||
|
||||
const upstream = resolveImagerBase();
|
||||
const upstreamUrl = `${upstream}?${params.toString()}`;
|
||||
const upstream = resolveImagerBase(new URL(request.url).origin);
|
||||
const upstreamUrl = new URL(upstream);
|
||||
for (const [key, value] of params) upstreamUrl.searchParams.set(key, value);
|
||||
|
||||
try {
|
||||
const res = await fetch(upstreamUrl, {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import { clientIp, rateLimit } from "@/lib/rate-limit";
|
||||
import { resolveBadgeBase } from "@/lib/runtime-asset-config";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
/**
|
||||
@@ -28,9 +29,14 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
const configured = (
|
||||
(await siteSettings.get("badge_base_url", "")) ?? ""
|
||||
(resolveBadgeBase() || (await siteSettings.get("badge_base_url", ""))) ??
|
||||
""
|
||||
).trim();
|
||||
const base = (configured || DEFAULT_BASE).replace(/\/+$/, "");
|
||||
const fallback =
|
||||
request.nextUrl.searchParams.get("source") === "local"
|
||||
? "/swf/c_images/album1584"
|
||||
: DEFAULT_BASE;
|
||||
const base = (configured || fallback).replace(/\/+$/, "");
|
||||
const isAbsolute = /^https?:\/\//i.test(base);
|
||||
const target = `${base}/${code}.gif`;
|
||||
const absoluteTarget = isAbsolute
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { type NextRequest, NextResponse } from "next/server";
|
||||
import sharp from "sharp";
|
||||
import { resolveImagerBase } from "@/lib/imager";
|
||||
import { resolveImagerBase } from "@/lib/runtime-asset-config";
|
||||
|
||||
const FIGURE_RE = /^([a-z]{2}-\d+)(\.[a-z]{2}-\d+)*$/i;
|
||||
const FIGURE_RE = /^[a-z]{2}-\d+(?:-\d+)*(?:\.[a-z]{2}-\d+(?:-\d+)*)*$/i;
|
||||
const FIGURE_MAX_LEN = 512;
|
||||
const FIGURE_MAX_PARTS = 24;
|
||||
const UPSTREAM_TIMEOUT_MS = 10_000;
|
||||
@@ -82,8 +82,9 @@ export async function GET(request: NextRequest) {
|
||||
|
||||
const format = resolveFormat(searchParams.get("format"));
|
||||
|
||||
const upstream = resolveImagerBase();
|
||||
const upstreamUrl = `${upstream}?${params.toString()}`;
|
||||
const upstream = resolveImagerBase(new URL(request.url).origin);
|
||||
const upstreamUrl = new URL(upstream);
|
||||
for (const [key, value] of params) upstreamUrl.searchParams.set(key, value);
|
||||
|
||||
try {
|
||||
const res = await fetch(upstreamUrl, {
|
||||
|
||||
@@ -2,6 +2,9 @@ import type { MetadataRoute } from "next";
|
||||
import { resolveHotelName } from "@/lib/hotel-name";
|
||||
import { siteSettings } from "@/lib/services/site-settings";
|
||||
|
||||
// Installation metadata must be resolved from the running container.
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
// Web app manifest — makes the hotel installable as a PWA (AtomCMS exposed PWA
|
||||
// settings but the rewrite ships a real, themeable manifest). Name + theme
|
||||
// colour follow the live website_settings; falls back to defaults with no DB.
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import type { MetadataRoute } from "next";
|
||||
|
||||
// Installation metadata must be resolved from the running container.
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
export default function robots(): MetadataRoute.Robots {
|
||||
const appUrl = process.env.APP_URL ?? "http://localhost:3000";
|
||||
return {
|
||||
|
||||
@@ -2,6 +2,9 @@ import { desc } from "drizzle-orm";
|
||||
import type { MetadataRoute } from "next";
|
||||
import { db, Guilds, WebsiteArticles } from "@/lib/db";
|
||||
|
||||
// Installation metadata must be resolved from the running container.
|
||||
export const dynamic = "force-dynamic";
|
||||
|
||||
// Built at request time — avoids competing with SSG workers for scarce DB
|
||||
// connections during `next build` (pool timeouts killed deploy on sitemap).
|
||||
|
||||
|
||||
Reference in new issue
Block a user