|
1 | 1 | 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'; |
45 | 5 |
|
46 | 6 | // ─── CRUD ───────────────────────────────────────────────────────────────────── |
47 | 7 |
|
48 | 8 | export async function listRopa(): Promise<RopaRecord[]> { |
49 | 9 | if (!pool) return []; |
50 | | - const { rows: recordRows } = await pool.query( |
| 10 | + const { rows: recordRows } = await pool.query<RopaRecordRow>( |
51 | 11 | `SELECT * FROM ropa_records ORDER BY updated_at DESC` |
52 | 12 | ); |
53 | 13 | if (recordRows.length === 0) return []; |
54 | 14 | const ids = recordRows.map((r) => r.id); |
55 | | - const { rows: fieldRows } = await pool.query( |
| 15 | + const { rows: fieldRows } = await pool.query<RopaFieldRow>( |
56 | 16 | `SELECT * FROM ropa_personal_data_fields |
57 | 17 | WHERE ropa_record_id = ANY($1) ORDER BY sort_order ASC`, |
58 | 18 | [ids] |
59 | 19 | ); |
60 | 20 | 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)) |
62 | 22 | ); |
63 | 23 | } |
64 | 24 |
|
65 | 25 | export async function getRopaById(id: string): Promise<RopaRecord | null> { |
66 | 26 | 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]); |
68 | 28 | if (rows.length === 0) return null; |
69 | | - const { rows: fieldRows } = await pool.query( |
| 29 | + const { rows: fieldRows } = await pool.query<RopaFieldRow>( |
70 | 30 | `SELECT * FROM ropa_personal_data_fields WHERE ropa_record_id = $1 ORDER BY sort_order ASC`, |
71 | 31 | [id] |
72 | 32 | ); |
73 | | - return rowToRecord(rows[0], fieldRows.map(rowToField)); |
| 33 | + return mapRopaRecord(rows[0], fieldRows.map(mapRopaField)); |
74 | 34 | } |
75 | 35 |
|
76 | 36 | export async function getRopaByBpmnProcessId(bpmnProcessId: string): Promise<RopaRecord | null> { |
77 | 37 | if (!pool) return null; |
78 | | - const { rows } = await pool.query( |
| 38 | + const { rows } = await pool.query<RopaRecordRow>( |
79 | 39 | `SELECT * FROM ropa_records WHERE bpmn_process_id = $1 ORDER BY updated_at DESC LIMIT 1`, |
80 | 40 | [bpmnProcessId] |
81 | 41 | ); |
@@ -187,18 +147,18 @@ export async function listPublicRopa(organisation?: string): Promise<PublicRopaR |
187 | 147 | params.push(`%${organisation}%`); |
188 | 148 | where += ` AND r.controller_name ILIKE $${params.length}`; |
189 | 149 | } |
190 | | - const { rows: recordRows } = await pool.query( |
| 150 | + const { rows: recordRows } = await pool.query<RopaRecordRow>( |
191 | 151 | `SELECT * FROM ropa_records r ${where} ORDER BY r.updated_at DESC`, |
192 | 152 | params |
193 | 153 | ); |
194 | 154 | if (recordRows.length === 0) return []; |
195 | 155 | const ids = recordRows.map((r) => r.id); |
196 | | - const { rows: fieldRows } = await pool.query( |
| 156 | + const { rows: fieldRows } = await pool.query<RopaFieldRow>( |
197 | 157 | `SELECT * FROM ropa_personal_data_fields WHERE ropa_record_id = ANY($1) ORDER BY sort_order ASC`, |
198 | 158 | [ids] |
199 | 159 | ); |
200 | 160 | 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)); |
202 | 162 | // Strip internal fields before exposing publicly |
203 | 163 | // eslint-disable-next-line @typescript-eslint/no-unused-vars |
204 | 164 | const { schemaVersion, controllerContact, dpoContact, ...pub } = full; |
|
0 commit comments