Fas 1a: product analytics schema + GDPR controls
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
/**
|
||||
* 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";
|
||||
}
|
||||
Reference in New Issue
Block a user