feat: implement TASK-WP1-05 account deletion
This commit is contained in:
@@ -0,0 +1,46 @@
|
||||
import { createRequire } from "node:module";
|
||||
|
||||
import type BetterSqlite3 from "better-sqlite3";
|
||||
|
||||
const require = createRequire(import.meta.url);
|
||||
const Database = require("better-sqlite3") as typeof BetterSqlite3;
|
||||
|
||||
export class RetentionCleanup {
|
||||
private readonly clock: () => number;
|
||||
private readonly database: BetterSqlite3.Database;
|
||||
private retentionPurgeActive = false;
|
||||
|
||||
constructor(input: { clock?: () => number; databasePath: string }) {
|
||||
this.clock = input.clock ?? Date.now;
|
||||
const nativeBinding = process.env.DADA_SQLITE_NATIVE_BINDING;
|
||||
this.database = new Database(input.databasePath, nativeBinding ? { nativeBinding } : undefined);
|
||||
this.database.pragma("foreign_keys = ON");
|
||||
this.database.pragma("busy_timeout = 5000");
|
||||
this.database.function("dada_allow_privacy_purge", { deterministic: false }, () => 0);
|
||||
this.database.function("dada_allow_retention_purge", { deterministic: false }, () => this.retentionPurgeActive ? 1 : 0);
|
||||
}
|
||||
|
||||
purgeExpired() {
|
||||
const now = this.clock();
|
||||
this.database.exec("BEGIN IMMEDIATE");
|
||||
try {
|
||||
this.retentionPurgeActive = true;
|
||||
const privateAccess = this.database.prepare("DELETE FROM private_content_access_logs WHERE expires_at <= ?").run(now);
|
||||
const anonymous = this.database.prepare("DELETE FROM anonymous_retained_events WHERE expires_at <= ?").run(now);
|
||||
this.retentionPurgeActive = false;
|
||||
this.database.exec("COMMIT");
|
||||
return {
|
||||
anonymous_events: anonymous.changes,
|
||||
private_access_logs: privateAccess.changes,
|
||||
};
|
||||
} catch (error) {
|
||||
this.retentionPurgeActive = false;
|
||||
if (this.database.inTransaction) this.database.exec("ROLLBACK");
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
close() {
|
||||
this.database.close();
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,7 @@ import { join } from "node:path";
|
||||
|
||||
import { WorkerAiCallGate } from "./ai-call-gate.js";
|
||||
import { readConfiguredLocalDataRoot } from "./runtime-config.js";
|
||||
import { RetentionCleanup } from "./retention-cleanup.js";
|
||||
import { StructuredJsonlLogger } from "./structured-log.js";
|
||||
import { WorkerStorageStatus } from "./storage-status.js";
|
||||
import { attachWorkerSupervisorControl, initializeWorkerCredentialClient, receiveWorkerCredentials } from "./supervisor-channel.js";
|
||||
@@ -26,14 +27,31 @@ if (!workerPort && process.argv.includes("--dada-credential-stdin")) {
|
||||
if (controlPipeIndex < 0 || !controlPipe) throw new Error("Supervisor control pipe name is required.");
|
||||
const keepAlive = setInterval(() => undefined, 30_000);
|
||||
let storage: WorkerStorageStatus | undefined;
|
||||
let retention: RetentionCleanup | undefined;
|
||||
let retentionTimer: ReturnType<typeof setInterval> | undefined;
|
||||
const control = attachWorkerSupervisorControl(controlPipe, () => {
|
||||
clearInterval(keepAlive);
|
||||
if (retentionTimer) clearInterval(retentionTimer);
|
||||
retention?.close();
|
||||
storage?.close();
|
||||
});
|
||||
let storageStatus: "active" | "unavailable" = "active";
|
||||
try {
|
||||
const dataRoot = readConfiguredLocalDataRoot();
|
||||
storage = new WorkerStorageStatus(join(dataRoot, "db", "dada.sqlite3"));
|
||||
const databasePath = join(dataRoot, "db", "dada.sqlite3");
|
||||
storage = new WorkerStorageStatus(databasePath);
|
||||
retention = new RetentionCleanup({ databasePath });
|
||||
const runRetentionCleanup = () => {
|
||||
try {
|
||||
retention?.purgeExpired();
|
||||
} catch {
|
||||
storageStatus = "unavailable";
|
||||
storage?.markLogUnavailable();
|
||||
control.reportStatus("storage_unavailable");
|
||||
}
|
||||
};
|
||||
runRetentionCleanup();
|
||||
retentionTimer = setInterval(runRetentionCleanup, 24 * 60 * 60 * 1_000);
|
||||
const logger = new StructuredJsonlLogger({
|
||||
component: "worker",
|
||||
directory: join(dataRoot, "logs", "worker"),
|
||||
|
||||
Reference in New Issue
Block a user