Merged in jmathison/demo-table (pull request #213)

Add demo dashboard settings table and CRUD API

* Add demo dashboard settings table and CRUD API

- Migration 00000000000123: create demoDashboardSettings table (id, guid, key, value JSONB, isDeleted, timestamps)
- SQLC queries and repository for create, list, listByGuid, get, update, soft delete
- OpenAPI paths and schemas in queryAPI.yaml; regenerate api.gen.go (embedded spec)
- Handlers in api/queryAPI/demodashboardsettings.go; ConfigProvider GetDBQueries for repo access
- Unit tests for demo dashboard settings CRUD

* Fix sqlc-generated demo dashboard setting types

Align demo dashboard settings handlers with sqlc output (DemoDashboardSetting model + pointer slices) so CI codegen/build succeeds.

* Fix CI gosec and gofmt issues

- Suppress gosec G115 for TotalCount len->int32 cast (consistent with other list endpoints)
- gofmt mock GetDBQueries() in test helpers

* gofmt query API demo settings

* Tighten demo dashboard settings OpenAPI constraints

* Regenerate query API embedded spec

* refactor: generic UI settings (table, API, service) and three-layer architecture

- Replace demo-dashboard-settings with generic UI settings:
  - Table uiSettings with namespace (was guid), key, value JSONB; UNIQUE(namespace, key)
  - Migration 123: create_ui_settings (single migration, no rename)
  - OpenAPI: /ui-settings, UISettingsService, UISetting schemas with namespace
  - API types and handlers: CreateUISetting, ListUISettings, GetUISetting, UpdateUISetting, DeleteUISetting

- Add service layer (architecture fix):
  - internal/uisettings: Service with Create, List, ListByNamespace, Get, Update, SoftDelete
  - Controllers call s.svc.UISettings only; remove GetDBQueries from queryAPI ConfigProvider
  - Wire UISettings in Services, main.go, and testutils

- Repository: uisettings.sql + uisettings.sql.go, UISetting model; remove demodashboardsettings
- Controller: uisettings.go (replaces demodashboardsettings.go), ErrNotFound from uisettings package
- Tests: uisettings_test.go with namespace-based list/cre…
* Add UISetting model and generated queries for uiSettings table

- models.go: add UISetting struct (sqlc 1.30.0)
- uisettings.sql.go: generated CRUD for ui_settings feature

Made-with: Cursor

* Use repository.UiSetting to match sqlc-generated type

- service and API use repository.UiSetting (sqlc struct name for uiSettings table)
- uisettings.sql.go: keep CreateUISetting/GetUISetting etc. from query names, return *UiSetting
- Fixes Docker/CI build undefined repository.UISetting

* Fix import formatting for golangci-lint

* Lint fix

* uisettings list test

* tests fixes

* Merge main: resolve api.gen.go conflicts (UI settings + DeleteDocument)

* Doc updates and elimination of index redundancy


Approved-by: Jay Brown
This commit is contained in:
Jacob Mathison
2026-03-04 23:00:50 +00:00
parent 09c61ea9b4
commit 8a4029058b
17 changed files with 1693 additions and 289 deletions
+80
View File
@@ -28,6 +28,7 @@ The API is organized into the following service groups:
| AuthService | Operations related to authentication |
| AdminService | Operations related to user management and administration |
| EulaService | Operations related to End User License Agreement management and tracking |
| UISettingsService | Operations related to UI-scoped key-value settings (per user, client, or global) |
## Authentication and Authorization
@@ -1600,6 +1601,74 @@ Returns a comprehensive compliance report showing which users have and have not
---
## UI Settings Operations (UISettingsService)
The UI Settings service provides a generic key-value store scoped by namespace. Use it for user preferences, feature flags, saved filters, or other UI state. Namespaces scope data (e.g. `demo-dashboard:{userId}`, `feature-flags:global`, `saved-filters:{clientId}`). Records use soft delete (`isDeleted`); list/get exclude soft-deleted rows.
**Security**: All UI settings endpoints require JWT authentication (auth-only, no Permit.io resource check).
#### `GET /ui-settings`
Returns settings where `isDeleted` is false. Optional query param `namespace` filters by namespace.
**Query Parameters**:
| Parameter | Type | Required | Description |
| --------- | ------ | -------- | ------------------------------------------------ |
| namespace | string | No | Filter by namespace (omit to list all). Max 255 chars. |
**Response** (`UISettingListResponse`): `{ settings: UISetting[], totalCount: number }`
**Responses**: `200` OK, `400` Bad Request, `401` Unauthorized, `429` Too Many Requests, `500` Internal Server Error
#### `POST /ui-settings`
Creates a new UI setting. Returns 201 with the created resource.
**Request Body** (`UISettingCreate`):
| Field | Type | Required | Description |
| --------- | ------ | -------- | --------------------------------------------------------------------------- |
| namespace | string | Yes | Scope for the setting (e.g. demo-dashboard:{userId}, feature-flags:global). Max 255. |
| key | string | Yes | Setting key within the namespace. Max 255. |
| value | object | Yes | JSON value (object) for the setting. |
**Response** (`UISetting`): Created setting with `id`, `namespace`, `key`, `value`, `isDeleted`, `createdAt`, `updatedAt`
**Responses**: `201` Created, `400` Bad Request, `401` Unauthorized, `429` Too Many Requests, `500` Internal Server Error
#### `GET /ui-settings/{id}`
Returns a single setting by id. Returns 404 if not found or soft-deleted.
**Path Parameters**: `id` (uuid) UI setting ID
**Response** (`UISetting`): Full setting object
**Responses**: `200` OK, `400` Bad Request, `401` Unauthorized, `404` Not Found, `429` Too Many Requests, `500` Internal Server Error
#### `PATCH /ui-settings/{id}`
Updates the value of an existing setting. Returns 404 if not found or soft-deleted.
**Path Parameters**: `id` (uuid) UI setting ID
**Request Body** (`UISettingUpdate`): `{ value: object }` New JSON value for the setting
**Response** (`UISetting`): Updated setting object
**Responses**: `200` OK, `400` Bad Request, `401` Unauthorized, `404` Not Found, `429` Too Many Requests, `500` Internal Server Error
#### `DELETE /ui-settings/{id}`
Soft-deletes a UI setting (sets `isDeleted` to true). Record is retained. Returns 204 on success, 404 if not found.
**Path Parameters**: `id` (uuid) UI setting ID
**Responses**: `204` No Content, `400` Bad Request, `401` Unauthorized, `404` Not Found, `429` Too Many Requests, `500` Internal Server Error
---
## Request/Response Schemas
### Standard Response Headers
@@ -1710,6 +1779,11 @@ The following endpoints require a valid JWT token but do **not** check Permit.io
| `GET /identity` | Users need to discover their roles |
| `GET /eula/status` | Users need to check their EULA agreement status |
| `POST /eula/agree` | Users need to be able to agree to the EULA |
| `GET /ui-settings` | List UI settings (optional namespace filter) |
| `POST /ui-settings` | Create UI setting |
| `GET /ui-settings/{id}` | Get UI setting by ID |
| `PATCH /ui-settings/{id}` | Update UI setting value |
| `DELETE /ui-settings/{id}` | Soft-delete UI setting |
### Authorization Reference Table
@@ -1775,6 +1849,12 @@ The following table shows what data is passed to Permit.io for each protected en
| `POST` | `/admin/eula/{version_id}/activate` | `admin` | `post` | Activate EULA version |
| `GET` | `/admin/eula/agreements` | `admin` | `get` | List EULA agreements |
| `GET` | `/admin/eula/compliance` | `admin` | `get` | Get compliance report |
| **UI Settings Operations** |
| `GET` | `/ui-settings` | - | - | Auth-only (no Permit.io)|
| `POST` | `/ui-settings` | - | - | Auth-only (no Permit.io)|
| `GET` | `/ui-settings/{id}` | - | - | Auth-only (no Permit.io)|
| `PATCH` | `/ui-settings/{id}` | - | - | Auth-only (no Permit.io)|
| `DELETE` | `/ui-settings/{id}` | - | - | Auth-only (no Permit.io)|
### Permit.io Configuration Requirements
+47
View File
@@ -67,6 +67,16 @@ erDiagram
eulaVersions ||--o{ eulaAgreements : "has agreements"
uiSettings {
uuid id PK
varchar namespace
varchar key
jsonb value
boolean isDeleted
timestamptz createdAt
timestamptz updatedAt
}
clients {
varchar clientId PK
text name UK
@@ -318,6 +328,16 @@ erDiagram
timestamptz agreedAt
varchar agreedFromIp
}
uiSettings {
uuid id PK
varchar namespace
varchar key
jsonb value
boolean isDeleted
timestamptz createdAt
timestamptz updatedAt
}
```
## Core Entities
@@ -589,6 +609,33 @@ CREATE INDEX idx_eulaagreements_user_time ON eulaAgreements(cognitoSubjectId, ag
- ON DELETE RESTRICT prevents deleting EULA versions that have agreements
- Composite indexes optimize common query patterns (agreements by version, user history)
### UI Settings (`uiSettings`)
Generic key-value store scoped by namespace for UI state (preferences, feature flags, saved filters). Added in migration 123.
```sql
CREATE TABLE "uiSettings" (
id uuid PRIMARY KEY DEFAULT uuid_generate_v7(),
namespace VARCHAR(255) NOT NULL,
key VARCHAR(255) NOT NULL,
value JSONB NOT NULL,
"isDeleted" BOOLEAN NOT NULL DEFAULT FALSE,
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
"updatedAt" TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(namespace, key)
);
-- Index for list/filter by namespace; UNIQUE(namespace, key) already creates an index on (namespace, key)
CREATE INDEX idx_uisettings_namespace ON "uiSettings"(namespace);
```
**Key Features**:
- Namespace scopes data (e.g. `demo-dashboard:{userId}`, `feature-flags:global`, `saved-filters:{clientId}`)
- One setting per (namespace, key); value is JSONB for flexible structure
- Soft delete via `isDeleted`; list/get exclude soft-deleted rows
- UNIQUE(namespace, key) enforces one record per scope+key and provides an implicit index for lookups by (namespace, key)
- Single explicit index on `namespace` for listing/filtering by namespace
### Queries (`queries`)
Defines data extraction logic with type-specific implementations.
+27
View File
@@ -44,6 +44,8 @@ This document provides a comprehensive summary of all REST API endpoints availab
| /admin/eula/{version_id}/activate | POST | EulaService |
| /admin/eula/agreements | GET | EulaService |
| /admin/eula/compliance | GET | EulaService |
| /ui-settings | GET, POST | UISettingsService |
| /ui-settings/{id} | GET, PATCH, DELETE | UISettingsService |
---
@@ -353,6 +355,30 @@ This document provides a comprehensive summary of all REST API endpoints availab
- Defaults to current EULA version
- Returns: `{version_id, version, total_users, agreed_count, not_agreed_count, compliance_percentage, users[], page, page_size, has_more}`
## UI Settings Service
### UI Settings Management
- **GET /ui-settings** - List UI settings (soft-deleted excluded)
- Query Parameters:
- `namespace` (optional) - Filter by namespace (e.g. demo-dashboard:{userId}, feature-flags:global)
- Returns: `{settings[], totalCount}`
- **POST /ui-settings** - Create a UI setting
- Request Body: `{namespace, key, value}` (value is a JSON object)
- Use namespace to scope data (e.g. demo-dashboard:{userId}, feature-flags:global, saved-filters:{clientId})
- Returns: Created `UISetting` (201 Created)
- **GET /ui-settings/{id}** - Get a UI setting by ID
- Returns: `{id, namespace, key, value, isDeleted, createdAt, updatedAt}` (200 OK) or 404 if not found or soft-deleted
- **PATCH /ui-settings/{id}** - Update a UI setting value
- Request Body: `{value}` (new JSON object)
- Returns: Updated `UISetting` (200 OK) or 404 if not found or soft-deleted
- **DELETE /ui-settings/{id}** - Soft-delete a UI setting
- Sets `isDeleted` to true; record is retained
- Returns: 204 No Content or 404 if not found
## Common Response Codes
All endpoints may return the following standard HTTP response codes:
@@ -395,6 +421,7 @@ Rate limit headers included in all responses:
- `GET /swagger/*` - Swagger UI
- `GET /metrics` - Prometheus metrics
- `GET /eula` - Current public EULA version
- `GET /ui-settings`, `POST /ui-settings`, `GET /ui-settings/{id}`, `PATCH /ui-settings/{id}`, `DELETE /ui-settings/{id}` - UI settings (JWT required)
## API Versioning