Skip to content

Implement Sponsors Program Support #4520

Draft
Mr-Rahul-Paul wants to merge 15 commits intoOWASP:mainfrom
Mr-Rahul-Paul:sponsor
Draft

Implement Sponsors Program Support #4520
Mr-Rahul-Paul wants to merge 15 commits intoOWASP:mainfrom
Mr-Rahul-Paul:sponsor

Conversation

@Mr-Rahul-Paul
Copy link
Copy Markdown
Contributor

@Mr-Rahul-Paul Mr-Rahul-Paul commented Apr 8, 2026

Resolves #4259

This PR introduces PoC for sponsor management within the Nest platform. It implements both the backend, alongside a clean frontend workflow for sponsors to apply.

Features introduced and features extended

  • Sponsor Model Expansion : Extended the existing owasp_sponsors database table to support entity associations , sponsors levels and application status
  • Extentend and altered existing DB tables , requiring migrations.
  • Created a new public REST endpoint /api/v0/sponsors that surfaces active sponsors for external dashboards, and GitHub Actions.
  • /sponsors page and a /sponsors/apply page that has the application form (I am assuming there is no payment gateway needed and sponsorship will be processed externally)
  • Existing Orgs in the backend are being fetched and displayed on the sponsors page.

Notes

  • the Sponsors page was directly inspired by other OSS project's sponsor page (Blender) , github sponsors and OWASP community page
  • Backend endpoint remains unprotected and can be spammed , I would appreciate if maintainers can guide / tell what is their preferred way to protect the backend API from getting spammed (rate limiting)

Demo

  • demo video will be added shortly.

Checklist

  • Required: I followed the contributing workflow
  • Required: I verified that my code works as intended and resolves the issue as described
  • Required: I ran make check-test locally: all warnings addressed, tests passed
  • I used AI for code, documentation, tests, or communication related to this PR

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 42a4257a-b58a-4e4f-bf16-71342433761e

📥 Commits

Reviewing files that changed from the base of the PR and between ca79cd1 and 94c2912.

📒 Files selected for processing (1)
  • backend/apps/common/management/commands/dump_data.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/apps/common/management/commands/dump_data.py

Summary by CodeRabbit

  • New Features
    • Sponsor application flow: public form with validation and confirmation, plus a sponsors directory page showing sponsors by tier and a “Become a sponsor” CTA.
  • Bug Fixes
    • Sponsors API and public listings now surface only active sponsors.
  • Improvements
    • Enhanced form UX (validation, naming, blur tracking), loading skeletons, metadata/links, and admin bulk actions to activate or archive sponsors.

Walkthrough

Adds sponsor support: model fields and migrations; admin actions; REST apply endpoint and active-only filtering; dump-data backfills; GraphQL filter change; new frontend sponsors pages, layouts, components, types, and tests; form component adjustments and Jest coverage exclusions.

Changes

Cohort / File(s) Summary
Sponsor model & migrations
backend/apps/owasp/models/sponsor.py, backend/apps/owasp/migrations/0073_sponsor_status_contact_email_chapter_project.py, backend/apps/owasp/migrations/0074_backfill_sponsor_status_not_null.py, backend/apps/owasp/migrations/0075_backfill_sponsor_contact_email_not_null.py
Add status (draft/active/archived), contact_email, optional chapter/project FKs, make sort_name blankable; backfill and enforce defaults/NOT NULL.
REST API endpoints & schemas
backend/apps/api/rest/v0/sponsor.py
Add SponsorApplyRequest/SponsorApplyResponse; new POST /apply (slugifies key, empty/duplicate checks, creates draft sponsor); restrict list_sponsors/get_sponsor to ACTIVE sponsors.
Django admin
backend/apps/owasp/admin/sponsor.py
Add admin actions activate_sponsors/archive_sponsors; expose status and contact_email in list/display; add status filter and reorganize fieldsets (Entity Associations).
Data dump command & tests
backend/apps/common/management/commands/dump_data.py, backend/tests/unit/apps/common/management/commands/dump_data_test.py
Add SQL updates to normalize sponsor status and contact_email before dump; tests assert new SQL and tighten detection logic.
Backend resolvers & tests
backend/apps/owasp/api/internal/queries/sponsor.py, backend/tests/unit/apps/owasp/api/internal/queries/sponsor_test.py, backend/tests/unit/apps/api/rest/v0/sponsor_test.py
GraphQL resolver now filters ACTIVE sponsors; REST tests updated and new tests added for apply_sponsor (empty-key BAD_REQUEST and successful create with trimmed name).
Frontend pages & layouts
frontend/src/app/sponsors/page.tsx, frontend/src/app/sponsors/layout.tsx, frontend/src/app/sponsors/apply/page.tsx, frontend/src/app/sponsors/apply/layout.tsx
Add /sponsors and /sponsors/apply pages and layouts; sponsors page queries and groups sponsors by tier; apply page composes hero + application form.
Frontend sponsor components
frontend/src/components/sponsors/...
frontend/src/components/sponsors/SponsorApplicationForm.tsx, .../SponsorApplyHero.tsx, .../SponsorHero.tsx, .../SponsorDiamondCard.tsx, .../SponsorTierCard.tsx, .../SponsorTierSection.tsx, .../SponsorsFaqSection.tsx, .../SponsorsOpenSourceShowcase.tsx, .../BecomeSponsorCTA.tsx
New client components: application form, hero/CTA, tiered cards, FAQ, showcase, CTA linking to apply/donate.
Skeleton / loading UI
frontend/src/components/skeletons/SponsorsSkeleton.tsx
Add SponsorsSkeleton loading component using Skeleton elements.
GraphQL query & TS types
frontend/src/server/queries/sponsorQueries.ts, frontend/src/types/sponsor.ts
Add GetSponsorsPageData query and TypeScript types: SponsorTier, SponsorData, SponsorsByTier, TIER_ORDER, TIER_LABEL.
Form components & usage updates
frontend/src/components/forms/shared/{FormDateInput.tsx,FormTextInput.tsx,FormTextarea.tsx}, frontend/src/components/{ModuleForm.tsx,ProgramForm.tsx}
Add required name props (plus optional autoComplete/onBlur), move inline styles to Tailwind classes, refine invalid/error styling, add touch-on-blur for textareas.
Header / UI refactors & constants
frontend/src/components/Header.tsx, frontend/src/components/LogoCarousel.tsx, frontend/src/components/NavButton.tsx, frontend/src/utils/constants.ts, frontend/src/utils/metadata.ts
Refactor Header (filtered links, logo extraction, actionButtons), replace hard-coded donate URL with OWASP_NEST_* constants, update footer link to /sponsors, add sponsors metadata.
Frontend tests & Jest config
frontend/__tests__/unit/components/forms/shared/{FormDateInput.test.tsx,FormTextarea.test.tsx}, frontend/jest.config.ts
Adjust tests to assert Tailwind class usage / classname regexes; add coverage exclusions for sponsor paths in Jest config.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • kasya
  • arkid15r
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.77% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Implement Sponsors Program Support' accurately reflects the main objective of the PR, which introduces a complete sponsor management system with backend and frontend components.
Description check ✅ Passed The PR description is well-related to the changeset, explaining features introduced (sponsor model expansion, new endpoints, pages), database changes, and noting the unprotected endpoint issue.
Linked Issues check ✅ Passed The PR implementation meets the core requirements from issue #4259: extended sponsor model with status/level fields, created /sponsors and /sponsors/apply pages, implemented REST API endpoint, and added Django Admin management.
Out of Scope Changes check ✅ Passed All code changes are aligned with the stated objectives of implementing sponsor management. Minor styling updates to form components and header refactoring are supportive changes for the sponsor application form.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

2 issues found across 38 files

Confidence score: 2/5

  • There is a concrete privacy risk in backend/apps/common/management/commands/dump_data.py: masked dumps only clear email, so populated contact_email values can be exported and leak sponsor PII.
  • backend/apps/api/rest/v0/sponsor.py has a data-quality/regression risk where apply_sponsor can save an empty sponsor key when slugify returns an empty string, which can cause downstream lookup or validation issues.
  • The score is lowered because the top issue is user-impacting and high-confidence (PII exposure), with an additional medium-confidence persistence bug increasing merge risk.
  • Pay close attention to backend/apps/common/management/commands/dump_data.py and backend/apps/api/rest/v0/sponsor.py - prevent contact_email leakage in dumps and block empty sponsor keys from being persisted.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/apps/common/management/commands/dump_data.py">

<violation number="1" location="backend/apps/common/management/commands/dump_data.py:123">
P2: Masked dump only clears columns named `email`; the new `contact_email` field is only normalized when NULL, so real sponsor contact emails will remain in the dump and leak PII.</violation>
</file>

<file name="backend/apps/api/rest/v0/sponsor.py">

<violation number="1" location="backend/apps/api/rest/v0/sponsor.py:146">
P2: apply_sponsor can persist an empty sponsor key when slugify yields an empty string (e.g., whitespace/punctuation-only organization_name).</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Copy Markdown
Contributor

@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: 4

🧹 Nitpick comments (17)
frontend/__tests__/unit/components/forms/shared/FormTextarea.test.tsx (1)

7-7: Add an explicit assertion for the new required name prop.

The fixture now provides name, but no test verifies it reaches the rendered <textarea>.

Suggested test addition
   it('renders with default props (required=false, rows=4)', () => {
     render(<FormTextarea {...defaultProps} />)
     const textarea = screen.getByRole('textbox')
     expect(textarea).toBeInTheDocument()
+    expect(textarea).toHaveAttribute('name', 'test-textarea')
     expect(textarea).toHaveAttribute('rows', '4')
     expect(textarea).not.toBeRequired()
     expect(screen.queryByText('*')).not.toBeInTheDocument()
   })
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/__tests__/unit/components/forms/shared/FormTextarea.test.tsx` at
line 7, Add an assertion in the FormTextarea unit test to verify the required
name prop is passed through to the rendered <textarea>: after rendering the
FormTextarea in
frontend/__tests__/unit/components/forms/shared/FormTextarea.test.tsx (the test
that supplies name: 'test-textarea'), query the rendered textarea (e.g.,
getByRole('textbox') or container.querySelector('textarea')) and assert its name
attribute equals 'test-textarea' so the fixture's name prop is explicitly
verified.
frontend/jest.config.ts (1)

11-13: Avoid blanket coverage exclusion for new sponsors code.

Excluding src/app/sponsors/** and src/components/sponsors/** removes the new feature surface from coverage enforcement. Please keep these included (or add narrower, justified exclusions) so regressions are still caught.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/jest.config.ts` around lines 11 - 13, The coverage config currently
excludes the new sponsors feature via the patterns '!src/app/sponsors/**' and
'!src/components/sponsors/**' in frontend/jest.config.ts; remove those two
exclusion patterns from the coverage ignore list so the sponsors code is tracked
by coverage (or if there is a specific, justified subdirectory to exclude,
replace each broad pattern with a narrow one such as excluding only
generated/assets or styles within those folders, e.g.
'!src/app/sponsors/**/styles/**', and keep the rest included).
backend/tests/unit/apps/api/rest/v0/sponsor_test.py (1)

107-127: Add an explicit inactive-sponsor test case.

You now validate the active success path, but there’s still no direct assertion that an existing inactive sponsor returns 404. Adding that case would lock in the active-only contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/tests/unit/apps/api/rest/v0/sponsor_test.py` around lines 107 - 127,
Add a unit test that asserts an existing sponsor with an inactive status returns
HTTP 404: in the test file add a new test (similar to test_get_sponsor_success)
that patches SponsorModel (or reuses mock_sponsor_model), sets
mock_sponsor_model.objects.filter.return_value.first.return_value to a
mock_sponsor whose .status is set to SponsorModel.Status.INACTIVE (or equivalent
inactive enum), call get_sponsor(mock_request, "<key>") and assert the
result.status_code == HTTPStatus.NOT_FOUND; reference SponsorModel and
get_sponsor to locate where to add this case.
frontend/src/utils/constants.ts (1)

11-12: Reuse the new Slack constant in footerIcons to avoid divergence.

You introduced OWASP_NEST_SLACK_CHANNEL_URL; wiring it into footerIcons keeps one source of truth.

Proposed change
   {
     icon: FaSlack,
-    href: 'https://owasp.slack.com/archives/project-nest',
+    href: OWASP_NEST_SLACK_CHANNEL_URL,
     label: 'Slack',
   },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/utils/constants.ts` around lines 11 - 12, The footer currently
hardcodes the OWASP Slack URL instead of reusing the new constant; update the
footerIcons usage to reference the exported OWASP_NEST_SLACK_CHANNEL_URL
constant (from constants.ts) wherever the Slack link is defined (e.g., the Slack
entry in footerIcons) so there is a single source of truth and avoid duplicate
strings; ensure you import OWASP_NEST_SLACK_CHANNEL_URL in the file that defines
footerIcons and replace the literal URL with that constant.
frontend/src/components/sponsors/SponsorsFaqSection.tsx (1)

25-28: Attach punctuation directly to the link text node.

This avoids ambiguous whitespace around the inline anchor and resolves the Sonar warning.

Proposed change
-        </a>
-        . Tax treatment depends on your jurisdiction and OWASP Foundation policies—consult a tax
+        </a>. Tax treatment depends on your jurisdiction and OWASP Foundation policies—consult a tax
         professional if needed.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/sponsors/SponsorsFaqSection.tsx` around lines 25 -
28, The inline anchor in SponsorsFaqSection.tsx currently has punctuation
outside the <a> anchor text which creates ambiguous whitespace and triggers a
Sonar warning; update the anchor's text node so the punctuation (e.g., the
period or comma following "official donate link") is included inside the <a>
element instead of as a separate text node, ensuring the link text becomes
"official donate link." (or similar) and preserving surrounding spacing and
sentence flow.
frontend/src/components/sponsors/SponsorHero.tsx (1)

24-27: Make text spacing before the arrow explicit.

This removes the Sonar ambiguity warning and keeps rendering deterministic.

Proposed change
-          Become a sponsor
+          Become a sponsor{' '}
           <span aria-hidden className="opacity-70">
             &rarr;
           </span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/sponsors/SponsorHero.tsx` around lines 24 - 27, The
spacing before the arrow is implicit and triggers Sonar ambiguity; update the
SponsorHero JSX where the arrow span is rendered (the span with aria-hidden and
className="opacity-70") to include an explicit space token before the span
(e.g., add {' '} or &nbsp; in the text node immediately before the span) so the
space is deterministic and Sonar warning is resolved.
frontend/src/components/sponsors/BecomeSponsorCTA.tsx (1)

19-22: Use explicit spacing before decorative arrows.

This clears the Sonar spacing warnings in both CTA buttons.

Proposed change
-            Apply to sponsor
+            Apply to sponsor{' '}
             <span aria-hidden className="opacity-70">
               &rarr;
             </span>
...
-            Donate once
+            Donate once{' '}
             <span aria-hidden className="opacity-70">
               &rarr;
             </span>

Also applies to: 40-43

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/sponsors/BecomeSponsorCTA.tsx` around lines 19 - 22,
The decorative arrow span (the span with aria-hidden and className="opacity-70")
in the BecomeSponsorCTA component lacks explicit spacing from the preceding
text; update both CTA occurrences in BecomeSponsorCTA to add explicit spacing
(either a non-breaking space character before the span or a small utility margin
such as adding "ml-2" to the span) so the arrow is visually separated and Sonar
spacing warnings are resolved.
frontend/src/app/sponsors/apply/page.tsx (1)

1-1: Remove unnecessary 'use client' directive from this page.

The page component doesn't use client-only hooks or APIs, so it can remain a Server Component. Child components (SponsorApplicationForm, SponsorApplyHero) properly define their own client boundaries when needed. Removing 'use client' here reduces the client bundle size.

Proposed change
-'use client'
-
 import SponsorApplicationForm from 'components/sponsors/SponsorApplicationForm'
 import SponsorApplyHero from 'components/sponsors/SponsorApplyHero'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/sponsors/apply/page.tsx` at line 1, Remove the top-level
'use client' directive from this page component so it remains a Server
Component; locate the page component (the default-exported page function, e.g.,
SponsorApplyPage or the exported default React component) and delete the "'use
client'" line at the top, leaving child components like SponsorApplicationForm
and SponsorApplyHero unchanged since they declare their own client boundaries
when needed.
frontend/src/components/sponsors/SponsorDiamondCard.tsx (2)

14-24: Consider adding meaningful alt text for the logo image.

The alt="" attribute marks the image as decorative, but sponsor logos convey meaningful information. Consider using the sponsor name for better accessibility.

♿ Proposed fix
     <Image
       src={sponsor.imageUrl}
-      alt=""
+      alt={`${sponsor.name} logo`}
       width={220}
       height={110}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/sponsors/SponsorDiamondCard.tsx` around lines 14 -
24, The image for sponsor logos in SponsorDiamondCard currently uses alt=""
making it inaccessible; update the logoInner Image element to provide meaningful
alt text using the sponsor's name (e.g., alt={sponsor.name || 'Sponsor logo'})
so screen readers receive context, and keep the fallback <span> when
sponsor.imageUrl is missing; ensure you reference the sponsor object in
SponsorDiamondCard and handle null/undefined sponsor.name safely.

7-9: Consider marking props interface as Readonly.

Per SonarCloud hint, marking props as read-only is a TypeScript best practice.

♻️ Proposed fix
-interface SponsorDiamondCardProps {
-  sponsor: SponsorData
-}
+interface SponsorDiamondCardProps {
+  readonly sponsor: SponsorData
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/sponsors/SponsorDiamondCard.tsx` around lines 7 - 9,
Change the props interface to make properties immutable: update
SponsorDiamondCardProps so its sponsor field is readonly (e.g., declare
interface SponsorDiamondCardProps { readonly sponsor: SponsorData }) or replace
the interface with a Readonly mapped type (e.g., type SponsorDiamondCardProps =
Readonly<{ sponsor: SponsorData }>), and ensure any usages of
SponsorDiamondCardProps / SponsorDiamondCard component still type-check.
frontend/src/app/sponsors/page.tsx (1)

50-51: Use nullish coalescing assignment (??=) for cleaner syntax.

Per SonarCloud suggestion, the nullish coalescing assignment operator is more concise.

♻️ Proposed fix
     for (const sponsor of data.sponsors) {
       const tier = normalizeTier(sponsor.sponsorType)
-      if (!grouped[tier]) grouped[tier] = []
+      grouped[tier] ??= []
       grouped[tier].push(mapSponsor(sponsor))
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/app/sponsors/page.tsx` around lines 50 - 51, Replace the
explicit existence check and assignment for grouped[tier] with the nullish
coalescing assignment operator: instead of "if (!grouped[tier]) grouped[tier] =
[]" use "grouped[tier] ??= []" before calling
grouped[tier].push(mapSponsor(sponsor)); this change should be made where
grouped, tier, mapSponsor, and sponsor are used so grouped[tier] is initialized
only if nullish.
frontend/src/components/sponsors/SponsorTierSection.tsx (1)

6-9: Consider marking props interface as Readonly.

SonarCloud flagged this as a best practice. Marking props as read-only prevents accidental mutation and improves type safety.

♻️ Proposed fix
-interface SponsorTierSectionProps {
-  tier: SponsorTier
-  sponsors: SponsorData[]
-}
+interface SponsorTierSectionProps {
+  readonly tier: SponsorTier
+  readonly sponsors: readonly SponsorData[]
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/sponsors/SponsorTierSection.tsx` around lines 6 - 9,
The props interface SponsorTierSectionProps should be made immutable to prevent
accidental mutation: update SponsorTierSectionProps so its properties are
readonly (e.g., make tier readonly and sponsors a ReadonlyArray or mark the
whole shape Readonly<...>), and ensure the component signature (where
SponsorTierSectionProps is used) still types props accordingly; target the
SponsorTierSectionProps interface and any use sites that construct/receive these
props to apply Readonly semantics for SponsorTier and SponsorData references.
backend/apps/owasp/migrations/0074_backfill_sponsor_status_not_null.py (1)

11-25: Migration is irreversible - ensure this is intentional.

Using migrations.RunSQL.noop for reverse_sql means this migration cannot be rolled back automatically. If you need to revert, you'll have to manually drop the NOT NULL constraint and default. This is acceptable for a one-way data normalization, but consider adding a comment documenting this decision.

Additionally, for very large tables, the UPDATE statement without batching could cause extended table locks. If owasp_sponsors is expected to remain small, this is fine.

📝 Suggested documentation
     operations = [
         migrations.RunSQL(
+            # Note: This migration is intentionally irreversible.
+            # Legacy NULL status values are normalized to 'active'.
             sql="""
             UPDATE public.owasp_sponsors
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/owasp/migrations/0074_backfill_sponsor_status_not_null.py`
around lines 11 - 25, The migration uses migrations.RunSQL with
reverse_sql=migrations.RunSQL.noop which makes it irreversible; either document
that decision clearly in the migration file (add a concise comment above the
RunSQL block referencing the intentional one-way data normalization for
public.owasp_sponsors.status) or provide a proper reverse_sql that reverts the
schema changes (for example run SQL that ALTER TABLE public.owasp_sponsors ALTER
COLUMN status DROP DEFAULT; ALTER TABLE public.owasp_sponsors ALTER COLUMN
status DROP NOT NULL; and optionally UPDATE public.owasp_sponsors SET status =
NULL WHERE /* criteria to target rows created by this migration if you can
identify them */), and replace migrations.RunSQL.noop with that SQL in the
migrations.RunSQL(reverse_sql=...) argument so the migration can be rolled back.
frontend/src/components/sponsors/SponsorApplicationForm.tsx (2)

93-104: Simplify by using controlled state directly instead of re-reading from FormData.

SonarCloud warns that fd.get() can return File | string | null, and wrapping in String() on a File yields [object Object]. Since this form uses controlled inputs, the form state already contains the current values. Re-extracting from FormData is redundant and introduces this edge case.

♻️ Proposed fix: use controlled state directly
     setTouched({
       organizationName: true,
       website: true,
       contactEmail: true,
       message: true,
     })
 
-    const fd = new FormData(e.currentTarget)
-    const current: FormState = {
-      organizationName: String(fd.get('organizationName') ?? ''),
-      website: String(fd.get('website') ?? ''),
-      contactEmail: String(fd.get('contactEmail') ?? ''),
-      message: String(fd.get('message') ?? ''),
-    }
-    setForm(current)
-
-    if (Object.keys(validate(current)).length > 0) {
+    if (Object.keys(validate(form)).length > 0) {
       return
     }

Then update the JSON.stringify call to use form directly:

         body: JSON.stringify(
-          Object.fromEntries([
-            ['organization_name', current.organizationName.trim()],
-            ['website', current.website.trim()],
-            ['contact_email', current.contactEmail.trim()],
-            ['message', current.message.trim()],
-          ])
+          {
+            organization_name: form.organizationName.trim(),
+            website: form.website.trim(),
+            contact_email: form.contactEmail.trim(),
+            message: form.message.trim(),
+          }
         ),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/sponsors/SponsorApplicationForm.tsx` around lines 93
- 104, The handler re-reads inputs via FormData causing File|string|null issues;
instead use the existing controlled state: remove the FormData(fd) and local
current FormState creation, stop calling setForm(current), and call
validate(form) (not validate(current)) to gate submission; also update the
payload/JSON.stringify call to use form directly. Reference: FormState, form,
setForm, and validate.

240-251: Inconsistent event handler pattern between FormTextInput and FormTextarea.

FormTextInput uses onValueChange (receiving the string value directly), while FormTextarea uses a raw onChange handler with e.target.value. Consider using handleFieldChange('message') for consistency if FormTextarea supports an onValueChange prop, or extract a helper if it doesn't.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/sponsors/SponsorApplicationForm.tsx` around lines 240
- 251, The FormTextarea usage is inconsistent with FormTextInput: replace the
inline onChange={(e) => setForm((prev) => ({ ...prev, message: e.target.value
}))} with the same handler pattern used by FormTextInput
(handleFieldChange('message')) to keep event handling consistent; if
FormTextarea exposes an onValueChange prop, pass handleFieldChange('message')
there (so it receives the string directly), otherwise refactor FormTextarea to
support onValueChange or extract a small adapter helper (e.g., a wrapper that
calls handleFieldChange('message') with e.target.value)—make the change around
the FormTextarea component where id/name/message, value={form.message},
onBlur={handleBlur('message')} and error/touched props are set.
frontend/src/components/sponsors/SponsorTierCard.tsx (1)

37-40: Consider marking props as read-only per SonarCloud hint.

The static analysis suggests marking component props as read-only. This is a TypeScript best practice for React components to prevent accidental mutation.

♻️ Proposed fix
-export default function SponsorTierCard({ sponsor, size, tier }: SponsorTierCardProps) {
+export default function SponsorTierCard({ sponsor, size, tier }: Readonly<SponsorTierCardProps>) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/sponsors/SponsorTierCard.tsx` around lines 37 - 40,
The component props for SponsorTierCard should be marked read-only to satisfy
the TypeScript/SonarCloud hint; update the function signature to take a
Readonly<SponsorTierCardProps> (e.g., change the parameter annotation to
Readonly<SponsorTierCardProps> or accept props: Readonly<SponsorTierCardProps>
and then destructure), so sponsor, size, and tier cannot be mutated inside
SponsorTierCard; ensure the rest of the file (and any typings for
SponsorTierCardProps) remain compatible with the Readonly wrapper.
backend/apps/api/rest/v0/sponsor.py (1)

123-128: Optimize by filtering status in the query.

The current implementation fetches the sponsor first, then checks status in Python. This could return a sponsor to memory that won't be used. Consider filtering in the query:

♻️ Proposed fix
 def get_sponsor(
     request: HttpRequest,
     sponsor_id: str = Path(..., example="adobe"),
 ) -> SponsorDetail | SponsorError:
     """Get a single active sponsor."""
-    sponsor = SponsorModel.objects.filter(key__iexact=sponsor_id).first()
-    if sponsor and sponsor.status == SponsorModel.Status.ACTIVE:
+    sponsor = SponsorModel.objects.filter(
+        key__iexact=sponsor_id,
+        status=SponsorModel.Status.ACTIVE,
+    ).first()
+    if sponsor:
         return sponsor
 
     return Response({"message": "Sponsor not found"}, status=HTTPStatus.NOT_FOUND)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/apps/api/rest/v0/sponsor.py` around lines 123 - 128, Filter by status
at the database level instead of checking it in Python: change the query that
fetches SponsorModel (currently using
SponsorModel.objects.filter(key__iexact=sponsor_id).first()) to include the
active status filter (e.g., filter(...,
status=SponsorModel.Status.ACTIVE).first()) so the returned sponsor is already
active before the function returns, then keep the existing Response({"message":
"Sponsor not found"}, status=HTTPStatus.NOT_FOUND) fallback.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/src/components/forms/shared/FormTextarea.tsx`:
- Line 12: The onBlur prop on FormTextarea is currently typed as onBlur?: () =>
void which is too narrow; update the prop type in the FormTextarea
props/interface to accept React's standard blur handler (e.g.,
React.FocusEventHandler<HTMLTextAreaElement> or (e:
React.FocusEvent<HTMLTextAreaElement>) => void) so consumers receive the
FocusEvent object like onChange does; adjust any usages of FormTextarea.onBlur
to match the new signature and ensure the component forwards the event to the
provided handler.

In `@frontend/src/components/Header.tsx`:
- Around line 120-128: The desktop nav Link currently sets aria-current
unconditionally, causing every link to be announced as the current page; update
the Link in Header.tsx (the Link element that uses cn, pathname and link.href)
to set aria-current only when pathname === link.href (e.g.,
aria-current={pathname === link.href ? 'page' : undefined}) so that only the
active route has aria-current; ensure you use the same pathname and link.href
comparison used for the active class logic.

In `@frontend/src/server/queries/sponsorQueries.ts`:
- Around line 3-12: The current GET_SPONSORS_PAGE_DATA GraphQL query requests
the unfiltered sponsors field and can return non-public entries; change the
query to use an active-only source or add an active filter: update the frontend
query named GetSponsorsPageData (constant GET_SPONSORS_PAGE_DATA) to call the
public/filtered field (e.g., sponsorsPublic or sponsors(active: true)) that only
returns active sponsors, or if you prefer server-side, update the backend
resolver in sponsor.py to enforce active-only filtering and keep the frontend
query pointing to the filtered field; ensure the returned fields (id, imageUrl,
name, sponsorType, url) remain the same.

---

Nitpick comments:
In `@backend/apps/api/rest/v0/sponsor.py`:
- Around line 123-128: Filter by status at the database level instead of
checking it in Python: change the query that fetches SponsorModel (currently
using SponsorModel.objects.filter(key__iexact=sponsor_id).first()) to include
the active status filter (e.g., filter(...,
status=SponsorModel.Status.ACTIVE).first()) so the returned sponsor is already
active before the function returns, then keep the existing Response({"message":
"Sponsor not found"}, status=HTTPStatus.NOT_FOUND) fallback.

In `@backend/apps/owasp/migrations/0074_backfill_sponsor_status_not_null.py`:
- Around line 11-25: The migration uses migrations.RunSQL with
reverse_sql=migrations.RunSQL.noop which makes it irreversible; either document
that decision clearly in the migration file (add a concise comment above the
RunSQL block referencing the intentional one-way data normalization for
public.owasp_sponsors.status) or provide a proper reverse_sql that reverts the
schema changes (for example run SQL that ALTER TABLE public.owasp_sponsors ALTER
COLUMN status DROP DEFAULT; ALTER TABLE public.owasp_sponsors ALTER COLUMN
status DROP NOT NULL; and optionally UPDATE public.owasp_sponsors SET status =
NULL WHERE /* criteria to target rows created by this migration if you can
identify them */), and replace migrations.RunSQL.noop with that SQL in the
migrations.RunSQL(reverse_sql=...) argument so the migration can be rolled back.

In `@backend/tests/unit/apps/api/rest/v0/sponsor_test.py`:
- Around line 107-127: Add a unit test that asserts an existing sponsor with an
inactive status returns HTTP 404: in the test file add a new test (similar to
test_get_sponsor_success) that patches SponsorModel (or reuses
mock_sponsor_model), sets
mock_sponsor_model.objects.filter.return_value.first.return_value to a
mock_sponsor whose .status is set to SponsorModel.Status.INACTIVE (or equivalent
inactive enum), call get_sponsor(mock_request, "<key>") and assert the
result.status_code == HTTPStatus.NOT_FOUND; reference SponsorModel and
get_sponsor to locate where to add this case.

In `@frontend/__tests__/unit/components/forms/shared/FormTextarea.test.tsx`:
- Line 7: Add an assertion in the FormTextarea unit test to verify the required
name prop is passed through to the rendered <textarea>: after rendering the
FormTextarea in
frontend/__tests__/unit/components/forms/shared/FormTextarea.test.tsx (the test
that supplies name: 'test-textarea'), query the rendered textarea (e.g.,
getByRole('textbox') or container.querySelector('textarea')) and assert its name
attribute equals 'test-textarea' so the fixture's name prop is explicitly
verified.

In `@frontend/jest.config.ts`:
- Around line 11-13: The coverage config currently excludes the new sponsors
feature via the patterns '!src/app/sponsors/**' and
'!src/components/sponsors/**' in frontend/jest.config.ts; remove those two
exclusion patterns from the coverage ignore list so the sponsors code is tracked
by coverage (or if there is a specific, justified subdirectory to exclude,
replace each broad pattern with a narrow one such as excluding only
generated/assets or styles within those folders, e.g.
'!src/app/sponsors/**/styles/**', and keep the rest included).

In `@frontend/src/app/sponsors/apply/page.tsx`:
- Line 1: Remove the top-level 'use client' directive from this page component
so it remains a Server Component; locate the page component (the
default-exported page function, e.g., SponsorApplyPage or the exported default
React component) and delete the "'use client'" line at the top, leaving child
components like SponsorApplicationForm and SponsorApplyHero unchanged since they
declare their own client boundaries when needed.

In `@frontend/src/app/sponsors/page.tsx`:
- Around line 50-51: Replace the explicit existence check and assignment for
grouped[tier] with the nullish coalescing assignment operator: instead of "if
(!grouped[tier]) grouped[tier] = []" use "grouped[tier] ??= []" before calling
grouped[tier].push(mapSponsor(sponsor)); this change should be made where
grouped, tier, mapSponsor, and sponsor are used so grouped[tier] is initialized
only if nullish.

In `@frontend/src/components/sponsors/BecomeSponsorCTA.tsx`:
- Around line 19-22: The decorative arrow span (the span with aria-hidden and
className="opacity-70") in the BecomeSponsorCTA component lacks explicit spacing
from the preceding text; update both CTA occurrences in BecomeSponsorCTA to add
explicit spacing (either a non-breaking space character before the span or a
small utility margin such as adding "ml-2" to the span) so the arrow is visually
separated and Sonar spacing warnings are resolved.

In `@frontend/src/components/sponsors/SponsorApplicationForm.tsx`:
- Around line 93-104: The handler re-reads inputs via FormData causing
File|string|null issues; instead use the existing controlled state: remove the
FormData(fd) and local current FormState creation, stop calling
setForm(current), and call validate(form) (not validate(current)) to gate
submission; also update the payload/JSON.stringify call to use form directly.
Reference: FormState, form, setForm, and validate.
- Around line 240-251: The FormTextarea usage is inconsistent with
FormTextInput: replace the inline onChange={(e) => setForm((prev) => ({ ...prev,
message: e.target.value }))} with the same handler pattern used by FormTextInput
(handleFieldChange('message')) to keep event handling consistent; if
FormTextarea exposes an onValueChange prop, pass handleFieldChange('message')
there (so it receives the string directly), otherwise refactor FormTextarea to
support onValueChange or extract a small adapter helper (e.g., a wrapper that
calls handleFieldChange('message') with e.target.value)—make the change around
the FormTextarea component where id/name/message, value={form.message},
onBlur={handleBlur('message')} and error/touched props are set.

In `@frontend/src/components/sponsors/SponsorDiamondCard.tsx`:
- Around line 14-24: The image for sponsor logos in SponsorDiamondCard currently
uses alt="" making it inaccessible; update the logoInner Image element to
provide meaningful alt text using the sponsor's name (e.g., alt={sponsor.name ||
'Sponsor logo'}) so screen readers receive context, and keep the fallback <span>
when sponsor.imageUrl is missing; ensure you reference the sponsor object in
SponsorDiamondCard and handle null/undefined sponsor.name safely.
- Around line 7-9: Change the props interface to make properties immutable:
update SponsorDiamondCardProps so its sponsor field is readonly (e.g., declare
interface SponsorDiamondCardProps { readonly sponsor: SponsorData }) or replace
the interface with a Readonly mapped type (e.g., type SponsorDiamondCardProps =
Readonly<{ sponsor: SponsorData }>), and ensure any usages of
SponsorDiamondCardProps / SponsorDiamondCard component still type-check.

In `@frontend/src/components/sponsors/SponsorHero.tsx`:
- Around line 24-27: The spacing before the arrow is implicit and triggers Sonar
ambiguity; update the SponsorHero JSX where the arrow span is rendered (the span
with aria-hidden and className="opacity-70") to include an explicit space token
before the span (e.g., add {' '} or &nbsp; in the text node immediately before
the span) so the space is deterministic and Sonar warning is resolved.

In `@frontend/src/components/sponsors/SponsorsFaqSection.tsx`:
- Around line 25-28: The inline anchor in SponsorsFaqSection.tsx currently has
punctuation outside the <a> anchor text which creates ambiguous whitespace and
triggers a Sonar warning; update the anchor's text node so the punctuation
(e.g., the period or comma following "official donate link") is included inside
the <a> element instead of as a separate text node, ensuring the link text
becomes "official donate link." (or similar) and preserving surrounding spacing
and sentence flow.

In `@frontend/src/components/sponsors/SponsorTierCard.tsx`:
- Around line 37-40: The component props for SponsorTierCard should be marked
read-only to satisfy the TypeScript/SonarCloud hint; update the function
signature to take a Readonly<SponsorTierCardProps> (e.g., change the parameter
annotation to Readonly<SponsorTierCardProps> or accept props:
Readonly<SponsorTierCardProps> and then destructure), so sponsor, size, and tier
cannot be mutated inside SponsorTierCard; ensure the rest of the file (and any
typings for SponsorTierCardProps) remain compatible with the Readonly wrapper.

In `@frontend/src/components/sponsors/SponsorTierSection.tsx`:
- Around line 6-9: The props interface SponsorTierSectionProps should be made
immutable to prevent accidental mutation: update SponsorTierSectionProps so its
properties are readonly (e.g., make tier readonly and sponsors a ReadonlyArray
or mark the whole shape Readonly<...>), and ensure the component signature
(where SponsorTierSectionProps is used) still types props accordingly; target
the SponsorTierSectionProps interface and any use sites that construct/receive
these props to apply Readonly semantics for SponsorTier and SponsorData
references.

In `@frontend/src/utils/constants.ts`:
- Around line 11-12: The footer currently hardcodes the OWASP Slack URL instead
of reusing the new constant; update the footerIcons usage to reference the
exported OWASP_NEST_SLACK_CHANNEL_URL constant (from constants.ts) wherever the
Slack link is defined (e.g., the Slack entry in footerIcons) so there is a
single source of truth and avoid duplicate strings; ensure you import
OWASP_NEST_SLACK_CHANNEL_URL in the file that defines footerIcons and replace
the literal URL with that constant.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ba6636ed-4900-4ee3-ab1e-3362e080c73e

📥 Commits

Reviewing files that changed from the base of the PR and between 38d215e and 555372f.

⛔ Files ignored due to path filters (1)
  • frontend/src/types/__generated__/sponsorQueries.generated.ts is excluded by !**/__generated__/**
📒 Files selected for processing (37)
  • backend/apps/api/rest/v0/sponsor.py
  • backend/apps/common/management/commands/dump_data.py
  • backend/apps/owasp/admin/sponsor.py
  • backend/apps/owasp/migrations/0073_sponsor_status_contact_email_chapter_project.py
  • backend/apps/owasp/migrations/0074_backfill_sponsor_status_not_null.py
  • backend/apps/owasp/migrations/0075_backfill_sponsor_contact_email_not_null.py
  • backend/apps/owasp/models/sponsor.py
  • backend/tests/unit/apps/api/rest/v0/sponsor_test.py
  • frontend/__tests__/unit/components/forms/shared/FormDateInput.test.tsx
  • frontend/__tests__/unit/components/forms/shared/FormTextarea.test.tsx
  • frontend/jest.config.ts
  • frontend/src/app/sponsors/apply/layout.tsx
  • frontend/src/app/sponsors/apply/page.tsx
  • frontend/src/app/sponsors/layout.tsx
  • frontend/src/app/sponsors/page.tsx
  • frontend/src/components/Header.tsx
  • frontend/src/components/LogoCarousel.tsx
  • frontend/src/components/ModuleForm.tsx
  • frontend/src/components/NavButton.tsx
  • frontend/src/components/ProgramForm.tsx
  • frontend/src/components/forms/shared/FormDateInput.tsx
  • frontend/src/components/forms/shared/FormTextInput.tsx
  • frontend/src/components/forms/shared/FormTextarea.tsx
  • frontend/src/components/skeletons/SponsorsSkeleton.tsx
  • frontend/src/components/sponsors/BecomeSponsorCTA.tsx
  • frontend/src/components/sponsors/SponsorApplicationForm.tsx
  • frontend/src/components/sponsors/SponsorApplyHero.tsx
  • frontend/src/components/sponsors/SponsorDiamondCard.tsx
  • frontend/src/components/sponsors/SponsorHero.tsx
  • frontend/src/components/sponsors/SponsorTierCard.tsx
  • frontend/src/components/sponsors/SponsorTierSection.tsx
  • frontend/src/components/sponsors/SponsorsFaqSection.tsx
  • frontend/src/components/sponsors/SponsorsOpenSourceShowcase.tsx
  • frontend/src/server/queries/sponsorQueries.ts
  • frontend/src/types/sponsor.ts
  • frontend/src/utils/constants.ts
  • frontend/src/utils/metadata.ts

Comment on lines +141 to +166
def apply_sponsor(
request: HttpRequest,
payload: SponsorApplyRequest,
) -> tuple[int, SponsorApplyResponse | SponsorError]:
"""Create a draft sponsor application."""
key = slugify(payload.organization_name)

if SponsorModel.objects.filter(key=key).exists():
return HTTPStatus.BAD_REQUEST, SponsorError(
message=(f"An application for '{payload.organization_name}' already exists.")
)

SponsorModel.objects.create(
key=key,
name=payload.organization_name,
sort_name=payload.organization_name,
contact_email=payload.contact_email,
url=payload.website,
description=payload.message,
status=SponsorModel.Status.DRAFT,
)

return HTTPStatus.CREATED, SponsorApplyResponse(
key=key,
message=("Application received. The Nest team will review and follow up."),
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if rate limiting is configured elsewhere for this router
rg -n "rate.?limit|throttle" backend/apps/api/ --type=py -C2

Repository: OWASP/Nest

Length of output: 1332


🏁 Script executed:

# First, check the sponsor.py file to see the apply_sponsor endpoint definition and route
cat -n backend/apps/api/rest/v0/sponsor.py | head -50

Repository: OWASP/Nest

Length of output: 1553


🏁 Script executed:

# Look for SponsorApplyRequest schema definition
rg -n "class SponsorApplyRequest" backend/apps/api/ --type=py -A 10

Repository: OWASP/Nest

Length of output: 988


🏁 Script executed:

# Check SponsorModel fields and validation
rg -n "class SponsorModel" backend/apps/api/ --type=py -A 30

Repository: OWASP/Nest

Length of output: 36


🏁 Script executed:

# Check the sponsor router/endpoint registration to see if apply_sponsor has specific throttle settings
rg -n "apply_sponsor|@router.*sponsor" backend/apps/api/rest/v0/sponsor.py --type=py -B 2 -A 5

Repository: OWASP/Nest

Length of output: 638


🏁 Script executed:

# Check SponsorModel definition in the database models
fd -type f "sponsor.py" backend/apps/owasp/models/

Repository: OWASP/Nest

Length of output: 224


🏁 Script executed:

# Read the SponsorModel
cat backend/apps/owasp/models/sponsor.py

Repository: OWASP/Nest

Length of output: 6023


🏁 Script executed:

# Check if apply_sponsor endpoint requires authentication
rg -n "apply_sponsor|@router" backend/apps/api/rest/v0/sponsor.py --type=py -B 5 | head -40

Repository: OWASP/Nest

Length of output: 731


🏁 Script executed:

# Check the router configuration and if throttle applies to apply_sponsor
rg -n "auth|permission" backend/apps/api/rest/v0/sponsor.py --type=py

Repository: OWASP/Nest

Length of output: 36


Add email and URL format validation to the request schema.

This endpoint is publicly accessible and creates database records without server-side format validation. While the SponsorModel has EmailField and URLField at the database level, the endpoint uses objects.create() which bypasses Django's full_clean() validation. The schema should enforce format constraints:

  • Email validation: contact_email has min_length=1 but no format validation; should add an email pattern.
  • URL validation: website field accepts any string; should add URL format validation.

Rate limiting is configured globally at "10/s" in the router config, but this is environment-dependent (empty in non-production). Verify the production environment applies the throttle or consider endpoint-specific configuration if stricter limits are needed.

♻️ Example: Add format validation to schema
 class SponsorApplyRequest(Schema):
     """Request schema for sponsor application."""
 
     organization_name: str = Field(..., min_length=1, description="Organization name")
-    website: str = Field("", description="Organization website URL")
-    contact_email: str = Field(..., min_length=1, description="Contact email address")
+    website: str = Field(
+        "",
+        pattern=r"^(https?://)?.+",
+        description="Organization website URL",
+    )
+    contact_email: str = Field(
+        ...,
+        min_length=1,
+        pattern=r"^[^\s@]+@[^\s@]+\.[^\s@]+$",
+        description="Contact email address",
+    )
     message: str = Field("", description="Sponsorship interest / message")

placeholder: string
value: string
onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void
onBlur?: () => void
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify FormTextarea onBlur typing and callsites that may require event access.

rg -n --type=tsx 'interface FormTextareaProps|onBlur\?:' frontend/src/components/forms/shared/FormTextarea.tsx
rg -n --type=tsx '<FormTextarea[^>]*onBlur=' frontend
rg -n --type=tsx 'onBlur=\{\(\w+' frontend

Repository: OWASP/Nest

Length of output: 137


🏁 Script executed:

# Verify file exists and read FormTextarea.tsx
cat -n frontend/src/components/forms/shared/FormTextarea.tsx

Repository: OWASP/Nest

Length of output: 2218


🏁 Script executed:

# Search for FormTextarea component usage in the codebase
rg -n 'FormTextarea' frontend --type=ts --type=js

Repository: OWASP/Nest

Length of output: 2163


🏁 Script executed:

# Also search without type restriction to capture all usages
rg -n '<FormTextarea' frontend -A 3

Repository: OWASP/Nest

Length of output: 4111


🏁 Script executed:

# Check ModuleForm for onBlur usage
rg -n 'onBlur|description' frontend/src/components/ModuleForm.tsx -B 2 -A 5 | head -50

Repository: OWASP/Nest

Length of output: 1802


🏁 Script executed:

# Check SponsorApplicationForm for onBlur usage
rg -n 'onBlur|message' frontend/src/components/sponsors/SponsorApplicationForm.tsx -B 2 -A 5 | head -50

Repository: OWASP/Nest

Length of output: 1364


🏁 Script executed:

# Check ProgramForm for onBlur usage
rg -n 'onBlur|description' frontend/src/components/ProgramForm.tsx -B 2 -A 5 | head -50

Repository: OWASP/Nest

Length of output: 1645


🏁 Script executed:

# Also check test file to see if tests cover onBlur
cat -n frontend/__tests__/unit/components/forms/shared/FormTextarea.test.tsx

Repository: OWASP/Nest

Length of output: 2715


🏁 Script executed:

# Check other form input components for onChange/onBlur typing consistency
fd 'Form.*\.tsx' frontend/src/components/forms/shared/ | head -10

Repository: OWASP/Nest

Length of output: 325


🏁 Script executed:

# Check FormInput for comparison
cat -n frontend/src/components/forms/shared/FormInput.tsx 2>/dev/null | head -50

Repository: OWASP/Nest

Length of output: 36


🏁 Script executed:

# Check FormTextInput for onBlur typing pattern
cat -n frontend/src/components/forms/shared/FormTextInput.tsx | head -60

Repository: OWASP/Nest

Length of output: 2224


🏁 Script executed:

# Check FormDateInput for onBlur typing pattern
cat -n frontend/src/components/forms/shared/FormDateInput.tsx | head -60

Repository: OWASP/Nest

Length of output: 1657


Widen onBlur prop type to accept React's standard blur handler.

onBlur?: () => void is stricter than necessary and inconsistent with onChange, which accepts a React event. This prevents handlers that need access to the focus event object.

Suggested fix
-  onBlur?: () => void
+  onBlur?: React.FocusEventHandler<HTMLTextAreaElement>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
onBlur?: () => void
onBlur?: React.FocusEventHandler<HTMLTextAreaElement>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/forms/shared/FormTextarea.tsx` at line 12, The onBlur
prop on FormTextarea is currently typed as onBlur?: () => void which is too
narrow; update the prop type in the FormTextarea props/interface to accept
React's standard blur handler (e.g.,
React.FocusEventHandler<HTMLTextAreaElement> or (e:
React.FocusEvent<HTMLTextAreaElement>) => void) so consumers receive the
FocusEvent object like onChange does; adjust any usages of FormTextarea.onBlur
to match the new signature and ensure the component forwards the event to the
provided handler.

Comment on lines +120 to +128
<Link
key={link.text}
href={link.href || '/'}
className={cn(
'navlink px-3 py-2 text-slate-700 hover:text-slate-800 dark:text-slate-300 dark:hover:text-slate-200',
pathname === link.href && 'font-bold text-blue-800 dark:text-white'
)}
aria-current="page"
>
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Set aria-current conditionally for the active route only.

At Line 127, every desktop nav link is marked as current page. This breaks screen-reader navigation context.

Suggested fix
-                  aria-current="page"
+                  aria-current={pathname === link.href ? 'page' : undefined}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<Link
key={link.text}
href={link.href || '/'}
className={cn(
'navlink px-3 py-2 text-slate-700 hover:text-slate-800 dark:text-slate-300 dark:hover:text-slate-200',
pathname === link.href && 'font-bold text-blue-800 dark:text-white'
)}
aria-current="page"
>
<Link
key={link.text}
href={link.href || '/'}
className={cn(
'navlink px-3 py-2 text-slate-700 hover:text-slate-800 dark:text-slate-300 dark:hover:text-slate-200',
pathname === link.href && 'font-bold text-blue-800 dark:text-white'
)}
aria-current={pathname === link.href ? 'page' : undefined}
>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/Header.tsx` around lines 120 - 128, The desktop nav
Link currently sets aria-current unconditionally, causing every link to be
announced as the current page; update the Link in Header.tsx (the Link element
that uses cn, pathname and link.href) to set aria-current only when pathname ===
link.href (e.g., aria-current={pathname === link.href ? 'page' : undefined}) so
that only the active route has aria-current; ensure you use the same pathname
and link.href comparison used for the active class logic.

Copy link
Copy Markdown
Contributor

@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: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/apps/common/management/commands/dump_data.py`:
- Around line 119-122: The UPDATE currently blanks all sponsor emails; modify
the SQL passed to self._execute_sql in dump_data.py so it only updates legacy
NULLs by adding a WHERE contact_email IS NULL clause (i.e., change the statement
used in the call that executes "UPDATE public.owasp_sponsors SET contact_email =
'';" to scope to NULLs only), ensuring only NULL contact_email rows are replaced
while leaving existing non-null emails intact.

In `@frontend/src/components/sponsors/SponsorApplicationForm.tsx`:
- Around line 216-219: The server error paragraph in SponsorApplicationForm (the
JSX block that renders {serverError}) lacks live-region semantics so screen
readers won't announce it; update that <p> to include aria-live="assertive" (or
role="alert") and aria-atomic="true" so assistive tech announces the error
immediately when serverError is set, preserving the existing classes and
content.
- Around line 152-157: In SponsorApplicationForm, the current catch conflates
JSON parse failures with network errors because res.json() can throw for
empty/non-JSON error bodies; modify the error-handling path so you attempt to
safely parse the response body before falling back: when res.ok is false, read
the raw body (e.g., await res.text()), try JSON.parse or a guarded res.json() in
its own try/catch to extract data.message, and if parsing fails use a sensible
fallback like response.statusText or a generic "Something went wrong" message
via setServerError; ensure the outer try/catch only reports network failures
(e.g., when fetch itself throws) so setServerError('Could not reach the
server...') is only used for true network errors.
- Around line 80-82: The website validation treats whitespace-only strings as
present because it checks form.website truthiness; update the check in
SponsorApplicationForm (the block that references form.website and isHttpUrl) to
first trim the value (e.g., use form.website?.trim()) and only run isHttpUrl
when the trimmed string is non-empty, otherwise treat it as empty so the
optional field does not fail validation; ensure errors.website is only set when
the trimmed value is non-empty and fails isHttpUrl.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 2df35d1f-b3ee-46d4-a495-7c1f7a7f66c1

📥 Commits

Reviewing files that changed from the base of the PR and between 555372f and 24a5e4c.

📒 Files selected for processing (5)
  • backend/apps/api/rest/v0/sponsor.py
  • backend/apps/common/management/commands/dump_data.py
  • backend/tests/unit/apps/api/rest/v0/sponsor_test.py
  • backend/tests/unit/apps/common/management/commands/dump_data_test.py
  • frontend/src/components/sponsors/SponsorApplicationForm.tsx
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/tests/unit/apps/api/rest/v0/sponsor_test.py
  • backend/apps/api/rest/v0/sponsor.py

cubic-dev-ai[bot]
cubic-dev-ai bot previously approved these changes Apr 8, 2026
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 5 files (changes from recent commits).

Copy link
Copy Markdown
Contributor

@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: 1

♻️ Duplicate comments (1)
frontend/src/components/sponsors/SponsorApplicationForm.tsx (1)

152-157: ⚠️ Potential issue | 🟡 Minor

Defensively parse non-OK responses so server errors aren’t mislabeled as network failures.

If res.json() throws on an empty/non-JSON error body, the outer catch shows “Could not reach the server” even when the server responded.

Proposed fix
       if (res.ok) {
         setSubmitted(true)
       } else {
-        const data = (await res.json()) as { message?: string }
-        setServerError(data.message ?? 'Something went wrong. Please try again.')
+        let message = 'Something went wrong. Please try again.'
+        try {
+          const data = (await res.json()) as { message?: string }
+          if (data?.message) message = data.message
+        } catch {
+          // Keep generic server message for non-JSON/empty bodies
+        }
+        setServerError(message)
       }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/src/components/sponsors/SponsorApplicationForm.tsx` around lines 152
- 157, The current else branch calls await res.json() which can throw and fall
into the outer catch (mislabeling server responses as network failures); update
the non-OK response handling in SponsorApplicationForm (the block that calls
res.json() and setServerError) to defensively parse the body: first try to await
res.json() inside its own try/catch, on JSON parse failure fall back to await
res.text() (or res.statusText) and construct a meaningful message including
res.status/res.statusText, then call setServerError with that message; ensure
only true fetch/network exceptions reach the outer catch so "Could not reach the
server" remains for real network errors.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@backend/tests/unit/apps/owasp/api/internal/queries/sponsor_test.py`:
- Line 19: The test currently patches Sponsor.objects.filter but only checks
sorting; update the test in sponsor_test.py to explicitly assert that
Sponsor.objects.filter was called with the ACTIVE status filter by adding an
assertion that filter was invoked with status=Sponsor.Status.ACTIVE (or the
equivalent enum/member used in the codebase) so regressions removing the status
constraint will fail; reference the mocked call to Sponsor.objects.filter and
use its assert_called_with / assert_any_call (or inspect call_args) to verify
the status argument.

---

Duplicate comments:
In `@frontend/src/components/sponsors/SponsorApplicationForm.tsx`:
- Around line 152-157: The current else branch calls await res.json() which can
throw and fall into the outer catch (mislabeling server responses as network
failures); update the non-OK response handling in SponsorApplicationForm (the
block that calls res.json() and setServerError) to defensively parse the body:
first try to await res.json() inside its own try/catch, on JSON parse failure
fall back to await res.text() (or res.statusText) and construct a meaningful
message including res.status/res.statusText, then call setServerError with that
message; ensure only true fetch/network exceptions reach the outer catch so
"Could not reach the server" remains for real network errors.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6e0be37d-0ea1-4874-ad0a-5c6def14a638

📥 Commits

Reviewing files that changed from the base of the PR and between 24a5e4c and 1104d7e.

📒 Files selected for processing (5)
  • backend/apps/common/management/commands/dump_data.py
  • backend/apps/owasp/api/internal/queries/sponsor.py
  • backend/tests/unit/apps/common/management/commands/dump_data_test.py
  • backend/tests/unit/apps/owasp/api/internal/queries/sponsor_test.py
  • frontend/src/components/sponsors/SponsorApplicationForm.tsx
✅ Files skipped from review due to trivial changes (1)
  • backend/apps/common/management/commands/dump_data.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/tests/unit/apps/common/management/commands/dump_data_test.py

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/apps/common/management/commands/dump_data.py">

<violation number="1" location="backend/apps/common/management/commands/dump_data.py:124">
P1: PII masking regression: sponsor `contact_email` is no longer fully scrubbed, so non-null email addresses can leak into generated dumps.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

Requires human review: Auto-approval blocked by 1 unresolved issue from previous reviews.

@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 9, 2026

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

0 issues found across 1 file (changes from recent commits).

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement Sponsors Program Support

1 participant