fix(api): wrap audit log writes inside their parent transactions
Prevents mutations from committing without an audit trail if the auditLog.create call fails after the main write already succeeded. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -135,18 +135,22 @@ export async function createRole(
|
||||
requirePermission(ctx, PermissionKey.MANAGE_ROLES);
|
||||
await assertRoleNameAvailable(ctx.db, input.name);
|
||||
|
||||
const role = await ctx.db.role.create({
|
||||
data: buildRoleCreateData(input),
|
||||
include: { _count: { select: { resourceRoles: true } } },
|
||||
});
|
||||
const role = await ctx.db.$transaction(async (tx) => {
|
||||
const created = await tx.role.create({
|
||||
data: buildRoleCreateData(input),
|
||||
include: { _count: { select: { resourceRoles: true } } },
|
||||
});
|
||||
|
||||
await ctx.db.auditLog.create({
|
||||
data: {
|
||||
entityType: "Role",
|
||||
entityId: role.id,
|
||||
action: "CREATE",
|
||||
changes: { after: role },
|
||||
},
|
||||
await tx.auditLog.create({
|
||||
data: {
|
||||
entityType: "Role",
|
||||
entityId: created.id,
|
||||
action: "CREATE",
|
||||
changes: { after: created },
|
||||
},
|
||||
});
|
||||
|
||||
return created;
|
||||
});
|
||||
|
||||
emitRoleCreated({ id: role.id, name: role.name });
|
||||
@@ -168,19 +172,23 @@ export async function updateRole(
|
||||
await assertRoleNameAvailable(ctx.db, input.data.name, input.id);
|
||||
}
|
||||
|
||||
const updated = await ctx.db.role.update({
|
||||
where: { id: input.id },
|
||||
data: buildRoleUpdateData(input.data),
|
||||
include: { _count: { select: { resourceRoles: true } } },
|
||||
});
|
||||
const updated = await ctx.db.$transaction(async (tx) => {
|
||||
const result = await tx.role.update({
|
||||
where: { id: input.id },
|
||||
data: buildRoleUpdateData(input.data),
|
||||
include: { _count: { select: { resourceRoles: true } } },
|
||||
});
|
||||
|
||||
await ctx.db.auditLog.create({
|
||||
data: {
|
||||
entityType: "Role",
|
||||
entityId: input.id,
|
||||
action: "UPDATE",
|
||||
changes: { before: existing, after: updated },
|
||||
},
|
||||
await tx.auditLog.create({
|
||||
data: {
|
||||
entityType: "Role",
|
||||
entityId: input.id,
|
||||
action: "UPDATE",
|
||||
changes: { before: existing, after: result },
|
||||
},
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
emitRoleUpdated({ id: updated.id, name: updated.name });
|
||||
@@ -213,15 +221,17 @@ export async function deleteRole(
|
||||
});
|
||||
}
|
||||
|
||||
await ctx.db.role.delete({ where: { id: input.id } });
|
||||
await ctx.db.$transaction(async (tx) => {
|
||||
await tx.role.delete({ where: { id: input.id } });
|
||||
|
||||
await ctx.db.auditLog.create({
|
||||
data: {
|
||||
entityType: "Role",
|
||||
entityId: input.id,
|
||||
action: "DELETE",
|
||||
changes: { before: role },
|
||||
},
|
||||
await tx.auditLog.create({
|
||||
data: {
|
||||
entityType: "Role",
|
||||
entityId: input.id,
|
||||
action: "DELETE",
|
||||
changes: { before: role },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
emitRoleDeleted(input.id);
|
||||
@@ -234,19 +244,23 @@ export async function deactivateRole(
|
||||
input: z.infer<typeof RoleIdInputSchema>,
|
||||
) {
|
||||
requirePermission(ctx, PermissionKey.MANAGE_ROLES);
|
||||
const role = await ctx.db.role.update({
|
||||
where: { id: input.id },
|
||||
data: { isActive: false },
|
||||
include: { _count: { select: { resourceRoles: true } } },
|
||||
});
|
||||
const role = await ctx.db.$transaction(async (tx) => {
|
||||
const result = await tx.role.update({
|
||||
where: { id: input.id },
|
||||
data: { isActive: false },
|
||||
include: { _count: { select: { resourceRoles: true } } },
|
||||
});
|
||||
|
||||
await ctx.db.auditLog.create({
|
||||
data: {
|
||||
entityType: "Role",
|
||||
entityId: input.id,
|
||||
action: "UPDATE",
|
||||
changes: { after: { isActive: false } },
|
||||
},
|
||||
await tx.auditLog.create({
|
||||
data: {
|
||||
entityType: "Role",
|
||||
entityId: input.id,
|
||||
action: "UPDATE",
|
||||
changes: { after: { isActive: false } },
|
||||
},
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
|
||||
emitRoleUpdated({ id: role.id, isActive: false });
|
||||
|
||||
Reference in New Issue
Block a user