Skip to content

Comments

Revive captcha#443

Merged
Blaumaus merged 10 commits intoswetrix-revampfrom
revive-captcha
Dec 6, 2025
Merged

Revive captcha#443
Blaumaus merged 10 commits intoswetrix-revampfrom
revive-captcha

Conversation

@Blaumaus
Copy link
Member

@Blaumaus Blaumaus commented Dec 6, 2025

Changes

Community Edition support

  • Your feature is implemented for the Swetrix Community Edition
  • This PR only updates the Cloud (Enterprise) Edition code (e.g. Paddle webhooks, blog, payouts, etc.)

Database migrations

  • Clickhouse / MySQL migrations added for this PR
  • No table schemas changed in this PR

Documentation

  • You have updated the documentation according to your PR
  • This PR did not change any publicly documented endpoints

Summary by CodeRabbit

  • New Features

    • Added CAPTCHA landing page introducing the product.
    • Introduced configurable difficulty levels (1-6) for CAPTCHA in project settings.
    • Integrated CAPTCHA management into project settings with secret key regeneration.
  • Bug Fixes & Changes

    • Updated CAPTCHA verification to use Proof-of-Work validation.
    • Simplified project creation workflow by removing CAPTCHA project type selection.
    • Removed separate CAPTCHA analytics and settings pages.
  • Documentation

    • Added comprehensive localization for CAPTCHA features and settings across the platform.

✏️ Tip: You can customize this high-level summary in your review settings.

@Blaumaus Blaumaus self-assigned this Dec 6, 2025
@coderabbitai
Copy link

coderabbitai bot commented Dec 6, 2025

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

This pull request migrates the CAPTCHA system from hash-based verification to a Proof-of-Work (PoW) model. Changes span backend verification logic, database schema, frontend routing, and analytics views. Old CAPTCHA configuration pages are removed; a new marketing landing page and per-project CAPTCHA analytics dashboard are introduced.

Changes

Cohort / File(s) Summary
Backend CAPTCHA Core
backend/apps/cloud/src/captcha/captcha.controller.ts, backend/apps/cloud/src/captcha/captcha.service.ts, backend/apps/cloud/src/captcha/dtos/generate.dto.ts, backend/apps/cloud/src/captcha/dtos/manual.dto.ts, backend/apps/cloud/src/captcha/interfaces/generated-captcha.ts
Replaced hash-based CAPTCHA with PoW verification. Controller now calls generateChallenge(pid) and verifyPoW(challenge, nonce, solution). Service adds PoW constants (DEFAULT_POW_DIFFICULTY, CHALLENGE_TTL), challenge caching in Redis, SHA256 hashing, and prefix validation. DTOs updated: GenerateDto drops theme, VerifyDto shifts from {code, hash} to {solution, challenge, nonce}. Interface GeneratedCaptcha renamed to GeneratedChallenge with fields {challenge, difficulty}.
Backend Project Schema & Data
backend/apps/cloud/src/project/entity/project.entity.ts, backend/apps/cloud/src/project/dto/create-project.dto.ts, backend/apps/cloud/src/project/dto/project.dto.ts, backend/apps/cloud/src/project/dto/update-project.dto.ts
Project entity removes isCaptchaProject and isCaptchaEnabled booleans; adds captchaSecretKey (varchar) and captchaDifficulty (tinyint, default 4). CreateProjectDto removes isCaptcha field. ProjectDto removes isCaptcha. UpdateProjectDto adds captchaDifficulty with validation (1–6 range).
Backend Project Logic
backend/apps/cloud/src/project/project.controller.ts, backend/apps/cloud/src/project/project.service.ts
Controller removes CAPTCHA-specific project initialization; unifies project quota checks without per-type splitting. Adds captchaDifficulty update support. Adds isCaptchaDataExists query. Service method getRedisProject now selects captchaDifficulty instead of isCaptchaEnabled. New method getPIDsWhereCaptchaDataExists() checks ClickHouse for CAPTCHA data.
Backend Community API
backend/apps/community/src/user/user.controller.ts
Removes isCaptchaProject and isCaptchaEnabled from user project responses.
Database Migrations
backend/migrations/mysql/2025_12_06_captcha_difficulty.sql, backend/migrations/mysql/2025_12_06_remove_captcha_flags.sql
Adds captchaDifficulty (TINYINT UNSIGNED, default 4) column; drops isCaptchaProject and isCaptchaEnabled columns.
Frontend Routing
web/app/routes/captcha.tsx, web/app/routes/captchas.$id.tsx, web/app/routes/captchas.new.tsx, web/app/routes/captchas.settings.$id.tsx
New landing page route /captcha with sitemap and conditional redirect for self-hosted/disabled deployments. Removed three old CAPTCHA management routes (view, new, settings).
Frontend CAPTCHA UI Pages
web/app/pages/Captcha/index.tsx, web/app/pages/Captcha/Settings/CaptchaSettings.tsx, web/app/pages/Captcha/Settings/index.tsx, web/app/pages/Captcha/View/ViewCaptcha.tsx, web/app/pages/Captcha/View/ViewCaptcha.helpers.tsx, web/app/pages/Captcha/View/index.tsx
Removed old CaptchaSettings and ViewCaptcha pages (settings UI, chart helpers). Introduced new public marketing page at /pages/Captcha/index.tsx with features grid, how-it-works section, and CTAs.
Frontend Project Analytics Integration
web/app/pages/Project/View/ViewProject.tsx, web/app/pages/Project/View/ViewProject.helpers.tsx, web/app/pages/Project/View/components/CaptchaChart.tsx, web/app/pages/Project/View/components/CaptchaView.tsx
ViewProject extends tabs to include CAPTCHA with lazy-loaded CaptchaView. Adds captchaRefreshTrigger to context for re-fetch control. New CaptchaChart component renders PoW analytics via new getSettingsCaptcha helper. New CaptchaView component fetches CAPTCHA data and renders panels (country, device, browser, OS) with filtering and charting.
Frontend Project Settings
web/app/pages/Project/Settings/ProjectSettings.tsx, web/app/pages/Project/View/components/ProjectSidebar.tsx
ProjectSettings adds CAPTCHA management tab with secret key generation/regeneration, difficulty selector (1–6), and save logic. ProjectSidebar groups CAPTCHA-related tabs with ShieldCheckIcon.
Frontend Dashboard & Header
web/app/pages/Dashboard/Dashboard.tsx, web/app/pages/Dashboard/ProjectCard.tsx, web/app/components/Header/index.tsx, web/app/components/Footer/index.tsx
Dashboard removes captcha-specific stats fetch and update. ProjectCard removes captcha badge and conditional routing/labels. Header and Footer switch CAPTCHA link from external CAPTCHA_URL to internal /captcha route.
Frontend Models & Constants
web/app/lib/models/Project.ts, web/app/lib/constants/index.ts, web/app/utils/routes.ts, web/app/api/index.ts
Project model replaces isCaptchaProject and isCaptchaEnabled with captchaDifficulty and isCaptchaDataExists; removes CaptchaProject interface. Constants add captcha to PROJECT_TABS. Routes add captchaLanding (/captcha), remove new_captcha, captcha_settings, captcha entries. createProject API removes isCaptcha parameter.
Frontend Utilities & Cleanup
web/app/utils/analytics.ts, web/app/utils/server.ts, web/app/ui/Input.tsx, web/app/ui/icons/BackgroundSvg.tsx, web/app/pages/ErrorTracking/index.tsx, web/app/pages/Performance/index.tsx
Analytics removes captcha URL path rewrites. Server removes new_captcha page title case. Input changes disabled cursor from not-allowed to text. Removed BackgroundSvg component. Removed PeopleLoveSwetrix component from ErrorTracking and Performance pages.
Localization
web/public/locales/en.json
Adds comprehensive CAPTCHA localization: common.captcha, captchaPage (marketing content, features, how-it-works), project.captchaCompletions, dashboard.captcha, settings.tabs.captcha, settings.captcha (secret key, regeneration, difficulty levels veryEasy–veryHard), profileSettings.captcha.settings.

Sequence Diagram

sequenceDiagram
    actor Client
    participant Backend as Backend Controller
    participant CaptchaService as CAPTCHA Service
    participant Redis
    participant ClickHouse as Verification Logic
    
    Client->>Backend: POST /captcha/generate { pid }
    activate Backend
    Backend->>CaptchaService: generateChallenge(pid)
    activate CaptchaService
    CaptchaService->>CaptchaService: Compute challenge hash
    CaptchaService->>CaptchaService: Get project difficulty (pid)
    CaptchaService->>Redis: Store challenge + difficulty (TTL)
    Redis-->>CaptchaService: Stored
    CaptchaService-->>Backend: { challenge, difficulty }
    deactivate CaptchaService
    Backend-->>Client: { challenge, difficulty }
    deactivate Backend
    
    Note over Client: Client solves PoW:<br/>finds nonce where<br/>hash(challenge+nonce)<br/>has N leading zeros
    
    Client->>Backend: POST /captcha/verify { challenge, nonce, solution, pid }
    activate Backend
    Backend->>CaptchaService: verifyPoW(challenge, nonce, solution)
    activate CaptchaService
    CaptchaService->>Redis: Get stored difficulty for challenge
    Redis-->>CaptchaService: difficulty
    CaptchaService->>ClickHouse: hasValidPrefix(hash, difficulty)
    ClickHouse-->>CaptchaService: Valid ✓ / Invalid ✗
    alt PoW Valid
        CaptchaService->>CaptchaService: generateToken(pid, challenge, timestamp)
        CaptchaService-->>Backend: token
        Backend-->>Client: { token }
    else PoW Invalid
        Backend-->>Client: 403 Forbidden
    end
    deactivate CaptchaService
    deactivate Backend
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas requiring extra attention:

  • PoW verification logic (captcha.service.ts): Hash computation, prefix validation, and difficulty scaling must be cryptographically sound; verify SHA256 implementation and edge cases in nonce handling.
  • CAPTCHA data migration & schema changes: Verify database migrations execute without data loss; confirm queries referencing removed columns are fully updated across services.
  • Redis TTL management: Challenge caching and expiration logic; ensure TTL conversion from milliseconds to seconds is consistent across all calls.
  • Frontend context propagation: captchaRefreshTrigger added to ViewProject context; verify it does not cause unnecessary re-renders and that dependencies are correctly listed.
  • CAPTCHA analytics integration: New CaptchaView component and getSettingsCaptcha helper; confirm chart data structure, filtering, and date range handling align with backend API responses.
  • API surface changes: DTO shifts from {code, hash} to {solution, challenge, nonce}; verify all frontend consumers and tests are updated.

Poem

🐰 A PoW-ful Leap!

From hashes to challenges, we hop and weave,
Proof-of-work now guards what users achieve.
Nonce solutions bloom like morning dew,
A CAPTCHA reborn, cryptographically true! 🛡️✨

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is largely incomplete. It omits critical sections: no detailed description of changes, database migrations status is unchecked despite two schema migrations being present, and documentation status is unaddressed. Complete the description by: detailing the CAPTCHA refactor (SVG→PoW, new DTO fields, service changes), checking or clarifying the database migrations section (two MySQL migrations added), and addressing documentation updates or confirming no public API changes.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Title check ❓ Inconclusive The title 'Revive captcha' is vague and generic, using a non-descriptive term that doesn't clearly convey the scope or nature of the specific changes made. Use a more specific and descriptive title that explains the main changes, such as 'Migrate CAPTCHA from SVG to Proof-of-Work verification system' or 'Implement PoW-based CAPTCHA with difficulty settings'.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Blaumaus Blaumaus marked this pull request as ready for review December 6, 2025 02:00
@Blaumaus
Copy link
Member Author

Blaumaus commented Dec 6, 2025

@coderabbitai review

@coderabbitai
Copy link

coderabbitai bot commented Dec 6, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (11)
web/app/pages/Project/View/components/ProjectSidebar.tsx (1)

32-34: Consider tightening typings for icon color maps

ICON_COLORS and GROUP_ICON_COLORS correctly gain captcha entries and will style the new group/tab consistently.

If you want to catch typos at compile time, consider typing these as something like Record<ProjectTabKey | 'webAnalytics' | 'productAnalytics', string> (or a dedicated union for group ids) instead of Record<string, string>, so missing/extra keys surface during refactors.

Also applies to: 39-43

backend/apps/cloud/src/project/dto/update-project.dto.ts (1)

35-47: Align captchaDifficulty validation/docs with UI options

DTO and docs advertise a 1–6 range (@Min(1), minimum: 1), but the settings UI (ProjectSettings CAPTCHA tab) only allows 2–6. Consider either:

  • Exposing difficulty 1 in the UI, or
  • Tightening the DTO to Min(2) and updating the description to “Range: 2–6”.

Keeping these in sync avoids confusing/hidden states depending on whether clients use the UI or call the API directly.

backend/apps/cloud/src/project/entity/project.entity.ts (1)

72-81: Make captchaSecretKey explicitly nullable in the entity

captchaSecretKey is used as a presence flag (“if set, CAPTCHA is enabled”) and is initialized with default: null, but:

  • The TypeScript type is string (not string | null), and
  • The column options omit nullable: true, unlike passwordHash below.

For consistency and to avoid surprises when the key is absent/cleared, consider:

// CAPTCHA secret key - if set, CAPTCHA is enabled for this project
@ApiProperty()
@Column('varchar', {
  default: null,
  length: CAPTCHA_SECRET_KEY_LENGTH,
  nullable: true,
})
captchaSecretKey: string | null

(Ensuring the underlying migration also treats this column as nullable.)

backend/apps/cloud/src/project/project.controller.ts (2)

746-779: Tighten typing in secretGen to avoid // @ts-ignore

secretGen correctly validates PID, checks access, generates a new secret, and clears Redis. The only smell is the // @ts-ignore before projectService.update:

// @ts-ignore
await this.projectService.update({ id: pid }, { captchaSecretKey: secret })

You can avoid the ignore by leveraging a typed helper or a cast, e.g.:

await this.projectService.update(
  { id: pid },
  { captchaSecretKey: secret } as Partial<Project>,
)

or by using a dedicated updateProject(pid, { captchaSecretKey: secret }) helper from the service. This keeps the route type-safe without changing runtime behavior.


1850-1859: Avoid redundant await inside Promise.all for data-existence checks

The new isCaptchaDataExists flag is wired correctly, but the pattern:

const [isDataExists, isErrorDataExists, isCaptchaDataExists] =
  await Promise.all([
    !_isEmpty(await this.projectService.getPIDsWhereAnalyticsDataExists([id])),
    !_isEmpty(await this.projectService.getPIDsWhereErrorsDataExists([id])),
    !_isEmpty(await this.projectService.getPIDsWhereCaptchaDataExists([id])),
  ])

runs the three service calls sequentially because of the inner awaits. A more idiomatic (and concurrently executed) version would be:

const [analyticsPids, errorPids, captchaPids] = await Promise.all([
  this.projectService.getPIDsWhereAnalyticsDataExists([id]),
  this.projectService.getPIDsWhereErrorsDataExists([id]),
  this.projectService.getPIDsWhereCaptchaDataExists([id]),
])

const isDataExists = !_isEmpty(analyticsPids)
const isErrorDataExists = !_isEmpty(errorPids)
const isCaptchaDataExists = !_isEmpty(captchaPids)

This keeps behavior identical but avoids unnecessary latency on this hot path.

Also applies to: 1895-1898

web/app/pages/Project/View/components/CaptchaChart.tsx (1)

1-57: CaptchaChart implementation is solid; consider making chartId configurable

The component correctly defers to getSettingsCaptcha, short-circuits when chart is absent, and passes a well-scoped deps array into MainChart. Only minor nit: chartId is hard-coded to 'captcha-chart', which could cause collisions if multiple captcha charts are ever rendered simultaneously.

If you foresee that scenario, consider either:

  • Accepting chartId as a prop, or
  • Deriving a unique ID (e.g., from chartType or PID) to avoid clashes in useChartManager.

Otherwise, this looks good.

web/app/pages/Project/Settings/ProjectSettings.tsx (1)

14-25: CAPTCHA settings flow is well-integrated; align difficulty range with backend

The new CAPTCHA tab integrates cleanly:

  • It’s only visible on cloud (visible: !isSelfhosted).
  • loadProject seeds captchaSecretKey and captchaDifficulty.
  • Secret generation/regeneration uses reGenerateCaptchaSecretKey(id) with appropriate confirmation for regen.
  • Difficulty changes are pushed via updateProject(id, { captchaDifficulty }) and gated behind an explicit Save.

Two minor points to consider:

  1. Difficulty range mismatch vs backend DTO

    Here the UI only exposes difficulty values 2–6:

    items={[
      { value: 2, label: t('...veryEasy') },
      { value: 3, label: t('...easy') },
      { value: 4, label: t('...medium') },
      { value: 5, label: t('...hard') },
      { value: 6, label: t('...veryHard') },
    ]}

    while UpdateProjectDto and the entity describe/validate a 1–6 range. If 1 is intentionally not user-facing, it would be clearer to update the DTO validation/docs (e.g. Min(2), “Range: 2–6”) so API and UI tell the same story. Otherwise, consider adding the “1” option here.

  2. Optional UX polish

    • Key generation / regeneration currently has no loading state. If calls to reGenerateCaptchaSecretKey can be slow, you might eventually want an isRegenerating flag to disable the buttons and show a spinner, similar to isSavingDifficulty.

None of these are blockers; the overall CAPTCHA settings integration looks solid.

Also applies to: 31-47, 321-361, 380-385, 438-442, 988-1090, 1198-1217

web/app/pages/Project/View/ViewProject.tsx (2)

390-433: Captcha context wiring looks consistent; consider exposing a refresh helper only if needed later

The addition of captchaRefreshTrigger to ViewProjectContextType, defaultViewProjectContext, state, and provider value is internally consistent and type-safe; consumers like CaptchaView can now react to refreshes without touching the main analytics state.

If more components eventually need to initiate a captcha refresh, you might later factor this into a small context helper (e.g. refreshCaptcha()) instead of exposing the raw counter, but that’s optional at this stage.

Also applies to: 540-541, 3414-3415


1461-1534: Captcha tab integration is correct; minor optional tweaks to loading/auto‑refresh behavior

  • The new PROJECT_TABS.captcha entry (with ShieldCheckIcon) is correctly limited to the non‑selfhosted branch and flows through ProjectSidebar / MobileTabSelector.
  • The extended !project.isDataExists guard to special‑case (activeTab === PROJECT_TABS.captcha && project.isCaptchaDataExists) avoids blocking the captcha tab when only captcha data exists, without changing behavior for other tabs.
  • The refreshStats branch that bumps captchaRefreshTrigger is a clean way to let CaptchaView re‑fetch independently of the main analytics loaders.

Two minor, non‑blocking considerations:

  • isAutoRefreshing is set but never reset on the captcha branch (since dataLoading is not used there). It currently only gates the top LoadingBar, so this is mostly cosmetic; if auto refreshes for captcha become more prominent, you may want to explicitly clear isAutoRefreshing when the captcha reload completes.
  • analyticsLoading is never driven by the captcha data path; that’s fine today because it’s used only for the main analytics tabs, but worth keeping in mind if you ever unify loading indicators across all project tabs.

Also applies to: 2611-2667, 3318-3323, 4298-4299

backend/apps/cloud/src/captcha/captcha.controller.ts (1)

39-47: Verify endpoint PoW flow is coherent and matches the new challenge API

The controller changes line up cleanly with the new PoW model:

  • /generate now validates pid and delegates to generateChallenge(pid).
  • /verify correctly destructures { challenge, nonce, solution, pid }, validates the project, runs verifyPoW, and on success issues a token via generateToken(pid, challenge, timestamp).
  • Dummy PIDs (ALWAYS_PASS / ALWAYS_FAIL) are handled explicitly and no longer rely on hash-based behavior.

One optional thing to consider is trimming the logged verifyDto to avoid noisy logs (e.g. log only pid and a shortened challenge), but functionally the handler looks good.

Also applies to: 58-61, 67-80, 82-98, 109-115

web/app/pages/Project/View/components/CaptchaView.tsx (1)

77-88: Reduce duplication by reusing shared helpers and trimming unused hooks

A couple of small refactors would simplify this component:

  1. Reuse existing date formatting helper instead of re‑implementing getFormatDate
    There’s already a shared getFormatDate in ViewProject.helpers.tsx used by other loaders. Importing and reusing it here will keep date formatting consistent and avoid yet another hand‑rolled variant.

    -import { Filter } from '../interfaces/traffic'
    -import { Panel } from '../Panels'
    -import { parseFilters } from '../utils/filters'
    -import { ViewProjectContext } from '../ViewProject'
    -import { deviceIconMapping } from '../ViewProject.helpers'
    +import { Filter } from '../interfaces/traffic'
    +import { Panel } from '../Panels'
    +import { parseFilters } from '../utils/filters'
    +import { ViewProjectContext } from '../ViewProject'
    +import { deviceIconMapping, getFormatDate } from '../ViewProject.helpers'
    ...
    -  const getFormatDate = (date: Date) => {
    -    const yyyy = date.getFullYear()
    -    let mm: string | number = date.getMonth() + 1
    -    let dd: string | number = date.getDate()
    -    if (dd < 10) dd = `0${dd}`
    -    if (mm < 10) mm = `0${mm}`
    -    return `${yyyy}-${mm}-${dd}`
    -  }
  2. Drop the local useSize call if you don’t actually use the measured size
    You currently do:

    const [ref] = useSize() as any
    ...
    <div ref={ref}>

    but rotateXAxis is derived from size in ViewProjectContext, not from this hook’s measurement. Unless you plan to use this local size for something else, you can remove useSize here and the ref on the outer <div> to avoid an unnecessary observer.

    -import useSize from '~/hooks/useSize'
    ...
    -  const tnMapping = typeNameMapping(t)
    -  const [ref] = useSize() as any
    +  const tnMapping = typeNameMapping(t)
    ...
    -  return (
    -    <div ref={ref}>
    +  return (
    +    <div>

These are purely cleanup changes and won’t affect behavior.

Also applies to: 158-170, 179-356

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 318761b and 3f0511c.

📒 Files selected for processing (47)
  • backend/apps/cloud/src/captcha/captcha.controller.ts (4 hunks)
  • backend/apps/cloud/src/captcha/captcha.service.ts (9 hunks)
  • backend/apps/cloud/src/captcha/dtos/generate.dto.ts (1 hunks)
  • backend/apps/cloud/src/captcha/dtos/manual.dto.ts (2 hunks)
  • backend/apps/cloud/src/captcha/interfaces/generated-captcha.ts (1 hunks)
  • backend/apps/cloud/src/project/dto/create-project.dto.ts (0 hunks)
  • backend/apps/cloud/src/project/dto/project.dto.ts (0 hunks)
  • backend/apps/cloud/src/project/dto/update-project.dto.ts (2 hunks)
  • backend/apps/cloud/src/project/entity/project.entity.ts (1 hunks)
  • backend/apps/cloud/src/project/project.controller.ts (4 hunks)
  • backend/apps/cloud/src/project/project.service.ts (2 hunks)
  • backend/apps/community/src/user/user.controller.ts (0 hunks)
  • backend/migrations/mysql/2025_12_06_captcha_difficulty.sql (1 hunks)
  • backend/migrations/mysql/2025_12_06_remove_captcha_flags.sql (1 hunks)
  • web/app/App.tsx (1 hunks)
  • web/app/api/index.ts (1 hunks)
  • web/app/components/Footer/index.tsx (1 hunks)
  • web/app/components/Header/index.tsx (2 hunks)
  • web/app/lib/constants/index.ts (1 hunks)
  • web/app/lib/models/Project.ts (2 hunks)
  • web/app/pages/Captcha/Settings/CaptchaSettings.tsx (0 hunks)
  • web/app/pages/Captcha/Settings/index.tsx (0 hunks)
  • web/app/pages/Captcha/View/ViewCaptcha.helpers.tsx (0 hunks)
  • web/app/pages/Captcha/View/ViewCaptcha.tsx (0 hunks)
  • web/app/pages/Captcha/View/index.tsx (0 hunks)
  • web/app/pages/Captcha/index.tsx (1 hunks)
  • web/app/pages/Dashboard/Dashboard.tsx (2 hunks)
  • web/app/pages/Dashboard/ProjectCard.tsx (5 hunks)
  • web/app/pages/ErrorTracking/index.tsx (0 hunks)
  • web/app/pages/Performance/index.tsx (0 hunks)
  • web/app/pages/Project/Settings/ProjectSettings.tsx (7 hunks)
  • web/app/pages/Project/View/ViewProject.helpers.tsx (2 hunks)
  • web/app/pages/Project/View/ViewProject.tsx (10 hunks)
  • web/app/pages/Project/View/components/CaptchaChart.tsx (1 hunks)
  • web/app/pages/Project/View/components/CaptchaView.tsx (1 hunks)
  • web/app/pages/Project/View/components/ProjectSidebar.tsx (5 hunks)
  • web/app/pages/UserSettings/UserSettings.tsx (1 hunks)
  • web/app/routes/captcha.tsx (1 hunks)
  • web/app/routes/captchas.$id.tsx (0 hunks)
  • web/app/routes/captchas.new.tsx (0 hunks)
  • web/app/routes/captchas.settings.$id.tsx (0 hunks)
  • web/app/ui/Input.tsx (1 hunks)
  • web/app/ui/icons/BackgroundSvg.tsx (0 hunks)
  • web/app/utils/analytics.ts (0 hunks)
  • web/app/utils/routes.ts (1 hunks)
  • web/app/utils/server.ts (1 hunks)
  • web/public/locales/en.json (4 hunks)
💤 Files with no reviewable changes (15)
  • backend/apps/cloud/src/project/dto/create-project.dto.ts
  • backend/apps/cloud/src/project/dto/project.dto.ts
  • backend/apps/community/src/user/user.controller.ts
  • web/app/utils/analytics.ts
  • web/app/pages/ErrorTracking/index.tsx
  • web/app/routes/captchas.new.tsx
  • web/app/pages/Performance/index.tsx
  • web/app/pages/Captcha/Settings/index.tsx
  • web/app/pages/Captcha/View/index.tsx
  • web/app/pages/Captcha/View/ViewCaptcha.helpers.tsx
  • web/app/pages/Captcha/View/ViewCaptcha.tsx
  • web/app/routes/captchas.settings.$id.tsx
  • web/app/pages/Captcha/Settings/CaptchaSettings.tsx
  • web/app/routes/captchas.$id.tsx
  • web/app/ui/icons/BackgroundSvg.tsx
🧰 Additional context used
🧬 Code graph analysis (10)
web/app/routes/captcha.tsx (1)
web/app/lib/constants/index.ts (2)
  • isSelfhosted (399-399)
  • isDisableMarketingPages (401-402)
web/app/pages/Project/View/components/ProjectSidebar.tsx (1)
web/app/lib/constants/index.ts (1)
  • PROJECT_TABS (491-493)
web/app/pages/Captcha/index.tsx (2)
web/app/lib/constants/index.ts (1)
  • DOCS_URL (366-366)
web/app/components/marketing/DitchGoogle.tsx (1)
  • DitchGoogle (8-64)
web/app/pages/Project/View/components/CaptchaChart.tsx (2)
web/app/pages/Project/View/ViewProject.helpers.tsx (1)
  • getSettingsCaptcha (1961-1961)
web/app/pages/Project/View/components/MainChart.tsx (1)
  • MainChart (16-42)
web/app/pages/Project/View/ViewProject.helpers.tsx (2)
web/app/lib/constants/index.ts (6)
  • chartTypes (921-924)
  • TimeFormat (323-326)
  • tbsFormatMapper24h (315-321)
  • tbsFormatMapper (291-297)
  • tbsFormatMapperTooltip24h (307-313)
  • tbsFormatMapperTooltip (299-305)
web/app/utils/generic.ts (1)
  • nFormatter (20-24)
web/app/pages/Project/Settings/ProjectSettings.tsx (3)
web/app/lib/constants/index.ts (1)
  • isSelfhosted (399-399)
backend/apps/cloud/src/project/project.service.ts (1)
  • updateProject (1590-1592)
web/app/api/index.ts (2)
  • updateProject (418-424)
  • reGenerateCaptchaSecretKey (1156-1162)
web/app/pages/Project/View/components/CaptchaView.tsx (6)
web/app/pages/Project/View/ViewProject.helpers.tsx (1)
  • deviceIconMapping (1646-1654)
web/app/providers/ThemeProvider.tsx (1)
  • useTheme (29-37)
web/app/pages/Project/View/ViewProject.tsx (1)
  • ViewProjectContext (435-435)
web/app/pages/Project/View/utils/filters.tsx (1)
  • parseFilters (97-133)
web/app/api/index.ts (1)
  • getCaptchaData (855-865)
web/app/lib/constants/index.ts (3)
  • BROWSER_LOGO_MAP (931-978)
  • OS_LOGO_MAP (980-1006)
  • OS_LOGO_MAP_DARK (1008-1013)
web/app/pages/Project/View/ViewProject.tsx (1)
web/app/lib/constants/index.ts (1)
  • PROJECT_TABS (491-493)
backend/apps/cloud/src/captcha/captcha.controller.ts (1)
backend/apps/cloud/src/captcha/captcha.service.ts (1)
  • DUMMY_PIDS (31-34)
backend/apps/cloud/src/captcha/captcha.service.ts (3)
backend/apps/community/src/common/utils.ts (1)
  • hash (79-85)
backend/apps/cloud/src/common/utils.ts (1)
  • hash (22-28)
backend/apps/cloud/src/captcha/interfaces/generated-captcha.ts (1)
  • GeneratedChallenge (1-4)
🔇 Additional comments (33)
web/app/pages/UserSettings/UserSettings.tsx (1)

39-39: LGTM! Clean import consolidation.

The duplicate Text import has been properly consolidated into a single import statement, improving code cleanliness without affecting functionality.

web/app/pages/Project/View/components/ProjectSidebar.tsx (3)

3-10: ShieldCheckIcon import fits existing icon usage

The added ShieldCheckIcon import aligns with how other lucide icons are used in this file and in the new CAPTCHA group. No issues here.


218-228: CAPTCHA group logic looks consistent with existing grouping

The CAPTCHA group:

  • Filters on PROJECT_TABS.captcha, matching the pattern used for other groups.
  • Uses its own icon and color via ShieldCheckIcon and GROUP_ICON_COLORS.captcha.
  • Starts collapsed via defaultExpanded: false but will auto-expand when active due to the shared CollapsibleGroup effect.

Assuming PROJECT_TABS.captcha and the common.captcha i18n key are defined elsewhere, this is a clean, non‑breaking extension of the sidebar.


238-238: Tooltip width tweak is safe and improves truncation behavior

Adding className='max-w-full' to Tooltip matches the Text node’s max-w-full and should prevent layout overflow/clipping for long project names without affecting behavior.

web/app/pages/Dashboard/ProjectCard.tsx (4)

148-148: LGTM! Dependency array correctly updated.

The removal of project.isCaptchaProject from the dependency array is correct since this field is no longer used in the badges calculation. All other dependencies are properly included.


261-261: Line 261 is not a change addressing CAPTCHA projects.

The review comment appears to be based on a misunderstanding. isCaptchaProject does not exist in the codebase—neither in current code nor in git history. The live visitors MiniCard at line 261 renders unconditionally with the live prop, and there is no evidence of conditional rendering being removed based on CAPTCHA project type. The backend /live-visitors API endpoint does not implement CAPTCHA-specific filtering for this data.

Likely an incorrect or invalid review comment.


240-240: The isCaptchaProject field has been removed from the database schema via migration, eliminating all CAPTCHA-specific logic from the project system. The label consolidation is part of this cleanup and does not present any concern for existing functionality.

Likely an incorrect or invalid review comment.


203-203: Verify backend routing is updated to match the consolidated project routing.

The pathname now always uses routes.project, removing the conditional CAPTCHA-specific routing that existed in the previous hash-based implementation. Confirm that the backend has been updated to handle PoW CAPTCHA projects through the standard project route and that all related routing logic throughout the application is consistent with this change.

backend/migrations/mysql/2025_12_06_captcha_difficulty.sql (1)

1-6: LGTM! Migration adds captchaDifficulty column appropriately.

The migration correctly adds the captchaDifficulty column with a sensible default value of 4. The TINYINT UNSIGNED type is appropriate for storing difficulty levels (0-255 range), and the default aligns with the PoW requirements mentioned in the comments.

backend/apps/cloud/src/captcha/dtos/manual.dto.ts (1)

1-37: LGTM! DTO correctly reflects PoW-based verification model.

The changes properly restructure the verification payload for Proof-of-Work CAPTCHA:

  • hashchallenge: Clearer naming for the server-generated challenge
  • codesolution: More descriptive for the resulting hash
  • New nonce field: Essential for PoW verification

All fields have appropriate validation decorators and API documentation.

web/app/utils/server.ts (1)

223-223: LGTM! Comment correctly updated to reflect route changes.

The removal of new_captcha from the comment aligns with the CAPTCHA feature restructuring. The new captchaLanding route relies on default title behavior, which is appropriate for a marketing page.

web/app/lib/models/Project.ts (1)

176-192: LGTM! Project interface correctly updated for new CAPTCHA model.

The changes properly reflect the migration from flag-based to secret-key-based CAPTCHA:

  • captchaDifficulty: Stores the PoW difficulty level
  • isCaptchaDataExists: Indicates whether analytics data exists for this project's CAPTCHA

The removal of isCaptchaProject and isCaptchaEnabled (mentioned in the summary) aligns with the backend entity changes and simplifies the model.

web/app/api/index.ts (1)

410-416: All call sites correctly updated after removing isCaptcha parameter.

The removal is complete and properly implemented. Both call sites in the frontend (Onboarding.tsx and Dashboard.tsx) pass only name and optional organisationId, matching the new signature. No remaining code passes the removed isCaptcha parameter. This aligns with the backend changes that moved CAPTCHA configuration to post-creation settings.

web/app/pages/Captcha/index.tsx (1)

1-182: LGTM! Well-structured CAPTCHA marketing page.

The implementation follows established patterns and best practices:

  • Proper i18n integration for all user-facing text — all 21 translation keys are present in the locale files
  • Reuses existing marketing components (DitchGoogle, MarketingPricing)
  • Responsive design with dark mode support
  • Clean component structure with hero, features, and how-it-works sections
  • Correct use of internal routing and external documentation links
backend/migrations/mysql/2025_12_06_remove_captcha_flags.sql (1)

1-5: Verify migration dependencies and data migration strategy before deployment.

This migration drops isCaptchaProject and isCaptchaEnabled columns without preserving existing CAPTCHA project settings. Before running this migration, confirm:

  1. Migration ordering: All data migrations that convert existing flag-based CAPTCHA projects to the new secret-key-based model have completed and captchaSecretKey is populated for all projects that previously had isCaptchaProject=true or isCaptchaEnabled=true.

  2. Backend deployment: Code expecting the new schema (without these columns) must be deployed and active before this migration executes, or the migration must be rolled back if incompatible code is running.

  3. Data audit: Verify no projects will lose CAPTCHA configuration by checking that all projects with the old flags have a non-null captchaSecretKey value before dropping the columns.

backend/apps/cloud/src/project/project.service.ts (2)

1185-1232: LGTM! Captcha data existence check follows established pattern.

The new getPIDsWhereCaptchaDataExists method correctly mirrors the implementation pattern of getPIDsWhereAnalyticsDataExists and getPIDsWhereErrorsDataExists. The SQL query properly checks for captcha data existence using parameterized queries.


324-324: Field migration is complete and consistent across the codebase.

The captchaDifficulty field (number) has successfully replaced isCaptchaEnabled throughout the codebase. All consuming code properly expects and handles the numeric type: the entity and DTO define it as number, the controller assigns it correctly, the captcha service uses it with a sensible default fallback, and the UI state management and rendering logic all handle it appropriately. No stray references to the old field remain.

web/app/pages/Project/View/ViewProject.helpers.tsx (1)

1804-1941: LGTM! Chart configuration follows established patterns.

The getSettingsCaptcha function is well-implemented and consistent with other chart configuration helpers in the file:

  • Properly builds data columns from chart.x and chart.results
  • Calculates optimal Y-axis ticks using the existing calculateOptimalTicks helper
  • Configures regions for dashed lines on recent data
  • Provides consistent tooltip and legend formatting
  • Color choice #16a34a (green-600) is semantically appropriate for captcha metrics
web/app/lib/constants/index.ts (1)

488-488: LGTM! Captcha tab correctly added to production tabs only.

The addition of the captcha tab to PRODUCTION_PROJECT_TABS (and not to SELFHOSTED_PROJECT_TABS) correctly reflects the PR's objective to update only Cloud/Enterprise Edition code.

web/app/App.tsx (1)

65-65: LGTM! Header suppression appropriate for landing page.

Adding routesPath.captchaLanding to routesWithOutHeader is consistent with other marketing/landing pages (main, performance, errorTracking) that also suppress the default header for custom hero sections.

backend/apps/cloud/src/captcha/dtos/generate.dto.ts (1)

1-12: LGTM! DTO simplified by removing theme option.

Removing the theme property streamlines the GenerateDto to only require the project ID, which aligns with the migration from hash-based to PoW-based CAPTCHA. The remaining validation on pid is appropriate.

web/app/components/Footer/index.tsx (1)

35-35: LGTM! Footer link updated to use internal captcha route.

Changing the captcha link from the external CAPTCHA_URL to the internal routesPath.captchaLanding is consistent with the new internal captcha landing page introduced in this PR.

web/app/routes/captcha.tsx (1)

1-22: LGTM! Route correctly handles selfhosted and marketing page restrictions.

The captcha landing route implementation is clean and follows Remix conventions:

  • Sitemap configuration appropriately excludes the page for selfhosted/disabled marketing scenarios
  • Loader redirect to /login (HTTP 302) is the correct approach for restricted environments
  • Route structure is consistent with other marketing pages in the codebase
backend/apps/cloud/src/captcha/interfaces/generated-captcha.ts (1)

1-4: Interface rename successfully completed across the codebase.

The migration from GeneratedCaptcha to GeneratedChallenge is complete. Verification confirms:

  • No remaining references to the old interface name
  • Old field names (.data, .hash) are not accessed anywhere in captcha-related code
  • The new interface is properly imported in captcha.service.ts and used as the return type for generateChallenge()
  • New fields (challenge, difficulty) are correctly implemented and utilized

All consumers have been updated successfully.

web/app/utils/routes.ts (1)

1-9: New captchaLanding route looks consistent

Adding captchaLanding: '/captcha' fits existing routing conventions and matches how the Header and other components now link to the CAPTCHA landing experience.

web/app/pages/Dashboard/Dashboard.tsx (1)

13-14: Consolidated overall stats fetch is sane

Switching to a single getOverallStats call over projects.map((p) => p.id) simplifies the dashboard live-update path and removes the captcha-specific stats branch, while still honoring the hostname-navigation guard inside updateOverallStats. No issues spotted here.

Also applies to: 346-360

web/app/components/Header/index.tsx (1)

34-35: Using routes.captchaLanding in header solutions is appropriate

Replacing the CAPTCHA solution link with routes.captchaLanding and dropping CAPTCHA_URL keeps navigation internal to the app and reuses your existing “internal vs external link” logic. This is a clean integration with the new route.

Also applies to: 73-76

backend/apps/cloud/src/project/project.controller.ts (2)

320-338: Unified project cap check looks good

Checking _size(user.projects) >= maxProjects regardless of project type simplifies quota enforcement and matches the semantics of “total projects per account/owner”. No functional issues here.


1746-1748: captchaDifficulty update path is straightforward

Conditionally assigning project.captchaDifficulty when projectDTO.captchaDifficulty !== undefined is consistent with the DTO’s optional field and avoids unintentionally resetting the value. Given the class-validator constraints on the DTO, this is safe.

web/app/pages/Project/View/components/CaptchaView.tsx (1)

48-76: CaptchaView integrates cleanly with existing dashboard context and refresh semantics

The component is wired appropriately:

  • It consumes period, timeBucket, dateRange, captchaRefreshTrigger, timeFormat, and size from ViewProjectContext, and uses captchaRefreshTrigger to respond to global refreshes.
  • Data loading mirrors the main dashboards: first load gated by analyticsLoading, subsequent reloads by dataLoading, with NoEvents on empty results.
  • Filters are parsed from searchParams via parseFilters, and getFilterLink toggles filter query params in a way consistent with other panels.
  • Chart rendering via CaptchaChart and the four panel blocks (cc, dv, br, os) reuse existing Panel, CCRow, device icon mapping, and logo maps, providing a consistent UI with the other analytics tabs.

Functionally this looks solid and in line with the rest of the project view implementation.

Also applies to: 89-143, 171-177, 179-357

web/public/locales/en.json (3)

217-261: Well-structured CAPTCHA landing page localization.

The new captchaPage section includes comprehensive copy for a marketing landing page covering slogans, features (privacy, open-source, customizable, easy to use), integration steps, and a preview. Structure follows established patterns in the file and content is clear and professional.


1241-1262: Verify naming consistency between account-level and project-level CAPTCHA settings.

There's a minor naming inconsistency between profileSettings.captcha keys and project.settings.captcha keys:

  • Account level: captchaSecretKey, generateSecretKey, regenerateSecretKey (fully qualified names)
  • Project level: secretKey, generateKey, regenerateKey (short names, relying on parent context)

This pattern difference may be intentional (account vs. project scope), but please verify it aligns with how these keys are referenced in the UI components and backend schema.

Also applies to: 727-734


114-114: Dashboard, metric, and project settings labels are well-integrated.

Additions to common.captcha, dashboard.captcha, project.captchaCompletions, project settings tab, and project captcha configuration follow established naming and structure conventions. The difficulty levels (veryEasy, easy, medium, hard, veryHard) align well with a Proof-of-Work model.

Also applies to: 834-834, 964-964, 1234-1234, 1362-1370

@Blaumaus Blaumaus merged commit d1d0615 into swetrix-revamp Dec 6, 2025
2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Dec 6, 2025
6 tasks
This was referenced Dec 19, 2025
@coderabbitai coderabbitai bot mentioned this pull request Feb 7, 2026
6 tasks
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.

1 participant