diff --git a/VEZA_COMPLETE_MVP_TODOLIST.json b/VEZA_COMPLETE_MVP_TODOLIST.json index dbb545838..77c0a937b 100644 --- a/VEZA_COMPLETE_MVP_TODOLIST.json +++ b/VEZA_COMPLETE_MVP_TODOLIST.json @@ -3280,7 +3280,7 @@ "description": "Add CHECK constraints for status values, enum constraints", "owner": "backend", "estimated_hours": 4, - "status": "todo", + "status": "completed", "files_involved": [], "implementation_steps": [ { @@ -3301,7 +3301,9 @@ "Unit tests", "Integration tests" ], - "notes": "" + "notes": "", + "completed_at": "2025-12-24T15:43:51.128637", + "implementation_notes": "Created migration 050_data_validation_constraints.sql for data validation constraints. Added CHECK constraints for: tracks.status and stream_status, track counts (non-negative), year validation, playlist counts, playlist_tracks position, hls_transcode_queue status and retry counts, track_plays duration, playback_analytics completion_rate, playlist_versions version and action, track_history action, bitrate_adaptation reason and bitrates, notifications type and title lengths, user_profiles counts." }, { "id": "BE-DB-012", diff --git a/veza-backend-api/migrations/050_data_validation_constraints.sql b/veza-backend-api/migrations/050_data_validation_constraints.sql new file mode 100644 index 000000000..e0d9ba144 --- /dev/null +++ b/veza-backend-api/migrations/050_data_validation_constraints.sql @@ -0,0 +1,127 @@ +-- 050_data_validation_constraints.sql +-- Data Validation Constraints (BE-DB-011: Add database constraints for data validation) +-- Add CHECK constraints for status values, enum constraints + +-- === TRACKS STATUS CONSTRAINTS === +-- Constraint for track status (uploading, processing, completed, failed) +ALTER TABLE public.tracks +ADD CONSTRAINT IF NOT EXISTS chk_tracks_status_valid +CHECK (status IN ('uploading', 'processing', 'completed', 'failed')); + +-- Constraint for stream_status (pending, processing, ready, error) +ALTER TABLE public.tracks +ADD CONSTRAINT IF NOT EXISTS chk_tracks_stream_status_valid +CHECK (stream_status IN ('pending', 'processing', 'ready', 'error')); + +-- Constraint for counts (non-negative) +ALTER TABLE public.tracks +ADD CONSTRAINT IF NOT EXISTS chk_tracks_counts_non_negative +CHECK (play_count >= 0 AND like_count >= 0 AND comment_count >= 0 AND download_count >= 0); + +-- Constraint for year (reasonable range) +ALTER TABLE public.tracks +ADD CONSTRAINT IF NOT EXISTS chk_tracks_year_valid +CHECK (year >= 0 AND year <= EXTRACT(YEAR FROM CURRENT_DATE) + 10); + +-- === PLAYLISTS CONSTRAINTS === +-- Constraint for counts (non-negative) +ALTER TABLE public.playlists +ADD CONSTRAINT IF NOT EXISTS chk_playlists_counts_non_negative +CHECK (track_count >= 0 AND follower_count >= 0); + +-- === PLAYLIST_TRACKS CONSTRAINTS === +-- Constraint for position (positive) +ALTER TABLE public.playlist_tracks +ADD CONSTRAINT IF NOT EXISTS chk_playlist_tracks_position_positive +CHECK (position >= 0); + +-- === HLSTranscodeQueue CONSTRAINTS === +-- Constraint for queue status (pending, processing, completed, failed) +ALTER TABLE public.hls_transcode_queue +ADD CONSTRAINT IF NOT EXISTS chk_hls_transcode_queue_status_valid +CHECK (status IN ('pending', 'processing', 'completed', 'failed')); + +-- Constraint for retry counts (non-negative) +ALTER TABLE public.hls_transcode_queue +ADD CONSTRAINT IF NOT EXISTS chk_hls_transcode_queue_retry_valid +CHECK (retry_count >= 0 AND max_retries >= 0); + +-- Constraint for priority (reasonable range) +ALTER TABLE public.hls_transcode_queue +ADD CONSTRAINT IF NOT EXISTS chk_hls_transcode_queue_priority_valid +CHECK (priority >= 1 AND priority <= 10); + +-- === TRACK_COMMENTS CONSTRAINTS === +-- Constraint for content length (already exists, but ensure it's there) +-- Note: chk_track_comments_content_length already exists in 041_streaming_analytics.sql + +-- === TRACK_PLAYS CONSTRAINTS === +-- Constraint for duration (non-negative) +ALTER TABLE public.track_plays +ADD CONSTRAINT IF NOT EXISTS chk_track_plays_duration_non_negative +CHECK (duration >= 0); + +-- === PLAYBACK_ANALYTICS CONSTRAINTS === +-- Constraint for completion_rate (0-100) +ALTER TABLE public.playback_analytics +ADD CONSTRAINT IF NOT EXISTS chk_playback_analytics_completion_rate +CHECK (completion_rate >= 0 AND completion_rate <= 100); + +-- Constraint for counts (non-negative) +ALTER TABLE public.playback_analytics +ADD CONSTRAINT IF NOT EXISTS chk_playback_analytics_counts_non_negative +CHECK (play_time >= 0 AND pause_count >= 0 AND seek_count >= 0); + +-- === PLAYLIST_VERSION CONSTRAINTS === +-- Constraint for version (positive) +ALTER TABLE public.playlist_versions +ADD CONSTRAINT IF NOT EXISTS chk_playlist_versions_version_positive +CHECK (version > 0); + +-- Constraint for action (created, updated, restored) +ALTER TABLE public.playlist_versions +ADD CONSTRAINT IF NOT EXISTS chk_playlist_versions_action_valid +CHECK (action IN ('created', 'updated', 'restored')); + +-- === TRACK_HISTORY CONSTRAINTS === +-- Constraint for action (created, updated, deleted, published, unpublished, restored) +ALTER TABLE public.track_history +ADD CONSTRAINT IF NOT EXISTS chk_track_history_action_valid +CHECK (action IN ('created', 'updated', 'deleted', 'published', 'unpublished', 'restored')); + +-- === BITRATE_ADAPTATION CONSTRAINTS === +-- Constraint for reason (network_slow, network_fast, user_selected, buffer_low) +ALTER TABLE public.bitrate_adaptation_logs +ADD CONSTRAINT IF NOT EXISTS chk_bitrate_adaptation_reason_valid +CHECK (reason IN ('network_slow', 'network_fast', 'user_selected', 'buffer_low')); + +-- Constraint for bitrates (positive) +ALTER TABLE public.bitrate_adaptation_logs +ADD CONSTRAINT IF NOT EXISTS chk_bitrate_adaptation_bitrates_positive +CHECK (old_bitrate > 0 AND new_bitrate > 0); + +-- === NOTIFICATIONS CONSTRAINTS === +-- Constraint for type (reasonable length) +ALTER TABLE public.notifications +ADD CONSTRAINT IF NOT EXISTS chk_notifications_type_length +CHECK (LENGTH(type) >= 1 AND LENGTH(type) <= 50); + +-- Constraint for title (reasonable length) +ALTER TABLE public.notifications +ADD CONSTRAINT IF NOT EXISTS chk_notifications_title_length +CHECK (LENGTH(title) >= 1 AND LENGTH(title) <= 255); + +-- === USER_PROFILES CONSTRAINTS === +-- Constraint for counts (non-negative) +ALTER TABLE public.user_profiles +ADD CONSTRAINT IF NOT EXISTS chk_user_profiles_counts_non_negative +CHECK (follower_count >= 0 AND following_count >= 0 AND track_count >= 0 AND playlist_count >= 0); + +-- Comments +COMMENT ON CONSTRAINT chk_tracks_status_valid ON public.tracks IS 'Validates track status values'; +COMMENT ON CONSTRAINT chk_tracks_stream_status_valid ON public.tracks IS 'Validates stream status values'; +COMMENT ON CONSTRAINT chk_hls_transcode_queue_status_valid ON public.hls_transcode_queue IS 'Validates queue status values'; +COMMENT ON CONSTRAINT chk_playlist_versions_action_valid ON public.playlist_versions IS 'Validates playlist version action values'; +COMMENT ON CONSTRAINT chk_track_history_action_valid ON public.track_history IS 'Validates track history action values'; +COMMENT ON CONSTRAINT chk_bitrate_adaptation_reason_valid ON public.bitrate_adaptation_logs IS 'Validates bitrate adaptation reason values'; +