Some checks failed
CI / Auth e2e pack (push) Waiting to run
CI / Import/export fidelity gate (push) Waiting to run
CI / Build container images (push) Waiting to run
CD / Build and push images (push) Failing after 1m33s
CD / Deploy to Test (push) Has been skipped
CD / Smoke tests against Test (push) Has been skipped
CD / Promote to Int (push) Has been skipped
CI / Lint, typecheck, test (push) Has been cancelled
Backend for installing plugin ZIPs (ADR 0008, plugin-architecture.md §Lifecycle, security.md §Plugins). Consumes the #70 SDK for validation. - Schema: `plugins` (id, name, version, apiVersion, kind, mode, manifest jsonb, removedAt soft-delete) + `pond_plugins` (per-pond activation) + `PluginInstanceMode` enum; migration 20260710130000_plugins. - `PluginPackageService`: pure, stateless ZIP → validated package via fflate — structure check, manifest validation (SDK), apiVersion gate, kind/bundle/styles rules, CSS sanitation (no @import / external url() / expression()), zip-slip and unpacked-size guards. Each failure carries a stable PluginErrorCode; manifest issues travel as ApiError details. - `PluginStorageService`: on-disk layout `<PLUGINS_DIR>/<id>/<version>/`; atomic writeVersion (staging dir + rename, no 404 window mid-update), removeVersion/removePlugin, traversal-safe asset resolution, dropzone + quarantine dirs. - `PluginsService`: install/update (update only to a strictly higher version, preserving the admin's instance mode; files land before the metadata pointer flips) / uninstall (refused while required; soft-delete + files removed + pond activations dropped) / list / get. - `POST/GET/DELETE /admin/plugins` (SiteAdminGuard, multer memory upload), error→HTTP-status mapping. Public version-pinned static serving at `GET /plugins/:id/:version/*rest` with immutable cache + nosniff, only for the installed current version. - `PluginWatcherService`: watches `<PLUGINS_DIR>/_dropzone/`, runs the same validation, installs valid drops and quarantines invalid ones with the error logged; inert under NODE_ENV=test (tests drive processDropped). - SDK: `compareVersions`/`isHigherVersion`. shared: `PluginView`, `PluginInstanceMode`, `PLUGIN_ERROR_CODES`, `PLUGINS_DIR` env, plugin error i18n (de+en). Compose: `plugins` volume + `PLUGINS_DIR`. - Tests: package unit test (valid + each invalid class) and an e2e DB test (GUI install + immutable serving, non-admin 403, invalid-manifest details, dropzone install + quarantine, atomic higher-only update, required-guarded uninstall that removes files and tombstones metadata). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EwZ4jR4KFAPvpjWevfUGX1
89 lines
5.2 KiB
JSON
89 lines
5.2 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.",
|
|
"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.",
|
|
"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."
|
|
}
|
|
}
|