Skip to content

Commit 5906595

Browse files
Restructure practice tests and clean up progress tracking
This commit restructures how practice test questions are stored by moving them from a JSONB column in the `practice_tests` table into a dedicated `practice_test_questions` table. This allows for better individual question querying and updating. Key changes: - Database migration to create the new `practice_test_questions` table and migrate existing JSONB data. - Updated GraphQL schema: - `TermATP` snapshots now use `termSnapshot` and `defSnapshot`. - Added `id` to the `Question` type. - Replaced `updatePracticeTest` with `updatePracticeTestQuestion` to allow targeted FRQ updates. - Removed all references to the unused Leitner system and term progress history. - Updated mutations and resolvers: - `RecordPracticeTest` now populates the new questions table. - `UpdatePracticeTestQuestion` implements manual FRQ correctness overrides and synchronizes practice test accuracy and term progress stats. - `PracticeTest.Questions` resolver now fetches and reconstructs questions from the database. - Cleanup of obsolete dataloaders, mapping tables, and models. - Updated integration tests to match the new schema and logic.
1 parent 4fd2fb6 commit 5906595

15 files changed

Lines changed: 669 additions & 1271 deletions
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
-- migrate:up
2+
3+
CREATE TYPE public.question_type AS ENUM ('MCQ', 'TFQ', 'FRQ');
4+
5+
CREATE TABLE public.practice_test_questions (
6+
id uuid DEFAULT gen_random_uuid() NOT NULL PRIMARY KEY,
7+
practice_test_id uuid NOT NULL REFERENCES public.practice_tests(id) ON DELETE CASCADE,
8+
term_id uuid NOT NULL REFERENCES public.terms(id) ON DELETE CASCADE,
9+
term_snapshot text NOT NULL,
10+
def_snapshot text NOT NULL,
11+
type public.question_type NOT NULL,
12+
answer_with public.answer_with_enum NOT NULL,
13+
correct boolean NOT NULL,
14+
position integer NOT NULL,
15+
data jsonb NOT NULL,
16+
UNIQUE (practice_test_id, position)
17+
);
18+
19+
-- Migrate data
20+
INSERT INTO public.practice_test_questions (
21+
practice_test_id, term_id, term_snapshot, def_snapshot, type, answer_with, correct, position, data
22+
)
23+
SELECT
24+
pt.id,
25+
(q->COALESCE(sub.type_lower, 'mcq')->'term'->>'id')::uuid,
26+
q->COALESCE(sub.type_lower, 'mcq')->'term'->>'term',
27+
q->COALESCE(sub.type_lower, 'mcq')->'term'->>'def',
28+
sub.type_upper,
29+
(q->COALESCE(sub.type_lower, 'mcq')->>'answerWith')::public.answer_with_enum,
30+
CASE
31+
WHEN sub.type_upper = 'FRQ' THEN
32+
COALESCE((q->'frq'->>'correct')::boolean, false) OR COALESCE((q->'frq'->>'userMarkedCorrect')::boolean, false)
33+
ELSE
34+
COALESCE((q->sub.type_lower->>'correct')::boolean, false)
35+
END,
36+
pos::int - 1,
37+
CASE
38+
WHEN sub.type_upper = 'MCQ' THEN
39+
jsonb_build_object(
40+
'distractors', (
41+
SELECT jsonb_agg(
42+
jsonb_build_object(
43+
'id', d->>'id',
44+
'termSnapshot', d->>'term',
45+
'defSnapshot', d->>'def'
46+
)
47+
)
48+
FROM jsonb_array_elements(q->'mcq'->'distractors') d
49+
),
50+
'correctChoiceIndex', (q->'mcq'->>'correctChoiceIndex')::int,
51+
'answeredIndex', (q->'mcq'->>'answeredIndex')::int
52+
)
53+
WHEN sub.type_upper = 'TFQ' THEN
54+
jsonb_build_object(
55+
'answeredBool', (q->'tfq'->>'answeredBool')::boolean,
56+
'distractor', CASE
57+
WHEN q->'tfq'->'distractor' IS NOT NULL AND q->'tfq'->'distractor' != 'null'::jsonb THEN
58+
jsonb_build_object(
59+
'id', q->'tfq'->'distractor'->>'id',
60+
'termSnapshot', q->'tfq'->'distractor'->>'term',
61+
'defSnapshot', q->'tfq'->'distractor'->>'def'
62+
)
63+
ELSE NULL
64+
END
65+
)
66+
WHEN sub.type_upper = 'FRQ' THEN
67+
jsonb_build_object(
68+
'answeredString', q->'frq'->>'answeredString',
69+
'userMarkedCorrect', COALESCE((q->'frq'->>'userMarkedCorrect')::boolean, false)
70+
)
71+
END
72+
FROM public.practice_tests pt
73+
CROSS JOIN LATERAL jsonb_array_elements(pt.questions) WITH ORDINALITY AS q_arr(q, pos)
74+
CROSS JOIN LATERAL (
75+
SELECT
76+
CASE
77+
WHEN q ? 'mcq' THEN 'mcq'
78+
WHEN q ? 'tfq' THEN 'tfq'
79+
WHEN q ? 'frq' THEN 'frq'
80+
END as type_lower,
81+
CASE
82+
WHEN q ? 'mcq' THEN 'MCQ'::public.question_type
83+
WHEN q ? 'tfq' THEN 'TFQ'::public.question_type
84+
WHEN q ? 'frq' THEN 'FRQ'::public.question_type
85+
END as type_upper
86+
) sub;
87+
88+
ALTER TABLE public.practice_tests DROP COLUMN questions;
89+
DROP TABLE IF EXISTS public.practice_test_question_terms;
90+
DROP TABLE IF EXISTS public.practice_test_distractor_terms;
91+
DROP TABLE IF EXISTS public.term_progress_history;
92+
ALTER TABLE public.term_progress DROP COLUMN IF EXISTS term_leitner_system_box;
93+
ALTER TABLE public.term_progress DROP COLUMN IF EXISTS def_leitner_system_box;
94+
95+
GRANT SELECT, INSERT, UPDATE, DELETE ON public.practice_test_questions TO quizfreely_api;
96+
97+
-- migrate:down
98+
DROP TABLE public.practice_test_questions;
99+
DROP TYPE public.question_type;

0 commit comments

Comments
 (0)