Files
Cibello-app/packages/database/src/analytics-gdpr.ts
T
2026-08-05 19:51:37 +07:00

51 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* GDPR helpers for product analytics.
* Hard deletion SET NULL is not enough because anonymous_id can link rows.
*
* Consent model: product_analytics is managed via user_consents
* (CONSENT_KINDS in @app/shared-types). It is registered as granted by default
* on account creation (legitimate interest with opt-out). Users revoke it via
* PUT /v1/me/consents { kind: "product_analytics", granted: false }.
*
* Semantics for existing accounts created before this feature:
* - Missing user_consents row for product_analytics → treated as granted.
* - Row with status = "granted" → granted.
* - Row with status = "revoked" or "denied" → not granted.
*/
import type { NodePgDatabase } from "drizzle-orm/node-postgres";
import { and, eq } from "drizzle-orm";
import * as schema from "./schema/index.js";
export type DbClient = NodePgDatabase<typeof schema>;
/**
* Delete all product analytics events for a user.
* Called during account deletion (spec §56).
*/
export async function deleteUserAnalyticsEvents(db: DbClient, userId: string): Promise<number> {
const result = await db
.delete(schema.productAnalyticsEvents)
.where(eq(schema.productAnalyticsEvents.userId, userId));
return result.rowCount ?? 0;
}
/**
* Check if a user has analytics collection enabled.
* Missing row → granted (legitimate interest, opt-out).
* Only an explicit revoked/denied row disables collection.
*/
export async function isAnalyticsOptedIn(db: DbClient, userId: string): Promise<boolean> {
const [row] = await db
.select({ status: schema.userConsents.status })
.from(schema.userConsents)
.where(
and(
eq(schema.userConsents.userId, userId),
eq(schema.userConsents.kind, "product_analytics"),
),
)
.limit(1);
if (!row) return true; // default granted for existing accounts
return row.status === "granted";
}