Files
school_compare/nextjs-app/collections/Users.ts
T
TudorandClaude Opus 5 c5a4d106da feat(cms): install Payload and serve the admin panel
Payload 3.88 runs inside the Next app against the existing Postgres, in
its own 'payload' schema so no pipeline operation on public — the app
tables, Airflow's metadata, migrate_csv_to_db.py --drop — can reach blog
content.

Its REST API is mounted at /cms-api. /api is the FastAPI proxy's
catch-all, which would swallow every admin call and forward it to the
backend with no error. The mount points live in lib/payloadRoutes.ts so
there is one definition and a test can assert it without importing
Payload: it is ESM-only, next/jest will not transform it, and appending
transformIgnorePatterns cannot un-ignore a package. Forcing it through
transpilePackages would change how the production build bundles Payload
to serve a test, so the live proof that /api still reaches FastAPI stays
where it belongs — the e2e journeys, which call /api/schools.

The package becomes ESM ("type": "module"), which Payload's CLI requires:
richtext-lexical has top-level await and the config cannot be require()d.
Only two files needed renaming, jest.config.cjs and a build script.

The build is verified to succeed with DATABASE_URL and PAYLOAD_SECRET
both unset, which is how CI builds it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017YmbBhr8s7GusjDE12hrZM
2026-09-02 16:17:01 +01:00

34 lines
981 B
TypeScript

import type { CollectionConfig } from 'payload';
/**
* The site's only authenticated surface. There is one account and no
* registration: `create` is closed to everyone, so the first user is seeded
* with `payload create-first-user` and no one can add another through the API.
*/
export const Users: CollectionConfig = {
slug: 'users',
auth: {
// Slows credential stuffing against a panel that is on the public
// internet. Five attempts, then a ten-minute lock.
maxLoginAttempts: 5,
lockTime: 10 * 60 * 1000,
},
access: {
create: () => false,
read: ({ req }) => Boolean(req.user),
update: ({ req }) => Boolean(req.user),
delete: () => false,
},
admin: { useAsTitle: 'email' },
fields: [
{
name: 'displayName',
type: 'text',
required: true,
// Rendered as the byline on every post. First name only — the site
// publishes no surname and no employer.
defaultValue: 'Tudor',
},
],
};