Skip to content

Feat/admin spam table#2

Open
DhirenMhatre wants to merge 59 commits into
mainfrom
feat/admin-spam-table
Open

Feat/admin spam table#2
DhirenMhatre wants to merge 59 commits into
mainfrom
feat/admin-spam-table

Conversation

@DhirenMhatre

Copy link
Copy Markdown

What does this PR do?

  • Fixes #XXXX (GitHub issue number)
  • Fixes CAL-XXXX (Linear issue number - should be visible at the bottom of the GitHub issue description)

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):

  • Show screen recordings of the issue or feature.
  • Demonstrate how to reproduce the issue, the behavior before and after the change.

Image Demo (if applicable):

  • Add side-by-side screenshots of the original and updated change.
  • Highlight any significant change(s).

Mandatory Tasks (DO NOT REMOVE)

  • I have self-reviewed the code (A decent size PR without self-review might be rejected).
  • I have updated the developer docs in /docs if this PR makes changes that would require a documentation change. If N/A, write N/A here and check the checkbox.
  • I confirm automated tests are in place that prove my fix is effective or that my feature works.

How should this be tested?

  • Are there environment variables that should be set?
  • What are the minimal test data to have?
  • What is expected (happy path) to have (input and output)?
  • Any other important info that could help to test that PR

Checklist

  • I haven't read the contributing guide
  • My code doesn't follow the style guidelines of this project
  • I haven't commented my code, particularly in hard-to-understand areas
  • I haven't checked if my changes generate no new warnings

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security High

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

---


Like Dislike Create Issue Jira

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functional Medium

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

---


Like Dislike Create Issue Jira

@codity-dm

codity-dm Bot commented May 22, 2026

Copy link
Copy Markdown

Nitpicks (Low Priority)

Found 1 low-priority suggestions for code improvement

Click to expand nitpicks

apps/web/modules/settings/admin/components/add-to-blocklist-modal.tsx (line 101)

Robustness Low

Splitting the email without validating the presence of '@' can render an undefined domain label; guard the split or fall back safely.

Also reported at: apps/web/modules/settings/admin/components/add-to-blocklist-modal.tsx L114

Code Suggestion or Comments
description: `${t("all_emails_from")} @$
{firstReport.bookerEmail.includes("@") ? firstReport.bookerEmail.split("@")[1] : ""}`,
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: 101-101
Issue Type: robustness-low
Severity: low

Issue Description:
Splitting the email without validating the presence of '@' can render an undefined domain label; guard the split or fall back safely.

_Also reported at: `apps/web/modules/settings/admin/components/add-to-blocklist-modal.tsx` L114_

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 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

---



Like Dislike

@codity-dm

codity-dm Bot commented May 22, 2026

Copy link
Copy Markdown

Security Scan Summary

Metric Value
Vulnerabilities Critical: 0
Overall Risk Clean
Files Scanned 23

No critical security issues detected

Scan completed in 30.2s

Security scan powered by Codity.ai

@codity-dm

codity-dm Bot commented May 22, 2026

Copy link
Copy Markdown

License Compliance Scan

Metric Value
Packages Scanned 442
High Risk (Strong Copyleft) 0
Medium Risk (Weak Copyleft) 7
Low Risk (Permissive) 387
Unknown License 48

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):

  • dompurify 3.2.3
  • dompurify 3.1.7

MPL-2.0 (5 packages):

  • @vercel/og 0.6.3
  • ical.js 1.4.0
  • eslint-config-turbo 0.0.7
  • web-push 3.6.7
  • @dub/analytics 0.0.27
Unknown Licenses - 48 packages
  • @types/react-dom 18
  • @types/react 18
  • @calcom/eslint-config workspace:*
  • react 18
  • @calcom/atoms workspace:*
  • react-dom 18
  • postcss 8
  • @calcom/config workspace:*
  • @calcom/dayjs workspace:*
  • @calcom/tsconfig workspace:*
  • @calcom/types workspace:*
  • @calcom/dailyvideo workspace:*
  • @calcom/features workspace:*
  • @calcom/lib workspace:*
  • @calcom/ui workspace:*
  • @calcom/office365video workspace:*
  • @calcom/zoomvideo workspace:*
  • @calcom/prisma workspace:*
  • @calcom/app-store workspace:*
  • @calcom/emails workspace:*

...and 28 more

Powered by Codity.ai · Docs

@codity-dm

codity-dm Bot commented May 22, 2026

Copy link
Copy Markdown

Code Quality Report — test-org-codity/cal.com · PR #2

Scanned: 2026-05-22 20:45 UTC | Score: 18/100 | Provider: github

Executive Summary

Severity Count
Critical 0
High 1
Medium 4
Low 156
Top Findings

[CQ-LLM-001] apps/web/modules/settings/admin/booking-reports-view.tsx:1 (Complexity · HIGH)

Issue: The BookingReportsTable function is quite large and complex, which may lead to maintainability issues.
Suggestion: Consider breaking down the BookingReportsTable function into smaller, more manageable functions.

function BookingReportsTable() { ... }

[CQ-LLM-002] apps/web/modules/settings/admin/booking-reports-view.tsx:1 (Error_Handling · MEDIUM)

Issue: The error handling in the deleteReportMutation does not account for all possible error scenarios.
Suggestion: Enhance error handling to cover more specific cases and provide user feedback accordingly.

onError: (error) => { showToast(error.message, 'error'); },

[CQ-LLM-003] apps/web/modules/settings/admin/booking-reports-view.tsx:1 (Performance · MEDIUM)

Issue: The use of multiple state variables for modal visibility can lead to unnecessary re-renders.
Suggestion: Consider using a single state object to manage modal visibility to reduce re-renders.

const [showWatchlistModal, setShowWatchlistModal] = useState(false); ...

[CQ-LLM-004] apps/web/modules/settings/admin/booking-reports-view.tsx:1 (Documentation · MEDIUM)

Issue: Missing documentation for the BookingReportsTable function and its purpose.
Suggestion: Add a docstring to describe the function's purpose, parameters, and return value.

function BookingReportsTable() { ... }

[CQ-001] apps/web/modules/settings/admin/booking-reports-view.tsx:44 (Complexity · MEDIUM)

Issue: Function/method has 389 added lines, exceeding 200-line threshold
Suggestion: Break into smaller, single-responsibility functions

[CQ-LLM-005] apps/web/modules/settings/admin/booking-reports-view.tsx:1 (Maintainability · LOW)

Issue: Magic strings are used for status values in the BookingReportsTable.
Suggestion: Define constants for status values to improve readability and maintainability.

accessorFn: (row) => (row.watchlistId ? 'blocked' : 'pending'),

[CQ-011] apps/web/modules/settings/admin/booking-reports-view.tsx:26 (Duplication · LOW)

Issue: Duplicate import: 'import {'
Suggestion: Remove the duplicate import statement

import {

[CQ-008] apps/web/modules/settings/admin/booking-reports-view.tsx:112 (Maintainability · LOW)

Issue: Magic number 30 in code
Suggestion: Extract to a named constant

size: 30,

[CQ-008] apps/web/modules/settings/admin/booking-reports-view.tsx:140 (Maintainability · LOW)

Issue: Magic number 180 in code
Suggestion: Extract to a named constant

size: 180,

[CQ-008] apps/web/modules/settings/admin/booking-reports-view.tsx:147 (Maintainability · LOW)

Issue: Magic number 90 in code
Suggestion: Extract to a named constant

size: 90,

Per-File Breakdown

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

  1. Resolve High severity issues, especially error handling gaps and performance bottlenecks.
  • Run automated tests after applying fixes to verify no regressions.

@DhirenMhatre

Copy link
Copy Markdown
Author

@codity review

@codity-dm

codity-dm Bot commented May 22, 2026

Copy link
Copy Markdown

Policy Check Failed

✗ 2/3 policy checks failed:

• Need 2 more approval(s) (0/2) — comment LGTM or approve via review
• Missing ticket reference (expected: JIRA-, ENG-, #*)

✓ 1 checks passed:
• Test files included (4 test file(s))


To merge this PR:

  1. Address the failed checks listed above
  2. Ensure branch protection requires the codity/policy-check status

Configure policies in your dashboard

@codity-dm

codity-dm Bot commented May 22, 2026

Copy link
Copy Markdown

PR review started! Estimated time: 5-10 minutes.
Using default review instructions (no custom configuration found)

Learn More

View Analytics Dashboard

Ask Codity questions: Mention @codity {your question} in a comment to get answers about the code.

Trigger a manual review: Comment @codity review on a PR or MR.

Generate unit tests: Comment /generate-tests to auto-generate tests for Go, Python, Ruby, JavaScript, TypeScript, and Java files.

Run security scan again: Comment /security-scan to run SAST and dependency vulnerability scans for all major languages in your repo.

View Full Docs

Comment on lines +48 to +51
const value =
input.type === "EMAIL"
? report.bookerEmail
: report.bookerEmail.split("@")[1] || report.bookerEmail;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security Medium

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

---


Like Dislike Create Issue Jira

@DhirenMhatre

Copy link
Copy Markdown
Author

@codity review

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

Policy Check Failed

✗ 2/3 policy checks failed:

• Need 2 more approval(s) (0/2) — comment LGTM or approve via review
• Missing ticket reference (expected: JIRA-, ENG-, #*)

✓ 1 checks passed:
• Test files included (4 test file(s))


To merge this PR:

  1. Address the failed checks listed above
  2. Ensure branch protection requires the codity/policy-check status

Configure policies in your dashboard

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

PR Summary

What Changed

  • Added admin UI for managing booking spam reports with filtering, bulk actions, and blocklist integration
  • Built backend infrastructure for cross-organization report querying and watchlist management
  • Connected reported bookers to global blocklist with email/domain blocking and duplicate prevention

Key Changes by Area

Admin UI: New /settings/admin/booking-reports page with data table, status filters, search, and bulk add-to-blocklist actions

tRPC API: Added listBookingReports, addToWatchlist, and deleteBookingReport admin procedures with proper access control

Repository Layer: Extended BookingReportRepository with paginated filtering and WatchlistRepository with global scope support

Files Changed

File Changes Summary
apps/web/app/(use-page-wrapper)/settings/(admin-layout)/admin/booking-reports/page.tsx New admin page with metadata
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx Added navigation link to booking reports
apps/web/modules/settings/admin/booking-reports-view.tsx Main data table with filtering and row actions
apps/web/modules/settings/admin/components/add-to-blocklist-modal.tsx Modal for single/bulk blocklist additions
apps/web/modules/settings/admin/components/booking-report-details-sheet.tsx Side panel for report details
apps/web/modules/settings/admin/components/bulk-add-to-blocklist.tsx Bulk action button component
apps/web/public/static/locales/en/common.json Localization strings
packages/lib/server/repository/bookingReport.interface.ts Extended interface with new methods and filter types
packages/lib/server/repository/bookingReport.test.ts Unit tests for repository methods
packages/lib/server/repository/bookingReport.ts findAllReportedBookings with pagination, search, and relations
packages/lib/server/repository/watchlist.interface.ts Interface updates for global watchlist
packages/lib/server/repository/watchlist.repository.ts Global watchlist support with isGlobal flag
packages/trpc/server/routers/viewer/admin/_router.ts Added three new admin procedures
packages/trpc/server/routers/viewer/admin/addToWatchlist.handler.test.ts Handler unit tests
packages/trpc/server/routers/viewer/admin/addToWatchlist.handler.ts Bulk add to global watchlist with deduplication
packages/trpc/server/routers/viewer/admin/addToWatchlist.schema.ts Input validation schema
packages/trpc/server/routers/viewer/admin/deleteBookingReport.handler.test.ts Handler unit tests
packages/trpc/server/routers/viewer/admin/deleteBookingReport.handler.ts Admin report deletion
packages/trpc/server/routers/viewer/admin/deleteBookingReport.schema.ts Input validation schema
packages/trpc/server/routers/viewer/admin/listBookingReports.handler.test.ts Handler unit tests
packages/trpc/server/routers/viewer/admin/listBookingReports.handler.ts Paginated cross-org report listing
packages/trpc/server/routers/viewer/admin/listBookingReports.schema.ts Filter schema with reason, status, date range
packages/trpc/server/routers/viewer/organizations/_router.tsx Minor formatting

Review Focus Areas

  • Cross-organization data access in listBookingReports.handler.ts - verify admin-only enforcement
  • Duplicate detection logic in addToWatchlist.handler.ts for email/domain collisions
  • Watchlist scope handling - confirm global vs organization-scoped entries work correctly

Architecture

Design Decisions: Global watchlist uses isGlobal flag rather than separate table. Cross-org visibility is intentional for admin oversight. Bulk operations process sequentially without transaction batching (acceptable for admin tool).

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({

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functional High

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

---


Like Dislike Create Issue Jira

Comment on lines +202 to +213
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,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security High

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

---


Like Dislike Create Issue Jira

type: params.type,
value: params.value,
organizationId: params.organizationId,
isGlobal: params.isGlobal,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functional High

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

---


Like Dislike Create Issue Jira

Comment on lines +53 to +73
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functional High

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

---


Like Dislike Create Issue Jira

Comment on lines +14 to +22
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,
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Security High

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

---


Like Dislike Create Issue Jira

Comment on lines +340 to +367
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)}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functional Medium

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

---


Like Dislike Create Issue Jira

Comment on lines +48 to +56
const value =
input.type === "EMAIL"
? report.bookerEmail
: report.bookerEmail.split("@")[1] || report.bookerEmail;

const existingWatchlist = await watchlistRepo.checkExists({
type: input.type,
value,
isGlobal: true,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Robustness Medium

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

---


Like Dislike Create Issue Jira

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

Security Scan Summary

Metric Value
Vulnerabilities Critical: 0
Overall Risk Clean
Files Scanned 23

No critical security issues detected

Scan completed in 34.0s

Security scan powered by Codity.ai

@DhirenMhatre

Copy link
Copy Markdown
Author

@codity review

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

Policy Check Failed

✗ 2/3 policy checks failed:

• Need 2 more approval(s) (0/2) — comment LGTM or approve via review
• Missing ticket reference (expected: JIRA-, ENG-, #*)

✓ 1 checks passed:
• Test files included (4 test file(s))


To merge this PR:

  1. Address the failed checks listed above
  2. Ensure branch protection requires the codity/policy-check status

Configure policies in your dashboard

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

PR Summary

What Changed

  • Added a new admin page at /settings/admin/booking-reports for managing user-submitted booking reports (spam/abuse).
  • Built backend infrastructure with tRPC handlers for listing, deleting reports and adding bookers to a global watchlist.
  • Extended repositories to support cross-organization admin queries and global watchlist entries.

Key Changes by Area

Admin UI: New booking reports page with data table, filtering, search, and bulk actions for adding reporters to blocklist.

tRPC API: Added listBookingReports, deleteBookingReport, and addToWatchlist admin procedures with pagination and filtering support.

Repository Layer: Extended BookingReportRepository with cross-org queries and WatchlistRepository with global entry support (isGlobal flag).

Booking Reports: Updated reportBooking handler to auto-cancel upcoming bookings when reports are submitted.

Files Changed

File Changes Summary
apps/web/app/(use-page-wrapper)/settings/(admin-layout)/admin/booking-reports/page.tsx New admin page wrapper for booking reports
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx Layout adjustments for admin navigation
apps/web/modules/settings/admin/booking-reports-view.tsx Main view component with data table and filters
apps/web/modules/settings/admin/components/add-to-blocklist-modal.tsx Modal for adding users to watchlist (EMAIL/DOMAIN)
apps/web/modules/settings/admin/components/booking-report-details-sheet.tsx Slide-out panel for report details
apps/web/modules/settings/admin/components/bulk-add-to-blocklist.tsx Bulk action button for selected rows
apps/web/public/static/locales/en/common.json Translation strings for UI labels
packages/lib/server/repository/bookingReport.interface.ts Added filter types and repository interface methods
packages/lib/server/repository/bookingReport.test.ts Unit tests for repository methods
packages/lib/server/repository/bookingReport.ts Implemented findAllReportedBookings with filtering/pagination
packages/lib/server/repository/watchlist.interface.ts Added isGlobal flag to interface
packages/lib/server/repository/watchlist.repository.ts Global watchlist entry support with duplicate prevention
packages/trpc/server/routers/viewer/admin/_router.ts Registered new admin procedures
packages/trpc/server/routers/viewer/admin/addToWatchlist.handler.test.ts Tests for bulk watchlist operations
packages/trpc/server/routers/viewer/admin/addToWatchlist.handler.ts Handler for adding bookers to global watchlist
packages/trpc/server/routers/viewer/admin/addToWatchlist.schema.ts Input validation schema
packages/trpc/server/routers/viewer/admin/deleteBookingReport.handler.test.ts Tests for report deletion
packages/trpc/server/routers/viewer/admin/deleteBookingReport.handler.ts Admin report deletion handler
packages/trpc/server/routers/viewer/admin/deleteBookingReport.schema.ts Input validation schema
packages/trpc/server/routers/viewer/admin/listBookingReports.handler.test.ts Tests for listing with filters/pagination
packages/trpc/server/routers/viewer/admin/listBookingReports.handler.ts Paginated report listing handler
packages/trpc/server/routers/viewer/admin/listBookingReports.schema.ts Input schema with search and filters
packages/trpc/server/routers/viewer/organizations/_router.tsx Minor router adjustments

Review Focus Areas

  • Domain extraction logic in add-to-blocklist-modal.tsx:99-114 assumes valid email format. Malformed addresses render undefined in UI.
  • Watchlist existence check vs. create scoping mismatch in watchlist.repository.ts:59-66. Global check with org-scoped insert may cause unexpected duplicates.

Architecture

Design Decisions: Global watchlist entries (isGlobal: true) allow cross-org blocking without per-org duplication. The repository reuses existing entries by value to prevent duplicates. Cross-organization access for admins is intentional and scoped to read-only listing with write restrictions via admin checks.

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 Status

NOT MERGEABLE — PR Score 10/100, below threshold (50)

  • [H4] PR quality score (10) is below merge floor (50)
  • [H5] 13 HIGH-severity inline review findings need resolution (threshold: 3)
  • [H6] Code quality raw score (17) is below merge floor (40)

}

return {
hasWatchlist: hasWatchlistValue,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functional Medium

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

---


Like Dislike Create Issue Jira

Comment on lines +24 to +27
const handleModalClose = () => {
setShowModal(false);
onSuccess();
};

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Robustness Medium

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

---


Like Dislike Create Issue Jira

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

Nitpicks (Low Priority)

Found 2 low-priority suggestions for code improvement

Click to expand nitpicks

apps/web/modules/settings/admin/components/add-to-blocklist-modal.tsx (lines 99-114)

Robustness Low

The domain option builds its label and description by unconditionally splitting firstReport.bookerEmail on @ and reading index 1. If a report record ever contains a non-email booker identifier or a malformed address, this renders undefined into the UI and can also send the wrong value if the user selects the domain blocklist option. For example, bookerEmail = "foo" produces @undefined in both places.

Code Suggestion or Comments
description: firstReport.bookerEmail.includes("@")
  ? `${t("all_emails_from")} @${firstReport.bookerEmail.split("@")[1]}`
  : firstReport.bookerEmail,
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: 99-114
Issue Type: robustness-low
Severity: low

Issue Description:
The domain option builds its label and description by unconditionally splitting `firstReport.bookerEmail` on `@` and reading index 1. If a report record ever contains a non-email booker identifier or a malformed address, this renders `undefined` into the UI and can also send the wrong value if the user selects the domain blocklist option. For example, `bookerEmail = "foo"` produces `@undefined` in both places.

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

---



packages/lib/server/repository/watchlist.repository.ts (lines 59-66)

Security Low

checkExists now returns a global match whenever params.isGlobal is truthy, but the create path also allows organizationId: params.organizationId ?? null on the same call. Because the existence check and the insert are no longer scoped by the same key set, a request that supplies both an organization and isGlobal can bypass the duplicate check for the organization-scoped record and create a second row. That produces a concrete duplicate-record failure mode in the repository layer.

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: packages/lib/server/repository/watchlist.repository.ts
Lines: 59-66
Issue Type: security-low
Severity: low

Issue Description:
`checkExists` now returns a global match whenever `params.isGlobal` is truthy, but the create path also allows `organizationId: params.organizationId ?? null` on the same call. Because the existence check and the insert are no longer scoped by the same key set, a request that supplies both an organization and `isGlobal` can bypass the duplicate check for the organization-scoped record and create a second row. That produces a concrete duplicate-record failure mode in the repository layer.

Current Code:
+     if (params.isGlobal) {
+       const entry = await this.prismaClient.watchlist.findFirst({
+         where: {
+           type: params.type,
+           value: params.value,
+           isGlobal: true,
+           organizationId: null,
+         },
+       });
+       return entry;
+     }

---

### 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

---



Like Dislike

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

Security Scan Summary

Metric Value
Vulnerabilities Critical: 0
Overall Risk Clean
Files Scanned 23

No critical security issues detected

Scan completed in 34.8s

Security scan powered by Codity.ai

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

License Compliance Scan

Metric Value
Packages Scanned 442
High Risk (Strong Copyleft) 0
Medium Risk (Weak Copyleft) 7
Low Risk (Permissive) 387
Unknown License 48

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):

  • dompurify 3.2.3
  • dompurify 3.1.7

MPL-2.0 (5 packages):

  • @vercel/og 0.6.3
  • ical.js 1.4.0
  • eslint-config-turbo 0.0.7
  • web-push 3.6.7
  • @dub/analytics 0.0.27
Unknown Licenses - 48 packages
  • @calcom/eslint-config workspace:*
  • @calcom/atoms workspace:*
  • @types/react-dom 18
  • @types/react 18
  • react 18
  • react-dom 18
  • postcss 8
  • @calcom/dayjs workspace:*
  • @calcom/config workspace:*
  • @calcom/tsconfig workspace:*
  • @calcom/types workspace:*
  • @calcom/features workspace:*
  • @calcom/dailyvideo workspace:*
  • @calcom/lib workspace:*
  • @calcom/office365video workspace:*
  • @calcom/ui workspace:*
  • @calcom/zoomvideo workspace:*
  • @calcom/prisma workspace:*
  • @calcom/emails workspace:*
  • @calcom/app-store workspace:*

...and 28 more

Powered by Codity.ai · Docs

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

Code Quality Report — test-org-codity/cal.com · PR #2

Scanned: 2026-05-23 11:04 UTC | Score: 17/100 | Provider: github

Executive Summary

Severity Count
Critical 0
High 2
Medium 5
Low 156
Top Findings

[CQ-LLM-001] apps/web/modules/settings/admin/booking-reports-view.tsx:0 (Complexity · HIGH)

Issue: The function BookingReportsTable is large and complex, which may lead to maintainability issues.
Suggestion: Consider breaking down BookingReportsTable into smaller, more manageable functions.

function BookingReportsTable() { ... }

[CQ-LLM-003] apps/web/modules/settings/admin/booking-reports-view.tsx:0 (Error_Handling · HIGH)

Issue: Error handling for the delete report mutation is present but could be improved by providing more context.
Suggestion: Consider logging the error or providing user feedback that includes the error context.

onError: (error) => { showToast(error.message, 'error'); },

[CQ-LLM-002] apps/web/modules/settings/admin/booking-reports-view.tsx:0 (Duplication · MEDIUM)

Issue: Repeated logic for handling dropdown items could lead to DRY violations.
Suggestion: Extract the dropdown item rendering logic into a separate reusable component.

DropdownMenuItem { ... }

[CQ-LLM-004] apps/web/modules/settings/admin/booking-reports-view.tsx:0 (Performance · MEDIUM)

Issue: The use of multiple state variables for modal visibility could lead to unnecessary re-renders.
Suggestion: Consider consolidating modal visibility states into a single state object.

const [showWatchlistModal, setShowWatchlistModal] = useState(false); ...

[CQ-LLM-006] apps/web/modules/settings/admin/booking-reports-view.tsx:0 (Testability · MEDIUM)

Issue: The component relies on global state and hooks, making it harder to test in isolation.
Suggestion: Consider using dependency injection for state management to improve testability.

const { t } = useLocale();

[CQ-LLM-007] apps/web/modules/settings/admin/booking-reports-view.tsx:0 (Maintainability · MEDIUM)

Issue: Magic strings are used for status and reason, which can lead to errors and maintenance issues.
Suggestion: Define constants for status and reason values to improve readability and maintainability.

const reasonColors: Record<string, 'red' | 'orange' | 'gray'> = { ... };

[CQ-001] apps/web/modules/settings/admin/booking-reports-view.tsx:44 (Complexity · MEDIUM)

Issue: Function/method has 389 added lines, exceeding 200-line threshold
Suggestion: Break into smaller, single-responsibility functions

[CQ-LLM-005] apps/web/modules/settings/admin/booking-reports-view.tsx:0 (Documentation · LOW)

Issue: Missing documentation for the BookingReportsTable function and its purpose.
Suggestion: Add a docstring to describe the function's purpose and usage.

function BookingReportsTable() { ... }

[CQ-011] apps/web/modules/settings/admin/booking-reports-view.tsx:26 (Duplication · LOW)

Issue: Duplicate import: 'import {'
Suggestion: Remove the duplicate import statement

import {

[CQ-008] apps/web/modules/settings/admin/booking-reports-view.tsx:112 (Maintainability · LOW)

Issue: Magic number 30 in code
Suggestion: Extract to a named constant

size: 30,

Per-File Breakdown

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

  1. Resolve High severity issues, especially error handling gaps and performance bottlenecks.
  • Run automated tests after applying fixes to verify no regressions.

@DhirenMhatre

Copy link
Copy Markdown
Author

@codity review

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

Policy Check Failed

✗ 2/3 policy checks failed:

• Need 2 more approval(s) (0/2) — comment LGTM or approve via review
• Missing ticket reference (expected: JIRA-, ENG-, #*)

✓ 1 checks passed:
• Test files included (4 test file(s))


To merge this PR:

  1. Address the failed checks listed above
  2. Ensure branch protection requires the codity/policy-check status

Configure policies in your dashboard

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

PR Summary

What Changed

  • New admin page for managing booking spam reports with filtering, bulk actions, and blocklist integration
  • Backend support for listing, filtering, and deleting reports across all organizations with watchlist linking
  • Auto-cancellation of bookings when reported as spam, including recurring events and seated bookings

Key Changes by Area

Admin UI: New booking reports page at /settings/admin/booking-reports with data table, column filters, and bulk "Add to Blocklist" action

tRPC API: Three new admin procedures: listBookingReports (paginated/filtered listing), addToWatchlist (bulk blocklist addition), deleteBookingReport (report removal)

Data Layer: Extended BookingReportRepository with query methods for filtering by reason, status, watchlist presence, and date range. Added global watchlist support in WatchlistRepository

Booking Reports: Enhanced reportBooking handler to auto-cancel upcoming bookings on spam reports with proper handling for recurring and seated events

Files Changed

File Changes Summary
apps/web/app/(use-page-wrapper)/settings/(admin-layout)/admin/booking-reports/page.tsx New admin page route with metadata
apps/web/app/(use-page-wrapper)/settings/(settings-layout)/SettingsLayoutAppDirClient.tsx Added booking_reports nav item
apps/web/modules/settings/admin/booking-reports-view.tsx Main table component with filters and bulk actions
apps/web/modules/settings/admin/components/add-to-blocklist-modal.tsx Modal for single report blocklist addition
apps/web/modules/settings/admin/components/booking-report-details-sheet.tsx Side sheet for report details
apps/web/modules/settings/admin/components/bulk-add-to-blocklist.tsx Bulk action button component
apps/web/public/static/locales/en/common.json UI text strings
packages/lib/server/repository/bookingReport.interface.ts Extended interface with new types
packages/lib/server/repository/bookingReport.test.ts Repository unit tests
packages/lib/server/repository/bookingReport.ts New query methods with filtering/pagination
packages/lib/server/repository/watchlist.interface.ts Added isGlobal flag
packages/lib/server/repository/watchlist.repository.ts Global watchlist support
packages/trpc/server/routers/viewer/admin/_router.ts New tRPC procedures
packages/trpc/server/routers/viewer/admin/addToWatchlist.handler.test.ts Handler unit tests
packages/trpc/server/routers/viewer/admin/addToWatchlist.handler.ts Bulk add to watchlist logic
packages/trpc/server/routers/viewer/admin/addToWatchlist.schema.ts Input validation schema
packages/trpc/server/routers/viewer/admin/deleteBookingReport.handler.test.ts Handler unit tests
packages/trpc/server/routers/viewer/admin/deleteBookingReport.handler.ts Delete report endpoint
packages/trpc/server/routers/viewer/admin/deleteBookingReport.schema.ts Input validation schema
packages/trpc/server/routers/viewer/admin/listBookingReports.handler.test.ts Handler unit tests
packages/trpc/server/routers/viewer/admin/listBookingReports.handler.ts Paginated listing endpoint
packages/trpc/server/routers/viewer/admin/listBookingReports.schema.ts Input validation schema
packages/trpc/server/routers/viewer/organizations/_router.tsx Minor formatting cleanup

Review Focus Areas

  • Cross-organization data access in admin handlers (verify proper scoping)
  • Auto-cancellation logic for recurring and seated bookings in reportBooking.handler.ts
  • Duplicate prevention in bulk watchlist additions

Architecture

Design Decisions: Global watchlist uses isGlobal flag rather than separate table to reuse existing schema. Repository pattern keeps Prisma details isolated from tRPC handlers. Admin endpoints intentionally bypass organization scoping for cross-tenant visibility.

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 Status

NOT MERGEABLE — PR Score 11/100, below threshold (50)

  • [H4] PR quality score (11) is below merge floor (50)
  • [H5] 13 HIGH-severity inline review findings need resolution (threshold: 3)
  • [H6] Code quality raw score (18) is below merge floor (40)

}

return {
hasWatchlist: hasWatchlistValue,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Functional Medium

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

---


Like Dislike Create Issue Jira

{
label: t("entire_domain"),
value: WatchlistType.DOMAIN,
description: `${t("all_emails_from")} @${firstReport.bookerEmail.split("@")[1]}`,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Robustness Medium

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.

Suggested change
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

---


Like Dislike Create Issue Jira

Comment on lines +22 to +31
const reportsToAdd = reports.filter((report) => !report.watchlistId);

const handleModalClose = () => {
setShowModal(false);
onSuccess();
};

if (reportsToAdd.length === 0) {
return null;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Robustness Medium

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

---


Like Dislike Create Issue Jira

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

Security Scan Summary

Metric Value
Vulnerabilities Critical: 0
Overall Risk Clean
Files Scanned 23

No critical security issues detected

Scan completed in 30.4s

Security scan powered by Codity.ai

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

License Compliance Scan

Metric Value
Packages Scanned 442
High Risk (Strong Copyleft) 0
Medium Risk (Weak Copyleft) 7
Low Risk (Permissive) 387
Unknown License 48

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):

  • dompurify 3.2.3
  • dompurify 3.1.7

MPL-2.0 (5 packages):

  • @vercel/og 0.6.3
  • ical.js 1.4.0
  • eslint-config-turbo 0.0.7
  • web-push 3.6.7
  • @dub/analytics 0.0.27
Unknown Licenses - 48 packages
  • @calcom/eslint-config workspace:*
  • @types/react-dom 18
  • @types/react 18
  • @calcom/atoms workspace:*
  • react 18
  • react-dom 18
  • postcss 8
  • @calcom/config workspace:*
  • @calcom/dayjs workspace:*
  • @calcom/tsconfig workspace:*
  • @calcom/types workspace:*
  • @calcom/dailyvideo workspace:*
  • @calcom/office365video workspace:*
  • @calcom/lib workspace:*
  • @calcom/features workspace:*
  • @calcom/ui workspace:*
  • @calcom/zoomvideo workspace:*
  • @calcom/prisma workspace:*
  • @calcom/emails workspace:*
  • @calcom/app-store workspace:*

...and 28 more

Powered by Codity.ai · Docs

@codity-dm

codity-dm Bot commented May 23, 2026

Copy link
Copy Markdown

Code Quality Report — test-org-codity/cal.com · PR #2

Scanned: 2026-05-23 11:19 UTC | Score: 18/100 | Provider: github

Executive Summary

Severity Count
Critical 0
High 1
Medium 4
Low 156
Top Findings

[CQ-LLM-001] apps/web/modules/settings/admin/booking-reports-view.tsx:0 (Complexity · HIGH)

Issue: The function BookingReportsTable is complex with multiple states and effects, leading to high cyclomatic complexity.
Suggestion: Consider breaking down BookingReportsTable into smaller components to reduce complexity.

function BookingReportsTable() { ... }

[CQ-LLM-002] apps/web/modules/settings/admin/booking-reports-view.tsx:0 (Error_Handling · MEDIUM)

Issue: The error handling in the deleteReportMutation does not account for all possible error scenarios.
Suggestion: Enhance error handling to cover more specific cases and provide user feedback accordingly.

onError: (error) => { showToast(error.message, 'error'); },

[CQ-LLM-003] apps/web/modules/settings/admin/booking-reports-view.tsx:0 (Performance · MEDIUM)

Issue: The use of multiple state variables can lead to unnecessary re-renders and performance issues.
Suggestion: Consider using a single state object to manage related state variables.

const [selectedReport, setSelectedReport] = useState<BookingReport | null>(null); ...

[CQ-LLM-005] apps/web/modules/settings/admin/booking-reports-view.tsx:0 (Maintainability · MEDIUM)

Issue: Magic strings are used for status and reason values, which can lead to maintenance issues.
Suggestion: Define constants for status and reason values to improve maintainability.

const reasonColors: Record<string, 'red' | 'orange' | 'gray'> = { SPAM: 'red', ... };

[CQ-001] apps/web/modules/settings/admin/booking-reports-view.tsx:44 (Complexity · MEDIUM)

Issue: Function/method has 389 added lines, exceeding 200-line threshold
Suggestion: Break into smaller, single-responsibility functions

[CQ-LLM-004] apps/web/modules/settings/admin/booking-reports-view.tsx:0 (Documentation · LOW)

Issue: Missing documentation for the BookingReportsTable component and its functions.
Suggestion: Add JSDoc comments to describe the purpose and usage of the BookingReportsTable component.

function BookingReportsTable() { ... }

[CQ-011] apps/web/modules/settings/admin/booking-reports-view.tsx:26 (Duplication · LOW)

Issue: Duplicate import: 'import {'
Suggestion: Remove the duplicate import statement

import {

[CQ-008] apps/web/modules/settings/admin/booking-reports-view.tsx:112 (Maintainability · LOW)

Issue: Magic number 30 in code
Suggestion: Extract to a named constant

size: 30,

[CQ-008] apps/web/modules/settings/admin/booking-reports-view.tsx:140 (Maintainability · LOW)

Issue: Magic number 180 in code
Suggestion: Extract to a named constant

size: 180,

[CQ-008] apps/web/modules/settings/admin/booking-reports-view.tsx:147 (Maintainability · LOW)

Issue: Magic number 90 in code
Suggestion: Extract to a named constant

size: 90,

Per-File Breakdown

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

  1. Resolve High severity issues, especially error handling gaps and performance bottlenecks.
  • Run automated tests after applying fixes to verify no regressions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants