/** * 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; /** * Delete all product analytics events for a user. * Called during account deletion (spec §56). */ export async function deleteUserAnalyticsEvents(db: DbClient, userId: string): Promise { 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 { 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"; }