Initial commit
This commit is contained in:
49
supabase/migrations/20240211000000_assessments.sql
Normal file
49
supabase/migrations/20240211000000_assessments.sql
Normal file
@@ -0,0 +1,49 @@
|
||||
-- Assessments table for PAT Stats
|
||||
create table if not exists public.assessments (
|
||||
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,
|
||||
datum date not null,
|
||||
name text not null,
|
||||
exercises jsonb not null default '[]'::jsonb,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
-- Keep updated_at current
|
||||
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_assessments_updated_at on public.assessments;
|
||||
create trigger trg_assessments_updated_at
|
||||
before update on public.assessments
|
||||
for each row execute function public.set_updated_at();
|
||||
|
||||
-- Row Level Security
|
||||
alter table public.assessments enable row level security;
|
||||
|
||||
-- Policies: authenticated users manage only their own rows
|
||||
create policy if not exists "Allow users to insert own assessments"
|
||||
on public.assessments
|
||||
for insert with check (auth.uid() = user_id);
|
||||
|
||||
create policy if not exists "Allow users to select own assessments"
|
||||
on public.assessments
|
||||
for select using (auth.uid() = user_id);
|
||||
|
||||
create policy if not exists "Allow users to update own assessments"
|
||||
on public.assessments
|
||||
for update using (auth.uid() = user_id);
|
||||
|
||||
create policy if not exists "Allow users to delete own assessments"
|
||||
on public.assessments
|
||||
for delete using (auth.uid() = user_id);
|
||||
|
||||
-- Helpful index
|
||||
create index if not exists idx_assessments_user_id_created_at
|
||||
on public.assessments (user_id, created_at desc);
|
||||
233
supabase/migrations/20260301000000_training_plans.sql
Normal file
233
supabase/migrations/20260301000000_training_plans.sql
Normal file
@@ -0,0 +1,233 @@
|
||||
-- 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;
|
||||
136
supabase/migrations/20260321000000_assessment_shares.sql
Normal file
136
supabase/migrations/20260321000000_assessment_shares.sql
Normal file
@@ -0,0 +1,136 @@
|
||||
-- Token-based sharing for individual assessments
|
||||
create table if not exists public.assessment_shares (
|
||||
assessment_id uuid primary key references public.assessments(id) on delete cascade,
|
||||
user_id uuid not null references auth.users(id) on delete cascade,
|
||||
share_token text not null unique,
|
||||
created_at timestamptz not null default now(),
|
||||
updated_at timestamptz not null default now()
|
||||
);
|
||||
|
||||
drop trigger if exists trg_assessment_shares_updated_at on public.assessment_shares;
|
||||
create trigger trg_assessment_shares_updated_at
|
||||
before update on public.assessment_shares
|
||||
for each row execute function public.set_updated_at();
|
||||
|
||||
alter table public.assessment_shares enable row level security;
|
||||
|
||||
drop policy if exists "Allow users to insert own assessment shares" on public.assessment_shares;
|
||||
create policy "Allow users to insert own assessment shares"
|
||||
on public.assessment_shares
|
||||
for insert
|
||||
with check (
|
||||
auth.uid() = user_id
|
||||
and exists (
|
||||
select 1
|
||||
from public.assessments a
|
||||
where a.id = assessment_shares.assessment_id
|
||||
and a.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
drop policy if exists "Allow users to select own assessment shares" on public.assessment_shares;
|
||||
create policy "Allow users to select own assessment shares"
|
||||
on public.assessment_shares
|
||||
for select
|
||||
using (auth.uid() = user_id);
|
||||
|
||||
drop policy if exists "Allow users to update own assessment shares" on public.assessment_shares;
|
||||
create policy "Allow users to update own assessment shares"
|
||||
on public.assessment_shares
|
||||
for update
|
||||
using (auth.uid() = user_id)
|
||||
with check (
|
||||
auth.uid() = user_id
|
||||
and exists (
|
||||
select 1
|
||||
from public.assessments a
|
||||
where a.id = assessment_shares.assessment_id
|
||||
and a.user_id = auth.uid()
|
||||
)
|
||||
);
|
||||
|
||||
drop policy if exists "Allow users to delete own assessment shares" on public.assessment_shares;
|
||||
create policy "Allow users to delete own assessment shares"
|
||||
on public.assessment_shares
|
||||
for delete
|
||||
using (auth.uid() = user_id);
|
||||
|
||||
create index if not exists idx_assessment_shares_user_id
|
||||
on public.assessment_shares(user_id);
|
||||
|
||||
create or replace function public.generate_share_token()
|
||||
returns text
|
||||
language sql
|
||||
as $$
|
||||
select replace(gen_random_uuid()::text, '-', '') || replace(gen_random_uuid()::text, '-', '');
|
||||
$$;
|
||||
|
||||
create or replace function public.create_or_get_assessment_share(p_assessment_id uuid)
|
||||
returns table (
|
||||
assessment_id uuid,
|
||||
share_token text
|
||||
)
|
||||
language plpgsql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
declare
|
||||
v_user_id uuid;
|
||||
begin
|
||||
v_user_id := auth.uid();
|
||||
|
||||
if v_user_id is null then
|
||||
raise exception 'Not authenticated';
|
||||
end if;
|
||||
|
||||
if not exists (
|
||||
select 1
|
||||
from public.assessments a
|
||||
where a.id = p_assessment_id
|
||||
and a.user_id = v_user_id
|
||||
) then
|
||||
raise exception 'Assessment not found';
|
||||
end if;
|
||||
|
||||
insert into public.assessment_shares (assessment_id, user_id, share_token)
|
||||
values (p_assessment_id, v_user_id, public.generate_share_token())
|
||||
on conflict on constraint assessment_shares_pkey do nothing;
|
||||
|
||||
return query
|
||||
select s.assessment_id, s.share_token
|
||||
from public.assessment_shares s
|
||||
where s.assessment_id = p_assessment_id
|
||||
and s.user_id = v_user_id
|
||||
limit 1;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function public.get_shared_assessment(p_share_token text)
|
||||
returns table (
|
||||
id uuid,
|
||||
pat_type text,
|
||||
datum date,
|
||||
name text,
|
||||
exercises jsonb
|
||||
)
|
||||
language sql
|
||||
security definer
|
||||
set search_path = public
|
||||
as $$
|
||||
select
|
||||
a.id,
|
||||
a.pat_type,
|
||||
a.datum,
|
||||
a.name,
|
||||
a.exercises
|
||||
from public.assessment_shares s
|
||||
join public.assessments a on a.id = s.assessment_id
|
||||
where s.share_token = p_share_token
|
||||
limit 1;
|
||||
$$;
|
||||
|
||||
revoke all on function public.create_or_get_assessment_share(uuid) from public;
|
||||
grant execute on function public.create_or_get_assessment_share(uuid) to authenticated;
|
||||
|
||||
revoke all on function public.get_shared_assessment(text) from public;
|
||||
grant execute on function public.get_shared_assessment(text) to anon, authenticated;
|
||||
@@ -0,0 +1,45 @@
|
||||
alter table public.assessments
|
||||
add column if not exists is_finalized boolean not null default false,
|
||||
add column if not exists finalized_at timestamptz,
|
||||
add column if not exists finalization_reminder_sent_at timestamptz;
|
||||
|
||||
create index if not exists idx_assessments_user_id_finalization
|
||||
on public.assessments (user_id, is_finalized, created_at desc);
|
||||
|
||||
create index if not exists idx_assessments_open_finalization_reminders
|
||||
on public.assessments (created_at)
|
||||
where is_finalized = false and finalization_reminder_sent_at is null;
|
||||
|
||||
create or replace function public.guard_assessment_finalization()
|
||||
returns trigger as $$
|
||||
begin
|
||||
if tg_op = 'UPDATE' and old.is_finalized then
|
||||
if new.pat_type is distinct from old.pat_type
|
||||
or new.datum is distinct from old.datum
|
||||
or new.name is distinct from old.name
|
||||
or new.exercises is distinct from old.exercises
|
||||
or new.is_finalized is distinct from old.is_finalized
|
||||
or new.finalized_at is distinct from old.finalized_at then
|
||||
raise exception 'Finalisierte Bewertungen koennen nicht mehr geaendert werden.';
|
||||
end if;
|
||||
end if;
|
||||
|
||||
if new.is_finalized then
|
||||
if tg_op = 'INSERT' then
|
||||
new.finalized_at := coalesce(new.finalized_at, now());
|
||||
else
|
||||
new.finalized_at := coalesce(new.finalized_at, old.finalized_at, now());
|
||||
end if;
|
||||
new.finalization_reminder_sent_at := null;
|
||||
else
|
||||
new.finalized_at := null;
|
||||
end if;
|
||||
|
||||
return new;
|
||||
end;
|
||||
$$ language plpgsql;
|
||||
|
||||
drop trigger if exists trg_assessments_finalization_guard on public.assessments;
|
||||
create trigger trg_assessments_finalization_guard
|
||||
before insert or update on public.assessments
|
||||
for each row execute function public.guard_assessment_finalization();
|
||||
Reference in New Issue
Block a user