234 lines
7.1 KiB
PL/PgSQL
234 lines
7.1 KiB
PL/PgSQL
-- 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;
|