feat(ee): audit logs (#1977)

feat: clickhouse driver
* sync
* updates
This commit is contained in:
Philip Okugbe
2026-03-01 01:29:03 +00:00
committed by GitHub
parent 85ce0d32bf
commit 69d7532c6c
62 changed files with 2600 additions and 191 deletions
@@ -14,6 +14,11 @@ import { SpaceMemberRepo } from '@docmost/db/repos/space/space-member.repo';
import { UserRepo } from '@docmost/db/repos/user/user.repo';
import { executeTx } from '@docmost/db/utils';
import { WatcherRepo } from '@docmost/db/repos/watcher/watcher.repo';
import { AuditEvent, AuditResource } from '../../../common/events/audit-events';
import {
AUDIT_SERVICE,
IAuditService,
} from '../../../integrations/audit/audit.service';
@Injectable()
export class GroupUserService {
@@ -25,6 +30,7 @@ export class GroupUserService {
private groupService: GroupService,
private readonly watcherRepo: WatcherRepo,
@InjectKysely() private readonly db: KyselyDB,
@Inject(AUDIT_SERVICE) private readonly auditService: IAuditService,
) {}
async getGroupUsers(
@@ -72,6 +78,20 @@ export class GroupUserService {
.values(groupUsersToInsert)
.onConflict((oc) => oc.columns(['userId', 'groupId']).doNothing())
.execute();
for (const user of validUsers) {
this.auditService.log({
event: AuditEvent.GROUP_MEMBER_ADDED,
resourceType: AuditResource.GROUP,
resourceId: groupId,
changes: {
after: {
userId: user.id,
userName: user.name,
},
},
});
}
}
async removeUserFromGroup(
@@ -115,8 +135,24 @@ export class GroupUserService {
await this.watcherRepo.deleteByUsersWithoutSpaceAccess(
[userId],
spaceId,
{ trx },
);
}
});
this.auditService.log({
event: AuditEvent.GROUP_MEMBER_REMOVED,
resourceType: AuditResource.GROUP,
resourceId: groupId,
changes: {
before: {
userId: user.id,
userName: user.name,
},
},
metadata: {
groupName: group.name,
},
});
}
}
@@ -18,6 +18,12 @@ import { GroupUserService } from './group-user.service';
import { WatcherRepo } from '@docmost/db/repos/watcher/watcher.repo';
import { executeTx } from '@docmost/db/utils';
import { InjectKysely } from 'nestjs-kysely';
import { AuditEvent, AuditResource } from '../../../common/events/audit-events';
import { diffAuditTrackedFields } from '../../../common/helpers';
import {
AUDIT_SERVICE,
IAuditService,
} from '../../../integrations/audit/audit.service';
@Injectable()
export class GroupService {
@@ -29,6 +35,7 @@ export class GroupService {
private groupUserService: GroupUserService,
private readonly watcherRepo: WatcherRepo,
@InjectKysely() private readonly db: KyselyDB,
@Inject(AUDIT_SERVICE) private readonly auditService: IAuditService,
) {}
async getGroupInfo(groupId: string, workspaceId: string): Promise<Group> {
@@ -74,6 +81,18 @@ export class GroupService {
);
}
this.auditService.log({
event: AuditEvent.GROUP_CREATED,
resourceType: AuditResource.GROUP,
resourceId: createdGroup.id,
changes: {
after: {
name: createdGroup.name,
description: createdGroup.description,
},
},
});
return createdGroup;
}
@@ -95,6 +114,8 @@ export class GroupService {
throw new BadRequestException('You cannot update a default group');
}
const groupBefore = { name: group.name, description: group.description };
if (updateGroupDto.name) {
const existingGroup = await this.groupRepo.findByName(
updateGroupDto.name,
@@ -121,6 +142,22 @@ export class GroupService {
workspaceId,
);
const changes = diffAuditTrackedFields(
['name', 'description'],
updateGroupDto,
groupBefore,
group,
);
if (changes) {
this.auditService.log({
event: AuditEvent.GROUP_UPDATED,
resourceType: AuditResource.GROUP,
resourceId: group.id,
changes,
});
}
return group;
}
@@ -154,6 +191,18 @@ export class GroupService {
);
}
});
this.auditService.log({
event: AuditEvent.GROUP_DELETED,
resourceType: AuditResource.GROUP,
resourceId: groupId,
changes: {
before: {
name: group.name,
description: group.description,
},
},
});
}
async findAndValidateGroup(