-- Training plans and sessions for analysis-driven coaching create table if not exists public.training_plans ( id uuid primary key default gen_random_uuid(), user_id uuid not null references auth.users(id) on delete cascade, pat_type text not null, status text not null default 'active' check (status in ('active', 'archived')), analysis_snapshot jsonb not null default '{}'::jsonb, duration_weeks integer not null check (duration_weeks in (2, 4, 6)), sessions_per_week integer not null check (sessions_per_week in (2, 3, 4)), generated_at timestamptz not null default now(), archived_at timestamptz, created_at timestamptz not null default now(), updated_at timestamptz not null default now() ); create table if not exists public.training_plan_sessions ( id uuid primary key default gen_random_uuid(), plan_id uuid not null references public.training_plans(id) on delete cascade, week_no integer not null, session_no integer not null, main_exercise text not null, secondary_exercises jsonb not null default '[]'::jsonb, tasks jsonb not null default '[]'::jsonb, state text not null default 'open' check (state in ('open', 'done', 'skipped')), notes text, completed_at timestamptz, created_at timestamptz not null default now(), updated_at timestamptz not null default now(), unique (plan_id, week_no, session_no) ); -- updated_at trigger reuse create or replace function public.set_updated_at() returns trigger as $$ begin new.updated_at = now(); return new; end; $$ language plpgsql; drop trigger if exists trg_training_plans_updated_at on public.training_plans; create trigger trg_training_plans_updated_at before update on public.training_plans for each row execute function public.set_updated_at(); drop trigger if exists trg_training_plan_sessions_updated_at on public.training_plan_sessions; create trigger trg_training_plan_sessions_updated_at before update on public.training_plan_sessions for each row execute function public.set_updated_at(); -- RLS alter table public.training_plans enable row level security; alter table public.training_plan_sessions enable row level security; drop policy if exists "Allow users to insert own training plans" on public.training_plans; create policy "Allow users to insert own training plans" on public.training_plans for insert with check (auth.uid() = user_id); drop policy if exists "Allow users to select own training plans" on public.training_plans; create policy "Allow users to select own training plans" on public.training_plans for select using (auth.uid() = user_id); drop policy if exists "Allow users to update own training plans" on public.training_plans; create policy "Allow users to update own training plans" on public.training_plans for update using (auth.uid() = user_id); drop policy if exists "Allow users to delete own training plans" on public.training_plans; create policy "Allow users to delete own training plans" on public.training_plans for delete using (auth.uid() = user_id); drop policy if exists "Allow users to insert own training plan sessions" on public.training_plan_sessions; create policy "Allow users to insert own training plan sessions" on public.training_plan_sessions for insert with check ( exists ( select 1 from public.training_plans tp where tp.id = training_plan_sessions.plan_id and tp.user_id = auth.uid() ) ); drop policy if exists "Allow users to select own training plan sessions" on public.training_plan_sessions; create policy "Allow users to select own training plan sessions" on public.training_plan_sessions for select using ( exists ( select 1 from public.training_plans tp where tp.id = training_plan_sessions.plan_id and tp.user_id = auth.uid() ) ); drop policy if exists "Allow users to update own training plan sessions" on public.training_plan_sessions; create policy "Allow users to update own training plan sessions" on public.training_plan_sessions for update using ( exists ( select 1 from public.training_plans tp where tp.id = training_plan_sessions.plan_id and tp.user_id = auth.uid() ) ); drop policy if exists "Allow users to delete own training plan sessions" on public.training_plan_sessions; create policy "Allow users to delete own training plan sessions" on public.training_plan_sessions for delete using ( exists ( select 1 from public.training_plans tp where tp.id = training_plan_sessions.plan_id and tp.user_id = auth.uid() ) ); create index if not exists idx_training_plans_user_pat_generated on public.training_plans(user_id, pat_type, generated_at desc); create unique index if not exists idx_training_plans_user_pat_active_unique on public.training_plans(user_id, pat_type) where status = 'active'; create index if not exists idx_training_plan_sessions_plan_week_session on public.training_plan_sessions(plan_id, week_no, session_no); create or replace function public.create_training_plan_with_sessions( p_pat_type text, p_analysis_snapshot jsonb, p_duration_weeks integer, p_sessions_per_week integer, p_sessions jsonb ) returns uuid language plpgsql security invoker as $$ declare v_user_id uuid; v_plan_id uuid; v_session jsonb; v_week_no integer; v_session_no integer; begin v_user_id := auth.uid(); if v_user_id is null then raise exception 'Not authenticated'; end if; if p_duration_weeks not in (2, 4, 6) then raise exception 'Invalid duration_weeks: %', p_duration_weeks; end if; if p_sessions_per_week not in (2, 3, 4) then raise exception 'Invalid sessions_per_week: %', p_sessions_per_week; end if; update public.training_plans set status = 'archived', archived_at = now() where user_id = v_user_id and pat_type = p_pat_type and status = 'active'; insert into public.training_plans ( user_id, pat_type, status, analysis_snapshot, duration_weeks, sessions_per_week, generated_at ) values ( v_user_id, p_pat_type, 'active', coalesce(p_analysis_snapshot, '{}'::jsonb), p_duration_weeks, p_sessions_per_week, now() ) returning id into v_plan_id; for v_session in select value from jsonb_array_elements(coalesce(p_sessions, '[]'::jsonb)) loop v_week_no := coalesce((v_session->>'weekNo')::integer, (v_session->>'week_no')::integer, 1); v_session_no := coalesce((v_session->>'sessionNo')::integer, (v_session->>'session_no')::integer, 1); insert into public.training_plan_sessions ( plan_id, week_no, session_no, main_exercise, secondary_exercises, tasks, state, notes ) values ( v_plan_id, v_week_no, v_session_no, coalesce(v_session->>'mainExercise', v_session->>'main_exercise', 'Basistechnik'), coalesce(v_session->'secondaryExercises', v_session->'secondary_exercises', '[]'::jsonb), coalesce(v_session->'tasks', '[]'::jsonb), coalesce(v_session->>'state', 'open'), nullif(v_session->>'notes', '') ); end loop; return v_plan_id; end; $$; grant execute on function public.create_training_plan_with_sessions(text, jsonb, integer, integer, jsonb) to authenticated;