description: Plan and implement Stage 5 Track Management with CRUD endpoints, soft-delete, and lifecycle worker (plan)
Implement Track Management Skill
Plan and implement Stage 5 Track Management for NovaTune: CRUD endpoints, soft-delete semantics, lifecycle worker, and observability.
Overview
Stage 5 implements user library management with:
- GET /tracks - List tracks with search, filter, sort, cursor-based pagination
- GET /tracks/{trackId} - Get track details
- PATCH /tracks/{trackId} - Update track metadata (merge policy)
- DELETE /tracks/{trackId} - Soft-delete with grace period
- POST /tracks/{trackId}/restore - Restore within grace period
- Lifecycle Worker - Physical deletion after grace period
Implementation Plan
Phase 1: Models and Configuration
-
Extend Track Model (
ApiService/Models/Track.cs)- Add soft-delete fields:
DeletedAt,ScheduledDeletionAt,StatusBeforeDeletion
- Add soft-delete fields:
-
Add Configuration (
ApiService/Configuration/TrackManagementOptions.cs)DeletionGracePeriod(default: 30 days)MaxPageSize(default: 100)DefaultPageSize(default: 20)
-
Add DTOs (
ApiService/Models/)TrackListQuery,TrackListQueryParamsTrackListItem,TrackDetailsUpdateTrackRequestPagedResult<T>,TrackListCursor
Phase 2: RavenDB Indexes
-
Tracks_ByUserForSearch (
ApiService/Infrastructure/Indexes/)Map = tracks => from track in tracks where track.Status != TrackStatus.Unknown select new { track.UserId, track.Status, track.Title, track.Artist, track.CreatedAt, track.UpdatedAt, track.Duration, SearchText = new[] { track.Title, track.Artist } }; Index("SearchText", FieldIndexing.Search); -
Tracks_ByScheduledDeletion (
ApiService/Infrastructure/Indexes/)Map = tracks => from track in tracks where track.Status == TrackStatus.Deleted && track.ScheduledDeletionAt != null select new { track.Status, track.ScheduledDeletionAt };
Phase 3: Service Layer
-
ITrackManagementService (
ApiService/Services/)ListTracksAsync(userId, query, ct)GetTrackAsync(trackId, userId, ct)UpdateTrackAsync(trackId, userId, request, ct)DeleteTrackAsync(trackId, userId, ct)RestoreTrackAsync(trackId, userId, ct)
-
Custom Exceptions (
ApiService/Infrastructure/Exceptions/)TrackNotFoundExceptionTrackAccessDeniedExceptionTrackDeletedExceptionRestorationExpiredException
Phase 4: API Endpoints
-
TrackEndpoints.cs (
ApiService/Endpoints/)group.MapGet("/", HandleListTracks).RequireRateLimiting("track-list"); group.MapGet("/{trackId}", HandleGetTrack); group.MapPatch("/{trackId}", HandleUpdateTrack).RequireRateLimiting("track-update"); group.MapDelete("/{trackId}", HandleDeleteTrack).RequireRateLimiting("track-delete"); group.MapPost("/{trackId}/restore", HandleRestoreTrack); -
Rate Limiting Policies
track-list: 60 req/mintrack-update: 30 req/mintrack-delete: 10 req/min
Phase 5: Event Publishing
-
TrackDeletedEvent (
ApiService/Infrastructure/Messaging/Messages/)- Migrate from Guid to ULID strings
- Include
ObjectKey,WaveformObjectKey,FileSizeBytes
-
Outbox Pattern
- Write event to
OutboxMessagescollection in same transaction - Outbox processor publishes to
{prefix}-track-deletionstopic
- Write event to
Phase 6: Lifecycle Worker
-
Create Worker Project (
Workers.Lifecycle/)- Use
add-aspire-worker-projectskill
- Use
-
TrackDeletedHandler - Kafka consumer for immediate cache invalidation
-
PhysicalDeletionService - Background service polling for expired tracks
- Delete MinIO objects (audio + waveform)
- Delete RavenDB document
- Update user quota
Phase 7: Observability
-
Metrics (
ApiService/Infrastructure/Observability/)track_list_requests_totaltrack_get_requests_totaltrack_update_requests_totaltrack_delete_requests_totaltrack_physical_deletions_total
-
Logging
- Track operations with
TrackId,UserId,CorrelationId - Never log object keys in production
- Track operations with
Phase 8: Testing
-
Unit Tests
TrackManagementServiceTests- Pagination cursor encoding/decoding
- Soft-delete state transitions
-
Integration Tests
- End-to-end CRUD flow
- Soft-delete → restore → delete cycle
- Physical deletion via lifecycle worker
Files to Create/Modify
New Files
| File | Purpose |
|---|---|
ApiService/Configuration/TrackManagementOptions.cs | Configuration |
ApiService/Services/ITrackManagementService.cs | Service interface |
ApiService/Services/TrackManagementService.cs | Service implementation |
ApiService/Endpoints/TrackEndpoints.cs | API endpoints |
ApiService/Models/TrackListQuery.cs | Query model |
ApiService/Models/PagedResult.cs | Pagination result |
ApiService/Infrastructure/Indexes/Tracks_ByUserForSearch.cs | Search index |
ApiService/Infrastructure/Indexes/Tracks_ByScheduledDeletion.cs | Deletion index |
ApiService/Infrastructure/Exceptions/TrackExceptions.cs | Custom exceptions |
Workers.Lifecycle/ | New worker project |
Modified Files
| File | Changes |
|---|---|
ApiService/Models/Track.cs | Add soft-delete fields |
ApiService/Program.cs | Register services, rate limiting |
AppHost/AppHost.cs | Add lifecycle worker |
Related Skills
- add-ravendb-index - For creating RavenDB indexes
- add-rate-limiting - For rate limiting policies
- add-background-service - For physical deletion service
- add-kafka-consumer - For TrackDeletedHandler
- add-aspire-worker-project - For lifecycle worker project
- add-observability - For metrics and tracing
Validation Checklist
- All CRUD endpoints return RFC 7807 problem details on error
- Rate limiting enforced on mutation endpoints
- Soft-delete preserves
StatusBeforeDeletionfor restore - Physical deletion only after grace period
- Cache invalidated immediately on soft-delete
- Quota updated only after physical deletion
- All operations logged with correlation ID
- Optimistic concurrency on updates