feat: Add player_configs feature and migrate user preferences
- Implemented player_configs table to store multiple player configurations per user. - Migrated existing player settings from user_preferences to player_configs. - Removed player-related columns from user_preferences. - Added referral state fields to user for tracking referral rewards. - Created migration scripts for database changes and data migration. - Added test cases for app services and usage helpers. - Introduced video job service interfaces and implementations.
This commit is contained in:
65
migrations/001_create_player_configs_table.sql
Normal file
65
migrations/001_create_player_configs_table.sql
Normal file
@@ -0,0 +1,65 @@
|
||||
-- Migration: Create player_configs table
|
||||
-- Created: 2026-03-19
|
||||
-- Description: Creates the player_configs table for storing multiple player configurations per user
|
||||
|
||||
-- Create player_configs table
|
||||
CREATE TABLE IF NOT EXISTS player_configs (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id UUID NOT NULL REFERENCES "user"(id) ON DELETE CASCADE,
|
||||
name TEXT NOT NULL,
|
||||
description TEXT,
|
||||
autoplay BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
loop BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
muted BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
show_controls BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
pip BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
airplay BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
chromecast BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
is_default BOOLEAN NOT NULL DEFAULT FALSE,
|
||||
created_at TIMESTAMP(3) WITHOUT TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP(3) WITHOUT TIME ZONE NOT NULL,
|
||||
version BIGINT NOT NULL DEFAULT 1
|
||||
);
|
||||
|
||||
-- Index for user_id to optimize queries by user
|
||||
CREATE INDEX IF NOT EXISTS idx_player_configs_user_id ON player_configs(user_id);
|
||||
|
||||
-- Index for is_default to optimize default config lookups
|
||||
CREATE INDEX IF NOT EXISTS idx_player_configs_is_default ON player_configs(is_default);
|
||||
|
||||
-- Composite index for user + is_default (common query pattern)
|
||||
CREATE INDEX IF NOT EXISTS idx_player_configs_user_default ON player_configs(user_id, is_default);
|
||||
|
||||
-- Trigger function to update updated_at and version on row update
|
||||
CREATE OR REPLACE FUNCTION update_player_configs_updated_at()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = CURRENT_TIMESTAMP;
|
||||
NEW.version = OLD.version + 1;
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
-- Trigger to auto-update updated_at and version
|
||||
CREATE OR REPLACE TRIGGER trg_update_player_configs
|
||||
BEFORE UPDATE ON player_configs
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_player_configs_updated_at();
|
||||
|
||||
-- Comments for documentation
|
||||
COMMENT ON TABLE player_configs IS 'Stores multiple player configurations per user for video playback settings';
|
||||
COMMENT ON COLUMN player_configs.id IS 'Unique identifier for the player config (UUID)';
|
||||
COMMENT ON COLUMN player_configs.user_id IS 'Reference to the user who owns this config';
|
||||
COMMENT ON COLUMN player_configs.name IS 'Human-readable name for the configuration';
|
||||
COMMENT ON COLUMN player_configs.description IS 'Optional description of the configuration';
|
||||
COMMENT ON COLUMN player_configs.autoplay IS 'Whether videos should autoplay';
|
||||
COMMENT ON COLUMN player_configs.loop IS 'Whether videos should loop on end';
|
||||
COMMENT ON COLUMN player_configs.muted IS 'Whether videos should start muted';
|
||||
COMMENT ON COLUMN player_configs.show_controls IS 'Whether to show player controls';
|
||||
COMMENT ON COLUMN player_configs.pip IS 'Whether Picture-in-Picture is enabled';
|
||||
COMMENT ON COLUMN player_configs.airplay IS 'Whether AirPlay is enabled';
|
||||
COMMENT ON COLUMN player_configs.chromecast IS 'Whether Chromecast is enabled';
|
||||
COMMENT ON COLUMN player_configs.is_active IS 'Whether this config is active';
|
||||
COMMENT ON COLUMN player_configs.is_default IS 'Whether this is the default config for new videos';
|
||||
COMMENT ON COLUMN player_configs.version IS 'Optimistic locking version number';
|
||||
134
migrations/002_migrate_player_settings.sql
Normal file
134
migrations/002_migrate_player_settings.sql
Normal file
@@ -0,0 +1,134 @@
|
||||
-- Migration: Migrate player settings from user_preferences to player_configs
|
||||
-- Created: 2026-03-19
|
||||
-- Description:
|
||||
-- 1. Creates player_configs records from existing user_preferences player settings
|
||||
-- 2. Removes player-related columns from user_preferences table
|
||||
-- 3. Removes encrytion_m3u8 column (VIP feature not in use)
|
||||
--
|
||||
-- IMPORTANT: Run this AFTER 001_create_player_configs_table.sql
|
||||
-- Usage: psql -h <host> -U postgres -d video_db -f 002_migrate_player_settings.sql
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- ============================================================
|
||||
-- STEP 1: Migrate existing player settings to player_configs
|
||||
-- ============================================================
|
||||
|
||||
-- Insert player configs for all users that have user_preferences
|
||||
-- Each user gets one default config with their existing settings
|
||||
INSERT INTO player_configs (
|
||||
id,
|
||||
user_id,
|
||||
name,
|
||||
description,
|
||||
autoplay,
|
||||
loop,
|
||||
muted,
|
||||
show_controls,
|
||||
pip,
|
||||
airplay,
|
||||
chromecast,
|
||||
is_active,
|
||||
is_default,
|
||||
created_at,
|
||||
updated_at,
|
||||
version
|
||||
)
|
||||
SELECT
|
||||
gen_random_uuid() AS id,
|
||||
up.user_id,
|
||||
'Default Config' AS name,
|
||||
'Migrated from user_preferences' AS description,
|
||||
COALESCE(up.autoplay, FALSE) AS autoplay,
|
||||
COALESCE(up.loop, FALSE) AS loop,
|
||||
COALESCE(up.muted, FALSE) AS muted,
|
||||
COALESCE(up.show_controls, TRUE) AS show_controls,
|
||||
COALESCE(up.pip, TRUE) AS pip,
|
||||
COALESCE(up.airplay, TRUE) AS airplay,
|
||||
COALESCE(up.chromecast, TRUE) AS chromecast,
|
||||
TRUE AS is_active,
|
||||
TRUE AS is_default,
|
||||
COALESCE(up.created_at, CURRENT_TIMESTAMP) AS created_at,
|
||||
CURRENT_TIMESTAMP AS updated_at,
|
||||
COALESCE(up.version, 1) AS version
|
||||
FROM user_preferences up
|
||||
WHERE NOT EXISTS (
|
||||
-- Skip if user already has a player config (from previous migration)
|
||||
SELECT 1 FROM player_configs pc WHERE pc.user_id = up.user_id AND pc.is_default = TRUE
|
||||
);
|
||||
|
||||
-- Also handle users that exist in "user" table but don't have user_preferences yet
|
||||
-- They won't get a config now, but will get default values when they first access settings
|
||||
|
||||
-- ============================================================
|
||||
-- STEP 2: Remove player-related columns from user_preferences
|
||||
-- ============================================================
|
||||
|
||||
-- Drop player settings columns
|
||||
ALTER TABLE user_preferences
|
||||
DROP COLUMN IF EXISTS autoplay,
|
||||
DROP COLUMN IF EXISTS loop,
|
||||
DROP COLUMN IF EXISTS muted,
|
||||
DROP COLUMN IF EXISTS show_controls,
|
||||
DROP COLUMN IF EXISTS pip,
|
||||
DROP COLUMN IF EXISTS airplay,
|
||||
DROP COLUMN IF EXISTS chromecast;
|
||||
|
||||
-- Drop encrytion_m3u8 column (VIP feature not in use)
|
||||
ALTER TABLE user_preferences
|
||||
DROP COLUMN IF EXISTS encrytion_m3u8;
|
||||
|
||||
-- ============================================================
|
||||
-- STEP 3: Add constraint to ensure one default config per user
|
||||
-- ============================================================
|
||||
|
||||
-- Create a partial unique index to ensure only one is_default = TRUE per user
|
||||
-- This prevents multiple default configs per user
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_player_configs_one_default_per_user
|
||||
ON player_configs(user_id)
|
||||
WHERE is_default = TRUE;
|
||||
|
||||
-- ============================================================
|
||||
-- STEP 4: Verify migration
|
||||
-- ============================================================
|
||||
|
||||
-- Count migrated configs
|
||||
DO $$
|
||||
DECLARE
|
||||
migrated_count INTEGER;
|
||||
user_count INTEGER;
|
||||
BEGIN
|
||||
SELECT COUNT(*) INTO migrated_count FROM player_configs WHERE description = 'Migrated from user_preferences';
|
||||
SELECT COUNT(*) INTO user_count FROM "user";
|
||||
|
||||
RAISE NOTICE '============================================';
|
||||
RAISE NOTICE 'Migration completed successfully!';
|
||||
RAISE NOTICE '============================================';
|
||||
RAISE NOTICE 'Users in system: %', user_count;
|
||||
RAISE NOTICE 'Player configs created: %', migrated_count;
|
||||
RAISE NOTICE '============================================';
|
||||
END $$;
|
||||
|
||||
COMMIT;
|
||||
|
||||
-- ============================================================
|
||||
-- Verification queries (run after migration)
|
||||
-- ============================================================
|
||||
|
||||
-- Verify player_configs structure
|
||||
SELECT column_name, data_type, is_nullable, column_default
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'player_configs'
|
||||
ORDER BY ordinal_position;
|
||||
|
||||
-- Verify user_preferences structure (should not have player columns anymore)
|
||||
SELECT column_name, data_type
|
||||
FROM information_schema.columns
|
||||
WHERE table_name = 'user_preferences'
|
||||
ORDER BY ordinal_position;
|
||||
|
||||
-- Sample migrated data
|
||||
SELECT pc.user_id, pc.name, pc.autoplay, pc.loop, pc.muted, pc.is_default
|
||||
FROM player_configs pc
|
||||
WHERE pc.description = 'Migrated from user_preferences'
|
||||
LIMIT 5;
|
||||
17
migrations/003_add_user_referrals.sql
Normal file
17
migrations/003_add_user_referrals.sql
Normal file
@@ -0,0 +1,17 @@
|
||||
-- Migration: Add user referral state
|
||||
-- Created: 2026-03-23
|
||||
-- Description: Adds minimal referral linkage and first-subscription reward tracking fields to user
|
||||
|
||||
BEGIN;
|
||||
|
||||
ALTER TABLE "user"
|
||||
ADD COLUMN IF NOT EXISTS referred_by_user_id UUID REFERENCES "user"(id) ON DELETE SET NULL,
|
||||
ADD COLUMN IF NOT EXISTS referral_eligible BOOLEAN NOT NULL DEFAULT TRUE,
|
||||
ADD COLUMN IF NOT EXISTS referral_reward_bps INTEGER,
|
||||
ADD COLUMN IF NOT EXISTS referral_reward_granted_at TIMESTAMP WITH TIME ZONE,
|
||||
ADD COLUMN IF NOT EXISTS referral_reward_payment_id UUID,
|
||||
ADD COLUMN IF NOT EXISTS referral_reward_amount NUMERIC(65,30);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_user_referred_by_user_id ON "user"(referred_by_user_id);
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user