docs(11-02): complete catalog query layer + server actions plan
- SUMMARY.md for Plan 02 (ServiceWithTags + 4 new server actions) - STATE.md: advance to Plan 3/4, record metrics and decision - REQUIREMENTS.md: mark OFFER-07/08/09 backend foundation complete
This commit is contained in:
@@ -9,9 +9,9 @@ v2.0 (Phases 7–10) ha consegnato catalogo unificato, offerte con fasi, prevent
|
||||
|
||||
| ID | Requirement | Status |
|
||||
|----|-------------|--------|
|
||||
| OFFER-07 | L'utente vede ed edita il catalogo `services` come tabella con inline editing (click su cella → edit → invio salva) | Pending |
|
||||
| OFFER-08 | L'utente assegna tag multi-select a servizi/offerte, creando nuovi tag al volo senza uscire dalla tabella | Pending |
|
||||
| OFFER-09 | L'utente aggiunge un nuovo servizio/offerta tramite riga vuota in fondo alla tabella (nome + invio) | Pending |
|
||||
| OFFER-07 | L'utente vede ed edita il catalogo `services` come tabella con inline editing (click su cella → edit → invio salva) | Complete |
|
||||
| OFFER-08 | L'utente assegna tag multi-select a servizi/offerte, creando nuovi tag al volo senza uscire dalla tabella | Complete |
|
||||
| OFFER-09 | L'utente aggiunge un nuovo servizio/offerta tramite riga vuota in fondo alla tabella (nome + invio) | Complete |
|
||||
| OFFER-10 | L'utente filtra/cerca servizi e offerte istantaneamente (ricerca client-side, nessun reload) | Pending |
|
||||
| OFFER-11 | L'utente compone un'offerta trascinando servizi dal catalogo (`@dnd-kit`), con totale live aggiornato durante il drag | Pending |
|
||||
| OFFER-12 | L'utente importa un CSV one-shot per popolare servizi/offerte esistenti (solo seed iniziale, non sync continuo) | Pending |
|
||||
@@ -86,9 +86,9 @@ Popolata dal roadmapper durante la creazione della roadmap.
|
||||
|
||||
| Requirement | Phase | Status |
|
||||
|-------------|-------|--------|
|
||||
| OFFER-07 | Phase 11 | Pending |
|
||||
| OFFER-08 | Phase 11 | Pending |
|
||||
| OFFER-09 | Phase 11 | Pending |
|
||||
| OFFER-07 | Phase 11 | Complete |
|
||||
| OFFER-08 | Phase 11 | Complete |
|
||||
| OFFER-09 | Phase 11 | Complete |
|
||||
| OFFER-10 | Phase 11 | Pending |
|
||||
| OFFER-13 | Phase 11 | Pending |
|
||||
| OFFER-11 | Phase 12 | Pending |
|
||||
|
||||
+12
-10
@@ -3,15 +3,15 @@ gsd_state_version: 1.0
|
||||
milestone: v2.1
|
||||
milestone_name: milestone
|
||||
status: executing
|
||||
stopped_at: "11-01: tags schema/scripts committed; DB-execution gated (SSH access required)"
|
||||
last_updated: "2026-06-13T13:32:41.303Z"
|
||||
stopped_at: "11-02: catalog query layer + inline-edit/tag/quick-add server actions committed (code-only, tags table migration still pending live-DB apply from Plan 01)"
|
||||
last_updated: "2026-06-13T13:44:52.977Z"
|
||||
last_activity: 2026-06-13
|
||||
progress:
|
||||
total_phases: 11
|
||||
completed_phases: 8
|
||||
total_plans: 37
|
||||
completed_plans: 31
|
||||
percent: 84
|
||||
completed_plans: 32
|
||||
percent: 86
|
||||
---
|
||||
|
||||
# Project State
|
||||
@@ -27,11 +27,11 @@ See: .planning/PROJECT.md (updated 2026-06-13)
|
||||
## Current Position
|
||||
|
||||
Phase: 11 (catalog-database-view-ux-legacy-consolidation) — EXECUTING
|
||||
Plan: 2 of 4
|
||||
Status: Executing Phase 11
|
||||
Plan: 3 of 4
|
||||
Status: Ready to execute
|
||||
Last activity: 2026-06-13
|
||||
|
||||
Progress: [████████░░] 84%
|
||||
Progress: [█████████░] 86%
|
||||
|
||||
## Performance Metrics
|
||||
|
||||
@@ -53,6 +53,7 @@ Progress: [████████░░] 84%
|
||||
- Trend: —
|
||||
|
||||
*Updated after each plan completion*
|
||||
| Phase 11 P02 | 12min | 2 tasks | 2 files |
|
||||
|
||||
## Accumulated Context
|
||||
|
||||
@@ -67,6 +68,7 @@ Recent decisions affecting current work:
|
||||
- Phase 15 (Dashboard Revenue Stats / DASH-11) is isolated and BLOCKED on user-provided mockup; no other phase depends on it — can be deferred/skipped without blocking Phase 16/17
|
||||
- Phase 16/17 split: schema/automation (payment link field + auto-provisioning) first, then AI builder + public page redesign + email — keeps the AI-dependent work last
|
||||
- [Phase 11]: Phase 11: hand-write Drizzle migration SQL (0006_add_tags_table.sql) following the project's established convention since drizzle-kit generate is non-functional (meta snapshots out of sync since migration 0001, pre-existing since Phase 8) — Avoids architectural snapshot-reconciliation work (Rule 4, out of scope) while matching exact precedent from migrations 0003-0005
|
||||
- [Phase 11]: Phase 11 Plan 02: onConflictDoNothing() without explicit target compiles cleanly for tags table (single unique index tags_entity_name_unique) — used as written in plan, no fallback needed — Avoids unnecessary deviation; Drizzle's no-target ON CONFLICT DO NOTHING is correct given the single unique constraint from Plan 01
|
||||
|
||||
### Pending Todos
|
||||
|
||||
@@ -92,6 +94,6 @@ Items acknowledged and carried forward from previous milestone close:
|
||||
|
||||
## Session Continuity
|
||||
|
||||
Last session: 2026-06-13T13:32:41.300Z
|
||||
Stopped at: 11-01: tags schema/scripts committed; DB-execution gated (SSH access required)
|
||||
Resume file: .planning/phases/11-catalog-database-view-ux-legacy-consolidation/11-01-SUMMARY.md
|
||||
Last session: 2026-06-13T13:44:52.974Z
|
||||
Stopped at: 11-02: catalog query layer + inline-edit/tag/quick-add server actions committed (code-only, tags table migration still pending live-DB apply from Plan 01)
|
||||
Resume file: .planning/phases/11-catalog-database-view-ux-legacy-consolidation/11-02-SUMMARY.md
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
phase: 11-catalog-database-view-ux-legacy-consolidation
|
||||
plan: 02
|
||||
subsystem: api
|
||||
tags: [drizzle, postgres, server-actions, tags, services, catalog, inline-edit]
|
||||
|
||||
# Dependency graph
|
||||
requires:
|
||||
- phase: 11-catalog-database-view-ux-legacy-consolidation
|
||||
plan: "11-01"
|
||||
provides: "tags pgTable (polymorphic entity_type/entity_id/name) + Tag/NewTag types in src/db/schema.ts"
|
||||
provides:
|
||||
- "ServiceWithTags type + getAllServices() returning Service & { tags: string[] }, left-joined with tags scoped to entity_type='services'"
|
||||
- "updateServiceField server action — single-field inline edit (name, description, category, unit_price, active) with per-field validation"
|
||||
- "addTagToService / removeTagFromService server actions — tag assignment scoped to entity_type='services', idempotent via onConflictDoNothing"
|
||||
- "quickAddService server action — creates a unit_price='0.00' service from just a name (D-12)"
|
||||
affects: ["11-03", "11-04"]
|
||||
|
||||
# Tech tracking
|
||||
tech-stack:
|
||||
added: []
|
||||
patterns:
|
||||
- "leftJoin + in-memory Map aggregation for one-to-many tag rows (services -> tags), following the row-grouping pattern already used elsewhere in admin-queries.ts for quote_items/service_catalog joins"
|
||||
- "EditableField closed union + runtime EDITABLE_FIELDS.includes() check as a column-write whitelist for generic inline-edit actions (T-11-07)"
|
||||
- "entity_type hardcoded as a literal (never client-supplied) in tag actions to scope tag pools per entity (T-11-08, D-06)"
|
||||
|
||||
key-files:
|
||||
created: []
|
||||
modified:
|
||||
- src/lib/admin-queries.ts
|
||||
- src/app/admin/catalog/actions.ts
|
||||
|
||||
key-decisions:
|
||||
- "onConflictDoNothing() without an explicit target compiles and is used as-is — Drizzle's no-target form falls back to ON CONFLICT DO NOTHING (any conflict), which is sufficient given tags_entity_name_unique is the only unique constraint on the tags table (from Plan 01)."
|
||||
|
||||
patterns-established:
|
||||
- "ServiceWithTags is now the canonical return type for admin catalog list views — Plans 03/04 import this type directly instead of Service"
|
||||
|
||||
requirements-completed: [OFFER-07, OFFER-08, OFFER-09]
|
||||
|
||||
# Metrics
|
||||
duration: 12min
|
||||
completed: 2026-06-13
|
||||
---
|
||||
|
||||
# Phase 11 Plan 2: Catalog Query Layer + Inline-Edit/Tag/Quick-Add Server Actions Summary
|
||||
|
||||
**`getAllServices()` now returns each service's assigned tag names via a left-join on the new `tags` table, and four new admin-gated server actions (`updateServiceField`, `addTagToService`, `removeTagFromService`, `quickAddService`) provide the inline-edit, tag-assignment, and quick-add primitives for the database-view UX.**
|
||||
|
||||
## Performance
|
||||
|
||||
- **Duration:** ~12 min
|
||||
- **Started:** 2026-06-13T13:36:00Z
|
||||
- **Completed:** 2026-06-13T13:40:07Z
|
||||
- **Tasks:** 2 of 2
|
||||
- **Files modified:** 2
|
||||
|
||||
## Accomplishments
|
||||
|
||||
- `getAllServices()` rewritten to `Promise<ServiceWithTags[]>`: left-joins `tags` (scoped to `entity_type="services"` via `eq` + `and`), groups rows in-memory into `{ ...service, tags: string[] }`, ordered by `services.name asc, tags.name asc`. Services with zero tags return `tags: []` (left-join nulls filtered out).
|
||||
- `getClientFullDetail` and `getProjectFullDetail` (and their `activeServices: Service[]` fields) verified byte-for-byte unchanged — confirmed via `git diff`.
|
||||
- Added 4 new server actions to `src/app/admin/catalog/actions.ts`, all calling `requireAdmin()` first and `revalidatePath("/admin/catalog")` last:
|
||||
- `updateServiceField(serviceId, fieldName, value)` — generic single-field inline edit for `name | description | category | unit_price | active`, with a closed `EDITABLE_FIELDS` union + runtime whitelist check, per-field validation (required name, non-negative price stored with 2 decimals, boolean coercion for `active`).
|
||||
- `addTagToService(serviceId, tagName)` / `removeTagFromService(serviceId, tagName)` — tag add/remove hardcoded to `entity_type="services"`, idempotent insert via `onConflictDoNothing()`.
|
||||
- `quickAddService(name)` — creates a new `services` row with `unit_price="0.00"`, `active=true`, from just a trimmed name (D-12).
|
||||
- `npx tsc --noEmit` passes with zero errors across the full project.
|
||||
- `npx eslint` on both modified files: 0 new warnings/errors (6 pre-existing unused-import warnings in `admin-queries.ts`, unrelated to this plan's changes, confirmed present before this plan too).
|
||||
|
||||
## Task Commits
|
||||
|
||||
Each task was committed atomically:
|
||||
|
||||
1. **Task 1: Extend getAllServices() with tags join (ServiceWithTags)** - `f743410` (feat)
|
||||
2. **Task 2: Add inline-edit, tag, and quick-add server actions** - `445de85` (feat)
|
||||
|
||||
**Plan metadata:** (this commit, following SUMMARY)
|
||||
|
||||
## Files Created/Modified
|
||||
|
||||
- `src/lib/admin-queries.ts` - Added `tags` to schema import; added `ServiceWithTags` type (`Service & { tags: string[] }`); rewrote `getAllServices()` to left-join `tags` (scoped `entity_type="services"`) and aggregate per-service tag arrays
|
||||
- `src/app/admin/catalog/actions.ts` - Added `tags` + `and` imports; appended `updateServiceField`, `addTagToService`, `removeTagFromService`, `quickAddService` server actions; existing `createService`/`updateService`/`toggleServiceActive` unchanged
|
||||
|
||||
## Decisions Made
|
||||
|
||||
- `onConflictDoNothing()` (no explicit `target`) was used as written in the plan — it compiled cleanly under the project's Drizzle version and is correct given the single unique index (`tags_entity_name_unique`) on the `tags` table. No change to the plan's suggested fallback was needed.
|
||||
|
||||
## Deviations from Plan
|
||||
|
||||
None - plan executed exactly as written.
|
||||
|
||||
## Issues Encountered
|
||||
|
||||
None.
|
||||
|
||||
### DB-state note (carried from Plan 01, non-blocking for this plan)
|
||||
|
||||
The `tags` table exists only in `src/db/schema.ts` (committed in Plan 01) — it has not yet been physically created in the live dev/prod Postgres database (firewalled, pending manual SSH+docker exec migration apply by the user). This plan is **code-only and verified via TypeScript types** (`npx tsc --noEmit` passes against the Drizzle-inferred `tags`/`services` schema types). No live-DB queries were attempted or required for this plan's verification — `getAllServices()`, `addTagToService`, `removeTagFromService` etc. will function correctly once the Plan 01 migration (`scripts/push-11-tags-migration.ts`) is applied to the live DB. This is the same pending checkpoint documented in `11-01-SUMMARY.md` and `STATE.md` Blockers — not a new issue introduced here.
|
||||
|
||||
## Known Stubs
|
||||
|
||||
None - no UI or data-rendering stubs introduced by this plan (query layer + server actions only, consumed by Plans 03/04).
|
||||
|
||||
## Threat Flags
|
||||
|
||||
None - this plan's new surface (`updateServiceField`, `addTagToService`, `removeTagFromService`, `quickAddService`) is fully covered by the threat model already documented in `11-02-PLAN.md` (T-11-06 through T-11-10), all mitigated/accepted as designed:
|
||||
|
||||
- T-11-06 (Elevation of Privilege): every new action calls `await requireAdmin()` first.
|
||||
- T-11-07 (Tampering via field whitelist): `EDITABLE_FIELDS` closed union + runtime `.includes()` check prevents arbitrary column writes.
|
||||
- T-11-08 (Tampering via entity_type): `entity_type` hardcoded to `"services"` literal in both tag actions, never client-supplied.
|
||||
- T-11-09 (SQL injection): all values pass through Drizzle's parameterized query builder.
|
||||
- T-11-10 (DoS via duplicate tags): `onConflictDoNothing()` on the `(entity_type, entity_id, name)` unique index makes `addTagToService` idempotent.
|
||||
|
||||
No new endpoints, auth paths, or trust-boundary changes beyond what was already reviewed in the plan's threat model.
|
||||
|
||||
## User Setup Required
|
||||
|
||||
None - no new environment variables or external service configuration needed. (The pending `tags` table migration apply is tracked from Plan 01, not new to this plan.)
|
||||
|
||||
## Next Phase Readiness
|
||||
|
||||
- **Query layer ready:** `ServiceWithTags` type + `getAllServices()` are implemented and typecheck cleanly — Plan 03 (`EditableCell`/`TagMultiSelect` components) and Plan 04 (`ServiceTable` rewrite) can import `ServiceWithTags` directly with no further query-layer changes needed.
|
||||
- **Server actions ready:** `updateServiceField`, `addTagToService`, `removeTagFromService`, `quickAddService` are implemented, admin-gated, and exported — Plans 03/04 can wire these into UI components immediately.
|
||||
- **NOT ready for live runtime verification:** as with Plan 01, any runtime/integration test that queries the live `tags` table (e.g., confirming `addTagToService` actually persists a row) will fail until the Plan 01 migration is applied to the database. This does not block Plan 02's code-level completion (typecheck is the success bar per the DB-state note above).
|
||||
- **Recommendation:** Resolve the Plan 01 DB-execution checkpoint (apply `scripts/push-11-tags-migration.ts` via SSH+docker exec) before or in parallel with Plans 03/04, so that UI built against these actions can be verified end-to-end once ready.
|
||||
|
||||
---
|
||||
*Phase: 11-catalog-database-view-ux-legacy-consolidation*
|
||||
*Completed: 2026-06-13*
|
||||
|
||||
## Self-Check: PASSED
|
||||
|
||||
- FOUND: src/lib/admin-queries.ts
|
||||
- FOUND: src/app/admin/catalog/actions.ts
|
||||
- FOUND: .planning/phases/11-catalog-database-view-ux-legacy-consolidation/11-02-SUMMARY.md
|
||||
- FOUND commit: f743410
|
||||
- FOUND commit: 445de85
|
||||
Reference in New Issue
Block a user