@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,100 @@
1
+ /**
2
+ * Next.js App Router — Consent API Route
3
+ * Generated by create-ndpr for: {{ORG_NAME}}
4
+ *
5
+ * NDPA §25 (lawful basis) and §26 (right to withdraw consent).
6
+ *
7
+ * Endpoints:
8
+ * GET /api/consent?subjectId=xxx — Load the active consent record
9
+ * POST /api/consent — Save new consent (revokes previous)
10
+ * DELETE /api/consent?subjectId=xxx — Revoke all active consent
11
+ */
12
+
13
+ import { NextRequest, NextResponse } from 'next/server';
14
+ import { PrismaClient } from '@prisma/client';
15
+
16
+ const prisma = new PrismaClient();
17
+
18
+ // GET /api/consent?subjectId=xxx
19
+ export async function GET(req: NextRequest) {
20
+ const subjectId = req.nextUrl.searchParams.get('subjectId');
21
+
22
+ if (!subjectId) {
23
+ return NextResponse.json({ error: 'subjectId required' }, { status: 400 });
24
+ }
25
+
26
+ const record = await prisma.consentRecord.findFirst({
27
+ where: { subjectId, revokedAt: null },
28
+ orderBy: { createdAt: 'desc' },
29
+ });
30
+
31
+ return NextResponse.json(record);
32
+ }
33
+
34
+ // POST /api/consent
35
+ export async function POST(req: NextRequest) {
36
+ const body = await req.json();
37
+ const { subjectId, consents, version, method, lawfulBasis } = body;
38
+
39
+ if (!subjectId || !consents || !version) {
40
+ return NextResponse.json(
41
+ { error: 'subjectId, consents, and version are required' },
42
+ { status: 400 },
43
+ );
44
+ }
45
+
46
+ // Revoke any previously active consent records (immutable-audit pattern — NDPA §44).
47
+ await prisma.consentRecord.updateMany({
48
+ where: { subjectId, revokedAt: null },
49
+ data: { revokedAt: new Date() },
50
+ });
51
+
52
+ const record = await prisma.consentRecord.create({
53
+ data: {
54
+ subjectId,
55
+ consents,
56
+ version,
57
+ method: method ?? 'api',
58
+ lawfulBasis: lawfulBasis ?? null,
59
+ ipAddress: req.headers.get('x-forwarded-for'),
60
+ userAgent: req.headers.get('user-agent'),
61
+ },
62
+ });
63
+
64
+ await prisma.complianceAuditLog.create({
65
+ data: {
66
+ module: 'consent',
67
+ action: 'created',
68
+ entityId: record.id,
69
+ entityType: 'ConsentRecord',
70
+ changes: { subjectId, version, consents },
71
+ },
72
+ });
73
+
74
+ return NextResponse.json(record, { status: 201 });
75
+ }
76
+
77
+ // DELETE /api/consent?subjectId=xxx
78
+ export async function DELETE(req: NextRequest) {
79
+ const subjectId = req.nextUrl.searchParams.get('subjectId');
80
+
81
+ if (!subjectId) {
82
+ return NextResponse.json({ error: 'subjectId required' }, { status: 400 });
83
+ }
84
+
85
+ await prisma.consentRecord.updateMany({
86
+ where: { subjectId, revokedAt: null },
87
+ data: { revokedAt: new Date() },
88
+ });
89
+
90
+ await prisma.complianceAuditLog.create({
91
+ data: {
92
+ module: 'consent',
93
+ action: 'revoked',
94
+ entityId: subjectId,
95
+ entityType: 'ConsentRecord',
96
+ },
97
+ });
98
+
99
+ return NextResponse.json({ success: true });
100
+ }
@@ -0,0 +1,80 @@
1
+ /**
2
+ * Next.js App Router — DSR (Data Subject Rights) Route
3
+ * Generated by create-ndpr for: {{ORG_NAME}}
4
+ *
5
+ * NDPA Part IV §34–38 — rights to access, rectification, erasure,
6
+ * portability, and objection. All requests must be acknowledged
7
+ * within 72 hours and fulfilled within 30 days.
8
+ *
9
+ * Endpoints:
10
+ * GET /api/dsr — List DSR requests (optional ?status= filter)
11
+ * POST /api/dsr — Submit a new DSR request
12
+ */
13
+
14
+ import { NextRequest, NextResponse } from 'next/server';
15
+ import { PrismaClient } from '@prisma/client';
16
+
17
+ const prisma = new PrismaClient();
18
+
19
+ // GET /api/dsr?status=pending
20
+ export async function GET(req: NextRequest) {
21
+ const status = req.nextUrl.searchParams.get('status');
22
+
23
+ const requests = await prisma.dSRRequest.findMany({
24
+ where: status ? { status } : undefined,
25
+ orderBy: { submittedAt: 'desc' },
26
+ });
27
+
28
+ return NextResponse.json(requests);
29
+ }
30
+
31
+ // POST /api/dsr
32
+ export async function POST(req: NextRequest) {
33
+ const body = await req.json();
34
+ const {
35
+ type,
36
+ subjectName,
37
+ subjectEmail,
38
+ subjectPhone,
39
+ identifierType,
40
+ identifierValue,
41
+ description,
42
+ } = body;
43
+
44
+ if (!type || !subjectName || !subjectEmail || !identifierType || !identifierValue) {
45
+ return NextResponse.json(
46
+ { error: 'type, subjectName, subjectEmail, identifierType, and identifierValue are required' },
47
+ { status: 400 },
48
+ );
49
+ }
50
+
51
+ // NDPA mandates a 30-day response window from date of submission.
52
+ const dueAt = new Date();
53
+ dueAt.setDate(dueAt.getDate() + 30);
54
+
55
+ const request = await prisma.dSRRequest.create({
56
+ data: {
57
+ type,
58
+ subjectName,
59
+ subjectEmail,
60
+ subjectPhone: subjectPhone ?? null,
61
+ identifierType,
62
+ identifierValue,
63
+ description: description ?? null,
64
+ status: 'pending',
65
+ dueAt,
66
+ },
67
+ });
68
+
69
+ await prisma.complianceAuditLog.create({
70
+ data: {
71
+ module: 'dsr',
72
+ action: 'submitted',
73
+ entityId: request.id,
74
+ entityType: 'DSRRequest',
75
+ changes: { type, subjectEmail, status: 'pending' },
76
+ },
77
+ });
78
+
79
+ return NextResponse.json(request, { status: 201 });
80
+ }
@@ -0,0 +1,64 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * NDPRLayout — generated by create-ndpr
5
+ *
6
+ * Wraps your app with the NDPA-compliant consent banner and provider.
7
+ *
8
+ * Usage in app/layout.tsx:
9
+ *
10
+ * import NDPRLayout from '@/app/ndpr-layout';
11
+ *
12
+ * export default async function RootLayout({ children }) {
13
+ * const session = await getServerSession();
14
+ * return (
15
+ * <html lang="en">
16
+ * <body>
17
+ * <NDPRLayout userId={session?.user?.id}>
18
+ * {children}
19
+ * </NDPRLayout>
20
+ * </body>
21
+ * </html>
22
+ * );
23
+ * }
24
+ */
25
+
26
+ import React from 'react';
27
+ import { NDPRProvider } from '@tantainnovative/ndpr-toolkit/core';
28
+ import { NDPRConsent } from '@tantainnovative/ndpr-toolkit/presets';
29
+ import { apiAdapter } from '@tantainnovative/ndpr-toolkit/adapters';
30
+
31
+ interface NDPRLayoutProps {
32
+ children: React.ReactNode;
33
+ /**
34
+ * Authenticated user ID, or a stable anonymous identifier (e.g. UUID from
35
+ * a cookie) for unauthenticated visitors. NDPA requires consent to be
36
+ * recorded even for anonymous users.
37
+ */
38
+ userId?: string;
39
+ }
40
+
41
+ export default function NDPRLayout({ children, userId }: NDPRLayoutProps) {
42
+ const subjectId = userId ?? 'anonymous';
43
+
44
+ // apiAdapter connects the consent banner to the /api/consent route.
45
+ // Swap for prismaConsentAdapter or drizzleConsentAdapter for direct
46
+ // server-side usage in Server Components or Route Handlers.
47
+ const consentStorageAdapter = apiAdapter(`/api/consent?subjectId=${subjectId}`);
48
+
49
+ return (
50
+ <NDPRProvider
51
+ organizationName="{{ORG_NAME}}"
52
+ dpoEmail="{{DPO_EMAIL}}"
53
+ >
54
+ {children}
55
+
56
+ {/*
57
+ * NDPA-compliant consent banner.
58
+ * The adapter persists decisions server-side so the banner is hidden
59
+ * on return visits without flicker or double-prompting.
60
+ */}
61
+ <NDPRConsent adapter={consentStorageAdapter} />
62
+ </NDPRProvider>
63
+ );
64
+ }
@@ -0,0 +1,117 @@
1
+ // NDPA Compliance Schema — generated by create-ndpr
2
+ // Organisation: {{ORG_NAME}}
3
+ // DPO: {{DPO_EMAIL}}
4
+ //
5
+ // Covers:
6
+ // - ConsentRecord — NDPA §25–26 (consent & withdrawal)
7
+ // - DSRRequest — NDPA Part IV §34–38 (data subject rights)
8
+ // - BreachReport — NDPA §40 (72-hour breach notification)
9
+ // - ProcessingRecord — ROPA (accountability principle)
10
+ // - ComplianceAuditLog — §44 audit trail
11
+ //
12
+ // Run `prisma migrate dev --name ndpr-init` to apply.
13
+
14
+ generator client {
15
+ provider = "prisma-client-js"
16
+ }
17
+
18
+ datasource db {
19
+ provider = "postgresql"
20
+ url = env("DATABASE_URL")
21
+ }
22
+
23
+ model ConsentRecord {
24
+ id String @id @default(cuid())
25
+ subjectId String
26
+ consents Json
27
+ version String
28
+ method String
29
+ lawfulBasis String?
30
+ ipAddress String?
31
+ userAgent String?
32
+ createdAt DateTime @default(now())
33
+ revokedAt DateTime?
34
+
35
+ @@index([subjectId])
36
+ @@map("ndpr_consent_records")
37
+ }
38
+
39
+ model DSRRequest {
40
+ id String @id @default(cuid())
41
+ type String
42
+ status String @default("pending")
43
+ subjectName String
44
+ subjectEmail String
45
+ subjectPhone String?
46
+ identifierType String
47
+ identifierValue String
48
+ description String?
49
+ internalNotes String?
50
+ assignedTo String?
51
+ submittedAt DateTime @default(now())
52
+ acknowledgedAt DateTime?
53
+ completedAt DateTime?
54
+ dueAt DateTime
55
+
56
+ @@index([status])
57
+ @@index([subjectEmail])
58
+ @@map("ndpr_dsr_requests")
59
+ }
60
+
61
+ model BreachReport {
62
+ id String @id @default(cuid())
63
+ title String
64
+ description String
65
+ category String
66
+ severity String
67
+ status String @default("ongoing")
68
+ discoveredAt DateTime
69
+ occurredAt DateTime?
70
+ reportedAt DateTime @default(now())
71
+ ndpcNotifiedAt DateTime?
72
+ reporterName String
73
+ reporterEmail String
74
+ reporterDepartment String?
75
+ affectedSystems Json
76
+ dataTypes Json
77
+ estimatedAffected Int?
78
+ initialActions String?
79
+ ndpcNotificationSent Boolean @default(false)
80
+
81
+ @@index([status])
82
+ @@index([severity])
83
+ @@map("ndpr_breach_reports")
84
+ }
85
+
86
+ model ProcessingRecord {
87
+ id String @id @default(cuid())
88
+ purpose String
89
+ lawfulBasis String
90
+ dataCategories Json
91
+ dataSubjects Json
92
+ recipients Json
93
+ retentionPeriod String
94
+ securityMeasures Json
95
+ transferCountries Json?
96
+ transferMechanism String?
97
+ dpiaConducted Boolean @default(false)
98
+ status String @default("active")
99
+ createdAt DateTime @default(now())
100
+ updatedAt DateTime @updatedAt
101
+
102
+ @@map("ndpr_processing_records")
103
+ }
104
+
105
+ model ComplianceAuditLog {
106
+ id String @id @default(cuid())
107
+ module String
108
+ action String
109
+ entityId String
110
+ entityType String
111
+ changes Json?
112
+ performedBy String?
113
+ createdAt DateTime @default(now())
114
+
115
+ @@index([module, entityId])
116
+ @@map("ndpr_audit_log")
117
+ }