auth-bulk-ops.ts117 lines · main
1/**
2 * Bulk operations for briven auth tenants (Phase 6.4).
3 *
4 * Dashboard-driven bulk actions on users: ban, delete, invite.
5 * Each operation returns per-item results so the UI can show partial
6 * successes (e.g. 47 of 50 banned, 3 not found).
7 */
8
9import { runInProjectDatabase } from '../db/data-plane.js';
10import { banUser } from './auth-security.js';
11import { createOrgInvite } from './auth-orgs.js';
12
13export interface BulkResult {
14 processed: number;
15 succeeded: number;
16 failed: number;
17 errors: Array<{ index: number; userId?: string; email?: string; message: string }>;
18}
19
20const BATCH_SIZE = 100;
21
22export async function bulkBanUsers(
23 projectId: string,
24 userIds: string[],
25 reason?: string,
26): Promise<BulkResult> {
27 const result: BulkResult = { processed: 0, succeeded: 0, failed: 0, errors: [] };
28 const ids = userIds.slice(0, BATCH_SIZE);
29
30 for (let i = 0; i < ids.length; i++) {
31 const userId = ids[i]!;
32 result.processed++;
33 try {
34 await banUser(projectId, userId, { reason });
35 result.succeeded++;
36 } catch (err) {
37 result.failed++;
38 result.errors.push({
39 index: i,
40 userId,
41 message: err instanceof Error ? err.message : String(err),
42 });
43 }
44 }
45
46 return result;
47}
48
49export async function bulkDeleteUsers(
50 projectId: string,
51 userIds: string[],
52): Promise<BulkResult> {
53 const result: BulkResult = { processed: 0, succeeded: 0, failed: 0, errors: [] };
54 const ids = userIds.slice(0, BATCH_SIZE);
55
56 await runInProjectDatabase(projectId, async (tx) => {
57 for (let i = 0; i < ids.length; i++) {
58 const userId = ids[i]!;
59 result.processed++;
60 try {
61 // Cascading delete via FKs handles sessions, accounts, org membership, etc.
62 await tx.unsafe(
63 `DELETE FROM "_briven_auth_users" WHERE id = $1`,
64 [userId] as never[],
65 );
66 result.succeeded++;
67 } catch (err) {
68 result.failed++;
69 result.errors.push({
70 index: i,
71 userId,
72 message: err instanceof Error ? err.message : String(err),
73 });
74 }
75 }
76 });
77
78 return result;
79}
80
81export interface BulkInviteInput {
82 orgId: string;
83 emails: string[];
84 role?: 'admin' | 'member';
85 invitedBy: string;
86}
87
88export async function bulkInviteUsers(
89 projectId: string,
90 input: BulkInviteInput,
91): Promise<BulkResult> {
92 const result: BulkResult = { processed: 0, succeeded: 0, failed: 0, errors: [] };
93 const emails = input.emails.slice(0, BATCH_SIZE);
94
95 for (let i = 0; i < emails.length; i++) {
96 const email = emails[i]!;
97 result.processed++;
98 try {
99 await createOrgInvite(
100 projectId,
101 input.orgId,
102 input.invitedBy,
103 { email, role: input.role ?? 'member' },
104 );
105 result.succeeded++;
106 } catch (err) {
107 result.failed++;
108 result.errors.push({
109 index: i,
110 email,
111 message: err instanceof Error ? err.message : String(err),
112 });
113 }
114 }
115
116 return result;
117}