Download supabase/migrations/20261031193200_math_answer_grading.sql from wzender/mathly: direct link, hf CLI and curl.
- Browser
- Download file 4.7 kB
-
https://huggingface.co/spaces/wzender/mathly/resolve/main/supabase/migrations/20261031193200_math_answer_grading.sql
- Command line
-
hf download hf://spaces/wzender/mathly/supabase/migrations/20261031193200_math_answer_grading.sql
-
curl -L -o 20261031193200_math_answer_grading.sql https://huggingface.co/spaces/wzender/mathly/resolve/main/supabase/migrations/20261031193200_math_answer_grading.sql
4.7 kB
| -- Durable, owner-scoped checks of saved photos. No existing answers are changed. | |
| begin; | |
| create table public.math_answer_grades ( | |
| id uuid primary key default gen_random_uuid(), | |
| user_id uuid not null references auth.users(id) on delete cascade, | |
| attempt_id uuid not null, | |
| attachment_id uuid not null references public.math_answer_attachments(id) on delete cascade, | |
| state text not null check (state in ('grading','completed','failed')), | |
| lease_id uuid not null, | |
| lease_until timestamptz not null, | |
| result jsonb, | |
| error text not null default '', | |
| created_at timestamptz not null default clock_timestamp(), | |
| finished_at timestamptz, | |
| foreign key (attempt_id,user_id) references public.math_question_attempts(id,user_id) on delete cascade, | |
| check ((state='completed') = (result is not null)), | |
| check (result is null or (jsonb_typeof(result)='object' and result ? 'verdict' | |
| and result->>'verdict' in ('correct','partial','incorrect','unreadable','needs_review'))) | |
| ); | |
| create index math_grade_attempt on public.math_answer_grades(attempt_id,created_at desc,id desc); | |
| create index math_grade_daily on public.math_answer_grades(user_id,created_at); | |
| create unique index math_grade_one_running on public.math_answer_grades(attachment_id) where state='grading'; | |
| alter table public.math_answer_grades enable row level security; | |
| revoke all on public.math_answer_grades from public,anon,authenticated; | |
| -- Read through the owner-checked backend; coordination tokens remain server-only. | |
| grant all on public.math_answer_grades to service_role; | |
| create function public.math_grade_action(p_user uuid,p_action text,p_data jsonb default '{}') | |
| returns jsonb language plpgsql security invoker set search_path='' as $$ | |
| declare | |
| a public.math_question_attempts; | |
| att public.math_answer_attachments; | |
| g public.math_answer_grades; | |
| t timestamptz; | |
| begin | |
| if p_user is null then raise exception 'not_found' using errcode='P0002'; end if; | |
| if p_action='start' then | |
| -- Serialize quota checks across this account, including distinct attempts/workers. | |
| perform pg_catalog.pg_advisory_xact_lock(pg_catalog.hashtextextended('grading:'||p_user::text,0)); | |
| end if; | |
| select * into a from public.math_question_attempts | |
| where id=(p_data->>'attempt_id')::uuid and user_id=p_user for update; | |
| if a.id is null then raise exception 'not_found' using errcode='P0002'; end if; | |
| select * into att from public.math_answer_attachments where attempt_id=a.id and user_id=p_user; | |
| select * into g from public.math_answer_grades where attempt_id=a.id | |
| order by created_at desc,id desc limit 1 for update; | |
| t := clock_timestamp(); | |
| if g.state='grading' and g.lease_until<=t then | |
| update public.math_answer_grades set state='failed',error='grading_interrupted',finished_at=t | |
| where id=g.id returning * into g; | |
| end if; | |
| if p_action='start' then | |
| if att.id is null then raise exception 'answer_required' using errcode='P0001'; end if; | |
| -- Repeated clicks and lost responses reuse a running or completed check. | |
| if g.id is null or g.state='failed' then | |
| if (select count(*) from public.math_answer_grades where user_id=p_user | |
| and created_at >= ((t at time zone 'Asia/Jerusalem')::date::timestamp at time zone 'Asia/Jerusalem')) >= 20 then | |
| raise exception 'grading_limit' using errcode='P0001'; | |
| end if; | |
| if (select count(*) from public.math_answer_grades where user_id=p_user | |
| and state='grading' and lease_until>t) >= 2 then | |
| raise exception 'grading_busy' using errcode='P0001'; | |
| end if; | |
| insert into public.math_answer_grades(user_id,attempt_id,attachment_id,state,lease_id,lease_until) | |
| values(p_user,a.id,att.id,'grading',(p_data->>'lease_id')::uuid,t+interval '5 minutes') returning * into g; | |
| end if; | |
| elsif p_action in ('complete','fail') then | |
| if g.state is distinct from 'grading' or g.lease_id is distinct from (p_data->>'lease_id')::uuid then | |
| raise exception 'grading_interrupted' using errcode='P0001'; | |
| end if; | |
| if p_action='complete' then | |
| update public.math_answer_grades set state='completed',result=p_data->'result',finished_at=t,error='' | |
| where id=g.id returning * into g; | |
| else | |
| update public.math_answer_grades set state='failed',finished_at=t,error='grading_failed' | |
| where id=g.id returning * into g; | |
| end if; | |
| elsif p_action<>'get' then | |
| raise exception 'invalid_action'; | |
| end if; | |
| return case when g.id is null then null else to_jsonb(g) end; | |
| end $$; | |
| revoke all on function public.math_grade_action(uuid,text,jsonb) from public,anon,authenticated; | |
| grant execute on function public.math_grade_action(uuid,text,jsonb) to service_role; | |
| commit; | |