Moliri API v1: designed as a standard

The Node backend gets an API of its own design instead of copying the PHP endpoints one by one. The full design is docs/plan/backend-node-api-design.md; this page shows what changes and the choices still open.

317PHP endpoint files read and mapped to v1
7 → 1response shapes; errors always RFC 9457
~40 → 0endpoints that report errors with HTTP 200
12 → 1poll endpoints for background work → one job resource

1. Saving a field

PHP todayPOST /api/U/object_field.php { "project_id": 12, "object_id": 345, "field_id": 101, "field_type": "text", "value_table": "d_varchar", "new_value": "Öl auf Holz", "batch_id": "7f6c…" } → 200 { "success": "1", … } last write wins; meta (hesitant, source…) is a second endpoint, one column per call
v1PATCH /api/v1/objects/345/values/101 ?project_id=12 If-Match: "17" Moliri-Batch-Id: 7f6c… { "value": "Öl auf Holz", "hesitant": true } → 200 the value with version "18" → 412 someone else saved this field first: the answer carries their value, the UI shows both instead of losing one

Each field value has its own version, so two people editing different fields of one object never collide.

2. Deleting a record that is still used

PHP todayPOST /api/D/normdata.php { "normdata_id": 5512, "project_id": 12 } → 200 { "error": "…", "usage_count": 14 } (D/layout, D/virtual_field and four more delete without checking at all)
v1DELETE /api/v1/normdata/5512 If-Match: "4" → 409 application/problem+json { "type": ".../problems/in_use", "title": "Still in use", "status": 409, "usage": { "total": 14, "by_project": […] } }

The same rule for every library record. The app translates the error by its type; the server sends no German text.

3. Loading the gallery

PHP todayPOST /api/R/gallery.php?p=12 { "view_config": …, "only_ids": true } POST /api/R/gallery.php?p=12 { "object_ids": [ …1000 ids ] } values keyed by field NAME ("Material~sub"); filter = flat list with "(" and ")" as tokens
v1POST /api/v1/projects/12/objects/search/ids { "filter": { "all": [ { "field": { "field_id": 101 }, "op": "contains", "value": "Öl" } ] }, "sort": [ … ] } → { "ids": […], "total": 812, … } POST /api/v1/projects/12/objects/search → { "items": [ { "values": { "101": … } } ], "next_cursor": "…" }

Values are keyed by field id, so renaming a field breaks nothing; the filter is a tree that never reaches SQL as text.

4. Long work: exports, OCR, AI runs, uploads

PHP todayR/excel.php streams up to the time limit R/export_progress.php?cancel=1 (a GET that writes) R/natural_filter.php?id=… R/ocr_assign.php?id=… R/template_rewrite.php … twelve shapes upload errors are only logged on the server
v1POST /api/v1/projects/12/exports Prefer: wait=10 → 201 result directly when done in 10 s → 202 Location: /api/v1/jobs/8812 GET /api/v1/jobs/8812 status, progress POST /api/v1/jobs/8812/cancel GET /api/v1/uploads/{id} → shows failures

Also in the design

Choices for the owner

QuestionRecommendationAlternative
AStyle of pathssnake_case like the database: /access_groupskebab-case: /access-groups
BProperty names in JSONsnake_case like the database: project_idcamelCase: projectId
CKey of field valuesfield id (survives renaming)field name (readable, breaks on rename)
DTwo people save the same fieldalways checked; the second sees both valuesonly checked when the client asks; otherwise last write wins (today)
EPaging through listscursor onlypage numbers as well for admin lists
JBatch where some items failapply what may be applied, report the restall or nothing