build: /spec-feature data-models --requirements 'Define Task, Project, Label...
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
This commit is contained in:
parent
265a2e5b90
commit
badb4c084c
@ -22,7 +22,7 @@ artifacts:
|
||||
status: pending
|
||||
path: review.md
|
||||
spec:
|
||||
status: pending
|
||||
status: draft
|
||||
path: spec.md
|
||||
tasks:
|
||||
status: pending
|
||||
|
||||
168
.sdlc/features/data-models/spec.md
Normal file
168
.sdlc/features/data-models/spec.md
Normal file
@ -0,0 +1,168 @@
|
||||
# Feature: Core Data Models & Persistence
|
||||
|
||||
## Problem Statement
|
||||
|
||||
The studio application currently uses an in-memory storage adapter with a single example entity. To build real product functionality, the system needs persistent domain entities — Projects, Tasks, Labels, and Assignments — backed by PostgreSQL. Without these core data models, no meaningful product workflows (task management, project organization, label categorization, team assignment) can be built.
|
||||
|
||||
## User Stories
|
||||
|
||||
- As a **developer**, I want Project, Task, Label, and Assignment entities with full CRUD so that the studio application has a foundation for product features.
|
||||
- As a **developer**, I want PostgreSQL-backed persistence via `studio-db` so that data survives service restarts.
|
||||
- As a **developer**, I want a repository layer with clean interfaces so that I can swap storage implementations and write testable code.
|
||||
- As a **developer**, I want service-layer business logic so that domain rules (validation, uniqueness, referential integrity) are enforced consistently.
|
||||
- As a **developer**, I want REST endpoints on `studio-api` so that frontends and external clients can manage these entities.
|
||||
- As a **developer**, I want database migrations so that schema changes are versioned, repeatable, and safe to apply.
|
||||
- As a **developer**, I want handler tests so that API behavior is verified and regressions are caught.
|
||||
|
||||
## Domain Model
|
||||
|
||||
### Project
|
||||
|
||||
| Field | Type | Constraints |
|
||||
|-------|------|-------------|
|
||||
| `id` | UUID | PK, generated |
|
||||
| `name` | string | required, 1–200 chars, unique |
|
||||
| `description` | string | optional, max 1000 chars |
|
||||
| `status` | enum | `active`, `archived`; default `active` |
|
||||
| `created_at` | timestamp | UTC, set on create |
|
||||
| `updated_at` | timestamp | UTC, set on create/update |
|
||||
|
||||
### Task
|
||||
|
||||
| Field | Type | Constraints |
|
||||
|-------|------|-------------|
|
||||
| `id` | UUID | PK, generated |
|
||||
| `project_id` | UUID | FK → projects, required |
|
||||
| `title` | string | required, 1–300 chars |
|
||||
| `description` | string | optional, max 5000 chars |
|
||||
| `status` | enum | `todo`, `in_progress`, `done`; default `todo` |
|
||||
| `priority` | enum | `low`, `medium`, `high`; default `medium` |
|
||||
| `position` | integer | ordering within project, default 0 |
|
||||
| `created_at` | timestamp | UTC |
|
||||
| `updated_at` | timestamp | UTC |
|
||||
|
||||
### Label
|
||||
|
||||
| Field | Type | Constraints |
|
||||
|-------|------|-------------|
|
||||
| `id` | UUID | PK, generated |
|
||||
| `project_id` | UUID | FK → projects, required |
|
||||
| `name` | string | required, 1–50 chars, unique per project |
|
||||
| `color` | string | required, hex color (e.g., `#FF5733`) |
|
||||
| `created_at` | timestamp | UTC |
|
||||
| `updated_at` | timestamp | UTC |
|
||||
|
||||
### Assignment (join entity: Task ↔ User)
|
||||
|
||||
| Field | Type | Constraints |
|
||||
|-------|------|-------------|
|
||||
| `id` | UUID | PK, generated |
|
||||
| `task_id` | UUID | FK → tasks, required |
|
||||
| `assignee` | string | required, 1–200 chars (user identifier) |
|
||||
| `created_at` | timestamp | UTC |
|
||||
|
||||
**Note:** `assignee` is a string identifier (email or user ID) rather than a FK to a users table, since user management is out of scope for this feature. A unique constraint on `(task_id, assignee)` prevents duplicate assignments.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
### Migrations
|
||||
- [ ] Migration `001_create_projects.sql` creates the `projects` table with all fields, constraints, and indexes
|
||||
- [ ] Migration `002_create_tasks.sql` creates the `tasks` table with FK to projects, indexes on `project_id` and `status`
|
||||
- [ ] Migration `003_create_labels.sql` creates the `labels` table with FK to projects, unique constraint on `(project_id, name)`
|
||||
- [ ] Migration `004_create_assignments.sql` creates the `assignments` table with FK to tasks, unique constraint on `(task_id, assignee)`
|
||||
- [ ] Migrations run via the existing `database.MustRunMigrations()` system with embedded SQL files
|
||||
- [ ] All migrations are idempotent and run in transactions
|
||||
|
||||
### Domain Layer
|
||||
- [ ] Domain entities `Project`, `Task`, `Label`, `Assignment` defined with strongly-typed IDs (e.g., `ProjectID`, `TaskID`)
|
||||
- [ ] Domain constructors (`NewProject`, `NewTask`, `NewLabel`, `NewAssignment`) validate all required fields
|
||||
- [ ] Domain errors defined: `ErrProjectNotFound`, `ErrTaskNotFound`, `ErrLabelNotFound`, `ErrAssignmentNotFound`, `ErrDuplicateProjectName`, `ErrDuplicateLabelName`, `ErrDuplicateAssignment`, `ErrInvalidStatus`, `ErrInvalidPriority`
|
||||
- [ ] Update methods validate mutable fields and set `updated_at`
|
||||
|
||||
### Repository Layer (Ports)
|
||||
- [ ] `ProjectRepository` interface with `List`, `Get`, `Create`, `Update`, `Delete`, `ExistsByName`
|
||||
- [ ] `TaskRepository` interface with `ListByProject`, `Get`, `Create`, `Update`, `Delete`
|
||||
- [ ] `LabelRepository` interface with `ListByProject`, `Get`, `Create`, `Update`, `Delete`, `ExistsByName`
|
||||
- [ ] `AssignmentRepository` interface with `ListByTask`, `Get`, `Create`, `Delete`, `ExistsByTaskAndAssignee`
|
||||
|
||||
### Adapter Layer (Postgres)
|
||||
- [ ] `adapter/postgres/` package implementing all repository interfaces using `sqlx`
|
||||
- [ ] Proper SQL parameterization (`$1`, `$2`, etc.) — no string concatenation
|
||||
- [ ] `sql.ErrNoRows` mapped to domain `ErrNotFound` errors
|
||||
- [ ] Unique constraint violations mapped to domain duplicate errors
|
||||
|
||||
### Service Layer
|
||||
- [ ] `ProjectService` with Create (duplicate name check), Get, List, Update, Delete (cascade consideration)
|
||||
- [ ] `TaskService` with Create (validate project exists), Get, ListByProject, Update, Delete
|
||||
- [ ] `LabelService` with Create (validate project exists, duplicate name per project), Get, ListByProject, Update, Delete
|
||||
- [ ] `AssignmentService` with Create (validate task exists, duplicate check), ListByTask, Delete
|
||||
|
||||
### Handler Layer (REST API)
|
||||
- [ ] **Projects:** `POST /api/studio-api/projects`, `GET /api/studio-api/projects`, `GET /api/studio-api/projects/{id}`, `PUT /api/studio-api/projects/{id}`, `DELETE /api/studio-api/projects/{id}`
|
||||
- [ ] **Tasks:** `POST /api/studio-api/projects/{projectId}/tasks`, `GET /api/studio-api/projects/{projectId}/tasks`, `GET /api/studio-api/tasks/{id}`, `PUT /api/studio-api/tasks/{id}`, `DELETE /api/studio-api/tasks/{id}`
|
||||
- [ ] **Labels:** `POST /api/studio-api/projects/{projectId}/labels`, `GET /api/studio-api/projects/{projectId}/labels`, `GET /api/studio-api/labels/{id}`, `PUT /api/studio-api/labels/{id}`, `DELETE /api/studio-api/labels/{id}`
|
||||
- [ ] **Assignments:** `POST /api/studio-api/tasks/{taskId}/assignments`, `GET /api/studio-api/tasks/{taskId}/assignments`, `DELETE /api/studio-api/assignments/{id}`
|
||||
- [ ] All handlers follow `app.Wrap()` + `app.BindAndValidate()` pattern
|
||||
- [ ] All responses use `httpresponse.OK`, `httpresponse.Created`, `httpresponse.NoContent` envelope
|
||||
- [ ] URL parameters use `{param}` brace syntax, extracted with `chi.URLParam()`
|
||||
- [ ] Domain errors mapped to HTTP errors via `mapDomainError()` functions
|
||||
|
||||
### OpenAPI Specification
|
||||
- [ ] All schemas defined in `spec.go` using `openapi.*` helpers
|
||||
- [ ] All endpoints documented with request/response types, status codes, and tags
|
||||
- [ ] Tags: `Projects`, `Tasks`, `Labels`, `Assignments`
|
||||
|
||||
### Tests
|
||||
- [ ] Handler tests for all CRUD operations on each entity using mock repositories
|
||||
- [ ] Table-driven tests covering: success, not found, validation failure, duplicate/conflict
|
||||
- [ ] Test setup uses `newTestHandler()` pattern with mock repositories
|
||||
- [ ] Tests use `chi.NewRouter()` + `httptest` for HTTP testing
|
||||
|
||||
### Wiring
|
||||
- [ ] `main.go` updated to: connect to database, run migrations, create postgres adapters, inject into services and handlers
|
||||
- [ ] Database connection uses `config.ReadDatabaseConfig()` + `database.MustConnect()`
|
||||
- [ ] Migrations embedded with `//go:embed migrations/*.sql`
|
||||
- [ ] Graceful shutdown closes database pool
|
||||
|
||||
## Technical Constraints
|
||||
|
||||
- **Database:** PostgreSQL via `sqlx` + `lib/pq` driver (already in `pkg/database`)
|
||||
- **Migration system:** Embedded SQL files using `database.MustRunMigrations()` — no external migration tool
|
||||
- **Architecture:** Hexagonal architecture — domain has no external dependencies; ports define interfaces; adapters implement them
|
||||
- **Routing:** chi router with `{param}` brace syntax; all handlers return `error` wrapped with `app.Wrap()`
|
||||
- **Validation:** Struct tags using `go-playground/validator` via `app.BindAndValidate()`
|
||||
- **ID generation:** UUIDs generated server-side (e.g., `uuid.New().String()`)
|
||||
- **Timestamps:** All UTC, set by domain constructors
|
||||
- **Cascading deletes:** Deleting a project should delete its tasks, labels, and associated assignments (via `ON DELETE CASCADE` in FK constraints)
|
||||
|
||||
## Dependencies
|
||||
|
||||
- `pkg/database` — PostgreSQL connection pool and migration runner (exists)
|
||||
- `pkg/config` — Database URL and pool configuration (exists)
|
||||
- `pkg/app` — Handler wrapping, binding, routing (exists)
|
||||
- `pkg/httperror`, `pkg/httpresponse`, `pkg/httpvalidation` — HTTP layer helpers (exists)
|
||||
- `pkg/openapi` — API documentation (exists)
|
||||
- Running PostgreSQL instance for local development and integration tests
|
||||
- `github.com/google/uuid` — UUID generation (add to go.mod if not present)
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- **User management / authentication:** `assignee` is a plain string, not a FK to a users table. Auth middleware integration is not part of this feature.
|
||||
- **Pagination:** List endpoints return all records. Pagination will be a follow-up feature.
|
||||
- **Filtering/sorting:** List endpoints are unfiltered. Query parameters for filtering by status, priority, etc. will be a follow-up.
|
||||
- **Task-Label association:** Many-to-many relationship between tasks and labels (a `task_labels` join table) is not included. This is a follow-up feature.
|
||||
- **Soft deletes:** All deletes are hard deletes. Soft delete (archive) behavior may be added later.
|
||||
- **Audit logging:** No change tracking or audit trail in this feature.
|
||||
- **Frontend integration:** No UI changes. This is backend-only.
|
||||
|
||||
## Open Questions
|
||||
|
||||
1. **Delete cascade vs. restrict:** Should deleting a project cascade-delete all tasks, labels, and assignments? The spec assumes `ON DELETE CASCADE`, but an alternative is to return an error if a project has tasks (forcing explicit cleanup). Which behavior is preferred?
|
||||
|
||||
2. **Assignment entity vs. simple field:** Should task assignment be a separate entity with its own table (supporting multiple assignees per task), or a simple `assignee` string field on the Task table (single assignee)? The spec assumes a separate join entity for multiple assignees.
|
||||
|
||||
3. **Task position/ordering:** The spec includes a `position` integer for ordering tasks within a project. Should this be included in the initial implementation, or deferred until a drag-and-drop UI feature is built?
|
||||
|
||||
4. **Project status transitions:** Should there be business rules governing status transitions (e.g., can only archive a project if all tasks are `done`)? The spec currently allows free status changes.
|
||||
|
||||
5. **Database name:** The requirements mention `studio-db` as the database. Should this be a new PostgreSQL database name, or should it refer to the database configured in `DATABASE_URL`? The spec assumes the existing `DATABASE_URL` configuration.
|
||||
Loading…
Reference in New Issue
Block a user