update cicd

This commit is contained in:
2026-04-02 11:01:30 +00:00
parent 863a0ea2f6
commit 5a7f29c116
54 changed files with 4298 additions and 473 deletions

188
script/MIGRATION_GUIDE.md Normal file
View File

@@ -0,0 +1,188 @@
# Player Configs Migration Guide
## Overview
Đây là tài liệu hướng dẫn migrate player settings từ bảng `user_preferences` sang bảng `player_configs` mới.
### Tại sao cần migrate?
- **user_preferences**: Một hàng mỗi user, không thể có nhiều cấu hình player
- **player_configs**: Nhiều hàng mỗi user, hỗ trợ nhiều cấu hình player khác nhau
### Các bước thực hiện
## Bước 1: Chạy Migration SQL
```bash
cd /home/dat/projects/stream/stream.api
# Cách 1: Dùng script tự động (khuyến nghị)
./migrate_player_configs.sh
# Cách 2: Chạy SQL thủ công
psql -h 47.84.63.130 -U postgres -d video_db -f full_player_configs_migration.sql
```
### Script sẽ thực hiện:
1. ✅ Tạo bảng `player_configs` với các columns:
- id, user_id, name, description
- autoplay, loop, muted, show_controls, pip, airplay, chromecast
- is_active, is_default, created_at, updated_at, version
2. ✅ Migrate dữ liệu từ `user_preferences` sang `player_configs`:
- Mỗi user sẽ có 1 config mặc định tên "Default Config"
- Description là "Migrated from user_preferences"
3. ✅ Xóa các columns player khỏi `user_preferences`:
- autoplay, loop, muted
- show_controls, pip, airplay, chromecast
- encrytion_m3u8 (VIP feature không dùng)
4. ✅ Tạo indexes và triggers
## Bước 2: Regenerate Go Models
Sau khi migration xong, chạy:
```bash
go run cmd/gendb/main.go
```
Script này sẽ đọc schema mới từ database và regenerate:
- `internal/database/model/user_preferences.gen.go` (không còn player fields)
- `internal/database/model/player_configs.gen.go` (mới)
- `internal/database/query/*.gen.go`
## Bước 3: Cập nhật Code
Sau khi models được regenerate, cập nhật các file sau:
### 3.1. Cập nhật `internal/api/preferences/service.go`
```go
// Xóa các field player settings khỏi UpdateInput
type UpdateInput struct {
EmailNotifications *bool
PushNotifications *bool
MarketingNotifications *bool
TelegramNotifications *bool
// XÓA: Autoplay, Loop, Muted, ShowControls, Pip, Airplay, Chromecast
Language *string
Locale *string
}
// Xóa logic update player settings khỏi UpdateUserPreferences
```
### 3.2. Cập nhật `internal/rpc/app/service_account.go`
```go
// UpdatePreferences chỉ update notification settings + language/locale
func (s *appServices) UpdatePreferences(...) {
// Chỉ update các fields không phải player settings
pref, err := preferencesapi.UpdateUserPreferences(ctx, s.db, s.logger, result.UserID, preferencesapi.UpdateInput{
EmailNotifications: req.EmailNotifications,
PushNotifications: req.PushNotifications,
MarketingNotifications: req.MarketingNotifications,
TelegramNotifications: req.TelegramNotifications,
Language: req.Language,
Locale: req.Locale,
// XÓA: Autoplay, Loop, Muted, ShowControls, Pip, Airplay, Chromecast
})
}
```
### 3.3. Sử dụng PlayerConfigs API cho player settings
Thay vì dùng `UpdatePreferences` cho player settings, dùng:
```go
// Tạo hoặc cập nhật default player config
func (s *appServices) UpdatePlayerSettings(ctx context.Context, req *appv1.UpdatePlayerSettingsRequest) {
// Dùng player_configs API đã implement
// ListPlayerConfigs, CreatePlayerConfig, UpdatePlayerConfig
}
```
## Bước 4: Build và Test
```bash
# Build
go build -o bin/api ./cmd/api
# Test migration
# 1. Kiểm tra bảng player_configs
psql -h 47.84.63.130 -U postgres -d video_db -c "SELECT COUNT(*) FROM player_configs;"
# 2. Kiểm tra user_preferences không còn player columns
psql -h 47.84.63.130 -U postgres -d video_db -c "\d user_preferences"
```
## Rollback (nếu cần)
Nếu muốn rollback:
```sql
-- Thêm lại columns vào user_preferences
ALTER TABLE user_preferences
ADD COLUMN IF NOT EXISTS autoplay BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS loop BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS muted BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS show_controls BOOLEAN DEFAULT TRUE,
ADD COLUMN IF NOT EXISTS pip BOOLEAN DEFAULT TRUE,
ADD COLUMN IF NOT EXISTS airplay BOOLEAN DEFAULT TRUE,
ADD COLUMN IF NOT EXISTS chromecast BOOLEAN DEFAULT TRUE,
ADD COLUMN IF NOT EXISTS encrytion_m3u8 BOOLEAN DEFAULT FALSE;
-- Copy dữ liệu từ player_configs về (cho default config)
UPDATE user_preferences up
SET autoplay = pc.autoplay,
loop = pc.loop,
muted = pc.muted,
show_controls = pc.show_controls,
pip = pc.pip,
airplay = pc.airplay,
chromecast = pc.chromecast
FROM player_configs pc
WHERE pc.user_id = up.user_id AND pc.is_default = TRUE;
-- Xóa bảng player_configs
DROP TABLE IF EXISTS player_configs CASCADE;
```
## Files liên quan
### Migration scripts:
- `migrations/001_create_player_configs_table.sql` - Tạo bảng
- `migrations/002_migrate_player_settings.sql` - Migrate data
- `full_player_configs_migration.sql` - Kết hợp cả 2
- `install_player_configs.sql` - Script đơn giản để chạy trực tiếp
- `migrate_player_configs.sh` - Shell script tự động hóa
### Models:
- `internal/database/model/player_configs.gen.go` - Model mới
- `internal/database/model/user_preferences.gen.go` - Model cũ (sẽ thay đổi)
### Services:
- `internal/rpc/app/service_user_features.go` - Player configs CRUD
- `internal/rpc/app/service_admin_finance_catalog.go` - Admin player configs
- `internal/api/preferences/service.go` - Legacy preferences (cần update)
### Frontend:
- `stream.ui/src/routes/settings/PlayerConfigs/PlayerConfigs.vue` - UI mới
- `stream.ui/src/routes/settings/Settings.vue` - Menu navigation
## Timeline khuyến nghị
1. **Tuần 1**: Chạy migration trên staging, test kỹ
2. **Tuần 2**: Cập nhật code backend (preferences service)
3. **Tuần 3**: Cập nhật frontend nếu cần
4. **Tuần 4**: Deploy production
## Lưu ý
- ✅ Backup database trước khi chạy migration
- ✅ Test trên staging trước khi production
- ✅ Migration có transaction, sẽ rollback nếu lỗi
- ✅ Dữ liệu user_preferences được giữ nguyên cho notification settings

376
script/create_database.sql Normal file
View File

@@ -0,0 +1,376 @@
-- DROP SCHEMA public;
CREATE SCHEMA public AUTHORIZATION pg_database_owner;
COMMENT ON SCHEMA public IS 'standard public schema';
-- public.ad_templates definition
-- Drop table
-- DROP TABLE public.ad_templates;
CREATE TABLE public.ad_templates (
id uuid NOT NULL,
user_id uuid NOT NULL,
"name" text NOT NULL,
description text NULL,
vast_tag_url text NOT NULL,
ad_format varchar(50) DEFAULT 'pre-roll'::character varying NOT NULL,
duration int8 NULL,
is_active bool DEFAULT true NOT NULL,
created_at timestamptz NULL,
updated_at timestamptz NULL,
is_default bool DEFAULT false NOT NULL,
"version" int8 DEFAULT 1 NOT NULL,
CONSTRAINT ad_templates_pkey PRIMARY KEY (id)
);
CREATE INDEX idx_ad_templates_user_id ON public.ad_templates USING btree (user_id);
-- public.domains definition
-- Drop table
-- DROP TABLE public.domains;
CREATE TABLE public.domains (
id uuid NOT NULL,
user_id uuid NOT NULL,
"name" text NOT NULL,
created_at timestamptz NULL,
updated_at timestamptz NULL,
"version" int8 DEFAULT 1 NOT NULL,
CONSTRAINT domains_pkey PRIMARY KEY (id)
);
CREATE INDEX idx_domains_user_id ON public.domains USING btree (user_id);
-- public.jobs definition
-- Drop table
-- DROP TABLE public.jobs;
CREATE TABLE public.jobs (
id uuid DEFAULT gen_random_uuid() NOT NULL,
status text NULL,
priority int8 DEFAULT 0 NULL,
input_url text NULL,
output_url text NULL,
total_duration int8 NULL,
current_time int8 NULL,
progress numeric NULL,
agent_id int8 NULL,
logs text NULL,
config text NULL,
cancelled bool DEFAULT false NULL,
retry_count int8 DEFAULT 0 NULL,
max_retries int8 DEFAULT 3 NULL,
created_at timestamptz NULL,
updated_at timestamptz NULL,
"version" int8 NULL,
video_id uuid NULL,
user_id uuid NULL,
time_limit int8 DEFAULT 3600000 NULL,
CONSTRAINT jobs_pkey PRIMARY KEY (id)
);
CREATE INDEX idx_jobs_priority ON public.jobs USING btree (priority);
-- public.notifications definition
-- Drop table
-- DROP TABLE public.notifications;
CREATE TABLE public.notifications (
id uuid NOT NULL,
user_id uuid NOT NULL,
"type" varchar(50) NOT NULL,
title text NOT NULL,
message text NOT NULL,
metadata text NULL,
action_url text NULL,
action_label text NULL,
is_read bool DEFAULT false NOT NULL,
created_at timestamptz NULL,
updated_at timestamptz NULL,
"version" int8 DEFAULT 1 NOT NULL,
CONSTRAINT notifications_pkey PRIMARY KEY (id)
);
CREATE INDEX idx_notifications_user_id ON public.notifications USING btree (user_id);
-- public."plan" definition
-- Drop table
-- DROP TABLE public."plan";
CREATE TABLE public."plan" (
id uuid DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
description text NULL,
price numeric(65, 30) NOT NULL,
"cycle" varchar(20) NOT NULL,
storage_limit int8 NOT NULL,
upload_limit int4 NOT NULL,
duration_limit int4 NOT NULL,
quality_limit text NOT NULL,
features _text NULL,
is_active bool DEFAULT true NOT NULL,
"version" int8 DEFAULT 1 NOT NULL,
CONSTRAINT plan_pkey PRIMARY KEY (id)
);
-- public.plan_subscriptions definition
-- Drop table
-- DROP TABLE public.plan_subscriptions;
CREATE TABLE public.plan_subscriptions (
id uuid NOT NULL,
user_id uuid NOT NULL,
payment_id uuid NOT NULL,
plan_id uuid NOT NULL,
term_months int4 NOT NULL,
payment_method varchar(20) NOT NULL,
wallet_amount numeric(65, 30) NOT NULL,
topup_amount numeric(65, 30) NOT NULL,
started_at timestamptz NOT NULL,
expires_at timestamptz NOT NULL,
reminder_7d_sent_at timestamptz NULL,
reminder_3d_sent_at timestamptz NULL,
reminder_1d_sent_at timestamptz NULL,
created_at timestamptz NULL,
updated_at timestamptz NULL,
"version" int8 DEFAULT 1 NOT NULL,
CONSTRAINT plan_subscriptions_pkey PRIMARY KEY (id)
);
CREATE INDEX idx_plan_subscriptions_expires_at ON public.plan_subscriptions USING btree (expires_at);
CREATE INDEX idx_plan_subscriptions_payment_id ON public.plan_subscriptions USING btree (payment_id);
CREATE INDEX idx_plan_subscriptions_plan_id ON public.plan_subscriptions USING btree (plan_id);
CREATE INDEX idx_plan_subscriptions_user_id ON public.plan_subscriptions USING btree (user_id);
-- public.user_preferences definition
-- Drop table
-- DROP TABLE public.user_preferences;
CREATE TABLE public.user_preferences (
user_id uuid NOT NULL,
"language" text DEFAULT 'en'::text NOT NULL,
locale text DEFAULT 'en'::text NOT NULL,
email_notifications bool DEFAULT true NOT NULL,
push_notifications bool DEFAULT true NOT NULL,
marketing_notifications bool DEFAULT false NOT NULL,
telegram_notifications bool DEFAULT false NOT NULL,
created_at timestamptz NULL,
updated_at timestamptz NULL,
"version" int8 DEFAULT 1 NOT NULL,
CONSTRAINT user_preferences_pkey PRIMARY KEY (user_id)
);
-- public.wallet_transactions definition
-- Drop table
-- DROP TABLE public.wallet_transactions;
CREATE TABLE public.wallet_transactions (
id uuid NOT NULL,
user_id uuid NOT NULL,
"type" varchar(50) NOT NULL,
amount numeric(65, 30) NOT NULL,
currency text DEFAULT 'USD'::text NOT NULL,
note text NULL,
created_at timestamptz NULL,
updated_at timestamptz NULL,
payment_id uuid NULL,
plan_id uuid NULL,
term_months int4 NULL,
"version" int8 DEFAULT 1 NOT NULL,
CONSTRAINT wallet_transactions_pkey PRIMARY KEY (id)
);
CREATE INDEX idx_wallet_transactions_payment_id ON public.wallet_transactions USING btree (payment_id);
CREATE INDEX idx_wallet_transactions_plan_id ON public.wallet_transactions USING btree (plan_id);
CREATE INDEX idx_wallet_transactions_user_id ON public.wallet_transactions USING btree (user_id);
-- public."user" definition
-- Drop table
-- DROP TABLE public."user";
CREATE TABLE public."user" (
id uuid DEFAULT gen_random_uuid() NOT NULL,
email text NOT NULL,
"password" text NULL,
username text NULL,
avatar text NULL,
"role" varchar(20) DEFAULT 'USER'::character varying NOT NULL,
google_id text NULL,
storage_used int8 DEFAULT 0 NOT NULL,
plan_id uuid NULL,
created_at timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at timestamp(3) NOT NULL,
"version" int8 DEFAULT 1 NOT NULL,
telegram_id varchar NULL,
referred_by_user_id uuid NULL,
referral_eligible bool DEFAULT true NOT NULL,
referral_reward_bps int4 NULL,
referral_reward_granted_at timestamptz NULL,
referral_reward_payment_id uuid NULL,
referral_reward_amount numeric(65, 30) NULL,
CONSTRAINT user_pkey PRIMARY KEY (id),
CONSTRAINT user_plan_id_fkey FOREIGN KEY (plan_id) REFERENCES public."plan"(id) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT user_referred_by_user_id_fkey FOREIGN KEY (referred_by_user_id) REFERENCES public."user"(id) ON DELETE SET NULL
);
CREATE INDEX idx_user_referred_by_user_id ON public."user" USING btree (referred_by_user_id);
CREATE UNIQUE INDEX user_email_key ON public."user" USING btree (email);
CREATE UNIQUE INDEX user_google_id_key ON public."user" USING btree (google_id);
-- public.video definition
-- Drop table
-- DROP TABLE public.video;
CREATE TABLE public.video (
id uuid DEFAULT gen_random_uuid() NOT NULL,
"name" text NOT NULL,
title text NOT NULL,
description text NULL,
url text NOT NULL,
thumbnail text NULL,
hls_token text NULL,
hls_path text NULL,
duration int4 NOT NULL,
"size" int8 NOT NULL,
storage_type varchar(20) DEFAULT 'tiktok_avatar'::character varying NOT NULL,
format text NOT NULL,
status varchar(20) DEFAULT 'PUBLIC'::character varying NOT NULL,
processing_status varchar(20) DEFAULT 'PENDING'::character varying NOT NULL,
"views" int4 DEFAULT 0 NOT NULL,
user_id uuid NOT NULL,
created_at timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at timestamp(3) NOT NULL,
"version" int8 DEFAULT 1 NOT NULL,
ad_id uuid NULL,
metadata jsonb NULL,
CONSTRAINT video_pkey PRIMARY KEY (id),
CONSTRAINT video_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE RESTRICT ON UPDATE CASCADE
);
-- public.payment definition
-- Drop table
-- DROP TABLE public.payment;
CREATE TABLE public.payment (
id uuid DEFAULT gen_random_uuid() NOT NULL,
user_id uuid NOT NULL,
plan_id uuid NULL,
amount numeric(65, 30) NOT NULL,
currency text DEFAULT 'USD'::text NOT NULL,
status varchar(20) DEFAULT 'PENDING'::character varying NOT NULL,
provider varchar(20) DEFAULT 'STRIPE'::character varying NOT NULL,
transaction_id text NULL,
created_at timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at timestamp(3) NOT NULL,
"version" int8 DEFAULT 1 NOT NULL,
CONSTRAINT payment_pkey PRIMARY KEY (id),
CONSTRAINT payment_plan_id_fkey FOREIGN KEY (plan_id) REFERENCES public."plan"(id) ON DELETE SET NULL ON UPDATE CASCADE,
CONSTRAINT payment_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE RESTRICT ON UPDATE CASCADE
);
-- public.player_configs definition
-- Drop table
-- DROP TABLE public.player_configs;
CREATE TABLE public.player_configs (
id uuid DEFAULT gen_random_uuid() NOT NULL,
user_id uuid NOT NULL,
"name" text NOT NULL,
description text NULL,
autoplay bool DEFAULT false NOT NULL,
"loop" bool DEFAULT false NOT NULL,
muted bool DEFAULT false NOT NULL,
show_controls bool DEFAULT true NOT NULL,
pip bool DEFAULT true NOT NULL,
airplay bool DEFAULT true NOT NULL,
chromecast bool DEFAULT true NOT NULL,
is_active bool DEFAULT true NOT NULL,
is_default bool DEFAULT false NOT NULL,
created_at timestamp(3) DEFAULT CURRENT_TIMESTAMP NOT NULL,
updated_at timestamp(3) NOT NULL,
"version" int8 DEFAULT 1 NOT NULL,
encrytion_m3u8 bool DEFAULT true NOT NULL,
logo_url varchar(500) NULL,
CONSTRAINT player_configs_pkey PRIMARY KEY (id),
CONSTRAINT player_configs_url_check CHECK (((logo_url)::text ~* '^https?://'::text)),
CONSTRAINT player_configs_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE
);
CREATE INDEX idx_player_configs_is_default ON public.player_configs USING btree (is_default);
CREATE UNIQUE INDEX idx_player_configs_one_default_per_user ON public.player_configs USING btree (user_id) WHERE (is_default = true);
CREATE INDEX idx_player_configs_user_default ON public.player_configs USING btree (user_id, is_default);
CREATE INDEX idx_player_configs_user_id ON public.player_configs USING btree (user_id);
-- Table Triggers
create trigger trg_update_player_configs before
update
on
public.player_configs for each row execute function update_player_configs_updated_at();
-- public.popup_ads definition
-- Drop table
-- DROP TABLE public.popup_ads;
CREATE TABLE public.popup_ads (
id uuid NOT NULL,
user_id uuid NOT NULL,
"type" varchar(20) NOT NULL,
"label" text NOT NULL,
value text NOT NULL,
is_active bool DEFAULT true NOT NULL,
max_triggers_per_session int4 DEFAULT 3 NOT NULL,
created_at timestamptz DEFAULT CURRENT_TIMESTAMP NULL,
updated_at timestamptz NULL,
"version" int8 DEFAULT 1 NOT NULL,
CONSTRAINT popup_ads_pkey PRIMARY KEY (id),
CONSTRAINT popup_ads_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id) ON DELETE CASCADE
);
CREATE INDEX idx_popup_ads_user_active ON public.popup_ads USING btree (user_id, is_active);
CREATE INDEX idx_popup_ads_user_id ON public.popup_ads USING btree (user_id);
-- DROP FUNCTION public.update_player_configs_updated_at();
CREATE OR REPLACE FUNCTION public.update_player_configs_updated_at()
RETURNS trigger
LANGUAGE plpgsql
AS $function$
BEGIN
NEW.updated_at = CURRENT_TIMESTAMP;
NEW.version = OLD.version + 1;
RETURN NEW;
END;
$function$
;

View File

@@ -0,0 +1,143 @@
-- Full migration script for player_configs
-- This combines all migrations into one file for easy execution
-- Run: psql -h 47.84.63.130 -U postgres -d video_db -f full_player_configs_migration.sql
\echo '=============================================='
\echo 'Starting full player_configs migration...'
\echo '=============================================='
BEGIN;
-- ============================================================
-- PART 1: 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
);
CREATE INDEX IF NOT EXISTS idx_player_configs_user_id ON player_configs(user_id);
CREATE INDEX IF NOT EXISTS idx_player_configs_is_default ON player_configs(is_default);
CREATE INDEX IF NOT EXISTS idx_player_configs_user_default ON player_configs(user_id, is_default);
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;
DROP TRIGGER IF EXISTS trg_update_player_configs ON player_configs;
CREATE TRIGGER trg_update_player_configs
BEFORE UPDATE ON player_configs
FOR EACH ROW
EXECUTE FUNCTION update_player_configs_updated_at();
-- ============================================================
-- PART 2: Migrate data from user_preferences
-- ============================================================
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(),
up.user_id,
'Default Config',
'Migrated from user_preferences',
COALESCE(up.autoplay, FALSE),
COALESCE(up.loop, FALSE),
COALESCE(up.muted, FALSE),
COALESCE(up.show_controls, TRUE),
COALESCE(up.pip, TRUE),
COALESCE(up.airplay, TRUE),
COALESCE(up.chromecast, TRUE),
TRUE,
TRUE,
COALESCE(up.created_at, CURRENT_TIMESTAMP),
CURRENT_TIMESTAMP,
COALESCE(up.version, 1)
FROM user_preferences up
WHERE NOT EXISTS (
SELECT 1 FROM player_configs pc WHERE pc.user_id = up.user_id AND pc.is_default = TRUE
);
-- ============================================================
-- PART 3: Remove old columns from user_preferences
-- ============================================================
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 COLUMN IF EXISTS encrytion_m3u8;
-- ============================================================
-- PART 4: Add constraints
-- ============================================================
CREATE UNIQUE INDEX IF NOT EXISTS idx_player_configs_one_default_per_user
ON player_configs(user_id)
WHERE is_default = TRUE;
-- ============================================================
-- Verification
-- ============================================================
DO $$
DECLARE
migrated_count INTEGER;
prefs_count INTEGER;
BEGIN
SELECT COUNT(*) INTO migrated_count FROM player_configs WHERE description = 'Migrated from user_preferences';
SELECT COUNT(*) INTO prefs_count FROM user_preferences;
RAISE NOTICE '============================================';
RAISE NOTICE 'Migration completed!';
RAISE NOTICE 'User preferences rows: %', prefs_count;
RAISE NOTICE 'Player configs created: %', migrated_count;
RAISE NOTICE '============================================';
END $$;
COMMIT;
-- Verify columns removed from user_preferences
SELECT 'user_preferences columns:' AS info;
SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'user_preferences' ORDER BY ordinal_position;
-- Verify player_configs structure
SELECT 'player_configs columns:' AS info;
SELECT column_name, data_type FROM information_schema.columns WHERE table_name = 'player_configs' ORDER BY ordinal_position;
-- Sample data
SELECT 'Sample migrated data:' AS info;
SELECT pc.user_id, pc.name, pc.autoplay, pc.loop, pc.muted, pc.show_controls, pc.is_default
FROM player_configs pc
WHERE pc.description = 'Migrated from user_preferences'
LIMIT 5;

View File

@@ -0,0 +1,55 @@
-- Quick install script for player_configs table
-- Run this directly in your PostgreSQL database
-- Usage: psql -d video_db -f install_player_configs.sql
BEGIN;
-- 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
);
-- Indexes
CREATE INDEX IF NOT EXISTS idx_player_configs_user_id ON player_configs(user_id);
CREATE INDEX IF NOT EXISTS idx_player_configs_is_default ON player_configs(is_default);
CREATE INDEX IF NOT EXISTS idx_player_configs_user_default ON player_configs(user_id, is_default);
-- Trigger to auto-update updated_at and version
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;
DROP TRIGGER IF EXISTS trg_update_player_configs ON player_configs;
CREATE TRIGGER trg_update_player_configs
BEFORE UPDATE ON player_configs
FOR EACH ROW
EXECUTE FUNCTION update_player_configs_updated_at();
COMMIT;
-- Verify installation
SELECT table_name, column_name, data_type
FROM information_schema.columns
WHERE table_name = 'player_configs'
ORDER BY ordinal_position;

View File

@@ -0,0 +1,84 @@
#!/bin/bash
# Full migration script for player_configs feature
# This script:
# 1. Runs the SQL migration to create player_configs table
# 2. Migrates data from user_preferences to player_configs
# 3. Removes player-related columns from user_preferences
# 4. Regenerates Go models to reflect schema changes
#
# Usage: ./migrate_player_configs.sh
set -e
# Colors
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
# Database config from config.yaml or environment
DB_HOST="${DB_HOST:-47.84.63.130}"
DB_PORT="${DB_PORT:-5432}"
DB_NAME="${DB_NAME:-video_db}"
DB_USER="${DB_USER:-postgres}"
DB_PASSWORD="${DB_PASSWORD:-D@tkhong9}"
export PGPASSWORD="$DB_PASSWORD"
echo -e "${BLUE}============================================${NC}"
echo -e "${BLUE} Player Configs Migration Script${NC}"
echo -e "${BLUE}============================================${NC}"
echo ""
# Step 1: Run SQL migration
echo -e "${YELLOW}[Step 1/3] Running SQL migration...${NC}"
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f full_player_configs_migration.sql
if [ $? -ne 0 ]; then
echo -e "${RED}SQL migration failed!${NC}"
exit 1
fi
echo -e "${GREEN}✓ SQL migration completed${NC}"
echo ""
# Step 2: Regenerate Go models
echo -e "${YELLOW}[Step 2/3] Regenerating Go models...${NC}"
go run cmd/gendb/main.go
if [ $? -ne 0 ]; then
echo -e "${RED}Model generation failed!${NC}"
echo -e "${YELLOW}Note: You may need to manually update the model files.${NC}"
exit 1
fi
echo -e "${GREEN}✓ Go models regenerated${NC}"
echo ""
# Step 3: Build to verify
echo -e "${YELLOW}[Step 3/3] Building to verify changes...${NC}"
go build -o bin/api ./cmd/api
if [ $? -ne 0 ]; then
echo -e "${RED}Build failed!${NC}"
exit 1
fi
echo -e "${GREEN}✓ Build successful${NC}"
echo ""
unset PGPASSWORD
echo -e "${GREEN}============================================${NC}"
echo -e "${GREEN} Migration completed successfully!${NC}"
echo -e "${GREEN}============================================${NC}"
echo ""
echo -e "${BLUE}Summary of changes:${NC}"
echo " ✓ player_configs table created"
echo " ✓ Data migrated from user_preferences"
echo " ✓ Player columns removed from user_preferences:"
echo " - autoplay, loop, muted"
echo " - show_controls, pip, airplay, chromecast"
echo " - encrytion_m3u8"
echo " ✓ Go models regenerated"
echo ""
echo -e "${YELLOW}Note: Please restart your application to apply changes.${NC}"

59
script/run_migration.sh Executable file
View File

@@ -0,0 +1,59 @@
#!/bin/bash
# Migration runner script for stream.api
# Usage: ./run_migration.sh [migration_file.sql]
set -e
# Load environment variables from config if exists
if [ -f "config.yaml" ]; then
echo "Loading configuration from config.yaml..."
fi
# Database connection parameters (adjust these based on your config)
DB_HOST="${DB_HOST:-localhost}"
DB_PORT="${DB_PORT:-5432}"
DB_NAME="${DB_NAME:-stream}"
DB_USER="${DB_USER:-postgres}"
DB_PASSWORD="${DB_PASSWORD:-}"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${GREEN}=== Stream API Migration Runner ===${NC}"
# Check if psql is available
if ! command -v psql &> /dev/null; then
echo -e "${RED}Error: psql command not found. Please install PostgreSQL client.${NC}"
exit 1
fi
# Build connection string
export PGPASSWORD="$DB_PASSWORD"
# Run specific migration or all migrations
if [ -n "$1" ]; then
MIGRATION_FILE="$1"
if [ ! -f "$MIGRATION_FILE" ]; then
echo -e "${RED}Error: Migration file '$MIGRATION_FILE' not found${NC}"
exit 1
fi
echo -e "${YELLOW}Running migration: $MIGRATION_FILE${NC}"
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$MIGRATION_FILE"
echo -e "${GREEN}✓ Migration completed successfully${NC}"
else
# Run all migrations in order
echo -e "${YELLOW}Running all migrations in ./migrations/...${NC}"
for migration in $(ls -1 migrations/*.sql 2>/dev/null | sort); do
echo -e "${YELLOW}Running: $migration${NC}"
psql -h "$DB_HOST" -p "$DB_PORT" -U "$DB_USER" -d "$DB_NAME" -f "$migration"
echo -e "${GREEN}✓ Completed: $migration${NC}"
echo ""
done
echo -e "${GREEN}=== All migrations completed ===${NC}"
fi
unset PGPASSWORD