Files
calendar/packages/features/watchlist/lib/service/OrganizationWatchlistQueryService.ts
T
Udit TakkarandGitHub 0330d14210 feat: system admin blocklist table (#25039)
* feat: system admin blocklist table

* chore: text

* fix: type error

* fix: type error

* fix: type error

* fix: frontend bug

* chore: improvments

* refactor: use shared components

* refactor: improvements

* fix: delete

* refactor: support multiple watchlist

* chore: update translation

* fix: type err

* fix: bulk bug

* tests: add unit test

* refactor: file

* refactor: file

* fix: type error

* fix: type error

* chore: translation

* refactor: use shared comments

* fix: imports

* fix: type error
2026-01-09 08:19:54 -03:00

129 lines
4.0 KiB
TypeScript

import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
import type { UserRepository } from "@calcom/features/users/repositories/UserRepository";
import type { WatchlistRepository } from "@calcom/features/watchlist/lib/repository/WatchlistRepository";
import { MembershipRole, WatchlistType, WatchlistSource } from "@calcom/prisma/enums";
import { WatchlistErrors } from "../errors/WatchlistErrors";
export interface ListWatchlistEntriesInput {
organizationId: number;
userId: number;
limit: number;
offset: number;
searchTerm?: string;
filters?: {
type?: WatchlistType;
source?: WatchlistSource;
};
}
export interface GetWatchlistEntryDetailsInput {
organizationId: number;
userId: number;
entryId: string;
}
type Deps = {
watchlistRepo: WatchlistRepository;
userRepo: UserRepository;
permissionCheckService: PermissionCheckService;
};
export class OrganizationWatchlistQueryService {
constructor(private readonly deps: Deps) {}
private async checkReadPermission(userId: number, organizationId: number): Promise<void> {
const hasPermission = await this.deps.permissionCheckService.checkPermission({
userId,
teamId: organizationId,
permission: "watchlist.read",
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
});
if (!hasPermission) {
throw WatchlistErrors.permissionDenied("You are not authorized to view watchlist entries");
}
}
async listWatchlistEntries(input: ListWatchlistEntriesInput) {
await this.checkReadPermission(input.userId, input.organizationId);
const result = await this.deps.watchlistRepo.findOrgAndGlobalEntries({
organizationId: input.organizationId,
limit: input.limit,
offset: input.offset,
searchTerm: input.searchTerm,
filters: input.filters,
});
const userIds = result.rows
.map((entry) => entry.latestAudit?.changedByUserId)
.filter((id): id is number => id !== null && id !== undefined);
const uniqueUserIds = Array.from(new Set(userIds));
const users = uniqueUserIds.length > 0 ? await this.deps.userRepo.findUsersByIds(uniqueUserIds) : [];
const userMap = new Map(users.map((u) => [u.id, u]));
const rowsWithCreators = result.rows.map((entry) => {
if (entry.latestAudit?.changedByUserId) {
const changedByUser = userMap.get(entry.latestAudit.changedByUserId);
return {
...entry,
latestAudit: {
...entry.latestAudit,
changedByUser,
},
};
}
return entry;
});
return {
rows: rowsWithCreators,
meta: result.meta,
};
}
async getWatchlistEntryDetails(input: GetWatchlistEntryDetailsInput) {
await this.checkReadPermission(input.userId, input.organizationId);
const result = await this.deps.watchlistRepo.findEntryWithAuditAndReports(input.entryId);
if (!result.entry) {
throw WatchlistErrors.notFound("Blocklist entry not found");
}
const isOrgEntry = result.entry.organizationId === input.organizationId;
const isGlobalEntry = result.entry.isGlobal && result.entry.organizationId === null;
if (!isOrgEntry && !isGlobalEntry) {
throw WatchlistErrors.permissionDenied(
"You can only view blocklist entries from your organization or global entries"
);
}
const userIds = result.auditHistory
.map((audit) => audit.changedByUserId)
.filter((id): id is number => id !== null && id !== undefined);
const uniqueUserIds = Array.from(new Set(userIds));
const users = uniqueUserIds.length > 0 ? await this.deps.userRepo.findUsersByIds(uniqueUserIds) : [];
const userMap = new Map(users.map((u) => [u.id, u]));
const auditHistoryWithUsers = result.auditHistory.map((audit) => ({
...audit,
changedByUser: audit.changedByUserId ? userMap.get(audit.changedByUserId) : undefined,
}));
return {
entry: result.entry,
auditHistory: auditHistoryWithUsers,
isReadOnly: isGlobalEntry,
};
}
}