Skip to content

Commit 2f4f018

Browse files
committed
fix: applied DB row type / domain type / mapper pattern also to ropa.services.ts
1 parent 5b501bf commit 2f4f018

3 files changed

Lines changed: 93 additions & 54 deletions

File tree

packages/backend/src/db/mappers.ts

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import { BpmnRow, FormRow, DocumentRow } from './types';
1+
import { BpmnRow, FormRow, DocumentRow, RopaRecordRow, RopaFieldRow } from './types';
22
import { Bpmn, Form, Document } from '../domain/types';
3+
import { RopaRecord, RopaPersonalDataField } from '../types/ropa.types';
34

45
export function mapBpmn(row: BpmnRow): Bpmn {
56
return {
@@ -49,3 +50,46 @@ export function mapDocument(r: DocumentRow): Document {
4950
readonly: false,
5051
};
5152
}
53+
54+
export function mapRopaRecord(
55+
row: RopaRecordRow,
56+
fields: RopaPersonalDataField[]
57+
): RopaRecord {
58+
return {
59+
id: row.id,
60+
bpmnProcessId: row.bpmn_process_id,
61+
processLevel: row.process_level as RopaRecord['processLevel'],
62+
title: row.title,
63+
controllerName: row.controller_name,
64+
controllerContact: row.controller_contact,
65+
dpoContact: row.dpo_contact ?? undefined,
66+
purpose: row.purpose,
67+
legalBasisUri: row.legal_basis_uri,
68+
legalBasisLabel: row.legal_basis_label,
69+
gdprArticle: row.gdpr_article,
70+
dataSubjects: row.data_subjects,
71+
recipients: row.recipients,
72+
thirdCountryTransfers: row.third_country_transfers,
73+
thirdCountryDetails: row.third_country_details ?? undefined,
74+
retentionPeriod: row.retention_period,
75+
securityMeasures: row.security_measures,
76+
status: row.status as RopaRecord['status'],
77+
schemaVersion: row.schema_version,
78+
personalDataFields: fields,
79+
createdAt: row.created_at.toISOString(),
80+
updatedAt: row.updated_at.toISOString(),
81+
};
82+
}
83+
84+
export function mapRopaField(row: RopaFieldRow): RopaPersonalDataField {
85+
return {
86+
id: row.id,
87+
ropaRecordId: row.ropa_record_id,
88+
formId: row.form_id,
89+
fieldKey: row.field_key,
90+
fieldLabel: row.field_label,
91+
dataCategory: row.data_category,
92+
specialCategory: row.special_category,
93+
sortOrder: row.sort_order,
94+
};
95+
}

packages/backend/src/db/types.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,3 +38,38 @@ export type DocumentRow = {
3838
created_at: Date;
3939
updated_at: Date;
4040
};
41+
42+
export type RopaRecordRow = {
43+
id: string;
44+
bpmn_process_id: string;
45+
process_level: string;
46+
title: string;
47+
controller_name: string;
48+
controller_contact: string;
49+
dpo_contact: string | null;
50+
purpose: string;
51+
legal_basis_uri: string;
52+
legal_basis_label: string;
53+
gdpr_article: string;
54+
data_subjects: string;
55+
recipients: string;
56+
third_country_transfers: boolean;
57+
third_country_details: string | null;
58+
retention_period: string;
59+
security_measures: string;
60+
status: string;
61+
schema_version: number;
62+
created_at: Date;
63+
updated_at: Date;
64+
};
65+
66+
export type RopaFieldRow = {
67+
id: string;
68+
ropa_record_id: string;
69+
form_id: string;
70+
field_key: string;
71+
field_label: string;
72+
data_category: string;
73+
special_category: boolean;
74+
sort_order: number;
75+
};

packages/backend/src/services/ropa.service.ts

Lines changed: 13 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1,81 +1,41 @@
11
import pool from '../db/pool';
2-
import { RopaRecord, RopaPersonalDataField, PublicRopaRecord } from '../types/ropa.types';
3-
4-
// ─── Mapping helpers ──────────────────────────────────────────────────────────
5-
6-
function rowToRecord(r: Record<string, unknown>, fields: RopaPersonalDataField[]): RopaRecord {
7-
return {
8-
id: r.id as string,
9-
bpmnProcessId: r.bpmn_process_id as string,
10-
processLevel: r.process_level as RopaRecord['processLevel'],
11-
title: r.title as string,
12-
controllerName: r.controller_name as string,
13-
controllerContact: r.controller_contact as string,
14-
dpoContact: (r.dpo_contact as string) ?? undefined,
15-
purpose: r.purpose as string,
16-
legalBasisUri: r.legal_basis_uri as string,
17-
legalBasisLabel: r.legal_basis_label as string,
18-
gdprArticle: r.gdpr_article as string,
19-
dataSubjects: r.data_subjects as string,
20-
recipients: r.recipients as string,
21-
thirdCountryTransfers: r.third_country_transfers as boolean,
22-
thirdCountryDetails: (r.third_country_details as string) ?? undefined,
23-
retentionPeriod: r.retention_period as string,
24-
securityMeasures: r.security_measures as string,
25-
status: r.status as RopaRecord['status'],
26-
schemaVersion: r.schema_version as number,
27-
personalDataFields: fields,
28-
createdAt: (r.created_at as Date).toISOString(),
29-
updatedAt: (r.updated_at as Date).toISOString(),
30-
};
31-
}
32-
33-
function rowToField(r: Record<string, unknown>): RopaPersonalDataField {
34-
return {
35-
id: r.id as string,
36-
ropaRecordId: r.ropa_record_id as string,
37-
formId: r.form_id as string,
38-
fieldKey: r.field_key as string,
39-
fieldLabel: r.field_label as string,
40-
dataCategory: r.data_category as string,
41-
specialCategory: r.special_category as boolean,
42-
sortOrder: r.sort_order as number,
43-
};
44-
}
2+
import { RopaRecordRow, RopaFieldRow } from '../db/types';
3+
import { RopaRecord, PublicRopaRecord } from '../types/ropa.types';
4+
import { mapRopaRecord, mapRopaField } from '../db/mappers';
455

466
// ─── CRUD ─────────────────────────────────────────────────────────────────────
477

488
export async function listRopa(): Promise<RopaRecord[]> {
499
if (!pool) return [];
50-
const { rows: recordRows } = await pool.query(
10+
const { rows: recordRows } = await pool.query<RopaRecordRow>(
5111
`SELECT * FROM ropa_records ORDER BY updated_at DESC`
5212
);
5313
if (recordRows.length === 0) return [];
5414
const ids = recordRows.map((r) => r.id);
55-
const { rows: fieldRows } = await pool.query(
15+
const { rows: fieldRows } = await pool.query<RopaFieldRow>(
5616
`SELECT * FROM ropa_personal_data_fields
5717
WHERE ropa_record_id = ANY($1) ORDER BY sort_order ASC`,
5818
[ids]
5919
);
6020
return recordRows.map((r) =>
61-
rowToRecord(r, fieldRows.filter((f) => f.ropa_record_id === r.id).map(rowToField))
21+
mapRopaRecord(r, fieldRows.filter((f) => f.ropa_record_id === r.id).map(mapRopaField))
6222
);
6323
}
6424

6525
export async function getRopaById(id: string): Promise<RopaRecord | null> {
6626
if (!pool) return null;
67-
const { rows } = await pool.query(`SELECT * FROM ropa_records WHERE id = $1`, [id]);
27+
const { rows } = await pool.query<RopaRecordRow>(`SELECT * FROM ropa_records WHERE id = $1`, [id]);
6828
if (rows.length === 0) return null;
69-
const { rows: fieldRows } = await pool.query(
29+
const { rows: fieldRows } = await pool.query<RopaFieldRow>(
7030
`SELECT * FROM ropa_personal_data_fields WHERE ropa_record_id = $1 ORDER BY sort_order ASC`,
7131
[id]
7232
);
73-
return rowToRecord(rows[0], fieldRows.map(rowToField));
33+
return mapRopaRecord(rows[0], fieldRows.map(mapRopaField));
7434
}
7535

7636
export async function getRopaByBpmnProcessId(bpmnProcessId: string): Promise<RopaRecord | null> {
7737
if (!pool) return null;
78-
const { rows } = await pool.query(
38+
const { rows } = await pool.query<RopaRecordRow>(
7939
`SELECT * FROM ropa_records WHERE bpmn_process_id = $1 ORDER BY updated_at DESC LIMIT 1`,
8040
[bpmnProcessId]
8141
);
@@ -187,18 +147,18 @@ export async function listPublicRopa(organisation?: string): Promise<PublicRopaR
187147
params.push(`%${organisation}%`);
188148
where += ` AND r.controller_name ILIKE $${params.length}`;
189149
}
190-
const { rows: recordRows } = await pool.query(
150+
const { rows: recordRows } = await pool.query<RopaRecordRow>(
191151
`SELECT * FROM ropa_records r ${where} ORDER BY r.updated_at DESC`,
192152
params
193153
);
194154
if (recordRows.length === 0) return [];
195155
const ids = recordRows.map((r) => r.id);
196-
const { rows: fieldRows } = await pool.query(
156+
const { rows: fieldRows } = await pool.query<RopaFieldRow>(
197157
`SELECT * FROM ropa_personal_data_fields WHERE ropa_record_id = ANY($1) ORDER BY sort_order ASC`,
198158
[ids]
199159
);
200160
return recordRows.map((r) => {
201-
const full = rowToRecord(r, fieldRows.filter((f) => f.ropa_record_id === r.id).map(rowToField));
161+
const full = mapRopaRecord(r, fieldRows.filter((f) => f.ropa_record_id === r.id).map(mapRopaField));
202162
// Strip internal fields before exposing publicly
203163
// eslint-disable-next-line @typescript-eslint/no-unused-vars
204164
const { schemaVersion, controllerContact, dpoContact, ...pub } = full;

0 commit comments

Comments
 (0)