@cosmicdrift/kumiko-bundled-features 0.2.1 → 0.2.3
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.
- package/CHANGELOG.md +108 -0
- package/package.json +12 -6
- package/src/auth-email-password/auth-user-row.ts +6 -0
- package/src/auth-email-password/constants.ts +11 -0
- package/src/auth-email-password/handlers/login.write.ts +31 -1
- package/src/auth-email-password/i18n.ts +4 -0
- package/src/compliance-profiles/README.md +88 -0
- package/src/compliance-profiles/__tests__/compliance-profiles.integration.ts +308 -0
- package/src/compliance-profiles/__tests__/seeding.integration.ts +93 -0
- package/src/compliance-profiles/feature.ts +51 -0
- package/src/compliance-profiles/handlers/for-tenant.query.ts +63 -0
- package/src/compliance-profiles/handlers/list-profiles.query.ts +44 -0
- package/src/compliance-profiles/handlers/needs-profile.query.ts +56 -0
- package/src/compliance-profiles/handlers/set-profile.write.ts +146 -0
- package/src/compliance-profiles/handlers/sub-processors.query.ts +43 -0
- package/src/compliance-profiles/index.ts +6 -0
- package/src/compliance-profiles/resolve-for-tenant.ts +61 -0
- package/src/compliance-profiles/schema/profile-selection.ts +52 -0
- package/src/compliance-profiles/seeding.ts +96 -0
- package/src/data-retention/__tests__/data-retention.integration.ts +49 -0
- package/src/data-retention/__tests__/keep-for.test.ts +77 -0
- package/src/data-retention/__tests__/override-schema.test.ts +96 -0
- package/src/data-retention/__tests__/policy-for.integration.ts +172 -0
- package/src/data-retention/__tests__/resolver.test.ts +201 -0
- package/src/data-retention/_internal/parse-override.ts +33 -0
- package/src/data-retention/feature.ts +57 -0
- package/src/data-retention/handlers/policy-for.query.ts +57 -0
- package/src/data-retention/index.ts +18 -0
- package/src/data-retention/keep-for.ts +75 -0
- package/src/data-retention/override-schema.ts +37 -0
- package/src/data-retention/presets.ts +72 -0
- package/src/data-retention/resolve-for-tenant.ts +50 -0
- package/src/data-retention/resolver.ts +107 -0
- package/src/data-retention/schema/tenant-retention-override.ts +47 -0
- package/src/file-foundation/feature.ts +43 -3
- package/src/file-foundation/index.ts +1 -0
- package/src/file-provider-inmemory/feature.ts +6 -3
- package/src/file-provider-s3/feature.ts +8 -10
- package/src/files/README.md +50 -0
- package/src/files/__tests__/files.integration.ts +157 -0
- package/src/files/feature.ts +34 -0
- package/src/files/index.ts +1 -0
- package/src/files/schema/file-ref.ts +58 -0
- package/src/files-provider-s3/s3-provider.ts +89 -0
- package/src/secrets/__tests__/require-secrets-context.test.ts +81 -0
- package/src/secrets/feature.ts +10 -6
- package/src/sessions/constants.ts +4 -0
- package/src/sessions/feature.ts +3 -0
- package/src/sessions/handlers/revoke-all-for-user.write.ts +42 -0
- package/src/tier-engine/__tests__/auto-default-tier.integration.ts +118 -0
- package/src/tier-engine/feature.ts +16 -6
- package/src/user/__tests__/user-status.test.ts +39 -0
- package/src/user/index.ts +11 -1
- package/src/user/schema/user.ts +76 -0
- package/src/user-data-rights/COMPLIANCE.md +182 -0
- package/src/user-data-rights/README.md +109 -0
- package/src/user-data-rights/__tests__/audit-log.integration.ts +199 -0
- package/src/user-data-rights/__tests__/cross-data-matrix.integration.ts +349 -0
- package/src/user-data-rights/__tests__/download.integration.ts +565 -0
- package/src/user-data-rights/__tests__/export-job-idempotency.integration.ts +244 -0
- package/src/user-data-rights/__tests__/export-job-schema.test.ts +163 -0
- package/src/user-data-rights/__tests__/policy-to-strategy.test.ts +30 -0
- package/src/user-data-rights/__tests__/request-cancel-deletion.integration.ts +370 -0
- package/src/user-data-rights/__tests__/request-deletion-callback.integration.ts +179 -0
- package/src/user-data-rights/__tests__/request-export.integration.ts +269 -0
- package/src/user-data-rights/__tests__/restriction-flow.integration.ts +309 -0
- package/src/user-data-rights/__tests__/run-export-jobs.integration.ts +1124 -0
- package/src/user-data-rights/__tests__/run-forget-cleanup.integration.ts +703 -0
- package/src/user-data-rights/__tests__/run-user-export.integration.ts +291 -0
- package/src/user-data-rights/__tests__/token-helpers.test.ts +63 -0
- package/src/user-data-rights/__tests__/user-data-rights.integration.ts +57 -0
- package/src/user-data-rights/__tests__/zip-path.test.ts +119 -0
- package/src/user-data-rights/audit-download.ts +125 -0
- package/src/user-data-rights/feature.ts +309 -0
- package/src/user-data-rights/handlers/cancel-deletion.write.ts +84 -0
- package/src/user-data-rights/handlers/download-by-job.query.ts +209 -0
- package/src/user-data-rights/handlers/download-by-token.query.ts +257 -0
- package/src/user-data-rights/handlers/export-status.query.ts +76 -0
- package/src/user-data-rights/handlers/lift-restriction.write.ts +68 -0
- package/src/user-data-rights/handlers/list-download-attempts.query.ts +53 -0
- package/src/user-data-rights/handlers/my-audit-log.query.ts +63 -0
- package/src/user-data-rights/handlers/request-deletion.write.ts +123 -0
- package/src/user-data-rights/handlers/request-export.write.ts +155 -0
- package/src/user-data-rights/handlers/restrict-account.write.ts +81 -0
- package/src/user-data-rights/handlers/run-forget-cleanup.write.ts +61 -0
- package/src/user-data-rights/i18n.ts +37 -0
- package/src/user-data-rights/index.ts +19 -0
- package/src/user-data-rights/run-export-jobs.ts +878 -0
- package/src/user-data-rights/run-forget-cleanup.ts +334 -0
- package/src/user-data-rights/run-user-export.ts +211 -0
- package/src/user-data-rights/schema/download-attempt.ts +37 -0
- package/src/user-data-rights/schema/download-token.ts +111 -0
- package/src/user-data-rights/schema/export-job.ts +166 -0
- package/src/user-data-rights/token-helpers.ts +67 -0
- package/src/user-data-rights/zip-path.ts +94 -0
- package/src/user-data-rights-defaults/__tests__/user-data-rights-defaults.integration.ts +337 -0
- package/src/user-data-rights-defaults/feature.ts +40 -0
- package/src/user-data-rights-defaults/hooks/file-ref.userdata-hook.ts +109 -0
- package/src/user-data-rights-defaults/hooks/user.userdata-hook.ts +91 -0
- package/src/user-data-rights-defaults/index.ts +6 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
// POST /api/user/request-export (S2.U3 Atom 2) — DSGVO Art. 15 + 20 Trigger.
|
|
2
|
+
//
|
|
3
|
+
// User triggert Export-Job. Persistenz via createEventStoreExecutor
|
|
4
|
+
// (ES-Pattern, Memory: ES kein CRUD). Idempotency-Strategie zweistufig:
|
|
5
|
+
//
|
|
6
|
+
// 1) **App-side-Pre-Check (primaerer Pfad):** fetchOne aktive Jobs
|
|
7
|
+
// (status pending|running) fuer den userId. Wenn existing,
|
|
8
|
+
// return `{jobId, isExisting: true}` — KEIN neues Event,
|
|
9
|
+
// sauberer Audit-Trail (1 Klick-Storm = 1 Event, nicht N).
|
|
10
|
+
//
|
|
11
|
+
// 2) **DB-Constraint als Race-Schutz (Sekundaerpfad):** Bei parallelem
|
|
12
|
+
// Klick zwischen fetchOne + crud.create wirft die Tx 23505 mit
|
|
13
|
+
// constraint_name === "read_export_jobs_one_active_per_user".
|
|
14
|
+
// Catch + re-fetch + return existing als isExisting=true. Race-
|
|
15
|
+
// Window <1ms, in Production extrem selten.
|
|
16
|
+
//
|
|
17
|
+
// **Cross-Tenant-Semantik:** ExportJob ist tenant-agnostisch (1 Job pro
|
|
18
|
+
// userId ueber alle Memberships). Pre-Check nutzt ctx.db.raw (kein
|
|
19
|
+
// TenantDb-Filter) — Alice klickt aus Tenant A, klickt dann aus Tenant
|
|
20
|
+
// B → Pre-Check aus B findet den A-Job, kein 2. Job entsteht.
|
|
21
|
+
// `requestedFromTenantId` persistiert den Initial-Tenant aus dem
|
|
22
|
+
// 1. Klick — Worker liest sein Compliance-Profile aus DIESEM Tenant
|
|
23
|
+
// fuer Job-TTL/Stale/Cleanup.
|
|
24
|
+
|
|
25
|
+
import { createEventStoreExecutor, fetchOne } from "@cosmicdrift/kumiko-framework/db";
|
|
26
|
+
import { defineWriteHandler, type SaveContext } from "@cosmicdrift/kumiko-framework/engine";
|
|
27
|
+
import type { WriteFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
28
|
+
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
29
|
+
import { eq, inArray } from "drizzle-orm";
|
|
30
|
+
import { z } from "zod";
|
|
31
|
+
import {
|
|
32
|
+
ACTIVE_JOB_CONSTRAINT,
|
|
33
|
+
EXPORT_JOB_STATUS,
|
|
34
|
+
exportJobEntity,
|
|
35
|
+
exportJobsTable,
|
|
36
|
+
} from "../schema/export-job";
|
|
37
|
+
|
|
38
|
+
const crud = createEventStoreExecutor(exportJobsTable, exportJobEntity, {
|
|
39
|
+
entityName: "export-job",
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Race-Loss-Detection: createEventStoreExecutor.create catched 23505
|
|
44
|
+
* intern + returnt `WriteFailure(UniqueViolationError)`, **kein throw**.
|
|
45
|
+
* Wir checken den Failure-Code + constraintName um den App-side-vs-Race-
|
|
46
|
+
* Pfad zu unterscheiden. Andere Failures (validation, version-conflict,
|
|
47
|
+
* ...) propagieren unveraendert.
|
|
48
|
+
*/
|
|
49
|
+
function isActiveJobConflict(failure: WriteFailure): boolean {
|
|
50
|
+
const error = failure.error as {
|
|
51
|
+
code?: string;
|
|
52
|
+
details?: { constraintName?: string };
|
|
53
|
+
};
|
|
54
|
+
return (
|
|
55
|
+
error.code === "unique_violation" && error.details?.constraintName === ACTIVE_JOB_CONSTRAINT
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const requestExportWrite = defineWriteHandler({
|
|
60
|
+
name: "request-export",
|
|
61
|
+
schema: z.object({}),
|
|
62
|
+
access: { openToAll: true },
|
|
63
|
+
handler: async (event, ctx) => {
|
|
64
|
+
const userId = event.user.id;
|
|
65
|
+
const T = getTemporal();
|
|
66
|
+
const now = T.Now.instant();
|
|
67
|
+
|
|
68
|
+
// Pre-Check: ctx.db.raw weil ExportJob tenant-agnostisch ist —
|
|
69
|
+
// der TenantDb-Wrapper wuerde Cross-Tenant-Jobs ausblenden.
|
|
70
|
+
const existing = await findActiveJob(ctx.db.raw, userId);
|
|
71
|
+
if (existing) {
|
|
72
|
+
// Snapshot-Status (kann zwischen fetchOne + Response stale werden
|
|
73
|
+
// wenn Worker parallel den State flippt; window minimal). User
|
|
74
|
+
// pollt fuer Live-Wahrheit ueber export-status.query.
|
|
75
|
+
return {
|
|
76
|
+
isSuccess: true as const,
|
|
77
|
+
data: {
|
|
78
|
+
jobId: existing.id,
|
|
79
|
+
status: existing.status,
|
|
80
|
+
isExisting: true,
|
|
81
|
+
},
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Kein active Job — neuen anlegen via crud.create. requestedFromTenantId
|
|
86
|
+
// = aktueller Tenant des Users; Worker liest sein Compliance-Profile
|
|
87
|
+
// aus diesem Tenant.
|
|
88
|
+
const result = await crud.create(
|
|
89
|
+
{
|
|
90
|
+
userId,
|
|
91
|
+
requestedFromTenantId: event.user.tenantId,
|
|
92
|
+
requestedAt: now,
|
|
93
|
+
tenantId: event.user.tenantId,
|
|
94
|
+
},
|
|
95
|
+
event.user,
|
|
96
|
+
ctx.db,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
if (!result.isSuccess) {
|
|
100
|
+
// Race-Pfad: paralleler Klick hat zwischen findActiveJob + crud.create
|
|
101
|
+
// einen aktiven Job angelegt → UniqueViolationError mit unserem
|
|
102
|
+
// Constraint. Re-fetch + return existing als isExisting=true.
|
|
103
|
+
// Andere Failures (validation, version-conflict, ...) propagieren.
|
|
104
|
+
if (isActiveJobConflict(result)) {
|
|
105
|
+
const winner = await findActiveJob(ctx.db.raw, userId);
|
|
106
|
+
if (!winner) {
|
|
107
|
+
// Sollte nie passieren — Constraint-Violation ohne existing
|
|
108
|
+
// active Job hiesse der Constraint matcht etwas anderes.
|
|
109
|
+
// Original-Failure zurueckgeben damit das auffaellt.
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
return {
|
|
113
|
+
isSuccess: true as const,
|
|
114
|
+
data: {
|
|
115
|
+
jobId: winner.id,
|
|
116
|
+
status: winner.status,
|
|
117
|
+
isExisting: true,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return result;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Happy path: neuer Job. SaveContext.id ist EntityId (number | string);
|
|
125
|
+
// exportJobEntity hat idType:"uuid" → garantiert string, String()
|
|
126
|
+
// schuetzt vor Drift falls jemand die Entity auf serial migriert.
|
|
127
|
+
const created = result.data as SaveContext;
|
|
128
|
+
return {
|
|
129
|
+
isSuccess: true as const,
|
|
130
|
+
data: {
|
|
131
|
+
jobId: String(created.id),
|
|
132
|
+
// Snapshot: just-created → pending. Live-Wahrheit kommt ueber
|
|
133
|
+
// export-status.query, status hier ist nice-to-have fuer das
|
|
134
|
+
// initial UI-Render ("Anfrage angenommen, currently pending").
|
|
135
|
+
status: EXPORT_JOB_STATUS.Pending,
|
|
136
|
+
isExisting: false,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
},
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
async function findActiveJob(
|
|
143
|
+
db: import("@cosmicdrift/kumiko-framework/db").DbRunner,
|
|
144
|
+
userId: string,
|
|
145
|
+
): Promise<{ id: string; status: string } | null> {
|
|
146
|
+
// @cast-boundary db-row — fetchOne liefert generic DbRow.
|
|
147
|
+
// Variadic-conditions werden intern mit AND verknuepft.
|
|
148
|
+
const row = (await fetchOne(
|
|
149
|
+
db,
|
|
150
|
+
exportJobsTable,
|
|
151
|
+
eq(exportJobsTable["userId"], userId),
|
|
152
|
+
inArray(exportJobsTable["status"], [EXPORT_JOB_STATUS.Pending, EXPORT_JOB_STATUS.Running]),
|
|
153
|
+
)) as { id: string; status: string } | null;
|
|
154
|
+
return row;
|
|
155
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { createSystemUser, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
2
|
+
import { UnprocessableError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
3
|
+
import { eq } from "drizzle-orm";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { USER_STATUS, userTable } from "../../user";
|
|
6
|
+
|
|
7
|
+
// POST /api/user/restrict (S2.U6) — DSGVO Art. 18 Account-Freeze.
|
|
8
|
+
// Flippt status=Active → Restricted und revoked alle live sessions
|
|
9
|
+
// des Users via cross-feature ctx.writeAs(sessions.revokeAllForUser).
|
|
10
|
+
//
|
|
11
|
+
// Plan-Doc-Verhalten ("Schreib-API geblockt"):
|
|
12
|
+
// - Login geblockt: login.write.ts checked status=Restricted (Atom 3).
|
|
13
|
+
// - Active sessions: revoked durch cross-feature-call (sessions-feature
|
|
14
|
+
// muss gemountet sein). App-Author ohne sessions-feature kriegt einen
|
|
15
|
+
// Boot-Resolver-Error via r.usesApi("sessions.revokeAllForUser").
|
|
16
|
+
//
|
|
17
|
+
// State-Transitions:
|
|
18
|
+
// Active → Restricted ✓ (dieser Handler)
|
|
19
|
+
// Restricted → Restricted ✗ 422 already_restricted (Idempotenz-Guard)
|
|
20
|
+
// DeletionRequested → ... ✗ 422 user_not_in_active_state
|
|
21
|
+
// Deleted → ... ✗ 422 user_not_in_active_state
|
|
22
|
+
export const restrictAccountWrite = defineWriteHandler({
|
|
23
|
+
name: "restrict-account",
|
|
24
|
+
schema: z.object({}),
|
|
25
|
+
access: { openToAll: true },
|
|
26
|
+
handler: async (event, ctx) => {
|
|
27
|
+
// ctx.db.raw weil User-Entity tenant-agnostisch ist (analog
|
|
28
|
+
// request-deletion.write.ts Cross-Tenant-Section).
|
|
29
|
+
const userRow = await ctx.db.raw
|
|
30
|
+
.select({ status: userTable["status"] })
|
|
31
|
+
.from(userTable)
|
|
32
|
+
.where(eq(userTable["id"], event.user.id))
|
|
33
|
+
.limit(1);
|
|
34
|
+
|
|
35
|
+
if (userRow.length === 0) {
|
|
36
|
+
return writeFailure(
|
|
37
|
+
new UnprocessableError("user_not_found", {
|
|
38
|
+
details: { reason: "user_not_found", userId: event.user.id },
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const currentStatus = userRow[0]?.status;
|
|
44
|
+
if (currentStatus === USER_STATUS.Restricted) {
|
|
45
|
+
return writeFailure(
|
|
46
|
+
new UnprocessableError("already_restricted", {
|
|
47
|
+
details: { reason: "already_restricted", currentStatus },
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
if (currentStatus !== USER_STATUS.Active) {
|
|
52
|
+
return writeFailure(
|
|
53
|
+
new UnprocessableError("user_not_in_active_state", {
|
|
54
|
+
details: { reason: "user_not_in_active_state", currentStatus },
|
|
55
|
+
}),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
await ctx.db.raw
|
|
60
|
+
.update(userTable)
|
|
61
|
+
.set({ status: USER_STATUS.Restricted })
|
|
62
|
+
.where(eq(userTable["id"], event.user.id));
|
|
63
|
+
|
|
64
|
+
// Cross-Feature: alle live sessions revoken — sonst koennte der User
|
|
65
|
+
// mit existierendem JWT bis zur Token-Expiry weiter schreiben.
|
|
66
|
+
// ctx.writeAs(systemUser, ...) damit der privileged-Handler die
|
|
67
|
+
// System-User-Roles im access-gate hat.
|
|
68
|
+
const systemUser = createSystemUser(event.user.tenantId);
|
|
69
|
+
await ctx.writeAs(systemUser, "sessions:write:user-session:revoke-all-for-user", {
|
|
70
|
+
userId: event.user.id,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
isSuccess: true as const,
|
|
75
|
+
data: {
|
|
76
|
+
userId: event.user.id,
|
|
77
|
+
status: USER_STATUS.Restricted,
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
},
|
|
81
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
// POST /userDataRights/run-forget-cleanup (S2.U5b).
|
|
2
|
+
//
|
|
3
|
+
// Cron-Trigger fuer den Forget-Cleanup-Pipeline. Der Handler wrapt nur
|
|
4
|
+
// `runForgetCleanup` damit er via dispatcher (System-User) aufrufbar
|
|
5
|
+
// wird — die Pipeline-Logik selbst lebt im pure-function Modul
|
|
6
|
+
// `run-forget-cleanup.ts` (testbar ohne dispatcher).
|
|
7
|
+
//
|
|
8
|
+
// Access: privileged — nur System-Caller (cron, ops-script). Im Cron-
|
|
9
|
+
// Setup laeuft der Job mit `createSystemUser(...)` als executor.
|
|
10
|
+
//
|
|
11
|
+
// Rueckgabe: Stats fuer Operator-Monitoring (processed-count, error-list).
|
|
12
|
+
|
|
13
|
+
import { access, defineWriteHandler } from "@cosmicdrift/kumiko-framework/engine";
|
|
14
|
+
import { InternalError, writeFailure } from "@cosmicdrift/kumiko-framework/errors";
|
|
15
|
+
import { getTemporal } from "@cosmicdrift/kumiko-framework/time";
|
|
16
|
+
import { z } from "zod";
|
|
17
|
+
import { runForgetCleanup, type SendDeletionExecutedEmailFn } from "../run-forget-cleanup";
|
|
18
|
+
|
|
19
|
+
export type RunForgetCleanupOptions = {
|
|
20
|
+
readonly sendDeletionExecutedEmail?: SendDeletionExecutedEmailFn;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export function createRunForgetCleanupHandler(opts: RunForgetCleanupOptions = {}) {
|
|
24
|
+
return defineWriteHandler({
|
|
25
|
+
name: "run-forget-cleanup",
|
|
26
|
+
schema: z.object({}),
|
|
27
|
+
access: { roles: access.privileged },
|
|
28
|
+
handler: async (_event, ctx) => {
|
|
29
|
+
if (!ctx.registry) {
|
|
30
|
+
return writeFailure(
|
|
31
|
+
new InternalError({
|
|
32
|
+
message: "run-forget-cleanup: ctx.registry missing",
|
|
33
|
+
}),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// ctx.db.raw ist DbRunner. runForgetCleanup oeffnet pro User eine
|
|
38
|
+
// Sub-Tx (SAVEPOINT wenn Outer-Dispatcher-Tx aktiv) — siehe
|
|
39
|
+
// run-forget-cleanup.ts Header. Kein DbConnection-Cast noetig.
|
|
40
|
+
const T = getTemporal();
|
|
41
|
+
const result = await runForgetCleanup({
|
|
42
|
+
db: ctx.db.raw,
|
|
43
|
+
registry: ctx.registry,
|
|
44
|
+
now: T.Now.instant(),
|
|
45
|
+
...(opts.sendDeletionExecutedEmail && {
|
|
46
|
+
sendDeletionExecutedEmail: opts.sendDeletionExecutedEmail,
|
|
47
|
+
}),
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return {
|
|
51
|
+
isSuccess: true as const,
|
|
52
|
+
data: {
|
|
53
|
+
processedUserIds: result.processedUserIds,
|
|
54
|
+
hookCallsAttempted: result.hookCallsAttempted,
|
|
55
|
+
errorCount: result.errors.length,
|
|
56
|
+
errors: result.errors,
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// @runtime client
|
|
2
|
+
//
|
|
3
|
+
// Default-Translations fuer user-data-rights Error-Wire-Keys (S2.U3 Atom 4b.fix3).
|
|
4
|
+
//
|
|
5
|
+
// Wire-Errors aus den download-handlers tragen i18nKeys statt fixe
|
|
6
|
+
// Strings — UI rendert die Keys ueber den Renderer-LocaleProvider. Apps
|
|
7
|
+
// koennen einzelne Keys via `userDataRightsClient({ translations: { de:
|
|
8
|
+
// {...} } })` ueberschreiben (Pattern matched auth-email-password).
|
|
9
|
+
//
|
|
10
|
+
// **Scope 4b.fix3:** nur die download-error-keys. Andere Keys (UI-
|
|
11
|
+
// Texte fuer Forget-Pfad, Status-Labels, Banner) kommen mit Atom 6+
|
|
12
|
+
// (UI-Integration).
|
|
13
|
+
|
|
14
|
+
import type { TranslationsByLocale } from "@cosmicdrift/kumiko-renderer";
|
|
15
|
+
|
|
16
|
+
export const defaultTranslations: TranslationsByLocale = {
|
|
17
|
+
de: {
|
|
18
|
+
"userDataRights.errors.download.notFound":
|
|
19
|
+
"Der Download-Link ist ungültig oder gehört zu einem anderen Konto.",
|
|
20
|
+
"userDataRights.errors.download.expired":
|
|
21
|
+
"Dein Download ist abgelaufen. Bitte fordere einen neuen Export an.",
|
|
22
|
+
"userDataRights.errors.download.unavailable":
|
|
23
|
+
"Der Export ist noch nicht fertig oder fehlgeschlagen. Bitte schau im Status-Polling nach.",
|
|
24
|
+
"userDataRights.errors.download.signedUrlNotSupported":
|
|
25
|
+
"Der Download steht aufgrund einer Server-Konfiguration aktuell nicht zur Verfügung. Der Operator wurde benachrichtigt.",
|
|
26
|
+
},
|
|
27
|
+
en: {
|
|
28
|
+
"userDataRights.errors.download.notFound":
|
|
29
|
+
"The download link is invalid or belongs to a different account.",
|
|
30
|
+
"userDataRights.errors.download.expired":
|
|
31
|
+
"Your download has expired. Please request a new export.",
|
|
32
|
+
"userDataRights.errors.download.unavailable":
|
|
33
|
+
"The export is not yet ready or has failed. Please check the status endpoint.",
|
|
34
|
+
"userDataRights.errors.download.signedUrlNotSupported":
|
|
35
|
+
"The download is currently unavailable due to a server configuration issue. The operator has been notified.",
|
|
36
|
+
},
|
|
37
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export { createUserDataRightsFeature, type UserDataRightsOptions } from "./feature";
|
|
2
|
+
export type {
|
|
3
|
+
SendExportFailedEmailFn,
|
|
4
|
+
SendExportReadyEmailFn,
|
|
5
|
+
} from "./run-export-jobs";
|
|
6
|
+
export type { SendDeletionExecutedEmailFn } from "./run-forget-cleanup";
|
|
7
|
+
// Runner-Exports — App-Tests dürfen export/forget deterministisch laufen
|
|
8
|
+
// lassen, statt über den Job-Cron zu warten (siehe sample
|
|
9
|
+
// user-data-rights-demo).
|
|
10
|
+
export { runForgetCleanup } from "./run-forget-cleanup";
|
|
11
|
+
export type { UserExportBundle } from "./run-user-export";
|
|
12
|
+
export { runUserExport } from "./run-user-export";
|
|
13
|
+
export {
|
|
14
|
+
ACTIVE_JOB_CONSTRAINT,
|
|
15
|
+
EXPORT_JOB_STATUS,
|
|
16
|
+
type ExportJobStatus,
|
|
17
|
+
exportJobEntity,
|
|
18
|
+
exportJobsTable,
|
|
19
|
+
} from "./schema/export-job";
|