Skip to content
Open
Show file tree
Hide file tree
Changes from 64 commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
6297c2f
update docs
leoraba Dec 12, 2025
74d088a
database changes
leoraba Jan 9, 2026
28f683a
update dbml
leoraba Jan 9, 2026
e1ada63
migration services
leoraba Jan 9, 2026
c1f87dd
updates migration repository
leoraba Jan 13, 2026
118fc42
Merge branch 'feat/dictionary_migration_db' into feat/migration_imple…
leoraba Jan 13, 2026
0a26540
update migration service
leoraba Jan 15, 2026
1ec4dc4
data validation
leoraba Jan 30, 2026
afc4f87
refactor migration process
leoraba Feb 3, 2026
58d4ad8
block commit submission
leoraba Feb 3, 2026
b081dd9
fix sort imports
leoraba Feb 3, 2026
3c4d846
Merge branch 'main' into feat/dictionary_migration
leoraba Feb 3, 2026
e6dc14d
Merge branch 'feat/dictionary_migration' into feat/dictionary_migrati…
leoraba Feb 3, 2026
608c096
Merge branch 'feat/dictionary_migration_db' into feat/migration_imple…
leoraba Feb 3, 2026
de3c362
Merge branch 'main' into feat/dictionary_migration
leoraba Apr 1, 2026
b23d648
Merge branch 'feat/dictionary_migration' into feat/dictionary_migrati…
leoraba Apr 1, 2026
8688240
update docs
leoraba Apr 2, 2026
2487bbe
migration table index
leoraba Apr 7, 2026
96b6c63
Merge branch 'feat/dictionary_migration_db' into feat/migration_imple…
leoraba Apr 10, 2026
d351b2e
fix typos and logs
leoraba Apr 13, 2026
fb05263
migration audit and logs
leoraba Apr 13, 2026
b5d6756
migration on worker thread
leoraba Apr 14, 2026
f5af236
GET migration endpoints
leoraba Apr 15, 2026
d7d17d0
fix unit tests
leoraba Apr 15, 2026
2d05348
Merge branch 'feat/migration_implementation' into feat/get_migration_…
leoraba Apr 15, 2026
2758202
GET migration records
leoraba Apr 16, 2026
b8cd961
adding ts config noUncheckedIndexedAccess
leoraba Apr 17, 2026
cdf32cc
rename migration enum status IN_PROGRESS
leoraba Apr 17, 2026
2c889d1
Merge branch 'feat/dictionary_migration_db' into feat/migration_imple…
leoraba Apr 17, 2026
430ea27
Merge branch 'feat/migration_implementation' into feat/get_migration_…
leoraba Apr 17, 2026
6916fac
Dictionary migration - Part 1 - Database changes (#182)
leoraba Apr 27, 2026
c512349
Merge branch 'feat/dictionary_migration' into feat/migration_implemen…
leoraba Apr 27, 2026
f5994eb
refactoring migration function with Result
leoraba Apr 28, 2026
280395f
Merge branch 'feat/migration_implementation' into feat/get_migration_…
leoraba Apr 29, 2026
c682a47
default constants pagination
leoraba Apr 29, 2026
b41d1ad
change logs and response NotFound
leoraba Apr 29, 2026
6f231ce
fix swagger docs
leoraba Apr 29, 2026
4f7a01b
include errors or migration changes
leoraba Apr 30, 2026
57cf05b
type paginated result
leoraba Apr 30, 2026
f900e72
Dictionary migration - Part 2 - Execution migration (#183)
leoraba Apr 30, 2026
8f69160
Merge branch 'feat/dictionary_migration' into feat/get_migration_endp…
leoraba Apr 30, 2026
a1c4d8f
using type paginated result
leoraba Apr 30, 2026
e588d0b
unit test migration formatter functions
leoraba Apr 30, 2026
d8b930d
remove unhandled error thrown
leoraba May 4, 2026
a7cfcf1
retry dictionary registration
leoraba May 4, 2026
a2b05e8
updating docs
leoraba May 4, 2026
0acab7b
fix integration tests
leoraba May 4, 2026
23a3f96
Dictionary Migration - Get migration endpoints (#200)
leoraba May 12, 2026
f28c9aa
Merge branch 'feat/dictionary_migration' into feat/retry_failed_migra…
leoraba May 12, 2026
feb65f5
update documentation
leoraba May 29, 2026
ec4a580
DRY refactor
leoraba May 29, 2026
4caeec8
fix accumulate submission records test
leoraba May 29, 2026
6512cee
shutdown test provider
leoraba Jun 1, 2026
b296421
create dictionary registration test
leoraba Jun 1, 2026
91948d7
dictionary migration tests
leoraba Jun 2, 2026
755b2ec
tsdocs
leoraba Jun 2, 2026
deac90f
pnpm allow build config
leoraba Jun 3, 2026
a353934
Merge branch 'main' into feat/dictionary_migration
leoraba Jun 3, 2026
7bc39c6
Merge branch 'feat/dictionary_migration' into feat/retry_failed_migra…
leoraba Jun 3, 2026
073f453
Merge branch 'feat/retry_failed_migrations' into feat/interation-test…
leoraba Jun 3, 2026
9397f1f
add Drizzle migration scripts
leoraba Jun 9, 2026
56ea702
Update README.md
leoraba Jun 9, 2026
8474fc6
fix custom script for file_name and file_size
leoraba Jun 9, 2026
fada6b2
Merge branch 'main' into feat/submission_records_separation
leoraba Jul 16, 2026
546c0d0
Merge branch 'main' into feat/submission_records_separation
leoraba Aug 10, 2026
7cb7cc7
Merge branch 'main' into feat/submission_records_separation
leoraba Aug 13, 2026
dc25b57
calculate text size in bytes
leoraba Aug 14, 2026
a94157a
file name submissions
leoraba Aug 14, 2026
8f065bb
Merge branch 'main' into feat/submission_records_separation
leoraba Aug 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions packages/data-model/docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,36 @@ To view the State diagram for submission status [click here](./stateDiagramSubmi
To view Submission commit workflow [click here](./submissionCommit.md)


### - `submission_files`
Stores file-level metadata for each submission. A submission can have multiple files associated with it.

Key fields in the submission_files table include:

- `submission_id`: Foreign key to `submissions.id`.

- `file_name`: Name of the file or batch associated with the submission.

- `entity_name`: Entity represented by the file.

- `file_size`: File size in bytes


### - `submission_records`
Stores record-level entries for each submission file.

Key fields in the submission_records table include:

- `file_id`: Foreign key to `submission_files.id`.

- `data`: JSON payload for the record operation.

- `action_type`: Record action and can include `INSERT`, `UPDATE`, or `DELETE`.

- `errors`: JSON array of validation or processing errors for the record.

- `state`: Record state and can include `RECEIVED`, `VALID`, or `INVALID`.


### - `submitted_data`
Stores individual data entries within a submission, capturing their validation status and relationships to specific schemas.

Expand Down
41 changes: 41 additions & 0 deletions packages/data-model/docs/schema.dbml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,18 @@ enum migration_status {
FAILED
}

enum submission_record_state {
RECEIVED
VALID
INVALID
}

enum submission_record_type {
INSERT
UPDATE
DELETE
}

enum submission_status {
OPEN
VALIDATING
Expand Down Expand Up @@ -85,6 +97,31 @@ table dictionary_migration {
}
}

table submission_files {
id serial [pk, not null, increment]
submission_id integer [not null]
file_name varchar [not null]
entity_name varchar [not null]
file_size integer [not null]

indexes {
submission_id [name: 'submission_files_submission_id_index']
}
}

table submission_records {
id serial [pk, not null, increment]
file_id integer [not null]
data jsonb [not null]
action_type submission_record_type [not null]
errors jsonb
state submission_record_state [not null]

indexes {
file_id [name: 'submission_records_file_id_index']
}
}

table submissions {
id serial [pk, not null, increment]
data jsonb [not null]
Expand Down Expand Up @@ -146,6 +183,10 @@ ref: dictionary_migration.to_dictionary_id - dictionaries.id

ref: dictionary_migration.submission_id - submissions.id

ref: submission_files.submission_id - submissions.id

ref: submission_records.file_id - submission_files.id

ref: submissions.dictionary_id - dictionaries.id

ref: submissions.dictionary_category_id > dictionary_categories.id
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
DO $$ BEGIN
CREATE TYPE "submission_record_state" AS ENUM('RECEIVED', 'VALID', 'INVALID');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
CREATE TYPE "submission_record_type" AS ENUM('INSERT', 'UPDATE', 'DELETE');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "submission_files" (
"id" serial PRIMARY KEY NOT NULL,
"submission_id" integer NOT NULL,
"file_name" varchar NOT NULL,
"entity_name" varchar NOT NULL,
"file_size" integer NOT NULL
);
--> statement-breakpoint
CREATE TABLE IF NOT EXISTS "submission_records" (
"id" serial PRIMARY KEY NOT NULL,
"file_id" integer NOT NULL,
"data" jsonb NOT NULL,
"action_type" "submission_record_type" NOT NULL,
"errors" jsonb,
"state" "submission_record_state" NOT NULL
);
--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "submission_files_submission_id_index" ON "submission_files" ("submission_id");--> statement-breakpoint
CREATE INDEX IF NOT EXISTS "submission_records_file_id_index" ON "submission_records" ("file_id");--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "submission_files" ADD CONSTRAINT "submission_files_submission_id_submissions_id_fk" FOREIGN KEY ("submission_id") REFERENCES "submissions"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
--> statement-breakpoint
DO $$ BEGIN
ALTER TABLE "submission_records" ADD CONSTRAINT "submission_records_file_id_submission_files_id_fk" FOREIGN KEY ("file_id") REFERENCES "submission_files"("id") ON DELETE no action ON UPDATE no action;
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
/**
Migrates legacy submission JSON payloads into submission_files and submission_records.

Assumptions:
- Migration 0014_create_submission_records_table has already run.
- submissions.data shape:
inserts: { entity: { batchName, records: [...] } }
updates: { entity: [...] }
deletes: { entity: [...] }
- submissions.errors shape:
inserts: { entity: [{ index, reason, fieldName, fieldValue }] }
updates: { entity: [...] }
deletes: { entity: [...] }
- Each item in data.{inserts|updates|deletes}.{entity}.records[] maps to one
row in submission_records.
- A single record index may have multiple errors; they are aggregated into
submission_records.errors as a JSON array per file/entity/index.
*/

WITH
-- Step 1: choose submissions that needs migration.
target_submissions AS (
SELECT
s.id,
s.status,
s.data,
s.errors
FROM submissions s
),

-- Step 2a: normalize INSERT buckets to a common shape.
insert_buckets AS (
SELECT
ts.id AS submission_id,
ts.status AS submission_status,
ts.errors AS submission_errors,
'INSERT'::text AS action_type,
'inserts'::text AS error_bucket,
ins.key AS entity_name,
format('inserts_%s', COALESCE(NULLIF(ins.value ->> 'batchName', ''), ins.key)) AS file_name,
CASE
WHEN jsonb_typeof(ins.value) = 'array' THEN ins.value
ELSE COALESCE(ins.value -> 'records', '[]'::jsonb)
END AS records
FROM target_submissions ts
CROSS JOIN LATERAL jsonb_each(COALESCE(ts.data -> 'inserts', '{}'::jsonb)) AS ins(key, value)
WHERE jsonb_typeof(
CASE
WHEN jsonb_typeof(ins.value) = 'array' THEN ins.value
ELSE COALESCE(ins.value -> 'records', '[]'::jsonb)
END
) = 'array'
),

-- Step 2b: normalize UPDATE buckets to the same shape.
update_buckets AS (
SELECT
ts.id AS submission_id,
ts.status AS submission_status,
ts.errors AS submission_errors,
'UPDATE'::text AS action_type,
'updates'::text AS error_bucket,
upd.key AS entity_name,
format('updates_%s', upd.key) AS file_name,
CASE
WHEN jsonb_typeof(upd.value) = 'array' THEN upd.value
ELSE COALESCE(upd.value -> 'records', '[]'::jsonb)
END AS records
FROM target_submissions ts
CROSS JOIN LATERAL jsonb_each(COALESCE(ts.data -> 'updates', '{}'::jsonb)) AS upd(key, value)
WHERE jsonb_typeof(
CASE
WHEN jsonb_typeof(upd.value) = 'array' THEN upd.value
ELSE COALESCE(upd.value -> 'records', '[]'::jsonb)
END
) = 'array'
),

-- Step 2c: normalize DELETE buckets to the same shape.
delete_buckets AS (
SELECT
ts.id AS submission_id,
ts.status AS submission_status,
ts.errors AS submission_errors,
'DELETE'::text AS action_type,
'deletes'::text AS error_bucket,
del.key AS entity_name,
format('deletes_%s', del.key) AS file_name,
CASE
WHEN jsonb_typeof(del.value) = 'array' THEN del.value
ELSE COALESCE(del.value -> 'records', '[]'::jsonb)
END AS records
FROM target_submissions ts
CROSS JOIN LATERAL jsonb_each(COALESCE(ts.data -> 'deletes', '{}'::jsonb)) AS del(key, value)
WHERE jsonb_typeof(
CASE
WHEN jsonb_typeof(del.value) = 'array' THEN del.value
ELSE COALESCE(del.value -> 'records', '[]'::jsonb)
END
) = 'array'
),

-- Step 3: union all action buckets.
source_submission_files AS (
SELECT * FROM insert_buckets
UNION ALL
SELECT * FROM update_buckets
UNION ALL
SELECT * FROM delete_buckets
),

-- Step 4: create one submission_files row per action/entity bucket.
inserted_submission_files AS (
INSERT INTO submission_files (submission_id, file_name, entity_name, file_size)
SELECT
submission_id,
file_name,
entity_name,
0 AS file_size
FROM source_submission_files
RETURNING id, submission_id, file_name, entity_name
),

-- Step 5: map each normalized bucket to its inserted file_id.
file_map AS (
SELECT
ssf.submission_status,
ssf.submission_errors,
ssf.action_type,
ssf.error_bucket,
ssf.entity_name,
ssf.records,
isf.id AS file_id
FROM source_submission_files ssf
INNER JOIN inserted_submission_files isf
ON isf.submission_id = ssf.submission_id
AND isf.file_name = ssf.file_name
AND isf.entity_name = ssf.entity_name
),

-- Step 6: explode each file's records array and keep record index.
expanded_records AS (
SELECT
fm.file_id,
fm.submission_status,
fm.action_type,
fm.error_bucket,
fm.entity_name,
rec.value AS record_data,
(rec.ordinality - 1)::integer AS record_index
FROM file_map fm
CROSS JOIN LATERAL jsonb_array_elements(fm.records) WITH ORDINALITY AS rec(value, ordinality)
),

-- Step 7: explode errors once, keyed by file + record index.
expanded_errors AS (
SELECT
fm.file_id,
fm.error_bucket,
fm.entity_name,
(err.value ->> 'index')::integer AS record_index,
(err.value - 'index') AS error_data
FROM file_map fm
CROSS JOIN LATERAL jsonb_array_elements(
COALESCE(fm.submission_errors -> fm.error_bucket -> fm.entity_name, '[]'::jsonb)
) AS err(value)
),

-- Step 7b: group all errors for the same record index into a JSON array.
aggregated_errors AS (
SELECT
ee.file_id,
ee.error_bucket,
ee.entity_name,
ee.record_index,
jsonb_agg(ee.error_data) AS error_data
FROM expanded_errors ee
GROUP BY ee.file_id, ee.error_bucket, ee.entity_name, ee.record_index
)

-- Step 8: insert records with mapped errors and derived state.
INSERT INTO submission_records (file_id, data, action_type, errors, state)
SELECT
er.file_id,
er.record_data AS data,
er.action_type::submission_record_type,
ae.error_data,
CASE
WHEN er.submission_status IN ('OPEN', 'VALIDATING') THEN 'RECEIVED'::submission_record_state
WHEN er.submission_status = 'INVALID' OR ae.error_data IS NOT NULL THEN 'INVALID'::submission_record_state
ELSE 'VALID'::submission_record_state
END AS state
FROM expanded_records er
LEFT JOIN aggregated_errors ae
ON ae.file_id = er.file_id
AND ae.error_bucket = er.error_bucket
AND ae.entity_name = er.entity_name
AND ae.record_index = er.record_index;
Loading