v0.7.0 — Suggestions module via @otm/account-panel

Wire the shared Gitea-backed suggestions panel: page, list/create + comments +
upload API routes, sidebar link. Suggestions file as issues on bonna61/Moonbase;
screenshot uploads persist via a mounted volume. Mirrors the FMB wiring.

Secrets (NEXTAUTH_SECRET, GITEA_TOKEN) moved out of docker-compose.yml to an
interpolated gitignored .env on the host, per CLAUDE.md (never commit the token).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-15 22:03:47 -05:00
parent 1f5aa001a8
commit 2e0931343f
14 changed files with 113 additions and 75 deletions

View File

@@ -0,0 +1,13 @@
import { Metadata } from "next";
import { redirect } from "next/navigation";
import { SuggestionsPanel } from "@otm/account-panel";
import { getSession } from "@/lib/auth";
export const metadata: Metadata = { title: "Suggestions" };
export const dynamic = "force-dynamic";
export default async function SuggestionsPage() {
const session = await getSession();
if (!session) redirect("/login");
return <SuggestionsPanel />;
}

View File

@@ -0,0 +1,9 @@
import { createSuggestionCommentsRoute } from "@otm/account-panel/server";
import { giteaConfig, suggestionGuard } from "@/lib/suggestions";
export const dynamic = "force-dynamic";
export const { GET, POST } = createSuggestionCommentsRoute({
gitea: giteaConfig(),
guard: suggestionGuard,
});

View File

@@ -0,0 +1,9 @@
import { createSuggestionsRoute } from "@otm/account-panel/server";
import { giteaConfig, suggestionGuard } from "@/lib/suggestions";
export const dynamic = "force-dynamic";
export const { GET, POST } = createSuggestionsRoute({
gitea: giteaConfig(),
guard: suggestionGuard,
});

View File

@@ -0,0 +1,21 @@
import { mkdir, writeFile } from "node:fs/promises";
import path from "node:path";
import { createSuggestionUploadRoute } from "@otm/account-panel/server";
import { suggestionGuard } from "@/lib/suggestions";
export const dynamic = "force-dynamic";
// Screenshots are saved under public/uploads/suggestions and referenced by
// absolute URL so Gitea's web UI can render them. In Docker this dir is mounted
// to a persistent volume (see docker-compose.yml) so images survive rebuilds.
export const { POST } = createSuggestionUploadRoute({
guard: suggestionGuard,
save: async ({ name, bytes }) => {
const safe = `${Date.now()}-${name.replace(/[^a-zA-Z0-9._-]/g, "_")}`;
const dir = path.join(process.cwd(), "public", "uploads", "suggestions");
await mkdir(dir, { recursive: true });
await writeFile(path.join(dir, safe), Buffer.from(bytes));
const base = (process.env.NEXT_PUBLIC_BASE_URL || process.env.NEXTAUTH_URL || "").replace(/\/+$/, "");
return { url: base ? `${base}/uploads/suggestions/${safe}` : `/uploads/suggestions/${safe}` };
},
});