Skip to content

epic(resources): generic master-data driver — 22 self-describing editable tables behind one endpoint #109

Closed as not planned

Description

@2000game

Extends #108, which asked for Bereiche specifically. The endpoint behind it turns out to be a
self-describing registry of 22 editable tables, so the right move is one generic driver, not a
hand-written resource per table.

What the endpoint gives you

POST /index.php?q=churchdb/ajax with func=getMasterData returns data.masterDataTables — every
editable table, with its label, shortname, physical table name, and a full column description
straight out of DESCRIBE (field, type, null, key, default, extra). Captured against eqrm prod
(CT 3.135.2, HAR 2026-08-13):

Label shortname table cols
Personenstatus status cdb_status 7
Standort station cdb_station 24
Bereich dep cdb_bereich 4
Gruppentypen groupTypes cdb_gruppentyp 14
Gruppentyp-Rolle grouptypeMemberstatus cdb_group_type_role 12
Sicherheitslevel securitylevel cc_securitylevel 3
Kommentare-Viewer comment_viewer cdb_comment_viewer 3
Personenfelder feld cdb_feld 20
Kategorie groupcategory cdb_groupcategory 8
Altersgruppen agegroups cdb_agegroups 5
Zielgruppen targetgroup cdb_targetgroup 3
growpath growpath cdb_growpath 7
Geschlecht, Familienstand, Beziehungstyp, Nationalität, Kontakt-Label, days, meeting_rhythmus, Gruppentreffen-Template, privacy-policy ×2 2–9

Writes go to the same endpoint (create = empty id, update = an id):

func=saveMasterData&table=cdb_bereich&id=&col0=bezeichnung&value0=…&col1=kuerzel&value1=…&col2=sortkey&value2=…
→ {"status":"success","data":null}

Because the schema is served by the instance, a generic driver needs no per-table knowledge: read
masterDataTables, validate declared columns against desc, emit colN/valueN pairs.

Three things this unblocks that are currently recorded as impossible

#96 — person statuses. The conclusion was that statuses are "neither declarable nor adoptable"
because /statuses is a read-only REST catalog. cdb_status is in this registry, fully editable
(bezeichnung, kuerzel, mitglied_yn, securitylevel_id, sortkey). The blocker was the REST
API, not ChurchTools.

#102 — cdb_comment_viewer-blocked grants. Three role instances on Bereich Pastoral Care are
blocked by exactly one cdb_comment_viewer-scoped grant each. That dimension is master data with
names (cdb_comment_viewer: id, bezeichnung, sortkey), so it can become a resolvable ref kind —
turning those from "unmanageable" into "declarable".

A latent assumption worth re-examining: cc_securitylevel is editable master data. ct-cli and
eqrm/ct-structure both treat security-level dataIds 1/2/3 as numeric-universal — safe to hard-code
because they "mean the same thing on every instance". If security levels are admin-editable rows with
a sortkey, that holds only by convention. Someone adding or reordering a level on one host silently
changes what a hard-coded scope: [1, 2, 3] grants there. At minimum this deserves a documented
check; possibly security levels should become refs like everything else.

Design notes

  • Read through REST where it exists. Campuses (/campuses), group types (/group/grouptypes),
    age groups, roles already have REST resources. Two write paths to the same row would be a mess —
    either keep REST authoritative for those tables and use master-data only for the ones REST cannot
    write, or migrate deliberately. My preference: master-data driver covers only tables with no REST
    write, and the registry is used to discover which those are.
  • Blast radius is instance-wide. These rows are referenced by every person and group; a botched
    bezeichnung update renames a status everywhere. Same protections as elsewhere: apply never
    deletes, preventDestroy honoured, and a plan that renders master-data changes distinctly.
  • Undocumented endpoint. Treat like the other reverse-engineered surfaces: record the capture,
    re-probe on CT major bumps, and fail loudly if masterDataTables lacks a declared table or column
    rather than posting an unknown colN.
  • func=deleteMasterData was not captured — unknown whether it exists. Not needed for apply.

Acceptance

Related: #108 (Bereiche, the case that found this), #96, #102, #105 (same endpoint family — the
permission catalog is generated from churchauth/ajax func=getMasterData), #98.

Activity

  1. 2000game commented on Aug 14, 2026

    @2000game
    MemberAuthor

    Probed live — the premise does not hold, so closing in favour of the narrow #108

    Ran getMasterData against eqrm-dev, CT 3.135.2, 2026-08-14 and classified all 24 registry tables against that instance's own OpenAPI spec. The epic assumed most of these tables lack a REST write path. On a live instance, most of them have one.

    Under the standing rule "if a feature has a REST API, use the REST API", a generic master-data driver would own exactly one table inside this tool's mandate.

    REST write path exists → REST stays authoritative (15)

    table REST
    cdb_station /campuses
    cdb_gruppentyp /group/grouptypes
    cdb_status /statuses
    cc_securitylevel /securitylevels/{id} — POST/PATCH/DELETE
    cdb_comment_viewer /person/commentviewers
    cdb_groupcategory /group/groupcategories
    cdb_group_type_role /group/roles
    cdb_agegroups /group/agegroups
    cdb_targetgroup /group/targetgroups
    cdb_gruppentreffen_template /group/meetingtemplates
    cdb_feld /dbfields
    cdb_geschlecht /person/sexes
    cdb_beziehungstyp /person/relationshiptypes
    cdb_growpath /person/growpaths
    cdb_contact_labels /contactlabels

    REST read-only (1)

    cdb_bereich → GET /departments, no write verb, no /departments/{id} at all.

    No REST endpoint (8)

    cdb_familienstand, cdb_nationalitaet, cdb_days, cdb_custom_choice, cdb_custom_multiselct, cdb_custom_selection, cdb_privacy_policy_agreement_types, cdb_privacy_policy_agreement_who — person master data and field option lists, all outside the structural mandate.

    What shipped instead (PR #112)

    What survives from this epic

    The audit lesson, which is #111's subject and shipped there: a "no write API" verdict now requires a REST probe and a getMasterData → masterDataTables probe, plus a UI capture where neither settles it. That is what turned three "impossible" claims into three shipped features.

    Closing as overtaken by the probe. If the mandate ever widens to person master data (marital status, nationality, field option lists), the generic driver becomes worth building and this comment is the inventory to start from.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    triageUnsorted intake — decide in the weekly sweep

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions