Load page.json by import, publish via fs, drop cookie auth
The editor no longer round-trips through /api/pages. Each route's editor/page.tsx imports its own `../page.json` and hands it to PageEditor as a prop, so the editor opens with the page already in hand — no fetch, no loading state, no undo history seeded from a placeholder. Globals still come from app.globals.json. Publishing moves from `PUT /api/pages` to a `publishPage` server action that writes the route's page.json with node:fs directly. The target path is still built from the lib/pages.ts registry rather than from the caller, so an unknown route key is rejected instead of escaping app/. Removes the customer account auth entirely: the httpOnly cookie session, the /api/account/* handlers, the customer service and GraphQL documents, the account-* blocks, and the /account/* routes. The template has no auth, so nothing reads a cookie now. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01STGDvL4X7FhHHnxdRE2ayo
This commit is contained in:
co-authored by
Claude Opus 5
parent
63ecc5e284
commit
f11a764426
+2
-24
@@ -3,8 +3,8 @@
|
||||
*
|
||||
* `key` is what the editor's page picker shows and what `onPublish` hands back;
|
||||
* `dir` is the folder under `app/` holding that route's `page.json`. Keeping
|
||||
* the list here means the publish API can validate a request against it rather
|
||||
* than trusting a path from the browser.
|
||||
* the list here means the publish action can validate a route key against it
|
||||
* rather than trusting a path from the browser.
|
||||
*/
|
||||
export interface PageRoute {
|
||||
key: string;
|
||||
@@ -29,28 +29,6 @@ export const PAGE_ROUTES: PageRoute[] = [
|
||||
},
|
||||
{ key: '/search', label: 'Search', dir: 'search' },
|
||||
{ key: '/policies/[handle]', label: 'Policy', dir: 'policies/[handle]' },
|
||||
{ key: '/account', label: 'Account — orders', dir: 'account' },
|
||||
{ key: '/account/login', label: 'Account — sign in', dir: 'account/login' },
|
||||
{
|
||||
key: '/account/register',
|
||||
label: 'Account — register',
|
||||
dir: 'account/register',
|
||||
},
|
||||
{
|
||||
key: '/account/recover',
|
||||
label: 'Account — recover',
|
||||
dir: 'account/recover',
|
||||
},
|
||||
{
|
||||
key: '/account/reset/[id]/[token]',
|
||||
label: 'Account — set password',
|
||||
dir: 'account/reset/[id]/[token]',
|
||||
},
|
||||
{
|
||||
key: '/account/activate/[id]/[token]',
|
||||
label: 'Account — activate',
|
||||
dir: 'account/activate/[id]/[token]',
|
||||
},
|
||||
];
|
||||
|
||||
export const ROUTE_KEYS = PAGE_ROUTES.map((route) => route.key);
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
'use server';
|
||||
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
import { findPageRoute } from '@/lib/pages';
|
||||
|
||||
/**
|
||||
* Writes a published page straight to its `page.json` on disk.
|
||||
*
|
||||
* A server action rather than a route handler: the editor calls it like a
|
||||
* function, and there is no HTTP endpoint sitting in front of the filesystem.
|
||||
*
|
||||
* The path is built from the registry in `lib/pages.ts`, never from the caller,
|
||||
* so an unknown or crafted route key is rejected outright rather than escaping
|
||||
* the `app/` directory.
|
||||
*/
|
||||
|
||||
// Props of blocks marked `global: true` (header, footer) live in one file that
|
||||
// every page.json references, so editing them once updates every route.
|
||||
const GLOBALS_FILE = path.join(process.cwd(), 'app.globals.json');
|
||||
|
||||
export interface PublishResult {
|
||||
/** Path of the written file, relative to the project root. */
|
||||
file?: string;
|
||||
error?: string;
|
||||
}
|
||||
|
||||
export async function publishPage(
|
||||
routeKey: string,
|
||||
page: unknown
|
||||
): Promise<PublishResult> {
|
||||
const route = findPageRoute(routeKey);
|
||||
if (!route) return { error: `Unknown route: ${routeKey}` };
|
||||
|
||||
if (!page || typeof page !== 'object') {
|
||||
return { error: 'Expected a page object.' };
|
||||
}
|
||||
|
||||
const appDir = path.join(process.cwd(), 'app');
|
||||
const file = path.join(appDir, route.dir, 'page.json');
|
||||
|
||||
// Belt-and-braces against a registry entry with a traversing `dir`.
|
||||
if (file !== path.join(appDir, 'page.json') && !file.startsWith(appDir + path.sep)) {
|
||||
return { error: `Unknown route: ${routeKey}` };
|
||||
}
|
||||
|
||||
// Globals belong to the whole site, not this route, so they go to their own
|
||||
// file and are stripped from the page before it is written.
|
||||
const { globals, ...pageData } = page as Record<string, unknown>;
|
||||
|
||||
try {
|
||||
await writeFile(file, `${JSON.stringify(pageData, null, 2)}\n`, 'utf8');
|
||||
|
||||
if (globals && typeof globals === 'object') {
|
||||
await writeFile(
|
||||
GLOBALS_FILE,
|
||||
`${JSON.stringify(globals, null, 2)}\n`,
|
||||
'utf8'
|
||||
);
|
||||
}
|
||||
|
||||
return { file: path.relative(process.cwd(), file) };
|
||||
} catch (err) {
|
||||
// Read-only filesystems (most serverless hosts) land here. Say so plainly
|
||||
// rather than reporting a save that did not happen.
|
||||
console.error(`Failed to write ${file}:`, err);
|
||||
return {
|
||||
error:
|
||||
'Could not write page.json. The filesystem is read-only — run the editor locally to save.',
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user