refactor: code quality cleanup — remove any types, add logger, rename Kine to Tabata

- Phase 0: Rename all Kine references to Tabata (types, files, imports, i18n, analytics events)
- Phase 1: Add test coverage for tabataProgramStore, workoutProgramStore, and color utils (47 tests)
- Phase 2: Remove all `any` types from production code with proper typed replacements
- Phase 3: Replace ~60 raw console.* calls with __DEV__-gated logger utility
- Phase 4: Verify .DS_Store housekeeping (already clean)

0 TypeScript errors, 583/583 tests passing.
This commit is contained in:
Millian Lamiaux
2026-04-17 18:56:24 +02:00
parent e0e02c4550
commit 791f432334
176 changed files with 16508 additions and 2305 deletions

View File

@@ -0,0 +1,80 @@
-- Migration Script: Reset trainers to Félia and Félix only
-- Run this in your Supabase SQL Editor
DO $$
DECLARE
felia_id UUID;
felix_id UUID;
old_trainer_ids UUID[];
BEGIN
-- Step 1: Get IDs of trainers to be replaced
SELECT ARRAY[
(SELECT id FROM trainers WHERE name = 'Emma'),
(SELECT id FROM trainers WHERE name = 'Jake'),
(SELECT id FROM trainers WHERE name = 'Mia'),
(SELECT id FROM trainers WHERE name = 'Alex'),
(SELECT id FROM trainers WHERE name = 'Sofia')
] INTO old_trainer_ids;
-- Step 2: Create Félia if not exists
SELECT COALESCE(
(SELECT id FROM trainers WHERE name = 'Félia' LIMIT 1),
gen_random_uuid()
) INTO felia_id;
INSERT INTO trainers (id, name, specialty, color, workout_count, avatar_url)
VALUES (felia_id, 'Félia', 'Core', '#30D158', 0, NULL)
ON CONFLICT (id) DO UPDATE SET
name = 'Félia',
specialty = 'Core',
color = '#30D158';
-- Step 3: Create Félix if not exists
SELECT COALESCE(
(SELECT id FROM trainers WHERE name = 'Félix' LIMIT 1),
gen_random_uuid()
) INTO felix_id;
INSERT INTO trainers (id, name, specialty, color, workout_count, avatar_url)
VALUES (felix_id, 'Félix', 'Strength', '#FFD60A', 0, NULL)
ON CONFLICT (id) DO UPDATE SET
name = 'Félix',
specialty = 'Strength',
color = '#FFD60A';
-- Step 4: Get the final trainer IDs
SELECT id INTO felia_id FROM trainers WHERE name = 'Félia';
SELECT id INTO felix_id FROM trainers WHERE name = 'Félix';
-- Step 5: Update workouts - female trainers → felia, male trainers → felix
UPDATE workouts
SET trainer_id = felia_id
WHERE trainer_id IN (
SELECT id FROM trainers WHERE name IN ('Emma', 'Mia', 'Sofia')
);
UPDATE workouts
SET trainer_id = felix_id
WHERE trainer_id IN (
SELECT id FROM trainers WHERE name IN ('Jake', 'Alex')
);
-- Step 6: Update workout counts
UPDATE trainers SET workout_count = (
SELECT COUNT(*) FROM workouts WHERE trainer_id = felia_id
) WHERE id = felia_id;
UPDATE trainers SET workout_count = (
SELECT COUNT(*) FROM workouts WHERE trainer_id = felix_id
) WHERE id = felix_id;
-- Step 7: Delete old trainers
DELETE FROM trainers WHERE name IN ('Emma', 'Jake', 'Mia', 'Alex', 'Sofia');
END $$;
-- Step 8: Verify
SELECT 'Trainers:' AS result;
SELECT id::text, name, specialty, color, workout_count FROM trainers ORDER BY name;
SELECT 'Workout trainer distribution:' AS result;
SELECT trainer_id::text, COUNT(*) as workout_count FROM workouts GROUP BY trainer_id;

View File

@@ -0,0 +1,60 @@
-- Storage RLS Policies for Trainer Assets
-- Run this in your Supabase SQL Editor
-- Enable RLS on storage buckets (if not already enabled)
ALTER TABLE storage.buckets ENABLE ROW LEVEL SECURITY;
-- Trainer Avatars Bucket Policies
DROP POLICY IF EXISTS "Public read access" ON storage.objects;
DROP POLICY IF EXISTS "Public upload access" ON storage.objects;
DROP POLICY IF EXISTS "Public update access" ON storage.objects;
DROP POLICY IF EXISTS "Public delete access" ON storage.objects;
-- Read access (anyone can view)
CREATE POLICY "Public read access" ON storage.objects
FOR SELECT USING (bucket_id = 'trainer-avatars');
-- Upload access (anyone can upload)
CREATE POLICY "Public upload access" ON storage.objects
FOR INSERT WITH CHECK (bucket_id = 'trainer-avatars');
-- Update access (anyone can update)
CREATE POLICY "Public update access" ON storage.objects
FOR UPDATE USING (bucket_id = 'trainer-avatars');
-- Delete access (anyone can delete)
CREATE POLICY "Public delete access" ON storage.objects
FOR DELETE USING (bucket_id = 'trainer-avatars');
-- Trainer Videos Bucket Policies
DROP POLICY IF EXISTS "Public read access videos" ON storage.objects;
DROP POLICY IF EXISTS "Public upload access videos" ON storage.objects;
DROP POLICY IF EXISTS "Public update access videos" ON storage.objects;
DROP POLICY IF EXISTS "Public delete access videos" ON storage.objects;
-- Read access (anyone can view)
CREATE POLICY "Public read access videos" ON storage.objects
FOR SELECT USING (bucket_id = 'trainer-videos');
-- Upload access (anyone can upload)
CREATE POLICY "Public upload access videos" ON storage.objects
FOR INSERT WITH CHECK (bucket_id = 'trainer-videos');
-- Update access (anyone can update)
CREATE POLICY "Public update access videos" ON storage.objects
FOR UPDATE USING (bucket_id = 'trainer-videos');
-- Delete access (anyone can delete)
CREATE POLICY "Public delete access videos" ON storage.objects
FOR DELETE USING (bucket_id = 'trainer-videos');
-- Exercise Videos Table Policies (if table exists)
DROP POLICY IF EXISTS "Public read access exercise_videos" ON exercise_videos;
DROP POLICY IF EXISTS "Public insert access exercise_videos" ON exercise_videos;
DROP POLICY IF EXISTS "Public update access exercise_videos" ON exercise_videos;
DROP POLICY IF EXISTS "Public delete access exercise_videos" ON exercise_videos;
CREATE POLICY "Public read access exercise_videos" ON exercise_videos FOR SELECT USING (true);
CREATE POLICY "Public insert access exercise_videos" ON exercise_videos FOR INSERT WITH CHECK (true);
CREATE POLICY "Public update access exercise_videos" ON exercise_videos FOR UPDATE USING (true);
CREATE POLICY "Public delete access exercise_videos" ON exercise_videos FOR DELETE USING (true);

View File

@@ -0,0 +1,24 @@
-- Create exercise_videos table
CREATE TABLE IF NOT EXISTS exercise_videos (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
workout_id UUID NOT NULL,
trainer_id UUID NOT NULL,
exercise_name TEXT NOT NULL,
video_url TEXT NOT NULL,
video_path TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Enable RLS
ALTER TABLE exercise_videos ENABLE ROW LEVEL SECURITY;
-- Public policies
DROP POLICY IF EXISTS "Public read access exercise_videos" ON exercise_videos;
DROP POLICY IF EXISTS "Public insert access exercise_videos" ON exercise_videos;
DROP POLICY IF EXISTS "Public update access exercise_videos" ON exercise_videos;
DROP POLICY IF EXISTS "Public delete access exercise_videos" ON exercise_videos;
CREATE POLICY "Public read access exercise_videos" ON exercise_videos FOR SELECT USING (true);
CREATE POLICY "Public insert access exercise_videos" ON exercise_videos FOR INSERT WITH CHECK (true);
CREATE POLICY "Public update access exercise_videos" ON exercise_videos FOR UPDATE USING (true);
CREATE POLICY "Public delete access exercise_videos" ON exercise_videos FOR DELETE USING (true);

View File

@@ -0,0 +1,11 @@
-- Add video_type and duration_seconds columns to exercise_videos table
ALTER TABLE exercise_videos
ADD COLUMN IF NOT EXISTS video_type TEXT DEFAULT 'exercise'
CHECK (video_type IN ('exercise', 'rest'));
ALTER TABLE exercise_videos
ADD COLUMN IF NOT EXISTS duration_seconds INTEGER;
-- Update existing rows to have appropriate values
UPDATE exercise_videos SET video_type = 'exercise' WHERE video_type IS NULL;
UPDATE exercise_videos SET duration_seconds = 8 WHERE duration_seconds IS NULL;

View File

@@ -0,0 +1,127 @@
-- Tabata Kine Programs Schema
-- Migration 005: New kiné program system
-- Replaces the old 3-program model with 4 kiné programs
-- ─── Kine Programs ──────────────────────────────────────────
CREATE TABLE IF NOT EXISTS public.kine_programs (
id TEXT PRIMARY KEY, -- 'debutant', 'intermediaire', 'avance', 'bureau'
title TEXT NOT NULL,
title_en TEXT NOT NULL,
description TEXT NOT NULL,
description_en TEXT NOT NULL,
tier TEXT NOT NULL CHECK (tier IN ('free', 'premium')),
accent_color TEXT NOT NULL DEFAULT '#30D158',
icon TEXT NOT NULL DEFAULT 'seedling',
duration_weeks INTEGER NOT NULL DEFAULT 4,
sessions_per_week INTEGER NOT NULL,
total_sessions INTEGER NOT NULL,
equipment JSONB NOT NULL DEFAULT '{"required": [], "optional": []}',
focus_areas TEXT[] DEFAULT '{}',
focus_areas_en TEXT[] DEFAULT '{}',
principles TEXT[] DEFAULT '{}',
principles_en TEXT[] DEFAULT '{}',
completion_criteria TEXT[] DEFAULT '{}',
completion_criteria_en TEXT[] DEFAULT '{}',
next_program_id TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- ─── Kine Sessions ──────────────────────────────────────────
CREATE TABLE IF NOT EXISTS public.kine_sessions (
id TEXT PRIMARY KEY, -- 'deb-w1-s1', 'int-w2-s3', etc.
program_id TEXT NOT NULL REFERENCES public.kine_programs(id) ON DELETE CASCADE,
week_number INTEGER NOT NULL CHECK (week_number >= 1 AND week_number <= 6),
day_number INTEGER NOT NULL CHECK (day_number >= 1 AND day_number <= 7),
title TEXT NOT NULL,
title_en TEXT NOT NULL,
description TEXT,
description_en TEXT,
focus TEXT[] DEFAULT '{}',
focus_en TEXT[] DEFAULT '{}',
warmup JSONB NOT NULL, -- WarmupPhase structure
blocks JSONB NOT NULL, -- TabataBlock[] array
cooldown JSONB NOT NULL, -- CooldownPhase structure
equipment TEXT[] DEFAULT '{}',
total_rounds INTEGER NOT NULL,
total_duration INTEGER NOT NULL, -- minutes
calories INTEGER NOT NULL DEFAULT 0,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ─── Kine Exercises Library ─────────────────────────────────
-- Track individual exercises for video generation
CREATE TABLE IF NOT EXISTS public.kine_exercises (
id TEXT PRIMARY KEY, -- slug: 'squat-classique', 'pont-fessier'
name_fr TEXT NOT NULL,
name_en TEXT NOT NULL,
conseil_fr TEXT,
conseil_en TEXT,
modification TEXT,
modification_en TEXT,
progression TEXT,
progression_en TEXT,
video_url TEXT,
video_generated BOOLEAN DEFAULT FALSE,
video_generation_status TEXT CHECK (video_generation_status IN ('pending', 'generating', 'completed', 'failed')),
video_generation_job_id TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- ─── Kine Weeks ─────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS public.kine_weeks (
id TEXT PRIMARY KEY, -- 'deb-w1', 'int-w2', etc.
program_id TEXT NOT NULL REFERENCES public.kine_programs(id) ON DELETE CASCADE,
week_number INTEGER NOT NULL,
title TEXT NOT NULL,
title_en TEXT NOT NULL,
description TEXT,
description_en TEXT,
focus TEXT,
focus_en TEXT,
is_deload BOOLEAN DEFAULT FALSE,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- ─── Indexes ────────────────────────────────────────────────
CREATE INDEX IF NOT EXISTS idx_kine_sessions_program ON public.kine_sessions(program_id);
CREATE INDEX IF NOT EXISTS idx_kine_sessions_week ON public.kine_sessions(program_id, week_number);
CREATE INDEX IF NOT EXISTS idx_kine_weeks_program ON public.kine_weeks(program_id);
CREATE INDEX IF NOT EXISTS idx_kine_exercises_generated ON public.kine_exercises(video_generated);
-- ─── Row Level Security ─────────────────────────────────────
ALTER TABLE public.kine_programs ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.kine_sessions ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.kine_exercises ENABLE ROW LEVEL SECURITY;
ALTER TABLE public.kine_weeks ENABLE ROW LEVEL SECURITY;
-- Public read access
CREATE POLICY "Public read kine_programs" ON public.kine_programs FOR SELECT USING (true);
CREATE POLICY "Public read kine_sessions" ON public.kine_sessions FOR SELECT USING (true);
CREATE POLICY "Public read kine_exercises" ON public.kine_exercises FOR SELECT USING (true);
CREATE POLICY "Public read kine_weeks" ON public.kine_weeks FOR SELECT USING (true);
-- ─── Seed: Debutant Program ─────────────────────────────────
INSERT INTO public.kine_programs (id, title, title_en, description, description_en, tier, accent_color, icon, duration_weeks, sessions_per_week, total_sessions) VALUES
('debutant', 'Débutant', 'Beginner',
'Apprendre le protocole tabata, construire les bases techniques de chaque mouvement fondamental.',
'Learn the tabata protocol, build technical foundations for each fundamental movement.',
'free', '#30D158', 'seedling', 4, 3, 12)
ON CONFLICT (id) DO NOTHING;
-- Seed weeks
INSERT INTO public.kine_weeks (id, program_id, week_number, title, title_en, description, description_en, focus, focus_en, is_deload) VALUES
('deb-w1', 'debutant', 1, 'Découverte du rythme', 'Finding Your Rhythm', 'Un bloc tabata par séance (4 min) + échauffement + retour au calme.', 'One tabata block per session + warmup + cooldown.', 'Apprentissage du protocole 20/10', 'Learning the 20/10 protocol', false),
('deb-w2', 'debutant', 2, 'Consolidation', 'Building Strength', '2 blocs tabata + 1 min récup entre les blocs.', '2 tabata blocks + 1 min recovery between blocks.', 'Consolidation des mouvements', 'Consolidating movements', false),
('deb-w3', 'debutant', 3, 'Montée en intensité', 'Building Intensity', '3 blocs tabata + 1 min récupération entre chaque.', '3 tabata blocks + 1 min recovery between each.', 'Impacts très légers, volume augmenté', 'Very light impact, increased volume', false),
('deb-w4', 'debutant', 4, 'Décharge & consolidation', 'Deload & Consolidation', 'Retour à 2 blocs. Volume réduit de 40%.', 'Back to 2 blocks. Volume reduced by 40%.', 'Technique parfaite, respiration', 'Perfect technique, breathing', true)
ON CONFLICT (id) DO NOTHING;