808b15bafb
Adds a long-lived bearer-token mechanism + scoped REST surface designed
for n8n-style automation: create a gallery, upload photos, fetch the
share URL — all via documented HTTPS endpoints instead of poking at the
admin UI's internal routes.
API
- Migration 081 adds `api_tokens` (hashed_token, scopes, owner FK,
last_used/expires/revoked timestamps).
- New apiTokenAuth middleware: parses `Authorization: Bearer pp_live_…`,
resolves to the owner admin user, attaches `req.admin` so existing
permission decorators (events.create etc.) still work. Token-level
scope check (read/write/admin) layers on top as defence in depth —
a leaked read-only token cannot mutate even if its owner is super_admin.
- adminApiTokens route exposes list/create/revoke for admins (cookie-
authed). Plaintext token is returned exactly once on creation.
- v1 surface mounted at /api/v1: POST/GET /events, GET /events/:id,
POST /events/:id/photos (multipart, single file), GET
/events/:id/share-link. Each endpoint annotated with @openapi JSDoc.
Documentation
- swagger-jsdoc + swagger-ui-express produce a live spec at
/api/openapi.json and a Swagger UI at /api/docs (admin-gated).
- backend/scripts/generate-openapi.js writes docs/openapi.{json,yaml}
to the repo so the spec is versioned.
- scripts/sync-api-docs.sh runs in pre-push: regenerates the spec and
copies it into the picpeak-docs Nextra site at app/api/. Writes only,
never commits or pushes the docs repo (PUSH_SKIP_DOCS=1 to bypass).
Frontend
- New Settings → API Tokens tab: generate, list, revoke. Plaintext
tokens are shown once with a copy-to-clipboard control.
71 lines
2.0 KiB
JavaScript
71 lines
2.0 KiB
JavaScript
/**
|
|
* OpenAPI 3.1 spec for /api/v1/* (#322). Source of truth for the
|
|
* picpeak-docs reference page. Built from JSDoc `@openapi` blocks
|
|
* scattered through src/routes/v1 — those stay co-located with the
|
|
* routes they describe so the spec can't drift in isolation.
|
|
*/
|
|
|
|
const swaggerJSDoc = require('swagger-jsdoc');
|
|
const path = require('path');
|
|
|
|
const baseDoc = {
|
|
openapi: '3.0.3',
|
|
info: {
|
|
title: 'PicPeak API',
|
|
version: 'v1',
|
|
description:
|
|
'Public REST API for PicPeak — create gallery events, upload photos, fetch share links. ' +
|
|
'Authenticate with a Bearer token issued via the admin **Settings → API Tokens** tab.'
|
|
},
|
|
servers: [
|
|
{ url: '/api/v1', description: 'Same-origin (production)' }
|
|
],
|
|
components: {
|
|
securitySchemes: {
|
|
bearerAuth: {
|
|
type: 'http',
|
|
scheme: 'bearer',
|
|
bearerFormat: 'pp_live_*',
|
|
description:
|
|
'Long-lived API token. Issue via Settings → API Tokens. ' +
|
|
'Token format: `pp_live_<random>`. Scopes: `read`, `write`, `admin`.'
|
|
}
|
|
},
|
|
schemas: {
|
|
EventSummary: {
|
|
type: 'object',
|
|
properties: {
|
|
id: { type: 'integer' },
|
|
slug: { type: 'string' },
|
|
event_name: { type: 'string' },
|
|
event_type: { type: 'string' },
|
|
event_date: { type: 'string', format: 'date', nullable: true },
|
|
expires_at: { type: 'string', format: 'date-time', nullable: true },
|
|
is_active: { type: 'boolean' },
|
|
is_archived: { type: 'boolean' },
|
|
is_draft: { type: 'boolean' },
|
|
created_at: { type: 'string', format: 'date-time' }
|
|
}
|
|
}
|
|
}
|
|
},
|
|
security: [{ bearerAuth: [] }]
|
|
};
|
|
|
|
const options = {
|
|
definition: baseDoc,
|
|
// Pull @openapi blocks from every v1 route file.
|
|
apis: [path.join(__dirname, '../routes/v1/**/*.js')]
|
|
};
|
|
|
|
let cached = null;
|
|
|
|
function getOpenApiSpec() {
|
|
if (!cached) {
|
|
cached = swaggerJSDoc(options);
|
|
}
|
|
return cached;
|
|
}
|
|
|
|
module.exports = { getOpenApiSpec };
|