feat: add invitations management and import wizard; enhance audit logging and member detail fetching
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 4m53s
Workflow / ⚫️ Test (push) Has been skipped

This commit is contained in:
T. Zehetbauer
2026-04-01 19:02:55 +02:00
parent 080ec1cb47
commit db4e19c3af
10 changed files with 1847 additions and 104 deletions

View File

@@ -172,3 +172,102 @@ export const lockRecord = authActionClient
return { success: true, data: record };
});
export const bulkImportRecords = authActionClient
.inputSchema(
z.object({
moduleId: z.string().uuid(),
accountId: z.string().uuid(),
records: z.array(z.record(z.string(), z.unknown())).min(1).max(1000),
dryRun: z.boolean().default(false),
}),
)
.action(async ({ parsedInput: input, ctx }) => {
const client = getSupabaseServerClient();
const logger = await getLogger();
const api = createModuleBuilderApi(client);
const userId = ctx.user.id;
const moduleWithFields = await api.modules.getModuleWithFields(
input.moduleId,
);
if (!moduleWithFields) {
throw new Error('Module not found');
}
const { fields } = moduleWithFields;
const errors: Array<{ row: number; field: string; message: string }> = [];
const validRows: Array<Record<string, unknown>> = [];
for (let i = 0; i < input.records.length; i++) {
const row = input.records[i]!;
const validation = validateRecordData(
row,
fields as Parameters<typeof validateRecordData>[1],
);
if (!validation.success) {
for (const err of validation.errors) {
errors.push({ row: i + 1, field: err.field, message: err.message });
}
} else {
validRows.push(row);
}
}
if (input.dryRun) {
return {
success: true,
data: {
totalRows: input.records.length,
validRows: validRows.length,
errorCount: errors.length,
errors: errors.slice(0, 50),
},
};
}
if (errors.length > 0) {
return {
success: false,
error: `${errors.length} Validierungsfehler in ${new Set(errors.map((e) => e.row)).size} Zeilen`,
validationErrors: errors.slice(0, 50).map((e) => ({
field: `Zeile ${e.row}: ${e.field}`,
message: e.message,
})),
};
}
logger.info(
{
name: 'records.bulkImport',
moduleId: input.moduleId,
count: validRows.length,
},
'Bulk importing records...',
);
const insertData = validRows.map((row) => ({
module_id: input.moduleId,
account_id: input.accountId,
data: row as any,
status: 'active' as const,
created_by: userId,
updated_by: userId,
}));
const { error } = await client.from('module_records').insert(insertData);
if (error) throw error;
logger.info(
{ name: 'records.bulkImport', count: validRows.length },
'Bulk import complete',
);
return {
success: true,
data: { imported: validRows.length },
};
});

View File

@@ -34,5 +34,36 @@ export function createAuditService(client: SupabaseClient<Database>) {
);
}
},
async query(opts?: {
accountId?: string;
userId?: string;
tableName?: string;
action?: string;
page?: number;
pageSize?: number;
}) {
let q = client
.from('audit_log')
.select('*', { count: 'exact' })
.order('created_at', { ascending: false });
if (opts?.accountId) q = q.eq('account_id', opts.accountId);
if (opts?.userId) q = q.eq('user_id', opts.userId);
if (opts?.tableName) q = q.eq('table_name', opts.tableName);
if (opts?.action)
q = q.eq(
'action',
opts.action as 'insert' | 'update' | 'delete' | 'lock',
);
const page = opts?.page ?? 1;
const pageSize = opts?.pageSize ?? 50;
q = q.range((page - 1) * pageSize, page * pageSize - 1);
const { data, error, count } = await q;
if (error) throw error;
return { data: data ?? [], total: count ?? 0, page, pageSize };
},
};
}