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,TrackListQueryParams - •
TrackListItem,TrackDetails - •
UpdateTrackRequest - •
PagedResult<T>,TrackListCursor
- •
Phase 2: RavenDB Indexes
- •
Tracks_ByUserForSearch (
ApiService/Infrastructure/Indexes/)csharpMap = 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/)csharpMap = 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/)- •
TrackNotFoundException - •
TrackAccessDeniedException - •
TrackDeletedException - •
RestorationExpiredException
- •
Phase 4: API Endpoints
- •
TrackEndpoints.cs (
ApiService/Endpoints/)csharpgroup.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/min - •
track-update: 30 req/min - •
track-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_total - •
track_get_requests_total - •
track_update_requests_total - •
track_delete_requests_total - •
track_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