@tantainnovative/create-ndpr 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,163 @@
1
+ /**
2
+ * NDPA Compliance Schema — generated by create-ndpr
3
+ * Organisation: {{ORG_NAME}}
4
+ * DPO: {{DPO_EMAIL}}
5
+ *
6
+ * Covers:
7
+ * - consentRecords — NDPA §25–26 (consent & withdrawal)
8
+ * - dsrRequests — NDPA Part IV §34–38 (data subject rights)
9
+ * - breachReports — NDPA §40 (72-hour breach notification)
10
+ * - processingRecords — ROPA (accountability principle)
11
+ * - auditLog — §44 audit trail
12
+ *
13
+ * Prerequisites: drizzle-orm, @paralleldrive/cuid2
14
+ * Run `drizzle-kit push` to apply.
15
+ */
16
+
17
+ import {
18
+ pgTable,
19
+ text,
20
+ timestamp,
21
+ json,
22
+ boolean,
23
+ integer,
24
+ index,
25
+ } from 'drizzle-orm/pg-core';
26
+ import { createId } from '@paralleldrive/cuid2';
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // ndpr_consent_records — NDPA §25–26
30
+ // ---------------------------------------------------------------------------
31
+
32
+ export const consentRecords = pgTable(
33
+ 'ndpr_consent_records',
34
+ {
35
+ id: text('id').primaryKey().$defaultFn(() => createId()),
36
+ subjectId: text('subject_id').notNull(),
37
+ consents: json('consents').notNull(),
38
+ version: text('version').notNull(),
39
+ method: text('method').notNull(),
40
+ lawfulBasis: text('lawful_basis'),
41
+ ipAddress: text('ip_address'),
42
+ userAgent: text('user_agent'),
43
+ createdAt: timestamp('created_at').defaultNow().notNull(),
44
+ revokedAt: timestamp('revoked_at'),
45
+ },
46
+ (t) => ({ subjectIdIdx: index('consent_subject_id_idx').on(t.subjectId) }),
47
+ );
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // ndpr_dsr_requests — NDPA §34–38
51
+ // ---------------------------------------------------------------------------
52
+
53
+ export const dsrRequests = pgTable(
54
+ 'ndpr_dsr_requests',
55
+ {
56
+ id: text('id').primaryKey().$defaultFn(() => createId()),
57
+ type: text('type').notNull(),
58
+ status: text('status').notNull().default('pending'),
59
+ subjectName: text('subject_name').notNull(),
60
+ subjectEmail: text('subject_email').notNull(),
61
+ subjectPhone: text('subject_phone'),
62
+ identifierType: text('identifier_type').notNull(),
63
+ identifierValue: text('identifier_value').notNull(),
64
+ description: text('description'),
65
+ internalNotes: text('internal_notes'),
66
+ assignedTo: text('assigned_to'),
67
+ submittedAt: timestamp('submitted_at').defaultNow().notNull(),
68
+ acknowledgedAt: timestamp('acknowledged_at'),
69
+ completedAt: timestamp('completed_at'),
70
+ dueAt: timestamp('due_at').notNull(),
71
+ },
72
+ (t) => ({
73
+ statusIdx: index('dsr_status_idx').on(t.status),
74
+ subjectEmailIdx: index('dsr_subject_email_idx').on(t.subjectEmail),
75
+ }),
76
+ );
77
+
78
+ // ---------------------------------------------------------------------------
79
+ // ndpr_breach_reports — NDPA §40
80
+ // ---------------------------------------------------------------------------
81
+
82
+ export const breachReports = pgTable(
83
+ 'ndpr_breach_reports',
84
+ {
85
+ id: text('id').primaryKey().$defaultFn(() => createId()),
86
+ title: text('title').notNull(),
87
+ description: text('description').notNull(),
88
+ category: text('category').notNull(),
89
+ severity: text('severity').notNull(),
90
+ status: text('status').notNull().default('ongoing'),
91
+ discoveredAt: timestamp('discovered_at').notNull(),
92
+ occurredAt: timestamp('occurred_at'),
93
+ reportedAt: timestamp('reported_at').defaultNow().notNull(),
94
+ ndpcNotifiedAt: timestamp('ndpc_notified_at'),
95
+ reporterName: text('reporter_name').notNull(),
96
+ reporterEmail: text('reporter_email').notNull(),
97
+ reporterDepartment: text('reporter_department'),
98
+ affectedSystems: json('affected_systems').notNull(),
99
+ dataTypes: json('data_types').notNull(),
100
+ estimatedAffected: integer('estimated_affected'),
101
+ initialActions: text('initial_actions'),
102
+ ndpcNotificationSent: boolean('ndpc_notification_sent').notNull().default(false),
103
+ },
104
+ (t) => ({
105
+ statusIdx: index('breach_status_idx').on(t.status),
106
+ severityIdx: index('breach_severity_idx').on(t.severity),
107
+ }),
108
+ );
109
+
110
+ // ---------------------------------------------------------------------------
111
+ // ndpr_processing_records — ROPA
112
+ // ---------------------------------------------------------------------------
113
+
114
+ export const processingRecords = pgTable('ndpr_processing_records', {
115
+ id: text('id').primaryKey().$defaultFn(() => createId()),
116
+ purpose: text('purpose').notNull(),
117
+ lawfulBasis: text('lawful_basis').notNull(),
118
+ dataCategories: json('data_categories').notNull(),
119
+ dataSubjects: json('data_subjects').notNull(),
120
+ recipients: json('recipients').notNull(),
121
+ retentionPeriod: text('retention_period').notNull(),
122
+ securityMeasures: json('security_measures').notNull(),
123
+ transferCountries: json('transfer_countries'),
124
+ transferMechanism: text('transfer_mechanism'),
125
+ dpiaConducted: boolean('dpia_conducted').notNull().default(false),
126
+ status: text('status').notNull().default('active'),
127
+ createdAt: timestamp('created_at').defaultNow().notNull(),
128
+ updatedAt: timestamp('updated_at').defaultNow().notNull(),
129
+ });
130
+
131
+ // ---------------------------------------------------------------------------
132
+ // ndpr_audit_log — §44 accountability
133
+ // ---------------------------------------------------------------------------
134
+
135
+ export const auditLog = pgTable(
136
+ 'ndpr_audit_log',
137
+ {
138
+ id: text('id').primaryKey().$defaultFn(() => createId()),
139
+ module: text('module').notNull(),
140
+ action: text('action').notNull(),
141
+ entityId: text('entity_id').notNull(),
142
+ entityType: text('entity_type').notNull(),
143
+ changes: json('changes'),
144
+ performedBy: text('performed_by'),
145
+ createdAt: timestamp('created_at').defaultNow().notNull(),
146
+ },
147
+ (t) => ({
148
+ moduleEntityIdx: index('audit_module_entity_idx').on(t.module, t.entityId),
149
+ }),
150
+ );
151
+
152
+ // ---------------------------------------------------------------------------
153
+ // Inferred types
154
+ // ---------------------------------------------------------------------------
155
+
156
+ export type ConsentRecord = typeof consentRecords.$inferSelect;
157
+ export type NewConsentRecord = typeof consentRecords.$inferInsert;
158
+ export type DSRRequest = typeof dsrRequests.$inferSelect;
159
+ export type NewDSRRequest = typeof dsrRequests.$inferInsert;
160
+ export type BreachReport = typeof breachReports.$inferSelect;
161
+ export type NewBreachReport = typeof breachReports.$inferInsert;
162
+ export type ProcessingRecord = typeof processingRecords.$inferSelect;
163
+ export type AuditLogEntry = typeof auditLog.$inferSelect;
@@ -0,0 +1,4 @@
1
+ # NDPA Compliance Database
2
+ # Organisation: {{ORG_NAME}}
3
+ # DPO: {{DPO_EMAIL}}
4
+ DATABASE_URL="postgresql://user:password@localhost:5432/myapp_dev"
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Express — Consent Router
3
+ * Generated by create-ndpr for: {{ORG_NAME}}
4
+ * DPO: {{DPO_EMAIL}}
5
+ *
6
+ * NDPA §25 (lawful basis) and §26 (right to withdraw consent).
7
+ *
8
+ * Routes:
9
+ * GET /consent?subjectId=xxx — Load the active consent record
10
+ * POST /consent — Save new consent (revokes previous)
11
+ * DELETE /consent?subjectId=xxx — Revoke all active consent
12
+ *
13
+ * Usage:
14
+ * import { consentRouter } from './routes/consent';
15
+ * app.use('/api/consent', consentRouter);
16
+ */
17
+
18
+ import { Router } from 'express';
19
+ import { PrismaClient } from '@prisma/client';
20
+
21
+ const prisma = new PrismaClient();
22
+ export const consentRouter = Router();
23
+
24
+ // GET /consent?subjectId=xxx
25
+ consentRouter.get('/', async (req, res) => {
26
+ const { subjectId } = req.query;
27
+
28
+ if (!subjectId || typeof subjectId !== 'string') {
29
+ return res.status(400).json({ error: 'subjectId required' });
30
+ }
31
+
32
+ const record = await prisma.consentRecord.findFirst({
33
+ where: { subjectId, revokedAt: null },
34
+ orderBy: { createdAt: 'desc' },
35
+ });
36
+
37
+ return res.json(record);
38
+ });
39
+
40
+ // POST /consent
41
+ consentRouter.post('/', async (req, res) => {
42
+ const { subjectId, consents, version, method, lawfulBasis } = req.body;
43
+
44
+ if (!subjectId || !consents || !version) {
45
+ return res.status(400).json({ error: 'subjectId, consents, and version are required' });
46
+ }
47
+
48
+ // Revoke any previously active records (immutable-audit pattern — NDPA §44).
49
+ await prisma.consentRecord.updateMany({
50
+ where: { subjectId, revokedAt: null },
51
+ data: { revokedAt: new Date() },
52
+ });
53
+
54
+ const record = await prisma.consentRecord.create({
55
+ data: {
56
+ subjectId,
57
+ consents,
58
+ version,
59
+ method: method ?? 'api',
60
+ lawfulBasis: lawfulBasis ?? null,
61
+ ipAddress:
62
+ (req.headers['x-forwarded-for'] as string)?.split(',')[0].trim() ??
63
+ req.socket.remoteAddress ??
64
+ null,
65
+ userAgent: req.headers['user-agent'] ?? null,
66
+ },
67
+ });
68
+
69
+ await prisma.complianceAuditLog.create({
70
+ data: {
71
+ module: 'consent',
72
+ action: 'created',
73
+ entityId: record.id,
74
+ entityType: 'ConsentRecord',
75
+ changes: { subjectId, version, consents },
76
+ },
77
+ });
78
+
79
+ return res.status(201).json(record);
80
+ });
81
+
82
+ // DELETE /consent?subjectId=xxx
83
+ consentRouter.delete('/', async (req, res) => {
84
+ const { subjectId } = req.query;
85
+
86
+ if (!subjectId || typeof subjectId !== 'string') {
87
+ return res.status(400).json({ error: 'subjectId required' });
88
+ }
89
+
90
+ await prisma.consentRecord.updateMany({
91
+ where: { subjectId, revokedAt: null },
92
+ data: { revokedAt: new Date() },
93
+ });
94
+
95
+ await prisma.complianceAuditLog.create({
96
+ data: {
97
+ module: 'consent',
98
+ action: 'revoked',
99
+ entityId: subjectId,
100
+ entityType: 'ConsentRecord',
101
+ },
102
+ });
103
+
104
+ return res.json({ success: true });
105
+ });
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Express — NDPR Compliance Router
3
+ * Generated by create-ndpr for: {{ORG_NAME}}
4
+ * DPO: {{DPO_EMAIL}}
5
+ *
6
+ * Mount this router in your Express app:
7
+ *
8
+ * import express from 'express';
9
+ * import cookieParser from 'cookie-parser';
10
+ * import { createNDPRRouter } from './src/ndpr';
11
+ *
12
+ * const app = express();
13
+ * app.use(express.json());
14
+ * app.use(cookieParser());
15
+ * app.use('/api/ndpr', createNDPRRouter());
16
+ *
17
+ * Routes exposed:
18
+ * GET/POST/DELETE /api/ndpr/consent — NDPA §25–26
19
+ * GET/POST /api/ndpr/dsr — NDPA §34–38
20
+ * GET/POST /api/ndpr/breach — NDPA §40
21
+ */
22
+
23
+ import { Router } from 'express';
24
+ import { PrismaClient } from '@prisma/client';
25
+
26
+ const prisma = new PrismaClient();
27
+
28
+ // ---------------------------------------------------------------------------
29
+ // Consent router — NDPA §25–26
30
+ // ---------------------------------------------------------------------------
31
+
32
+ function consentRouter(): Router {
33
+ const router = Router();
34
+
35
+ // GET /consent?subjectId=xxx
36
+ router.get('/', async (req, res) => {
37
+ const { subjectId } = req.query;
38
+ if (!subjectId || typeof subjectId !== 'string') {
39
+ return res.status(400).json({ error: 'subjectId required' });
40
+ }
41
+ const record = await prisma.consentRecord.findFirst({
42
+ where: { subjectId, revokedAt: null },
43
+ orderBy: { createdAt: 'desc' },
44
+ });
45
+ return res.json(record);
46
+ });
47
+
48
+ // POST /consent
49
+ router.post('/', async (req, res) => {
50
+ const { subjectId, consents, version, method, lawfulBasis } = req.body;
51
+ if (!subjectId || !consents || !version) {
52
+ return res.status(400).json({ error: 'subjectId, consents, and version are required' });
53
+ }
54
+ await prisma.consentRecord.updateMany({
55
+ where: { subjectId, revokedAt: null },
56
+ data: { revokedAt: new Date() },
57
+ });
58
+ const record = await prisma.consentRecord.create({
59
+ data: {
60
+ subjectId,
61
+ consents,
62
+ version,
63
+ method: method ?? 'api',
64
+ lawfulBasis: lawfulBasis ?? null,
65
+ ipAddress:
66
+ (req.headers['x-forwarded-for'] as string)?.split(',')[0].trim() ??
67
+ req.socket.remoteAddress ??
68
+ null,
69
+ userAgent: req.headers['user-agent'] ?? null,
70
+ },
71
+ });
72
+ await prisma.complianceAuditLog.create({
73
+ data: {
74
+ module: 'consent',
75
+ action: 'created',
76
+ entityId: record.id,
77
+ entityType: 'ConsentRecord',
78
+ changes: { subjectId, version, consents },
79
+ },
80
+ });
81
+ return res.status(201).json(record);
82
+ });
83
+
84
+ // DELETE /consent?subjectId=xxx
85
+ router.delete('/', async (req, res) => {
86
+ const { subjectId } = req.query;
87
+ if (!subjectId || typeof subjectId !== 'string') {
88
+ return res.status(400).json({ error: 'subjectId required' });
89
+ }
90
+ await prisma.consentRecord.updateMany({
91
+ where: { subjectId, revokedAt: null },
92
+ data: { revokedAt: new Date() },
93
+ });
94
+ await prisma.complianceAuditLog.create({
95
+ data: {
96
+ module: 'consent',
97
+ action: 'revoked',
98
+ entityId: subjectId,
99
+ entityType: 'ConsentRecord',
100
+ },
101
+ });
102
+ return res.json({ success: true });
103
+ });
104
+
105
+ return router;
106
+ }
107
+
108
+ // ---------------------------------------------------------------------------
109
+ // DSR router — NDPA §34–38
110
+ // ---------------------------------------------------------------------------
111
+
112
+ function dsrRouter(): Router {
113
+ const router = Router();
114
+
115
+ // GET /dsr?status=pending
116
+ router.get('/', async (req, res) => {
117
+ const { status } = req.query;
118
+ const requests = await prisma.dSRRequest.findMany({
119
+ where: status && typeof status === 'string' ? { status } : undefined,
120
+ orderBy: { submittedAt: 'desc' },
121
+ });
122
+ return res.json(requests);
123
+ });
124
+
125
+ // POST /dsr
126
+ router.post('/', async (req, res) => {
127
+ const { type, subjectName, subjectEmail, subjectPhone, identifierType, identifierValue, description } =
128
+ req.body;
129
+ if (!type || !subjectName || !subjectEmail || !identifierType || !identifierValue) {
130
+ return res
131
+ .status(400)
132
+ .json({ error: 'type, subjectName, subjectEmail, identifierType, and identifierValue are required' });
133
+ }
134
+ const dueAt = new Date();
135
+ dueAt.setDate(dueAt.getDate() + 30);
136
+ const request = await prisma.dSRRequest.create({
137
+ data: {
138
+ type,
139
+ subjectName,
140
+ subjectEmail,
141
+ subjectPhone: subjectPhone ?? null,
142
+ identifierType,
143
+ identifierValue,
144
+ description: description ?? null,
145
+ status: 'pending',
146
+ dueAt,
147
+ },
148
+ });
149
+ await prisma.complianceAuditLog.create({
150
+ data: {
151
+ module: 'dsr',
152
+ action: 'submitted',
153
+ entityId: request.id,
154
+ entityType: 'DSRRequest',
155
+ changes: { type, subjectEmail, status: 'pending' },
156
+ },
157
+ });
158
+ return res.status(201).json(request);
159
+ });
160
+
161
+ return router;
162
+ }
163
+
164
+ // ---------------------------------------------------------------------------
165
+ // Breach router — NDPA §40
166
+ // ---------------------------------------------------------------------------
167
+
168
+ function breachRouter(): Router {
169
+ const router = Router();
170
+
171
+ function calculateSeverity(category: string, estimatedAffected?: number) {
172
+ const highRisk = ['unauthorized_access', 'ransomware', 'data_exfiltration', 'identity_theft'];
173
+ if (highRisk.includes(category)) return (estimatedAffected ?? 0) > 1000 ? 'critical' : 'high';
174
+ if ((estimatedAffected ?? 0) > 500) return 'high';
175
+ if ((estimatedAffected ?? 0) > 50) return 'medium';
176
+ return 'low';
177
+ }
178
+
179
+ // GET /breach?status=ongoing
180
+ router.get('/', async (req, res) => {
181
+ const { status } = req.query;
182
+ const reports = await prisma.breachReport.findMany({
183
+ where: status && typeof status === 'string' ? { status } : undefined,
184
+ orderBy: { reportedAt: 'desc' },
185
+ });
186
+ return res.json(reports);
187
+ });
188
+
189
+ // POST /breach
190
+ router.post('/', async (req, res) => {
191
+ const {
192
+ title, description, category, discoveredAt, occurredAt,
193
+ reporterName, reporterEmail, reporterDepartment,
194
+ affectedSystems, dataTypes, estimatedAffected, initialActions,
195
+ } = req.body;
196
+
197
+ if (!title || !description || !category || !discoveredAt || !reporterName || !reporterEmail) {
198
+ return res.status(400).json({
199
+ error: 'title, description, category, discoveredAt, reporterName, and reporterEmail are required',
200
+ });
201
+ }
202
+ if (!Array.isArray(affectedSystems) || !Array.isArray(dataTypes)) {
203
+ return res.status(400).json({ error: 'affectedSystems and dataTypes must be arrays' });
204
+ }
205
+
206
+ const severity = calculateSeverity(category, estimatedAffected);
207
+ const report = await prisma.breachReport.create({
208
+ data: {
209
+ title, description, category, severity, status: 'ongoing',
210
+ discoveredAt: new Date(discoveredAt),
211
+ occurredAt: occurredAt ? new Date(occurredAt) : null,
212
+ reporterName, reporterEmail,
213
+ reporterDepartment: reporterDepartment ?? null,
214
+ affectedSystems, dataTypes,
215
+ estimatedAffected: estimatedAffected ?? null,
216
+ initialActions: initialActions ?? null,
217
+ },
218
+ });
219
+ await prisma.complianceAuditLog.create({
220
+ data: {
221
+ module: 'breach',
222
+ action: 'reported',
223
+ entityId: report.id,
224
+ entityType: 'BreachReport',
225
+ changes: { title, category, severity, status: 'ongoing' },
226
+ },
227
+ });
228
+ return res.status(201).json(report);
229
+ });
230
+
231
+ return router;
232
+ }
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // Main factory
236
+ // ---------------------------------------------------------------------------
237
+
238
+ /**
239
+ * Returns a fully configured NDPR compliance Express Router.
240
+ *
241
+ * Mount with:
242
+ * app.use('/api/ndpr', createNDPRRouter());
243
+ */
244
+ export function createNDPRRouter(): Router {
245
+ const router = Router();
246
+ router.use('/consent', consentRouter());
247
+ router.use('/dsr', dsrRouter());
248
+ router.use('/breach', breachRouter());
249
+ return router;
250
+ }
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Next.js App Router — Breach Notification Route
3
+ * Generated by create-ndpr for: {{ORG_NAME}}
4
+ *
5
+ * NDPA §40 — data controllers must notify the NDPC within 72 hours of
6
+ * discovering a breach that poses a risk to data subject rights and freedoms.
7
+ *
8
+ * Endpoints:
9
+ * GET /api/breach — List breach reports (optional ?status= filter)
10
+ * POST /api/breach — Create a new breach report
11
+ */
12
+
13
+ import { NextRequest, NextResponse } from 'next/server';
14
+ import { PrismaClient } from '@prisma/client';
15
+
16
+ const prisma = new PrismaClient();
17
+
18
+ function calculateSeverity(
19
+ category: string,
20
+ estimatedAffected?: number,
21
+ ): 'critical' | 'high' | 'medium' | 'low' {
22
+ const highRisk = ['unauthorized_access', 'ransomware', 'data_exfiltration', 'identity_theft'];
23
+ if (highRisk.includes(category)) {
24
+ return (estimatedAffected ?? 0) > 1000 ? 'critical' : 'high';
25
+ }
26
+ if ((estimatedAffected ?? 0) > 500) return 'high';
27
+ if ((estimatedAffected ?? 0) > 50) return 'medium';
28
+ return 'low';
29
+ }
30
+
31
+ // GET /api/breach?status=ongoing
32
+ export async function GET(req: NextRequest) {
33
+ const status = req.nextUrl.searchParams.get('status');
34
+
35
+ const reports = await prisma.breachReport.findMany({
36
+ where: status ? { status } : undefined,
37
+ orderBy: { reportedAt: 'desc' },
38
+ });
39
+
40
+ return NextResponse.json(reports);
41
+ }
42
+
43
+ // POST /api/breach
44
+ export async function POST(req: NextRequest) {
45
+ const body = await req.json();
46
+ const {
47
+ title,
48
+ description,
49
+ category,
50
+ discoveredAt,
51
+ occurredAt,
52
+ reporterName,
53
+ reporterEmail,
54
+ reporterDepartment,
55
+ affectedSystems,
56
+ dataTypes,
57
+ estimatedAffected,
58
+ initialActions,
59
+ } = body;
60
+
61
+ if (!title || !description || !category || !discoveredAt || !reporterName || !reporterEmail) {
62
+ return NextResponse.json(
63
+ { error: 'title, description, category, discoveredAt, reporterName, and reporterEmail are required' },
64
+ { status: 400 },
65
+ );
66
+ }
67
+
68
+ if (!Array.isArray(affectedSystems) || !Array.isArray(dataTypes)) {
69
+ return NextResponse.json(
70
+ { error: 'affectedSystems and dataTypes must be arrays' },
71
+ { status: 400 },
72
+ );
73
+ }
74
+
75
+ const severity = calculateSeverity(category, estimatedAffected);
76
+
77
+ const report = await prisma.breachReport.create({
78
+ data: {
79
+ title,
80
+ description,
81
+ category,
82
+ severity,
83
+ status: 'ongoing',
84
+ discoveredAt: new Date(discoveredAt),
85
+ occurredAt: occurredAt ? new Date(occurredAt) : null,
86
+ reporterName,
87
+ reporterEmail,
88
+ reporterDepartment: reporterDepartment ?? null,
89
+ affectedSystems,
90
+ dataTypes,
91
+ estimatedAffected: estimatedAffected ?? null,
92
+ initialActions: initialActions ?? null,
93
+ },
94
+ });
95
+
96
+ await prisma.complianceAuditLog.create({
97
+ data: {
98
+ module: 'breach',
99
+ action: 'reported',
100
+ entityId: report.id,
101
+ entityType: 'BreachReport',
102
+ changes: { title, category, severity, status: 'ongoing' },
103
+ },
104
+ });
105
+
106
+ return NextResponse.json(report, { status: 201 });
107
+ }