Feat/admin spam table#2
Conversation
There was a problem hiding this comment.
The admin list handler ignores ctx.user and organizationId, so pass tenant scoping into the repository query before returning report data.
Also reported at: packages/trpc/server/routers/viewer/admin/listBookingReports.handler.ts L14–L22
Suggested fix
export const listBookingReportsHandler = async ({ ctx, input }: ListBookingReportsOptions) => {
const bookingReportRepo = new PrismaBookingReportRepository(prisma);
const result = await bookingReportRepo.findAllReportedBookings({
organizationId: ctx.user.organizationId ?? undefined,
skip: input.offset,
take: input.limit,
searchTerm: input.searchTerm,
filters: input.filters,
});Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert typescript developer with deep knowledge of security, performance, and best practices.
### Context
File: packages/lib/server/repository/bookingReport.interface.ts
Lines: 16-23
Issue Type: security-high
Severity: high
Issue Description:
The admin list handler ignores ctx.user and organizationId, so pass tenant scoping into the repository query before returning report data.
_Also reported at: `packages/trpc/server/routers/viewer/admin/listBookingReports.handler.ts` L14–L22_
Current Code:
export const listBookingReportsHandler = async ({ input }: ListBookingReportsOptions) => {
const bookingReportRepo = new PrismaBookingReportRepository(prisma);
const result = await bookingReportRepo.findAllReportedBookings({
skip: input.offset,
take: input.limit,
searchTerm: input.searchTerm,
filters: input.filters,
});
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow typescript best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
There was a problem hiding this comment.
deleteReport passes a non-unique filter to delete, so use deleteMany or a unique composite constraint that includes organizationId.
Suggested fix
async deleteReport(params: { reportId: string; organizationId?: number }): Promise<void> {
await this.prismaClient.bookingReport.deleteMany({
where: {
id: params.reportId,
...(params.organizationId !== undefined ? { organizationId: params.organizationId } : {}),
},
});
}Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert typescript developer with deep knowledge of security, performance, and best practices.
### Context
File: packages/lib/server/repository/bookingReport.interface.ts
Lines: 151-161
Issue Type: functional-medium
Severity: medium
Issue Description:
deleteReport passes a non-unique filter to delete, so use deleteMany or a unique composite constraint that includes organizationId.
Current Code:
async deleteReport(params: { reportId: string; organizationId?: number }): Promise<void> {
const where: Prisma.BookingReportWhereInput & { id: string } = {
id: params.reportId,
};
if (params.organizationId !== undefined) {
where.organizationId = params.organizationId;
}
await this.prismaClient.bookingReport.delete({
where,
});
}
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow typescript best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
Security Scan Summary
No critical security issues detected Scan completed in 30.2sSecurity scan powered by Codity.ai |
License Compliance Scan
Weak copyleft licenses found - verify compatibility Some packages have unknown licenses - manual review required Medium Risk Licenses - 7 packages(MPL-2.0 OR Apache-2.0) (2 packages):
MPL-2.0 (5 packages):
Unknown Licenses - 48 packages
...and 28 more Powered by Codity.ai · Docs |
Code Quality Report — test-org-codity/cal.com · PR #2Scanned: 2026-05-22 20:45 UTC | Score: 18/100 | Provider: github Executive Summary
Top Findings[CQ-LLM-001]
|
| File | Critical | High | Medium | Low | Total |
|---|---|---|---|---|---|
apps/web/modules/settings/admin/booking-reports-view.tsx |
0 | 1 | 4 | 106 | 111 |
apps/web/modules/settings/admin/components/add-to-blocklist-modal.tsx |
0 | 0 | 0 | 17 | 17 |
apps/web/modules/settings/admin/components/booking-report-details-sheet.tsx |
0 | 0 | 0 | 4 | 4 |
packages/lib/server/repository/bookingReport.test.ts |
0 | 0 | 0 | 4 | 4 |
packages/trpc/server/routers/viewer/admin/addToWatchlist.handler.test.ts |
0 | 0 | 0 | 1 | 1 |
packages/trpc/server/routers/viewer/admin/deleteBookingReport.handler.test.ts |
0 | 0 | 0 | 6 | 6 |
packages/trpc/server/routers/viewer/admin/listBookingReports.handler.test.ts |
0 | 0 | 0 | 18 | 18 |
Recommendations
- Resolve High severity issues, especially error handling gaps and performance bottlenecks.
- Run automated tests after applying fixes to verify no regressions.
|
@codity review |
Policy Check Failed✗ 2/3 policy checks failed: • Need 2 more approval(s) (0/2) — comment LGTM or approve via review ✓ 1 checks passed: To merge this PR:
|
|
PR review started! Estimated time: 5-10 minutes. Learn MoreAsk Codity questions: Mention Trigger a manual review: Comment Generate unit tests: Comment Run security scan again: Comment |
| const value = | ||
| input.type === "EMAIL" | ||
| ? report.bookerEmail | ||
| : report.bookerEmail.split("@")[1] || report.bookerEmail; |
There was a problem hiding this comment.
DOMAIN entries are created from bookerEmail.split("@")[1] || report.bookerEmail without validation, so malformed emails can store full email values under DOMAIN type; validate and normalize the extracted domain before use.
Suggested fix
const value =
input.type === "EMAIL"
? report.bookerEmail.trim().toLowerCase()
: (() => {
const domain = report.bookerEmail.split("@")[1]?.trim().toLowerCase();
if (!domain) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Invalid booker email for report ${report.id}`,
});
}
return domain;
})();Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert typescript developer with deep knowledge of security, performance, and best practices.
### Context
File: packages/trpc/server/routers/viewer/admin/addToWatchlist.handler.ts
Lines: 48-51
Issue Type: security-medium
Severity: medium
Issue Description:
DOMAIN entries are created from `bookerEmail.split("@")[1] || report.bookerEmail` without validation, so malformed emails can store full email values under DOMAIN type; validate and normalize the extracted domain before use.
Current Code:
const value =
input.type === "EMAIL"
? report.bookerEmail
: report.bookerEmail.split("@")[1] || report.bookerEmail;
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow typescript best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
|
@codity review |
Policy Check Failed✗ 2/3 policy checks failed: • Need 2 more approval(s) (0/2) — comment LGTM or approve via review ✓ 1 checks passed: To merge this PR:
|
PR SummaryWhat Changed
Key Changes by AreaAdmin UI: New tRPC API: Added Repository Layer: Extended Files Changed
Review Focus Areas
ArchitectureDesign Decisions: Global watchlist uses Risks: Global watchlist entries bypass organization isolation. This is intentional for spam prevention but creates central point of blocking. No soft delete on reports - permanent deletion only. |
| const onSubmit = (data: FormValues) => { | ||
| const reportIds = isBulk ? reports.map((r) => r.id) : [firstReport.id]; | ||
|
|
||
| addToWatchlistMutation.mutate({ |
There was a problem hiding this comment.
Bulk-adding two reports from different domains (e.g. reportIds for alice@gmail.com and bob@yahoo.com) with type DOMAIN silently blocks only the first domain because the modal offers one shared type/value while the server derives a separate value per report.
In bulk mode, either restrict submission to reports sharing the same domain/email target, or group reports by derived watchlist value and make the UI/handler create one entry per distinct value with an explicit confirmation of all values that will be blocked.
Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert tsx developer with deep knowledge of security, performance, and best practices.
### Context
File: apps/web/modules/settings/admin/components/add-to-blocklist-modal.tsx
Lines: 68-68
Issue Type: functional-high
Severity: high
Issue Description:
Bulk-adding two reports from different domains (e.g. reportIds for `alice@gmail.com` and `bob@yahoo.com`) with type `DOMAIN` silently blocks only the first domain because the modal offers one shared type/value while the server derives a separate value per report.
Current Code:
addToWatchlistMutation.mutate({
reportIds,
type: data.type,
description: data.description,
});
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow tsx best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
| async deleteReport(params: { reportId: string; organizationId?: number }): Promise<void> { | ||
| const where: Prisma.BookingReportWhereInput & { id: string } = { | ||
| id: params.reportId, | ||
| }; | ||
|
|
||
| if (params.organizationId !== undefined) { | ||
| where.organizationId = params.organizationId; | ||
| } | ||
|
|
||
| await this.prismaClient.bookingReport.delete({ | ||
| where, | ||
| }); |
There was a problem hiding this comment.
The delete call no longer scopes by organization when callers omit organizationId, so require an organization constraint here or enforce admin-only access before using this repository method.
Also reported at: packages/lib/server/repository/bookingReport.ts L137–L141
Suggested fix
async deleteReport(params: { reportId: string; organizationId: number }): Promise<void> {
await this.prismaClient.bookingReport.deleteMany({
where: {
id: params.reportId,
organizationId: params.organizationId,
},
});
}Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert typescript developer with deep knowledge of security, performance, and best practices.
### Context
File: packages/lib/server/repository/bookingReport.ts
Lines: 202-213
Issue Type: security-high
Severity: high
Issue Description:
The delete call no longer scopes by organization when callers omit organizationId, so require an organization constraint here or enforce admin-only access before using this repository method.
_Also reported at: `packages/lib/server/repository/bookingReport.ts` L137–L141_
Current Code:
async deleteReport(params: { reportId: string; organizationId?: number }): Promise<void> {
const where: Prisma.BookingReportWhereInput & { id: string } = {
id: params.reportId,
};
if (params.organizationId !== undefined) {
where.organizationId = params.organizationId;
}
await this.prismaClient.bookingReport.delete({
where,
});
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow typescript best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
| type: params.type, | ||
| value: params.value, | ||
| organizationId: params.organizationId, | ||
| isGlobal: params.isGlobal, |
There was a problem hiding this comment.
The create path still performs a separate existence check before insert, so concurrent requests can both pass the check and race into duplicate global entry creation; enforce a database unique constraint for global rows and handle the unique-violation error here.
Suggested fix
isGlobal: params.isGlobal,
});
if (existing) {
throw new Error("Watchlist entry already exists for this organization");
}
try {
const watchlist = await this.prismaClient.$transaction(async (tx) => {
const created = await tx.watchlist.create({
data: {
type: params.type,
value: params.value,
organizationId: params.organizationId ?? null,
action: params.action,
description: params.description,
source: WatchlistSource.MANUAL,
isGlobal: params.isGlobal ?? false,
},
});
await tx.watchlistAudit.create({
data: {
watchlistId: created.id,
type: params.type,
value: params.value,
description: params.description,
action: params.action,
changedByUserId: params.userId,
},
});
return created;
});
return watchlist;
} catch (error) {
if ((error as { code?: string }).code === "P2002") {
throw new Error("Watchlist entry already exists for this organization");
}
throw error;
}Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert typescript developer with deep knowledge of security, performance, and best practices.
### Context
File: packages/lib/server/repository/watchlist.repository.ts
Lines: 21-21
Issue Type: functional-high
Severity: high
Issue Description:
The create path still performs a separate existence check before insert, so concurrent requests can both pass the check and race into duplicate global entry creation; enforce a database unique constraint for global rows and handle the unique-violation error here.
Current Code:
isGlobal: params.isGlobal,
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow typescript best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
| const existingWatchlist = await watchlistRepo.checkExists({ | ||
| type: input.type, | ||
| value, | ||
| isGlobal: true, | ||
| }); | ||
|
|
||
| let watchlistId: string; | ||
|
|
||
| if (existingWatchlist) { | ||
| watchlistId = existingWatchlist.id; | ||
| } else { | ||
| const newWatchlist = await watchlistRepo.createEntry({ | ||
| type: input.type, | ||
| value, | ||
| organizationId: null, | ||
| action: WatchlistAction.BLOCK, | ||
| description: input.description, | ||
| userId: user.id, | ||
| isGlobal: true, | ||
| }); | ||
| watchlistId = newWatchlist.id; |
There was a problem hiding this comment.
The check-then-create flow is racy under concurrent requests, so create or link the watchlist entry in a transaction or use an atomic upsert.
Suggested fix
const watchlistEntry = await watchlistRepo.upsertEntry({
type: input.type,
value,
organizationId: null,
action: WatchlistAction.BLOCK,
description: input.description,
userId: user.id,
isGlobal: true,
});
const watchlistId = watchlistEntry.id;Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert typescript developer with deep knowledge of security, performance, and best practices.
### Context
File: packages/trpc/server/routers/viewer/admin/addToWatchlist.handler.ts
Lines: 53-73
Issue Type: functional-high
Severity: high
Issue Description:
The check-then-create flow is racy under concurrent requests, so create or link the watchlist entry in a transaction or use an atomic upsert.
Current Code:
const existingWatchlist = await watchlistRepo.checkExists({
type: input.type,
value,
isGlobal: true,
});
let watchlistId: string;
if (existingWatchlist) {
watchlistId = existingWatchlist.id;
} else {
const newWatchlist = await watchlistRepo.createEntry({
type: input.type,
value,
organizationId: null,
action: WatchlistAction.BLOCK,
description: input.description,
userId: user.id,
isGlobal: true,
});
watchlistId = newWatchlist.id;
}
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow typescript best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
| export const listBookingReportsHandler = async ({ input }: ListBookingReportsOptions) => { | ||
| const bookingReportRepo = new PrismaBookingReportRepository(prisma); | ||
|
|
||
| const result = await bookingReportRepo.findAllReportedBookings({ | ||
| skip: input.offset, | ||
| take: input.limit, | ||
| searchTerm: input.searchTerm, | ||
| filters: input.filters, | ||
| }); |
There was a problem hiding this comment.
This admin handler ignores ctx.user.organizationId and lists reports across all organizations, so pass organizationId into the repository query.
Also reported at: packages/trpc/server/routers/viewer/admin/deleteBookingReport.handler.ts L14–L19
Suggested fix
export const listBookingReportsHandler = async ({ ctx, input }: ListBookingReportsOptions) => {
const bookingReportRepo = new PrismaBookingReportRepository(prisma);
const result = await bookingReportRepo.findAllReportedBookings({
organizationId: ctx.user.organizationId ?? undefined,
skip: input.offset,
take: input.limit,
searchTerm: input.searchTerm,
filters: input.filters,
});Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert typescript developer with deep knowledge of security, performance, and best practices.
### Context
File: packages/trpc/server/routers/viewer/admin/listBookingReports.handler.ts
Lines: 14-22
Issue Type: security-high
Severity: high
Issue Description:
This admin handler ignores ctx.user.organizationId and lists reports across all organizations, so pass organizationId into the repository query.
_Also reported at: `packages/trpc/server/routers/viewer/admin/deleteBookingReport.handler.ts` L14–L19_
Current Code:
export const listBookingReportsHandler = async ({ input }: ListBookingReportsOptions) => {
const bookingReportRepo = new PrismaBookingReportRepository(prisma);
const result = await bookingReportRepo.findAllReportedBookings({
skip: input.offset,
take: input.limit,
searchTerm: input.searchTerm,
filters: input.filters,
});
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow typescript best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
| const numberOfSelectedRows = table.getFilteredSelectedRowModel().rows.length; | ||
|
|
||
| return ( | ||
| <> | ||
| <DataTableWrapper | ||
| table={table} | ||
| isPending={isPending} | ||
| paginationMode="standard" | ||
| totalRowCount={totalRowCount} | ||
| ToolbarLeft={ | ||
| <> | ||
| <DataTableToolbar.SearchBar /> | ||
| <DataTableFilters.FilterBar table={table} /> | ||
| </> | ||
| } | ||
| ToolbarRight={ | ||
| <> | ||
| <DataTableFilters.ClearFiltersButton /> | ||
| </> | ||
| }> | ||
| {numberOfSelectedRows > 0 && ( | ||
| <DataTableSelectionBar.Root className="!bottom-16 justify-center md:w-max"> | ||
| <p className="text-brand-subtle px-2 text-center text-xs leading-none sm:text-sm sm:font-medium"> | ||
| {t("number_selected", { count: numberOfSelectedRows })} | ||
| </p> | ||
| <BulkAddToBlocklist | ||
| reports={table.getSelectedRowModel().flatRows.map((row) => row.original)} | ||
| onSuccess={() => table.toggleAllPageRowsSelected(false)} |
There was a problem hiding this comment.
The selection bar count is computed from filtered rows but the bulk action sends all selected rows, so use the same selected-row model for both count and payload.
Suggested fix
const selectedRows = table.getSelectedRowModel().flatRows;
const numberOfSelectedRows = selectedRows.length;
...
<BulkAddToBlocklist
reports={selectedRows.map((row) => row.original)}
onSuccess={() => table.toggleAllPageRowsSelected(false)}
/>Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert tsx developer with deep knowledge of security, performance, and best practices.
### Context
File: apps/web/modules/settings/admin/booking-reports-view.tsx
Lines: 340-367
Issue Type: functional-medium
Severity: medium
Issue Description:
The selection bar count is computed from filtered rows but the bulk action sends all selected rows, so use the same selected-row model for both count and payload.
Current Code:
const numberOfSelectedRows = table.getFilteredSelectedRowModel().rows.length;
...
<BulkAddToBlocklist
reports={table.getSelectedRowModel().flatRows.map((row) => row.original)}
onSuccess={() => table.toggleAllPageRowsSelected(false)}
/>
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow tsx best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
| const value = | ||
| input.type === "EMAIL" | ||
| ? report.bookerEmail | ||
| : report.bookerEmail.split("@")[1] || report.bookerEmail; | ||
|
|
||
| const existingWatchlist = await watchlistRepo.checkExists({ | ||
| type: input.type, | ||
| value, | ||
| isGlobal: true, |
There was a problem hiding this comment.
Email and domain values are not normalized before lookup and creation, so lowercase and trim them to avoid duplicate logical entries.
Suggested fix
const rawValue =
input.type === "EMAIL"
? report.bookerEmail
: report.bookerEmail.split("@")[1] || report.bookerEmail;
const value = rawValue.trim().toLowerCase();
const existingWatchlist = await watchlistRepo.checkExists({
type: input.type,
value,
isGlobal: true,
});Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert typescript developer with deep knowledge of security, performance, and best practices.
### Context
File: packages/trpc/server/routers/viewer/admin/addToWatchlist.handler.ts
Lines: 48-56
Issue Type: robustness-medium
Severity: medium
Issue Description:
Email and domain values are not normalized before lookup and creation, so lowercase and trim them to avoid duplicate logical entries.
Current Code:
const value =
input.type === "EMAIL"
? report.bookerEmail
: report.bookerEmail.split("@")[1] || report.bookerEmail;
const existingWatchlist = await watchlistRepo.checkExists({
type: input.type,
value,
isGlobal: true,
});
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow typescript best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
Security Scan Summary
No critical security issues detected Scan completed in 34.0sSecurity scan powered by Codity.ai |
|
@codity review |
Policy Check Failed✗ 2/3 policy checks failed: • Need 2 more approval(s) (0/2) — comment LGTM or approve via review ✓ 1 checks passed: To merge this PR:
|
PR SummaryWhat Changed
Key Changes by AreaAdmin UI: New booking reports page with data table, filtering, search, and bulk actions for adding reporters to blocklist. tRPC API: Added Repository Layer: Extended Booking Reports: Updated Files Changed
Review Focus Areas
ArchitectureDesign Decisions: Global watchlist entries ( Risks: The domain extraction assumes email format validity. This is acceptable if upstream validation guarantees emails, but creates UI bugs if violated. The watchlist existence/create key mismatch is an intentional tradeoff for global matching but requires careful parameter handling to avoid org-scoped duplicates. Merge StatusNOT MERGEABLE — PR Score 10/100, below threshold (50)
|
| } | ||
|
|
||
| return { | ||
| hasWatchlist: hasWatchlistValue, |
There was a problem hiding this comment.
Selecting the Reason filter (e.g. reason=SPAM) has no effect because this view only translates the status column filter into RPC input while the server schema/repository added support for filters.reason, so a user filtering for spam still sees non-spam reports.
Suggested fix
Read the "reason" column filter from columnFilters and include it in the query input, e.g. return { hasWatchlist: hasWatchlistValue, reason: reasonValues } where reasonValues is the MULTI_SELECT data for the reason column.Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert tsx developer with deep knowledge of security, performance, and best practices.
### Context
File: apps/web/modules/settings/admin/booking-reports-view.tsx
Lines: 72-72
Issue Type: functional-medium
Severity: medium
Issue Description:
Selecting the Reason filter (e.g. reason=SPAM) has no effect because this view only translates the status column filter into RPC input while the server schema/repository added support for filters.reason, so a user filtering for spam still sees non-spam reports.
Current Code:
return {
hasWatchlist: hasWatchlistValue,
};
}, [columnFilters]);
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow tsx best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
| const handleModalClose = () => { | ||
| setShowModal(false); | ||
| onSuccess(); | ||
| }; |
There was a problem hiding this comment.
handleModalClose unconditionally calls onSuccess() when the modal closes, so clicking the close button or dismissing the dialog without actually adding anyone to the blocklist will still trigger the parent success flow. In the bulk add flow this can refresh the table and clear selection even though no mutation happened, which is misleading UI behavior.
Suggested fix
const handleModalClose = () => {
setShowModal(false);
};Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert bash developer with deep knowledge of security, performance, and best practices.
### Context
File: apps/web/modules/settings/admin/components/bulk-add-to-blocklist.tsx
Lines: 24-27
Issue Type: robustness-medium
Severity: medium
Issue Description:
`handleModalClose` unconditionally calls `onSuccess()` when the modal closes, so clicking the close button or dismissing the dialog without actually adding anyone to the blocklist will still trigger the parent success flow. In the bulk add flow this can refresh the table and clear selection even though no mutation happened, which is misleading UI behavior.
Current Code:
const handleModalClose = () => {
setShowModal(false);
onSuccess();
};
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
Security Scan Summary
No critical security issues detected Scan completed in 34.8sSecurity scan powered by Codity.ai |
License Compliance Scan
Weak copyleft licenses found - verify compatibility Some packages have unknown licenses - manual review required Medium Risk Licenses - 7 packages(MPL-2.0 OR Apache-2.0) (2 packages):
MPL-2.0 (5 packages):
Unknown Licenses - 48 packages
...and 28 more Powered by Codity.ai · Docs |
Code Quality Report — test-org-codity/cal.com · PR #2Scanned: 2026-05-23 11:04 UTC | Score: 17/100 | Provider: github Executive Summary
Top Findings[CQ-LLM-001]
|
| File | Critical | High | Medium | Low | Total |
|---|---|---|---|---|---|
apps/web/modules/settings/admin/booking-reports-view.tsx |
0 | 2 | 5 | 106 | 113 |
apps/web/modules/settings/admin/components/add-to-blocklist-modal.tsx |
0 | 0 | 0 | 17 | 17 |
apps/web/modules/settings/admin/components/booking-report-details-sheet.tsx |
0 | 0 | 0 | 4 | 4 |
packages/lib/server/repository/bookingReport.test.ts |
0 | 0 | 0 | 4 | 4 |
packages/trpc/server/routers/viewer/admin/addToWatchlist.handler.test.ts |
0 | 0 | 0 | 1 | 1 |
packages/trpc/server/routers/viewer/admin/deleteBookingReport.handler.test.ts |
0 | 0 | 0 | 6 | 6 |
packages/trpc/server/routers/viewer/admin/listBookingReports.handler.test.ts |
0 | 0 | 0 | 18 | 18 |
Recommendations
- Resolve High severity issues, especially error handling gaps and performance bottlenecks.
- Run automated tests after applying fixes to verify no regressions.
|
@codity review |
Policy Check Failed✗ 2/3 policy checks failed: • Need 2 more approval(s) (0/2) — comment LGTM or approve via review ✓ 1 checks passed: To merge this PR:
|
PR SummaryWhat Changed
Key Changes by AreaAdmin UI: New booking reports page at tRPC API: Three new admin procedures: Data Layer: Extended Booking Reports: Enhanced Files Changed
Review Focus Areas
ArchitectureDesign Decisions: Global watchlist uses Scalability & Extensibility: Pagination and filtering at database level. Bulk operations processed in batches. Out of scope: rate limiting on report submission, watchlist entry expiration. Risks: Global watchlist entries affect all organizations (intentional for spam prevention). Auto-cancellation is irreversible (intentional). Admin endpoints expose cross-org booking data (acceptable for admin role). Merge StatusNOT MERGEABLE — PR Score 11/100, below threshold (50)
|
| } | ||
|
|
||
| return { | ||
| hasWatchlist: hasWatchlistValue, |
There was a problem hiding this comment.
Selecting the Reason filter (for example SPAM) has no effect because this client only translates the status column filter into the TRPC request while the server schema/repository added filters.reason support.
Also derive the reason filter from columnFilters and include it in the filters object passed to listBookingReports, e.g. map the reason multi-select values to filters.reason alongside hasWatchlist.
Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert tsx developer with deep knowledge of security, performance, and best practices.
### Context
File: apps/web/modules/settings/admin/booking-reports-view.tsx
Lines: 72-72
Issue Type: functional-medium
Severity: medium
Issue Description:
Selecting the Reason filter (for example `SPAM`) has no effect because this client only translates the `status` column filter into the TRPC request while the server schema/repository added `filters.reason` support.
Current Code:
return {
hasWatchlist: hasWatchlistValue,
};
}, [columnFilters]);
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow tsx best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
| { | ||
| label: t("entire_domain"), | ||
| value: WatchlistType.DOMAIN, | ||
| description: `${t("all_emails_from")} @${firstReport.bookerEmail.split("@")[1]}`, |
There was a problem hiding this comment.
The domain option is built by splitting firstReport.bookerEmail on @ and indexing [1] twice; if a report ever contains a malformed or empty email, the UI will render All emails from @undefined and submit a misleading blocklist choice. For example, a report with bookerEmail = "no-at-sign" would show an invalid domain description to the admin.
| description: `${t("all_emails_from")} @${firstReport.bookerEmail.split("@")[1]}`, | |
| description: firstReport.bookerEmail.includes("@") ? `${t("all_emails_from")} @${firstReport.bookerEmail.split("@")[1]}` : t("all_emails_from") |
Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert bash developer with deep knowledge of security, performance, and best practices.
### Context
File: apps/web/modules/settings/admin/components/add-to-blocklist-modal.tsx
Lines: 101-101
Issue Type: robustness-medium
Severity: medium
Issue Description:
The domain option is built by splitting `firstReport.bookerEmail` on `@` and indexing `[1]` twice; if a report ever contains a malformed or empty email, the UI will render `All emails from @undefined` and submit a misleading blocklist choice. For example, a report with `bookerEmail = "no-at-sign"` would show an invalid domain description to the admin.
Current Code:
description: `${t("all_emails_from")} @${firstReport.bookerEmail.split("@")[1]}`,
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
| const reportsToAdd = reports.filter((report) => !report.watchlistId); | ||
|
|
||
| const handleModalClose = () => { | ||
| setShowModal(false); | ||
| onSuccess(); | ||
| }; | ||
|
|
||
| if (reportsToAdd.length === 0) { | ||
| return null; | ||
| } |
There was a problem hiding this comment.
This bulk action silently drops any selected reports that already have a watchlistId and returns nothing when the filtered list is empty, so an admin can click the button and see no modal or feedback if every selected row is already blocked. That makes the bulk action appear broken for a valid selection state and prevents the user from understanding why nothing happened.
Suggested fix
const reportsToAdd = reports.filter((report) => !report.watchlistId);
if (reportsToAdd.length === 0) {
return (
<Button color="secondary" StartIcon="shield-check" disabled>
{t("add_to_blocklist")}
</Button>
);
}Prompt for AI assistance
Copy the prompt below and paste it into ChatGPT, Claude, or any LLM:
You are an expert bash developer with deep knowledge of security, performance, and best practices.
### Context
File: apps/web/modules/settings/admin/components/bulk-add-to-blocklist.tsx
Lines: 22-31
Issue Type: robustness-medium
Severity: medium
Issue Description:
This bulk action silently drops any selected reports that already have a `watchlistId` and returns nothing when the filtered list is empty, so an admin can click the button and see no modal or feedback if every selected row is already blocked. That makes the bulk action appear broken for a valid selection state and prevents the user from understanding why nothing happened.
Current Code:
const reportsToAdd = reports.filter((report) => !report.watchlistId);
if (reportsToAdd.length === 0) {
return null;
}
---
### Instructions
1. Fix the issue described above
2. Maintain the exact indentation and code style from the original
3. Follow bash best practices and language-specific idioms
4. Ensure the fix addresses the root cause, not just the symptoms
5. Add brief inline comments explaining the fix if needed
### Constraints
- Do not change functionality beyond fixing the identified issue
- Preserve existing variable names and function signatures unless they are part of the problem
- Ensure the fix is production-ready
---
Security Scan Summary
No critical security issues detected Scan completed in 30.4sSecurity scan powered by Codity.ai |
License Compliance Scan
Weak copyleft licenses found - verify compatibility Some packages have unknown licenses - manual review required Medium Risk Licenses - 7 packages(MPL-2.0 OR Apache-2.0) (2 packages):
MPL-2.0 (5 packages):
Unknown Licenses - 48 packages
...and 28 more Powered by Codity.ai · Docs |
Code Quality Report — test-org-codity/cal.com · PR #2Scanned: 2026-05-23 11:19 UTC | Score: 18/100 | Provider: github Executive Summary
Top Findings[CQ-LLM-001]
|
| File | Critical | High | Medium | Low | Total |
|---|---|---|---|---|---|
apps/web/modules/settings/admin/booking-reports-view.tsx |
0 | 1 | 4 | 106 | 111 |
apps/web/modules/settings/admin/components/add-to-blocklist-modal.tsx |
0 | 0 | 0 | 17 | 17 |
apps/web/modules/settings/admin/components/booking-report-details-sheet.tsx |
0 | 0 | 0 | 4 | 4 |
packages/lib/server/repository/bookingReport.test.ts |
0 | 0 | 0 | 4 | 4 |
packages/trpc/server/routers/viewer/admin/addToWatchlist.handler.test.ts |
0 | 0 | 0 | 1 | 1 |
packages/trpc/server/routers/viewer/admin/deleteBookingReport.handler.test.ts |
0 | 0 | 0 | 6 | 6 |
packages/trpc/server/routers/viewer/admin/listBookingReports.handler.test.ts |
0 | 0 | 0 | 18 | 18 |
Recommendations
- Resolve High severity issues, especially error handling gaps and performance bottlenecks.
- Run automated tests after applying fixes to verify no regressions.
What does this PR do?
Visual Demo (For contributors especially)
A visual demonstration is strongly recommended, for both the original and new change (video / image - any one).
Video Demo (if applicable):
Image Demo (if applicable):
Mandatory Tasks (DO NOT REMOVE)
How should this be tested?
Checklist