refac: DELETE /v1/user/{id}?expected_role_id=N, user.remove audit event, 409 target_role_not_found, GetAll returns its error, Own-scope visibility filter moved into repos, JSON tags removed from use-case types

This commit is contained in:
Florian Sylvain committed 2026-10-29 16:28:25 +01:00
1 parent 5d6f378fce
commit b39a41be88
124 files changed
+8816 -523

No files matched your search

+36 -43
View File
@@ -1,5 +1,5 @@
export type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
export type AccessLevel = 'Public' | 'Verified user' | 'Owner or admin' | 'Owner only'
export type AccessLevel = 'Public' | 'Verified user' | 'Needs permission' | 'Owner only'
export interface ApiField {
name: string
@@ -94,7 +94,7 @@ export const API_KEY_PLACEHOLDER = 'rcms_Kf3x9QwL0aBc…'
export const NO_CONTENT_RESPONSE = '204 No Content'
export const SESSION_REQUIRED_MESSAGE = 'This action needs a real session.'
export const OWNER_REQUIRED_MESSAGE = 'missing_permission'
export const OWNER_OR_ADMIN_MESSAGE = 'missing_permission'
export const MISSING_PERMISSION_BODY = 'missing_permission'
export const IMAGES_AFTER_GROUP = 'sites'
export const FIELD_TYPES_ANCHOR = 'ct-fields'
export const LOGIN_FAMILY_CAPTION = 'Made for the browser. A key is ignored here, with no 403.'
@@ -649,7 +649,7 @@ const literal = (status: number, caption: string, tail?: string): ApiError => ({
})
const e401 = e(401, UNAUTHORIZED)
const eOwnerOrAdmin = e(403, OWNER_OR_ADMIN_MESSAGE)
const eMissingPermission = literal(403, MISSING_PERMISSION_BODY)
const contentTypeNotFound = literal(404, 'content_type_not_found')
const entryNotFound = literal(404, 'entry_not_found')
const invalidEntryUnique = literal(409, 'invalid_entry: title must be unique')
@@ -662,11 +662,7 @@ const verified = (errors: ApiError[]) => [...errors, e(403, VERIFY_REQUIRED)]
const verifiedAfterLogin = (errors: ApiError[]) =>
errors.flatMap((error) => (error.status === 401 ? [error, e(403, VERIFY_REQUIRED)] : [error]))
const ownerOrAdminErrors = (...own: ApiError[]) => [
...own,
e(401, UNAUTHORIZED),
e(403, OWNER_OR_ADMIN_MESSAGE),
]
const permissionErrors = (...own: ApiError[]) => [...own, e(401, UNAUTHORIZED), eMissingPermission]
const articlePage = () => ({
items: [
@@ -1032,7 +1028,7 @@ export const API_GROUPS: ApiGroup[] = [
key: 'ctypes',
title: 'Content types',
description:
'Define your own kind of content, like events or recipes. An owner or admin creates a type made of 1 to 50 ordered fields; the type then has its own entry routes.',
'Define your own kind of content, like events or recipes. A user whose role can create and edit content types defines a type made of 1 to 50 ordered fields; the type then has its own entry routes.',
note: 'A content type has a `key` (2 to 40 lowercase letters, digits or hyphens, chosen at creation and never changed) and fields. Each field has a `key` (letters, digits and underscores, starting with a letter), a `label`, a `type` and rules.',
fieldTypes: FIELD_TYPE_ROWS,
snippet: { key: 'field', label: 'Field object', code: json(eventFields[2]) },
@@ -1059,12 +1055,12 @@ export const API_GROUPS: ApiGroup[] = [
'ct-create',
'POST',
'/v1/content-type/',
'Create a content type.',
'Owner or admin',
'Create a content type. Needs the permission "Create and edit content types".',
'Needs permission',
[
e(400, BODY_MISMATCH),
e401,
eOwnerOrAdmin,
eMissingPermission,
literal(409, 'content_type_key_taken'),
],
json(eventType),
@@ -1095,12 +1091,12 @@ export const API_GROUPS: ApiGroup[] = [
'ct-update',
'PUT',
'/v1/content-type/{key}',
"Replace the name, icon and fields of a type. The key never changes. Saved field keys and types can't change.",
'Owner or admin',
'Replace the name, icon and fields of a type. The key never changes. Saved field keys and types can\'t change. Needs the permission "Create and edit content types".',
'Needs permission',
[
e(400, BODY_MISMATCH),
e401,
eOwnerOrAdmin,
eMissingPermission,
contentTypeNotFound,
literal(
409,
@@ -1128,11 +1124,11 @@ export const API_GROUPS: ApiGroup[] = [
'ct-delete',
'DELETE',
'/v1/content-type/{key}',
'Delete a content type. Only possible when it has no entries and no relation field points at it.',
'Owner or admin',
'Delete a content type. Only possible when it has no entries and no relation field points at it. Needs the permission "Delete content types".',
'Needs permission',
[
e401,
eOwnerOrAdmin,
eMissingPermission,
contentTypeNotFound,
literal(409, 'content_type_has_entries'),
literal(409, 'content_type_referenced'),
@@ -1145,7 +1141,7 @@ export const API_GROUPS: ApiGroup[] = [
key: 'entries',
title: 'Entries',
description:
'Read and write the content of a custom type. Any verified user can write entries.',
"Read and write the content of a custom type. Each route needs the matching content permission of the key creator's role.",
note: "An entry's `data` is an object keyed by field key, validated against the type. Responses add `media` (the resolved images for media fields, keyed by field key: id, filename, width, height, size and thumbnail_url, or { id, missing: true } if the image was deleted) and `relations` (the resolved first field of related entries).",
snippet: { key: 'entry', label: 'Entry', code: json(eventEntry()) },
endpoints: [
@@ -1258,9 +1254,9 @@ export const API_GROUPS: ApiGroup[] = [
'bt-list',
'GET',
'/v1/builtin-type/',
'List the built-in types (Articles, Authors, Sites, Media, Users) and their fields. Keys are article, author, site, media and user.',
'Owner or admin',
[e401, eOwnerOrAdmin],
'List the built-in types (Articles, Authors, Sites, Media, Users) and their fields. Keys are article, author, site, media and user. Needs the permission "View content types".',
'Needs permission',
[e401, eMissingPermission],
json([
{ key: 'article', name: 'Articles', fields: articleBuiltinFields },
{ key: 'author', name: 'Authors', fields: authorBuiltinFields },
@@ -1270,9 +1266,9 @@ export const API_GROUPS: ApiGroup[] = [
'bt-get',
'GET',
'/v1/builtin-type/{key}',
'Get one built-in type. Keys are article, author, site, media and user.',
'Owner or admin',
[e401, eOwnerOrAdmin, literal(404, 'builtin_type_not_found')],
'Get one built-in type. Keys are article, author, site, media and user. Needs the permission "View content types".',
'Needs permission',
[e401, eMissingPermission, literal(404, 'builtin_type_not_found')],
json({ key: 'author', name: 'Authors', fields: authorBuiltinFields }),
),
],
@@ -1431,18 +1427,18 @@ export const API_GROUPS: ApiGroup[] = [
'med-purge-get',
'GET',
'/v1/media/purge-settings',
'Get the automatic duplicate purge settings.',
'Owner or admin',
ownerOrAdminErrors(),
'Get the automatic duplicate purge settings. Needs the permission "View settings".',
'Needs permission',
permissionErrors(),
json(PURGE_SETTINGS),
),
endpoint(
'med-purge-set',
'PATCH',
'/v1/media/purge-settings',
'Turn the automatic purge on or off, or change its interval.',
'Owner or admin',
ownerOrAdminErrors(e(400, BODY_MISMATCH), e(400, PURGE_INTERVAL)),
'Turn the automatic purge on or off, or change its interval. Needs the permission "Edit general settings".',
'Needs permission',
permissionErrors(e(400, BODY_MISMATCH), e(400, PURGE_INTERVAL)),
json(PURGE_SETTINGS),
{
bodyKind: 'json',
@@ -1470,9 +1466,9 @@ export const API_GROUPS: ApiGroup[] = [
'med-purge-prev',
'GET',
'/v1/media/purge-preview',
'Preview what a purge would merge. Nothing changes.',
'Owner or admin',
ownerOrAdminErrors(),
'Preview what a purge would merge. Nothing changes. Needs the permission "Delete files".',
'Needs permission',
permissionErrors(),
json({
groups: 1,
copies: 2,
@@ -1494,13 +1490,9 @@ export const API_GROUPS: ApiGroup[] = [
'med-purge-run',
'POST',
'/v1/media/purge',
'Merge every group of identical copies. Send the preview fingerprint to refuse a changed library. failed_groups counts groups that could not be purged; the others were still purged.',
'Owner or admin',
ownerOrAdminErrors(
e(400, BODY_MISMATCH),
e(409, PURGE_CONFLICT),
e(409, PURGE_BUSY),
),
'Merge every group of identical copies. Send the preview fingerprint to refuse a changed library. failed_groups counts groups that could not be purged; the others were still purged. Needs the permission "Delete files".',
'Needs permission',
permissionErrors(e(400, BODY_MISMATCH), e(409, PURGE_CONFLICT), e(409, PURGE_BUSY)),
json({
groups_total: 1,
groups_purged: 1,
@@ -1885,8 +1877,9 @@ export const ERROR_ROWS: ErrorRow[] = [
{
status: 403,
meaning: 'Forbidden',
messages: [SESSION_REQUIRED_MESSAGE, OWNER_OR_ADMIN_MESSAGE, VERIFY_REQUIRED],
note: 'Site SEO defaults are owner only. Article SEO fields need only the right to edit the article.',
messages: [SESSION_REQUIRED_MESSAGE, VERIFY_REQUIRED],
codes: [MISSING_PERMISSION_BODY],
note: '`missing_permission` is the literal body, not a sentence. Site SEO defaults need the permission to manage sites. Article SEO fields need only the right to edit the article.',
},
{
status: 404,