feat(t-paliad-189): instance_level on project Create/Update

Phase 3 Slice 8 part 1 — wire the instance_level data field (mig 080
column, shipped in Slice 1) through the project service + handler.

  - CreateProjectInput / UpdateProjectInput gain InstanceLevel *string.
    Empty string is the explicit "clear" sentinel.
  - validateInstanceLevel + nullableInstanceLevel helpers mirror the
    OurSide pattern. Allowed values per mig 080 CHECK: 'first' |
    'appeal' | 'cassation' | NULL.
  - Service rejects bad values with ErrInvalidInput (existing handler
    error-mapping surfaces this as HTTP 400 with the standard message).
  - projectColumns SELECT now includes instance_level so reads
    populate the field; Project struct already has the field from
    Slice 1.
  - handleCreateProject accepts instance_level from the raw map; Update
    handler uses the standard JSON decoder into UpdateProjectInput.

Live-DB test exercises:
  - Create with instance_level='first' → roundtrips.
  - Update to 'appeal' → roundtrips.
  - Update to '' → NULL after the trip.
  - Update to 'supreme' → ErrInvalidInput.

The DB CHECK on mig 080 is the defence-in-depth backstop should an
SQL-direct INSERT bypass the service.
This commit is contained in:
mAi
2026-05-15 01:28:45 +02:00
parent 6f77c8354c
commit a55f45ebea
3 changed files with 154 additions and 3 deletions

View File

@@ -271,6 +271,11 @@ func handleCreateProject(w http.ResponseWriter, r *http.Request) {
if v, ok := raw["netdocuments_url"].(string); ok && v != "" {
input.NetDocumentsURL = &v
}
if v, ok := raw["instance_level"].(string); ok {
// Empty string is the explicit "clear" sentinel for the
// service layer (nullableInstanceLevel writes NULL).
input.InstanceLevel = &v
}
p, err := dbSvc.projects.Create(r.Context(), uid, input)
if err != nil {
writeServiceError(w, err)