veza/veza-backend-api/migrations/945_creator_analytics_v0110.sql
senke b955a3c0b4 feat(v0.11.0): F381-F385 database migrations and models for creator analytics
Add daily_track_stats, geographic_play_stats, track_discovery_sources tables.
Add source and country_code columns to track_plays.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-10 16:21:01 +01:00

57 lines
3 KiB
SQL

-- Migration 945: Creator Analytics v0.11.0 (F381-F385)
-- Adds tables for geographic stats, discovery sources, and daily aggregated stats
-- Daily aggregated track stats for efficient creator dashboard queries
CREATE TABLE IF NOT EXISTS daily_track_stats (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
track_id UUID NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
date DATE NOT NULL,
total_plays BIGINT NOT NULL DEFAULT 0,
unique_listeners BIGINT NOT NULL DEFAULT 0,
complete_listens BIGINT NOT NULL DEFAULT 0,
total_play_time BIGINT NOT NULL DEFAULT 0, -- seconds
avg_completion_rate DECIMAL(5,2) NOT NULL DEFAULT 0, -- percentage 0-100
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT uq_daily_track_stats_track_date UNIQUE(track_id, date)
);
CREATE INDEX IF NOT EXISTS idx_daily_track_stats_track_id ON daily_track_stats(track_id);
CREATE INDEX IF NOT EXISTS idx_daily_track_stats_date ON daily_track_stats(date);
CREATE INDEX IF NOT EXISTS idx_daily_track_stats_track_date ON daily_track_stats(track_id, date);
-- Discovery sources: how users find tracks
CREATE TABLE IF NOT EXISTS track_discovery_sources (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
track_id UUID NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
source VARCHAR(50) NOT NULL, -- 'search', 'feed', 'share', 'profile', 'playlist', 'direct'
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
CREATE INDEX IF NOT EXISTS idx_track_discovery_track_id ON track_discovery_sources(track_id);
CREATE INDEX IF NOT EXISTS idx_track_discovery_source ON track_discovery_sources(source);
CREATE INDEX IF NOT EXISTS idx_track_discovery_created_at ON track_discovery_sources(created_at);
-- Geographic play stats (aggregated, anonymized — never store individual user locations)
CREATE TABLE IF NOT EXISTS geographic_play_stats (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
track_id UUID NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
country_code VARCHAR(2) NOT NULL, -- ISO 3166-1 alpha-2
region VARCHAR(100) NOT NULL DEFAULT '',
date DATE NOT NULL,
play_count BIGINT NOT NULL DEFAULT 0,
unique_listeners BIGINT NOT NULL DEFAULT 0,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
CONSTRAINT uq_geo_stats_track_country_region_date UNIQUE(track_id, country_code, region, date)
);
CREATE INDEX IF NOT EXISTS idx_geo_stats_track_id ON geographic_play_stats(track_id);
CREATE INDEX IF NOT EXISTS idx_geo_stats_country ON geographic_play_stats(country_code);
CREATE INDEX IF NOT EXISTS idx_geo_stats_date ON geographic_play_stats(date);
-- Add source column to track_plays for discovery tracking
ALTER TABLE track_plays ADD COLUMN IF NOT EXISTS source VARCHAR(50) DEFAULT '';
-- Add country_code to track_plays for geographic aggregation
ALTER TABLE track_plays ADD COLUMN IF NOT EXISTS country_code VARCHAR(2) DEFAULT '';