|
1 | | -# GDPR API Client Usage Guide |
| 1 | +# GDPR API Usage Guide |
2 | 2 |
|
3 | | -This guide shows how to use the type-safe GDPR API client instead of raw fetch calls. |
| 3 | +This guide now focuses on sharing contract types between the SSR endpoints and any client or server-side callers. Implementation code should live alongside the endpoint that owns it. Shared files only export types. |
4 | 4 |
|
5 | | -## 1. Import the client functions |
| 5 | +## 1. Import the contract types |
6 | 6 |
|
7 | 7 | ```typescript |
8 | | -import { recordConsent, submitDataRequest, recordConsentServerSide } from '@lib/api/gdpr.client' |
| 8 | +import type { |
| 9 | + ConsentRequest, |
| 10 | + ConsentResponse, |
| 11 | + DSARRequestInput, |
| 12 | + DSARResponse, |
| 13 | + ErrorResponse |
| 14 | +} from '@pages/api/_contracts/gdpr.contracts' |
9 | 15 | ``` |
10 | 16 |
|
11 | | -## 2. Client-side usage examples |
| 17 | +Using these shared types keeps both sides of the API honest without duplicating runtime logic. |
12 | 18 |
|
13 | | -### Recording consent (cookies modal) |
| 19 | +## 2. Client-side consent submission example |
14 | 20 |
|
15 | 21 | ```typescript |
16 | | -// Before (raw fetch): |
17 | | -await fetch('/api/gdpr/consent', { |
18 | | - method: 'POST', |
19 | | - headers: { 'Content-Type': 'application/json' }, |
20 | | - body: JSON.stringify({ |
21 | | - DataSubjectId: consentState.DataSubjectId, |
22 | | - purposes, |
23 | | - source: 'cookies_modal', |
24 | | - userAgent: navigator.userAgent, |
25 | | - verified: false, |
26 | | - }), |
27 | | -}) |
28 | | - |
29 | | -// After (typed client): |
30 | | -const result = await recordConsent({ |
| 22 | +const payload: ConsentRequest = { |
31 | 23 | DataSubjectId: consentState.DataSubjectId, |
32 | 24 | purposes, |
33 | 25 | source: 'cookies_modal', |
34 | 26 | userAgent: navigator.userAgent, |
35 | 27 | verified: false, |
36 | | -}) |
37 | | - |
38 | | -if (result.success) { |
39 | | - console.log('Consent recorded:', result.data.record.id) |
40 | | -} else { |
41 | | - console.error('Failed:', result.error.message) |
42 | 28 | } |
43 | | -``` |
44 | | - |
45 | | -### Submitting data requests (my-data page) |
46 | 29 |
|
47 | | -```typescript |
48 | | -// Before (raw fetch): |
49 | | -const response = await fetch('/api/gdpr/request-data', { |
| 30 | +const response = await fetch('/api/gdpr/consent', { |
50 | 31 | method: 'POST', |
51 | 32 | headers: { 'Content-Type': 'application/json' }, |
52 | | - body: JSON.stringify({ |
53 | | - email, |
54 | | - requestType: 'ACCESS' |
55 | | - }) |
| 33 | + body: JSON.stringify(payload), |
56 | 34 | }) |
57 | | -const data = await response.json() |
58 | 35 |
|
59 | | -// After (typed client): |
60 | | -const result = await submitDataRequest({ |
61 | | - email, |
62 | | - requestType: 'ACCESS' |
63 | | -}) |
64 | | - |
65 | | -if (result.success) { |
66 | | - accessMessage.textContent = result.data.message |
67 | | - accessMessage.className = 'form-message success' |
68 | | -} else { |
69 | | - accessMessage.textContent = result.error.message |
70 | | - accessMessage.className = 'form-message error' |
71 | | -} |
| 36 | +const data: ConsentResponse | ErrorResponse = await response.json() |
72 | 37 | ``` |
73 | 38 |
|
74 | | -## 3. Server-side usage examples |
| 39 | +Because the payload is typed, any contract drift is caught at build time. |
| 40 | + |
| 41 | +## 3. Server-side helper usage |
75 | 42 |
|
76 | | -### Contact form API |
| 43 | +Server routes can call the shared logger helper: |
77 | 44 |
|
78 | 45 | ```typescript |
79 | | -// Before (raw fetch): |
80 | | -const consentResponse = await fetch(`${new URL(request.url).origin}/api/gdpr/consent`, { |
81 | | - method: 'POST', |
82 | | - headers: { 'Content-Type': 'application/json' }, |
83 | | - body: JSON.stringify({ |
84 | | - DataSubjectId: subjectId, |
85 | | - email: formData.email, |
86 | | - purposes: ['contact'], |
87 | | - source: 'contact_form', |
88 | | - userAgent, |
89 | | - verified: true, |
90 | | - }), |
| 46 | +import { recordConsent } from '@pages/api/_logger' |
| 47 | + |
| 48 | +await recordConsent({ |
| 49 | + origin: new URL(request.url).origin, |
| 50 | + DataSubjectId: subjectId, |
| 51 | + email: validatedEmail, |
| 52 | + purposes: ['contact'], |
| 53 | + source: 'contact_form', |
| 54 | + userAgent, |
| 55 | + verified: true, |
91 | 56 | }) |
92 | | - |
93 | | -// After (typed client): |
94 | | -const result = await recordConsentServerSide( |
95 | | - new URL(request.url).origin, |
96 | | - { |
97 | | - DataSubjectId: subjectId, |
98 | | - email: formData.email, |
99 | | - purposes: ['contact'], |
100 | | - source: 'contact_form', |
101 | | - userAgent, |
102 | | - verified: true, |
103 | | - } |
104 | | -) |
105 | | - |
106 | | -if (!result.success) { |
107 | | - // Handle error with proper typing |
108 | | - console.error('Consent failed:', result.error.code, result.error.message) |
109 | | -} |
110 | 57 | ``` |
111 | 58 |
|
112 | | -## 4. Benefits of the typed client |
113 | | - |
114 | | -1. **Type Safety**: Compile-time validation of request/response structures |
115 | | -2. **Error Handling**: Consistent error response format across all functions |
116 | | -3. **IntelliSense**: Auto-completion for all fields and response properties |
117 | | -4. **Documentation**: JSDoc comments with usage examples |
118 | | -5. **Centralized**: Single place to manage API call logic and error handling |
119 | | - |
120 | | -## 5. Migration checklist |
| 59 | +`recordConsent` now encapsulates the fetch logic so API routes stay minimal while still using the shared contracts. |
121 | 60 |
|
122 | | -- [ ] `src/components/scripts/store/consent.ts` - Use `recordConsent()` |
123 | | -- [ ] `src/pages/privacy/my-data.astro` - Use `submitDataRequest()` |
124 | | -- [ ] `src/pages/api/contact/index.ts` - Use `recordConsentServerSide()` |
125 | | -- [ ] `src/pages/api/newsletter/index.ts` - Use `recordConsentServerSide()` |
| 61 | +## 4. Recommended workflow |
126 | 62 |
|
127 | | -## 6. Contract types |
| 63 | +1. Define every request/response shape in `@pages/api/_contracts`. |
| 64 | +2. Import those types in both the endpoint implementation and any caller (client or server). |
| 65 | +3. Keep shared files type-only to avoid duplicated runtime logic. |
| 66 | +4. Let each runtime (client, API route, worker) own the fetch/request mechanics that make sense for its environment. |
128 | 67 |
|
129 | | -All types are re-exported from `@pages/api/_contracts/gdpr.contracts`: |
| 68 | +## 5. Available GDPR types |
130 | 69 |
|
131 | | -- `ConsentRequest` - Consent recording payload |
132 | | -- `ConsentResponse` - Consent creation response |
133 | | -- `DSARRequestInput` - Data request payload |
134 | | -- `DSARResponse` - Data request confirmation response |
135 | | -- `ErrorResponse` - Standardized error format |
| 70 | +- `ConsentRequest` – Consent recording payload |
| 71 | +- `ConsentResponse` – Consent creation response |
| 72 | +- `DSARRequestInput` – Data request payload |
| 73 | +- `DSARResponse` – Data request confirmation response |
| 74 | +- `ErrorResponse` – Standardized error format |
0 commit comments