dorfteich/packages/shared/i18n/en/errors.json
Claude Fable 5 0c85293830
Some checks failed
CI / Lint, typecheck, test (push) Failing after 1m39s
CI / Auth e2e pack (push) Has been skipped
CI / Import/export fidelity gate (push) Has been skipped
CI / Build container images (push) Has been skipped
CD / Build and push images (push) Successful in 3m51s
CD / Deploy to Test (push) Successful in 9s
CD / Smoke tests against Test (push) Successful in 1m10s
CD / Promote to Int (push) Successful in 11s
Public REST API v1: personal access tokens, instance switch + per-pond opt-in (#104)
Token-authenticated machine access at /api/public/v1 — the foundation for
the built-in MCP endpoint (#105).

Personal access tokens:
- api_tokens table (SHA-256 hash, scope read|write, optional pond
  restriction, expiry, revocation, throttled last-used) + migration;
  secrets are dt_pat_<random>, shown exactly once
- lifecycle endpoints under /users/me/api-tokens (session-only — a leaked
  token can never mint more tokens) with audit entries
  api.token_created/api.token_revoked
- settings UI section (create with scope/expiry/pond restriction,
  one-time reveal with copy, list with status + revoke), de+en

Activation (404 semantics per #60 on both levels):
- instance setting api.enabled (default off, admin settings switch)
- pond setting apiEnabled (default off, pond settings toggle; the
  PondsService settings-merge learned the key — the #92 lesson)

Surface (/api/public/v1, excluded from the SPA's global prefix):
- me, ponds, pages (list/read as Markdown+HTML, create from Markdown via
  the shared pipeline, PATCH title/content, DELETE to trash), search
  (permission-filtered + narrowed to exposed ponds, highlights as **…**),
  markdown ZIP export, labels (tree, create/rename/recolour/move/delete,
  assign/unassign), comments (threads, create, resolve/reopen)
- content replacement travels the collab-owned document path: the new
  state lands as a MANUAL version "API update", then the established
  restore NOTIFY applies it — open editors converge, history stays
  append-only, no second lineage (VersionsService.replaceContent)
- hand-maintained OpenAPI 3.1 document at /openapi.json, pinned to the
  controller by a route-coverage test in both directions

Enforcement:
- PublicApiGuard: instance switch → bearer PAT auth (request.user is the
  token's user) → per-token rate limit (429 + Retry-After) → scope
  (403 scope_required) → pond opt-in + token restriction
- the shared PermissionGuard then applies the unchanged permission model;
  PageParamSource gained pondSlugParam for the slug+slug routes
- no cookies anywhere → no CSRF surface (pinned by a hostile-Origin test)
- every write audit-logged as api.write with the token attributed

Tests/verification:
- 12-test e2e pack: lifecycle, switches, permission matrix
  (reader/editor/outsider × scopes), restriction, page roundtrip incl.
  restore-NOTIFY assertion, labels, comments incl. policy, search
  narrowing, ZIP export, rate limit; full api suite 60/60 green
  (quota fixture via per-user override — never the instance default)
- new collab-pack test proves an open editor converges onto an API
  content replacement (green against a local seeded stack)
- UI smoke against the built SPA: token create/reveal/revoke, pond
  opt-in persists, admin switch persists (10/10)
- docs/self-hosting/public-api.md + README link

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
2026-07-12 11:17:03 +02:00

108 lines
6.6 KiB
JSON

{
"bad_request": "The request is invalid.",
"unauthorized": "Please sign in to continue.",
"forbidden": "You do not have permission for this action.",
"not_found": "The requested resource does not exist.",
"conflict": "The request conflicts with the current state.",
"gone": "This resource is no longer available.",
"payload_too_large": "The submitted data is too large.",
"rate_limited": "Too many requests — please try again later.",
"internal_error": "Internal server error.",
"registration_closed": "Registration is currently closed on this instance.",
"token_invalid": "This link is invalid or has expired.",
"login_failed": "Username/e-mail or password is incorrect.",
"login_backoff": "Too many failed attempts — please wait a few minutes.",
"email_unverified": "Please confirm your e-mail address first.",
"account_disabled": "This account has been disabled.",
"password_incorrect": "The current password is incorrect.",
"csrf_origin_mismatch": "The request came from an unexpected origin.",
"cannot_revoke_current_session": "Use sign-out to end your current session.",
"personal_pond_undeletable": "The personal pond cannot be deleted.",
"quota_exceeded": "The quota has been reached (limit: {{limit}}).",
"slug_taken": "This slug is already taken in this pond.",
"page_document_too_large": "The page is too large (limit: {{limitBytes}} bytes).",
"invalid_page_state": "The submitted page content is invalid.",
"page_trashed": "This page has been moved to the trash.",
"label_name_taken": "A label with this name already exists at this level.",
"label_cycle": "A label cannot be moved into its own subtree.",
"label_depth_exceeded": "Labels can be nested at most {{max}} levels deep.",
"label_has_pages": "This label still has pages assigned; confirm to detach them.",
"label_wrong_pond": "This label belongs to a different pond.",
"unsupported_file_type": "This file type is not supported.",
"upload_type_not_allowed": "This file type is not allowed on this instance.",
"file_too_large": "The file is too large (limit: {{limitBytes}} bytes).",
"converter_unavailable": "The document converter is currently unavailable. Please try again later.",
"converter_timeout": "The conversion took too long and was cancelled.",
"conversion_failed": "This document could not be converted.",
"renderer_unavailable": "The PDF renderer is currently unavailable. Please try again later.",
"render_failed": "This page could not be rendered as a PDF.",
"import_unsupported_format": "Only Word (.docx) and OpenDocument (.odt) documents can be imported.",
"network": "The server could not be reached.",
"grant_exists": "This grant already exists.",
"plugin_invalid_zip": "The uploaded file is not a valid ZIP archive.",
"plugin_bad_structure": "The plugin package has an invalid structure.",
"plugin_invalid_manifest": "The plugin manifest is invalid.",
"plugin_api_incompatible": "This plugin targets an unsupported plugin API version.",
"plugin_too_large": "The plugin package is too large (limit: {{limitBytes}} bytes).",
"plugin_missing_bundle": "A code plugin must include a plugin.js bundle.",
"plugin_missing_styles": "A style plugin must include a styles.css file.",
"plugin_css_unsafe": "The plugin stylesheet uses constructs that are not allowed.",
"plugin_version_not_higher": "An update must have a higher version than the installed one.",
"plugin_required_cannot_uninstall": "A required plugin cannot be uninstalled.",
"plugin_not_found": "This plugin is not installed.",
"plugin_not_optional": "Only optional plugins can be activated per pond.",
"grant_pond_admin_scope": "A Pond Admin grant must apply to the whole pond and a specific user.",
"grant_pond_admin_personal_pond": "A personal pond's only administrator is its owner.",
"grant_subject_id_mismatch": "The grant's subject is inconsistent.",
"grant_scope_id_mismatch": "The grant's scope is inconsistent.",
"grant_scope_not_found": "The label or page this grant points to does not exist in this pond.",
"grant_subject_not_found": "This user does not exist.",
"grant_last_admin": "The last Pond Admin cannot be removed.",
"member_not_found": "No user with that username or e-mail exists.",
"member_exists": "This user is already a member of this pond.",
"member_not_a_member": "This user is not a member of this pond.",
"member_is_owner": "The pond owner's membership cannot be changed here.",
"cannot_modify_self": "You cannot perform this action on your own account.",
"last_site_admin": "The last Site Admin cannot be removed.",
"setup_required": "This instance is not set up yet. Please run the first-run setup first.",
"setup_locked": "First-run setup has already been completed.",
"setup_admin_exists": "A Site Admin account already exists.",
"setup_admin_missing": "Create the Site Admin account first.",
"smtp_test_failed": "The SMTP test failed. Please check the connection details.",
"validation": {
"required": "This field is required.",
"taken": "This value is already taken.",
"username": {
"tooShort": "The username needs at least 3 characters.",
"tooLong": "The username can have at most 32 characters.",
"charset": "Only letters, digits, and hyphens are allowed."
},
"password": {
"tooShort": "The password needs at least 10 characters.",
"tooLong": "The password can have at most 128 characters.",
"tooCommon": "This password is too common."
},
"email": {
"invalid": "Please enter a valid e-mail address."
},
"displayName": {
"required": "Please enter a display name."
},
"labelColor": "Please enter a colour like #a1b2c3.",
"tooLong": "The input is too long."
},
"comments_editors_only": "Comments on this pond are limited to editors.",
"comment_not_author": "Only the author can change this comment.",
"comment_has_replies": "This comment has replies — only a pond admin can delete the whole thread.",
"comment_parent_invalid": "Replies must reference a top-level comment on the same page.",
"comment_not_root": "Only top-level comments can be resolved.",
"maintenance_mode": "The instance is in maintenance mode while a backup is being restored.",
"backup_connection_failed": "The Nextcloud connection test failed.",
"backup_restore_confirm_mismatch": "The confirmation text does not match the backup id.",
"backup_restore_running": "A restore is already running.",
"backup_remote_not_configured": "No Nextcloud backup target is configured.",
"backup_set_not_found": "The selected backup set was not found.",
"scope_required": "This API token does not have the required scope.",
"pond_not_found": "The pond does not exist."
}