build: /spec-feature data-models --requirements 'Define Task, Project, Label...
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
rdev-worker 2026-02-11 05:00:21 +00:00
parent b7ee567d80
commit 9c9e07706f
2 changed files with 125 additions and 1 deletions

View File

@ -22,7 +22,7 @@ artifacts:
status: pending
path: review.md
spec:
status: pending
status: draft
path: spec.md
tasks:
status: pending

View File

@ -0,0 +1,124 @@
# Feature: Core Data Models & Persistence
## Problem Statement
The studio-api service currently uses in-memory storage (via `adapter/memory`) with a single `Example` entity. To support real product development workflows, the service needs persistent, relational data models for managing **Projects**, **Tasks**, **Labels**, and **Assignments**. Without these, the application cannot track work items, organize them into projects, categorize with labels, or assign work to users.
These entities form the backbone of a project management domain and must be stored in PostgreSQL via the existing `pkg/database` infrastructure.
## User Stories
- As a **user**, I want to create and manage **projects** so that I can organize related work items together.
- As a **user**, I want to create, update, and delete **tasks** within a project so that I can track individual work items.
- As a **user**, I want to create and manage **labels** so that I can categorize and filter tasks.
- As a **user**, I want to attach labels to tasks so that tasks can be categorized across multiple dimensions.
- As a **user**, I want to assign tasks to users so that ownership and responsibility are clear.
- As a **user**, I want to list tasks with filtering (by project, label, assignee, status) so that I can find relevant work items quickly.
## Acceptance Criteria
### Projects
- [ ] Project entity with fields: id (UUID), name, description, created_at, updated_at
- [ ] Full CRUD endpoints: `POST /projects`, `GET /projects`, `GET /projects/{id}`, `PUT /projects/{id}`, `DELETE /projects/{id}`
- [ ] Project name is required (1-100 chars), description optional (max 500 chars)
- [ ] Deleting a project cascades to its tasks, task-label associations, and assignments
- [ ] Duplicate project names return 409 Conflict
### Tasks
- [ ] Task entity with fields: id (UUID), project_id (FK), title, description, status, priority, created_at, updated_at
- [ ] Status enum: `open`, `in_progress`, `done`, `cancelled`
- [ ] Priority enum: `low`, `medium`, `high`, `critical`
- [ ] Full CRUD endpoints: `POST /projects/{projectId}/tasks`, `GET /projects/{projectId}/tasks`, `GET /tasks/{id}`, `PUT /tasks/{id}`, `DELETE /tasks/{id}`
- [ ] Task title is required (1-200 chars), description optional (max 2000 chars)
- [ ] Tasks belong to exactly one project (project_id required, validated)
- [ ] Creating a task for a non-existent project returns 404
- [ ] List tasks supports filtering by status and priority via query parameters
### Labels
- [ ] Label entity with fields: id (UUID), name, color, created_at
- [ ] Full CRUD endpoints: `POST /labels`, `GET /labels`, `GET /labels/{id}`, `PUT /labels/{id}`, `DELETE /labels/{id}`
- [ ] Label name is required (1-50 chars), unique; color is optional (hex format, e.g. `#FF5733`)
- [ ] Deleting a label removes its task-label associations (cascade)
- [ ] Duplicate label names return 409 Conflict
### Assignments (Task-Label & Task-User associations)
- [ ] Task-Label join: `POST /tasks/{taskId}/labels/{labelId}`, `DELETE /tasks/{taskId}/labels/{labelId}`, `GET /tasks/{taskId}/labels`
- [ ] Task-User assignment: `POST /tasks/{taskId}/assignments`, `DELETE /tasks/{taskId}/assignments/{assignmentId}`, `GET /tasks/{taskId}/assignments`
- [ ] Assignment entity with fields: id (UUID), task_id (FK), user_id (string), assigned_at
- [ ] Assigning a non-existent label or task returns 404
- [ ] Duplicate task-label associations return 409 Conflict
- [ ] Duplicate task-user assignments return 409 Conflict
### Database & Migrations
- [ ] PostgreSQL migrations in `services/studio-api/migrations/` using `pkg/database.RunMigrations`
- [ ] Migration files follow `NNN_description.sql` naming convention
- [ ] All tables use UUID primary keys
- [ ] Foreign keys with appropriate ON DELETE CASCADE constraints
- [ ] Indexes on foreign key columns and commonly queried fields (status, priority)
- [ ] Schema supports the `sqlx` struct scanning pattern (snake_case column names)
### Architecture & Code Quality
- [ ] Domain entities in `internal/domain/` with constructor validation (follows `Example` pattern)
- [ ] Strong-typed IDs (`ProjectID`, `TaskID`, `LabelID`, `AssignmentID`) following `ExampleID` pattern
- [ ] Domain errors in `internal/domain/errors.go` for each entity (NotFound, Duplicate, Invalid)
- [ ] Port interfaces in `internal/port/` defining repository contracts
- [ ] PostgreSQL adapter implementations in `internal/adapter/postgres/`
- [ ] Service layer in `internal/service/` with business logic and validation
- [ ] HTTP handlers in `internal/api/handlers/` following `app.Wrap()` pattern
- [ ] Routes registered in `internal/api/routes.go` under `/api/studio-api/`
- [ ] OpenAPI spec updated in `internal/api/spec.go`
- [ ] `main.go` updated to wire PostgreSQL pool and new services
### Testing
- [ ] Unit tests for each service layer (mock repositories, follows `example_test.go` pattern)
- [ ] Unit tests for each handler (httptest, chi router, follows `example_test.go` pattern)
- [ ] Domain entity constructor and validation tests
- [ ] All tests pass with `cd services/studio-api && go test -v ./...`
### Response Format
- [ ] All endpoints use `httpresponse.OK/Created/NoContent` envelope pattern
- [ ] All errors use `httperror.BadRequest/NotFound/Conflict` typed errors
- [ ] Request bodies validated with `app.BindAndValidate()`
- [ ] URL parameters use `{param}` brace syntax (not colon syntax)
## Technical Constraints
- **Database**: PostgreSQL via `pkg/database` (sqlx-based, `*sqlx.DB`)
- **Migrations**: Embedded SQL files via `//go:embed`, run at startup with `database.RunMigrations`
- **IDs**: UUID v4, generated server-side (use `github.com/google/uuid`)
- **JSON serialization**: Standard `encoding/json` with `json:"field_name"` tags
- **Validation**: `app.BindAndValidate()` for request bodies, domain-level validation for business rules
- **Router**: Chi with `{param}` brace syntax for URL parameters
- **No ORM**: Use raw SQL with sqlx (`Get`, `Select`, `NamedExec`, `ExecContext`)
- **Transactions**: Use `database.WithTx` for multi-table mutations (e.g., cascade deletes if not handled by DB constraints)
## Dependencies
- `pkg/database` - PostgreSQL connection pool and migration runner (exists)
- `pkg/app` - Service bootstrapper, `Wrap`, `Bind`, `BindAndValidate` (exists)
- `pkg/httperror` - Typed HTTP errors (exists)
- `pkg/httpresponse` - Response envelope helpers (exists)
- `pkg/logging` - Structured logging (exists)
- `pkg/auth` - JWT middleware for protected routes (exists)
- `pkg/openapi` - OpenAPI spec builder (exists)
- `github.com/google/uuid` - UUID generation (add to go.mod)
- PostgreSQL instance accessible via `DATABASE_URL` environment variable
## Out of Scope
- User management / user CRUD (user_id in assignments is an opaque string, not a managed entity)
- File attachments on tasks
- Task comments or activity history
- Real-time notifications (WebSocket updates for task changes)
- Pagination (can be added later; initial list endpoints return all matching records)
- Full-text search on task titles/descriptions
- Soft deletes (hard deletes for simplicity; soft deletes can be added later)
- Removing the existing `Example` entity and its in-memory adapter (keep for reference)
## Open Questions
1. **User identity format**: Should `user_id` in assignments be a UUID (anticipating a future users table) or a free-form string (e.g., email or external auth ID)? *Spec assumes string for flexibility.*
2. **Task ordering**: Should tasks have an explicit `position`/`sort_order` field for manual reordering within a project, or is ordering by `created_at` sufficient for v1?
3. **Label scoping**: Should labels be global (shared across all projects) or scoped per-project? *Spec assumes global labels.*
4. **Cascade behavior**: When a project is deleted, should its tasks be deleted (cascade) or should deletion be blocked if tasks exist? *Spec assumes cascade delete via FK constraint.*
5. **Auth on reads**: Should `GET` (list/detail) endpoints be public or require authentication? *Current `Example` pattern has reads as public, writes as protected. Spec follows this pattern.*