Skip to content

Add pledge receipt endpoint pdf - #723

Open
sarah-obasi-analytics wants to merge 4 commits into
ritik4ever:mainfrom
sarah-obasi-analytics:add-pledge-receipt-endpoint-pdf
Open

Add pledge receipt endpoint pdf#723
sarah-obasi-analytics wants to merge 4 commits into
ritik4ever:mainfrom
sarah-obasi-analytics:add-pledge-receipt-endpoint-pdf

Conversation

@sarah-obasi-analytics

@sarah-obasi-analytics sarah-obasi-analytics commented Jul 29, 2026

Copy link
Copy Markdown

Pull Request

closes #563

What Changed

A clear and concise description of what this PR changes and why.

Related Issues

  • Closes #
  • Fixes #

Testing Done

Describe the tests you ran and how to reproduce them.

Security Review

If this PR touches API endpoints, authentication, database queries, or contract code, check the applicable items from SECURITY_CHECKLIST.md. Paste relevant items below:

- [ ] A01-1: Public endpoints correctly excluded from auth
- [ ] A03-1: SQL uses parameterised statements
- [ ] A03-2: User input validated with Zod schema
- [ ] A05-1: CORS scoped to known origins
- [ ] A09-1: API requests logged with request ID
- [ ] Dependency changes reviewed for vulnerabilities

Checklist

  • Code follows the existing style and patterns of the project
  • Tests added or updated to cover the change
  • All tests pass locally (npm test / cargo test)
  • UI changes include screenshots or a screen recording
  • PR title is descriptive and references the issue number
  • Security checklist items reviewed (if applicable)

Screenshots (if applicable)

Add screenshots or recordings for visual changes.

Summary by CodeRabbit

  • New Features

    • Added downloadable PDF receipts for campaign pledges, including contribution details and available transaction information.
    • Added a Codecov coverage badge to project documentation.
  • Validation

    • Added clearer minimum and maximum requirements for campaign titles and descriptions.
  • Tests

    • Added automated coverage for receipt downloads and invalid campaign or pledge requests.
    • Streamlined continuous integration checks for type checking, linting, unit tests, end-to-end tests, coverage reporting, and contract tests.

@vercel

vercel Bot commented Jul 29, 2026

Copy link
Copy Markdown

@sarah-obasi-analytics is attempting to deploy a commit to the ritik4ever's projects Team on Vercel.

A member of the Team first needs to authorize it.

@drips-wave

drips-wave Bot commented Jul 29, 2026

Copy link
Copy Markdown

@sarah-obasi-analytics Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@coderabbitai

coderabbitai Bot commented Jul 29, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The PR adds a PDF pledge receipt endpoint with integration tests, tightens campaign validation, changes Redis error handling, and replaces the CI workflow with consolidated application, coverage, and contract-test jobs.

Changes

Pledge receipt endpoint

Layer / File(s) Summary
Receipt lookup and PDF generation
backend/package.json, backend/src/index.ts, backend/src/services/campaignStore.ts
Adds PDFKit dependencies, pledge lookup by ID, and an endpoint that generates PDF receipts with campaign and pledge details.
Receipt endpoint integration tests
backend/src/receiptEndpoint.test.ts
Tests successful PDF responses and 404 responses for unknown pledges or campaigns using isolated database state.

Backend validation and resilience

Layer / File(s) Summary
Schema constraints and Redis error handling
backend/src/validation/schemas.ts, backend/src/services/cache.ts
Adds campaign text length constraints and changes Redis error handling. The added consecutive catch clauses make initRedisCache syntactically invalid.

CI and coverage workflow

Layer / File(s) Summary
Pull-request and main-branch pipelines
.github/workflows/ci.yml, README.md
Consolidates pull-request checks, uploads coverage to Codecov, runs Rust contract tests on main, and adds a Codecov badge.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Suggested reviewers: david-adegboyega

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant ExpressReceiptRoute
  participant campaignStore
  participant PDFDocument
  Client->>ExpressReceiptRoute: Request pledge receipt
  ExpressReceiptRoute->>campaignStore: Fetch campaign and pledge
  campaignStore-->>ExpressReceiptRoute: Return records
  ExpressReceiptRoute->>PDFDocument: Generate receipt content
  PDFDocument-->>Client: Stream PDF attachment
Loading
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The endpoint and tests cover the linked issue requirements, but an invalid catch sequence in cache.ts makes the backend syntactically invalid. Fix the consecutive catch clauses in cache.ts, then rerun type-checking and the backend tests.
Out of Scope Changes check ⚠️ Warning The PR includes unrelated CI, README, Redis cache, and validation schema changes outside issue #563. Remove unrelated CI, README, cache, and validation changes, or link them to separate issues.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding a PDF pledge receipt endpoint.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Biome (2.5.5)
backend/src/index.ts

File contains syntax errors that prevent linting: Line 531: await is only allowed within async functions and at the top levels of modules.; Line 546: await is only allowed within async functions and at the top levels of modules.; Line 552: expected , but instead found catch; Line 552: expected , but instead found (; Line 552: expected , but instead found {; Line 553: Expected a function body but instead found ';'.; Line 554: Expected a statement but instead found '}
})'.

backend/src/services/campaignStore.ts

File contains syntax errors that prevent linting: Line 217: Illegal use of an export declaration not at the top level; Line 234: Illegal use of an export declaration not at the top level; Line 263: Illegal use of an export declaration not at the top level; Line 301: Illegal use of an export declaration not at the top level; Line 302: Illegal use of an export declaration not at the top level; Line 304: Illegal use of an export declaration not at the top level; Line 318: Illegal use of an export declaration not at the top level; Line 324: Illegal use of an export declaration not at the top level; Line 329: Illegal use of an export declaration not at the top level; Line 334: Illegal use of an export declaration not at the top level; Line 341: Illegal use of an export declaration not at the top level; Line 352: Illegal use of an export declaration not at the top level; Line 368: Illegal use of an export declaration not at the top level; Line 527: Illegal use of an export declaration not at

... [truncated 797 characters] ...

use of an export declaration not at the top level; Line 1238: Illegal use of an export declaration not at the top level; Line 1272: Illegal use of an export declaration not at the top level; Line 1361: Illegal use of an export declaration not at the top level; Line 1383: Illegal use of an export declaration not at the top level; Line 1423: Illegal use of an export declaration not at the top level; Line 1436: Illegal use of an export declaration not at the top level; Line 1452: Illegal use of an export declaration not at the top level; Line 1497: Illegal use of an export declaration not at the top level; Line 1528: Illegal use of an export declaration not at the top level; Line 1538: Illegal use of an export declaration not at the top level; Line 1550: expected } but instead the file ends

backend/src/validation/schemas.ts

File contains syntax errors that prevent linting: Line 120: Expected a property, a shorthand property, a getter, a setter, or a method but instead found '.'.; Line 126: expected , but instead found :; Line 127: expected , but instead found .; Line 134: Const declarations must have an initialized value.; Line 134: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 135: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 145: Expected a semicolon or an implicit semicolon after a statement, but found none; Line 146: Expected a statement but instead found ')
.optional(),
maxPerContributor: optionalPositiveIntSchema,
})'.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

backend/src/index.ts

Parsing error: ',' expected.

backend/src/services/campaignStore.ts

Parsing error: '}' expected.

backend/src/validation/schemas.ts

Parsing error: Identifier expected.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (1)
backend/src/services/cache.ts (1)

39-42: 🩺 Stability & Availability | 🔵 Trivial

Preserve observability when Redis initialization fails.

This catch now prevents initRedisCache().catch(...) in backend/src/index.ts from observing connection failures, while the error is discarded entirely. Add structured logging or a metric before continuing without Redis so production cache outages remain diagnosable.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/cache.ts` around lines 39 - 42, Update the Redis
initialization catch block in initRedisCache to record the caught error through
the existing structured logger or metrics mechanism before resetting redisClient
and isConnected, ensuring failures remain observable while preserving the
current no-Redis fallback behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/ci.yml:
- Line 1: Normalize the CI workflow file formatting by converting all line
endings to LF and removing the extra trailing blank line at the end of the file,
while preserving the existing workflow content and structure.
- Around line 28-45: Update the CI steps for TypeScript Check, ESLint, Backend
Jest, Frontend Vitest, and Playwright E2E to remove the || echo fallbacks and
any --if-present or passWithNoTests options that allow failures or missing
scripts to pass. Invoke the required checks directly so every non-zero exit
fails the workflow.
- Around line 3-15: Update the workflow-level configuration in ci.yml to set
permissions to contents: read, and modify both checkout steps in the validate-pr
and corresponding other job to set persist-credentials: false. Preserve the
existing checkout actions and job behavior while restricting token access and
preventing credential persistence.
- Around line 22-23: Update the dependency-installation steps in the CI workflow
so the root npm install is followed by npm run install:all before backend tests
execute. Keep the existing backend test command unchanged, ensuring
child-package dependencies and the locally resolved backend Vitest installation
are available.

In `@backend/src/index.ts`:
- Around line 493-496: Update pledgeId validation in the request handler to
validate the entire req.params.pledgeId segment rather than relying on parseInt,
rejecting values with trailing characters or decimals. Require the resulting ID
to be a safe integer before querying, and preserve the existing AppError
response for invalid input.
- Around line 519-521: Update the receipt-generation flow around the
transactionHash check to reject pledges without a confirmed 64-character
hexadecimal transaction hash, including undefined or pending values, before
generating the PDF. Return an explicit pending-transaction response/error and
only render the receipt when the hash passes validation.

In `@backend/src/receiptEndpoint.test.ts`:
- Around line 69-73: Extend the receipt endpoint test around the existing
response assertions to parse res.body as a PDF and verify it is renderable.
Assert the extracted PDF text contains the campaign title, contributor, amount,
asset, timestamp, and transaction hash, while retaining the existing status,
header, Buffer, and non-empty checks.

In `@backend/src/services/cache.ts`:
- Around line 135-136: Update the catch block handling redisClient.quit() in the
cache service to reset isConnected to false and clear redisClient when closing
fails, ensuring isCacheAvailable() cannot report a failed client as available.

---

Nitpick comments:
In `@backend/src/services/cache.ts`:
- Around line 39-42: Update the Redis initialization catch block in
initRedisCache to record the caught error through the existing structured logger
or metrics mechanism before resetting redisClient and isConnected, ensuring
failures remain observable while preserving the current no-Redis fallback
behavior.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: dd766467-8eb7-4dc7-b912-e839c368df2d

📥 Commits

Reviewing files that changed from the base of the PR and between 10f827c and ac0e3af.

⛔ Files ignored due to path filters (1)
  • backend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (8)
  • .github/workflows/ci.yml
  • README.md
  • backend/package.json
  • backend/src/index.ts
  • backend/src/receiptEndpoint.test.ts
  • backend/src/services/cache.ts
  • backend/src/services/campaignStore.ts
  • backend/src/validation/schemas.ts

Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml
Comment thread .github/workflows/ci.yml
Comment thread backend/src/index.ts
Comment thread backend/src/index.ts
Comment thread backend/src/receiptEndpoint.test.ts
Comment thread backend/src/services/cache.ts
@ritik4ever

Copy link
Copy Markdown
Owner

Hi @sarah-obasi-analytics,

This PR could not be merged because it has merge conflicts with the target branch.

Please resolve the merge conflicts, push the updated changes, and the PR can be reviewed and merged.

Thank you!

1 similar comment
@ritik4ever

Copy link
Copy Markdown
Owner

Hi @sarah-obasi-analytics,

This PR could not be merged because it has merge conflicts with the target branch.

Please resolve the merge conflicts, push the updated changes, and the PR can be reviewed and merged.

Thank you!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
backend/src/services/campaignStore.ts (4)

1029-1045: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Select the average funding rate.

The query does not define avg_funding_rate_pct. avgFundingRatePct therefore always returns 0. Add the aggregate expression to this SELECT, with a zero-target guard.

-->

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/campaignStore.ts` around lines 1029 - 1045, The campaign
summary query in the aggregate flow around the visible SELECT must define
avg_funding_rate_pct. Add an aggregate average funding-rate expression based on
pledged_amount versus target_amount, guarding zero targets to avoid division
errors, so the existing avgFundingRatePct mapping receives the computed value
instead of defaulting to 0.

202-209: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Remove the duplicate getContributorPledgedTotal declaration before the export.

backend/src/services/campaignStore.ts:211-212 declares function getContributorPledgedTotal(...) and then immediately repeats it as export function getContributorPledgedTotal(...) inside the first body. This makes the export non-top-level and breaks TypeScript parsing. Keep one declaration/export boundary.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/campaignStore.ts` around lines 202 - 209, Remove the
duplicate nested getContributorPledgedTotal declaration near getPledgeById,
leaving a single top-level export function declaration so the function remains
valid and exported.

Source: Linters/SAST tools


1432-1435: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Pass milliseconds to calculateProgress.

now is in Unix seconds for the SQL query. calculateProgress compares its at argument with campaign.deadline * 1000. This call marks expired campaigns as open and returns an incorrect hoursLeft. Pass now * 1000.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/campaignStore.ts` around lines 1432 - 1435, Update the
calculateProgress call in the campaign result construction to pass now converted
from Unix seconds to milliseconds (now * 1000), matching its comparison with
campaign.deadline * 1000 and preserving correct expired status and hoursLeft
calculations.

491-504: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Make campaign_failed dispatch single-shot.

Both paths check failed_at before an unconditional update. Concurrent list and detail requests can both observe NULL and dispatch duplicate webhooks. Use UPDATE ... WHERE id = ? AND failed_at IS NULL, then dispatch only when changes === 1.

  • backend/src/services/campaignStore.ts#L491-L504: condition the update and webhook on the update result.
  • backend/src/services/campaignStore.ts#L530-L543: condition the update and webhook on the update result.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/campaignStore.ts` around lines 491 - 504, Make
campaign_failed webhook dispatch single-shot in both campaignStore.ts sites at
lines 491-504 and 530-543: change each failed_at update to include failed_at IS
NULL in its WHERE clause, capture the update result, and dispatch the webhook
only when changes equals 1; retain the existing eligibility checks and payload.
README.md (1)

443-457: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Remove the duplicated seed-database block and fix the malformed heading.

This section repeats the instructions already provided at Lines 427-441. The Build:eed text is also malformed. Keep one seed block and retain the Build: heading before the build command.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` around lines 443 - 457, Remove the duplicated seed-database
instructions from the README, keeping only one seed block. Correct the malformed
“Build:eed” heading to “Build:” and preserve it before the build command.
backend/src/services/cache.ts (1)

33-43: 🩺 Stability & Availability | 🔴 Critical | ⚡ Quick win

Replace the consecutive catch clauses with one handler per try block.

catch cannot follow another catch. Lines 36, 40, 43, and 137 cause parser errors. Since backend/src/index.ts calls initRedisCache() at startup, this prevents the backend from starting.

  • backend/src/services/cache.ts#L33-L43: Move logInfo('redis_connected', ...) after a successful await redisClient.connect(). Keep one failure catch that logs the error and clears redisClient and isConnected.
  • backend/src/services/cache.ts#L135-L139: Keep one catch for quit() failures. Clear redisClient and set isConnected = false in that handler.
Proposed fix
     await redisClient.connect();
     isConnected = true;
-  } catch (error) {
-
     logInfo('redis_connected', {}, config.logLevel);
   } catch (error) {
     logError(error instanceof Error ? error : new Error(String(error)), { event: 'redis_connection_failed' }, config.logLevel);
     redisClient = null;
     isConnected = false;
-  } catch {
-    redisClient = null;
-    isConnected = false;
-  } catch {
-    redisClient = null;
-    isConnected = false;
   }
@@
-    } catch (error) {
-      // Ignore errors on close
-    } catch {
+    } catch {
+      redisClient = null;
       isConnected = false;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/cache.ts` around lines 33 - 43, Replace the consecutive
catch clauses in backend/src/services/cache.ts lines 33-43 with one try/catch
flow: in the successful redisClient.connect() path, log redis_connected
afterward; in the single failure handler, log the error and clear redisClient
and isConnected. At lines 135-139, retain one catch for quit() failures and
clear redisClient while setting isConnected to false.

Source: Linters/SAST tools

backend/src/validation/schemas.ts (1)

120-134: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Replace the existing schema properties instead of appending this chain.

At backend/src/validation/schemas.ts:120, .min(4, ...) follows the completed acceptedTokens schema, not a property name. This is invalid TypeScript and creates duplicate title, description, and acceptedTokens keys. Replace the definitions at lines 108-119 with the intended validated schema instead of keeping the old definitions plus a second chain.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/validation/schemas.ts` around lines 120 - 134, Replace the
existing title, description, and acceptedTokens property definitions in the
schema with the validated chain shown in the diff; do not append it after the
completed acceptedTokens property. Ensure each property appears exactly once and
the chain remains attached to its corresponding zod schema.

Source: Linters/SAST tools

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@backend/src/services/cache.ts`:
- Around line 33-43: Replace the consecutive catch clauses in
backend/src/services/cache.ts lines 33-43 with one try/catch flow: in the
successful redisClient.connect() path, log redis_connected afterward; in the
single failure handler, log the error and clear redisClient and isConnected. At
lines 135-139, retain one catch for quit() failures and clear redisClient while
setting isConnected to false.

In `@backend/src/services/campaignStore.ts`:
- Around line 1029-1045: The campaign summary query in the aggregate flow around
the visible SELECT must define avg_funding_rate_pct. Add an aggregate average
funding-rate expression based on pledged_amount versus target_amount, guarding
zero targets to avoid division errors, so the existing avgFundingRatePct mapping
receives the computed value instead of defaulting to 0.
- Around line 202-209: Remove the duplicate nested getContributorPledgedTotal
declaration near getPledgeById, leaving a single top-level export function
declaration so the function remains valid and exported.
- Around line 1432-1435: Update the calculateProgress call in the campaign
result construction to pass now converted from Unix seconds to milliseconds (now
* 1000), matching its comparison with campaign.deadline * 1000 and preserving
correct expired status and hoursLeft calculations.
- Around line 491-504: Make campaign_failed webhook dispatch single-shot in both
campaignStore.ts sites at lines 491-504 and 530-543: change each failed_at
update to include failed_at IS NULL in its WHERE clause, capture the update
result, and dispatch the webhook only when changes equals 1; retain the existing
eligibility checks and payload.

In `@backend/src/validation/schemas.ts`:
- Around line 120-134: Replace the existing title, description, and
acceptedTokens property definitions in the schema with the validated chain shown
in the diff; do not append it after the completed acceptedTokens property.
Ensure each property appears exactly once and the chain remains attached to its
corresponding zod schema.

In `@README.md`:
- Around line 443-457: Remove the duplicated seed-database instructions from the
README, keeping only one seed block. Correct the malformed “Build:eed” heading
to “Build:” and preserve it before the build command.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f31ae892-55d3-4160-9c59-ffcd1fb2795d

📥 Commits

Reviewing files that changed from the base of the PR and between ac0e3af and 103233e.

⛔ Files ignored due to path filters (1)
  • backend/package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (6)
  • README.md
  • backend/package.json
  • backend/src/index.ts
  • backend/src/services/cache.ts
  • backend/src/services/campaignStore.ts
  • backend/src/validation/schemas.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • backend/package.json
  • backend/src/index.ts

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (8)
backend/src/validation/schemas.ts (3)

1-3: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Remove the duplicate import bindings.

z, extendZodWithOpenApi, and config are imported twice, which makes backend/src/validation/schemas.ts fail TypeScript compilation. Keep one declaration for each binding.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/validation/schemas.ts` around lines 1 - 3, Remove the duplicate
import declarations in schemas.ts for z, extendZodWithOpenApi, and config,
preserving one import binding for each symbol and leaving their existing module
sources unchanged.

111-119: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Remove the duplicate description minimum.

createCampaignPayloadSchema.description is parsed before the later fields run, so the .min(10, ...) branch exits when the 10–19-character input fails and the 20-character rule is never tested for that path. Remove one minimum and add a boundary test for the retained minimum.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/validation/schemas.ts` around lines 111 - 119, Update
createCampaignPayloadSchema.description to retain only one minimum-length
validation, removing the duplicate .min(10, ...) rule while preserving the
intended minimum requirement. Add a boundary test covering the retained minimum
length and verify inputs below it are rejected.

111-119: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Validate title length after sanitization.

sanitizeInput() expands <, >, and / into multi-character escapes. A title such as x< passes .max(100) once, but becomes x&lt; after transformation; if the enforced limit applies to the stored/returned sanitized value, add a .refine((val) => val.length <= 100) after the title transform and cover boundary inputs.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/validation/schemas.ts` around lines 111 - 119, Update the title
validation schema to enforce the 100-character limit after sanitizeInput
transforms the value, adding a post-transform length refinement while preserving
the existing minimum and maximum checks; cover titles whose sanitized output is
exactly 100 characters and exceeds 100.
backend/src/index.ts (3)

696-701: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Add return after the validation-failure response.

sendValidationError(parsedId.issues) sends a response when parsedId.ok is false, but there is no return afterward. Execution falls through to line 703-704 and calls addPledge(parsedId.value, body) with a value from a failed parse. This risks a second write attempt after headers are already sent, and it processes a pledge with an invalid campaign ID.

🐛 Suggested fix
     const parsedId = parseCampaignId(req.params.id);
     if (!parsedId.ok) {
-      sendValidationError(parsedId.issues);
+      sendValidationError(parsedId.issues);
+      return;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/index.ts` around lines 696 - 701, Update the validation-failure
branch in the async request handler around parseCampaignId so it returns
immediately after sendValidationError(parsedId.issues). Prevent execution from
reaching addPledge with parsedId.value when parsing fails, while preserving the
existing success path.

99-106: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Check for a merge-conflict artifact: two statements on one line.

Line 106 combines the import closing brace and export const app = express(); on a single line with no separating newline: } from './services/campaignCache';export const app = express();. This is syntactically valid, but it is an unusual pattern that commonly results from a mishandled merge conflict resolution. Given the PR has documented unresolved merge conflicts, verify this line was not corrupted during a rebase or merge.

✏️ Suggested formatting fix
-} from './services/campaignCache';export const app = express();
+} from './services/campaignCache';
+
+export const app = express();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/index.ts` around lines 99 - 106, Separate the campaignCache
import declaration from the app export by placing `export const app =
express();` on its own line after the import statement, and verify no
merge-conflict content was lost around the import boundary.

530-554: 🎯 Functional Correctness | 🔴 Critical | ⚡ Quick win

Resolve the syntax errors in backend/src/index.ts before review.

  • The cache handlers are embedded as bare try...catch/await bodies without enclosing function declarations, so await getCampaignCacheEntry(...) and await setCampaignCacheEntry(...) are not inside async functions. That also leaves the preceding statements invalid before catch.
  • The top-level import block is merged into export const app = express();, which mangles the module import/export boundary.

The campaignStore syntax appears valid in the exported handler, so this comment applies only to backend/src/index.ts.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/index.ts` around lines 530 - 554, The campaign detail cache flow
in backend/src/index.ts:530-554 must be enclosed in a valid async route-handler
function so its try/catch structure and await calls are syntactically valid;
preserve the existing campaign lookup and cache response behavior. Separate the
top-level imports from export const app = express() in
backend/src/index.ts:99-106 to restore the module boundary. No direct change is
required in backend/src/services/campaignStore.ts:610-642 because its exported
handler syntax is already valid.

Source: Linters/SAST tools

backend/src/services/campaignStore.ts (2)

1390-1393: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use toServiceError for the not-found case in updateCampaign.

Every other not-found path in this file (getCampaign callers via toServiceError('Campaign not found.', 404, 'NOT_FOUND') in addPledge, reconcileOnChainPledge, refundContributor) throws through toServiceError, which carries a statusCode. Here, the error is built with Object.assign(new Error(...), { code: 'CAMPAIGN_NOT_FOUND' }), which has no statusCode property and a differently-named code. If the central error handler reads error.statusCode (as the AppError-based flow elsewhere implies), this path likely returns 500 instead of 404 to the client.

🐛 Suggested fix
   if (!campaign) {
-    throw Object.assign(new Error(`Campaign ${campaignId} not found`), {
-      code: 'CAMPAIGN_NOT_FOUND',
-    });
+    throw toServiceError(`Campaign ${campaignId} not found`, 404, 'NOT_FOUND');
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/campaignStore.ts` around lines 1390 - 1393, Update the
not-found branch in updateCampaign to throw the established toServiceError
result with the campaign-not-found message, HTTP status 404, and the
repository’s standard NOT_FOUND code. Remove the Object.assign Error
construction and preserve the existing successful update flow.

378-401: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Avoid sending raw FTS5 booleans into MATCH.

ftsMatchTerm is the value from user input after only space normalization, so uppercase AND, OR, or NOT tokens are parsed as FTS5 operators. Use a literal quoted phrase, such as "${cleanQuery.replace(/\*/g, '')}"*, or lowercase before comparison.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/campaignStore.ts` around lines 378 - 401, Update the
search-query handling around ftsMatchTerm so user-provided boolean words cannot
be interpreted as FTS5 operators by MATCH. Build the FTS term as a quoted
literal phrase, removing any wildcard characters before quoting while preserving
the intended trailing prefix wildcard; keep the existing creator and campaign ID
matching branches unchanged.
🧹 Nitpick comments (2)
backend/src/index.ts (1)

105-106: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Add Helmet middleware to the Express app.

Static analysis flags express() as missing Helmet security headers. Add helmet() as early middleware to set standard protections (X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security, etc.).

🛡️ Suggested fix
+import helmet from 'helmet';
+
 export const app = express();
+app.use(helmet());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/index.ts` around lines 105 - 106, Update the Express app
initialization at app to import Helmet and register helmet() as early middleware
immediately after express() is created, before application routes or other
request handling, so standard security headers are applied.

Source: Linters/SAST tools

backend/src/services/campaignStore.ts (1)

769-780: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Extract the duplicated token-acceptance check into a shared helper.

addPledge and reconcileOnChainPledge contain the same token-matching block almost verbatim: direct canonical match, fallback to assetCode for legacy entries, and asset-code-prefix matching. Extract this into a shared function, e.g. isTokenAcceptedByCampaign(campaign, tokenId, assetCode), to keep the matching rule consistent and avoid it drifting between the two call sites.

♻️ Suggested refactor
+function isTokenAcceptedByCampaign(
+  campaign: CampaignRecord,
+  tokenId: string,
+  assetCode: string,
+): boolean {
+  return campaign.acceptedTokens.some((accepted) => {
+    if (accepted === tokenId) return true;
+    if (!accepted.includes(':')) {
+      return accepted === assetCode;
+    }
+    return accepted.split(':')[0] === assetCode;
+  });
+}

Then replace both inline blocks with isTokenAcceptedByCampaign(campaign, tokenId, assetCode).

Also applies to: 918-933

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/src/services/campaignStore.ts` around lines 769 - 780, Extract the
duplicated token-matching logic from addPledge and reconcileOnChainPledge into a
shared isTokenAcceptedByCampaign(campaign, tokenId, assetCode) helper. Preserve
the existing direct canonical match, legacy asset-code fallback, and
asset-code-prefix behavior, then replace both inline checks with calls to the
helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@backend/src/index.ts`:
- Around line 696-701: Update the validation-failure branch in the async request
handler around parseCampaignId so it returns immediately after
sendValidationError(parsedId.issues). Prevent execution from reaching addPledge
with parsedId.value when parsing fails, while preserving the existing success
path.
- Around line 99-106: Separate the campaignCache import declaration from the app
export by placing `export const app = express();` on its own line after the
import statement, and verify no merge-conflict content was lost around the
import boundary.
- Around line 530-554: The campaign detail cache flow in
backend/src/index.ts:530-554 must be enclosed in a valid async route-handler
function so its try/catch structure and await calls are syntactically valid;
preserve the existing campaign lookup and cache response behavior. Separate the
top-level imports from export const app = express() in
backend/src/index.ts:99-106 to restore the module boundary. No direct change is
required in backend/src/services/campaignStore.ts:610-642 because its exported
handler syntax is already valid.

In `@backend/src/services/campaignStore.ts`:
- Around line 1390-1393: Update the not-found branch in updateCampaign to throw
the established toServiceError result with the campaign-not-found message, HTTP
status 404, and the repository’s standard NOT_FOUND code. Remove the
Object.assign Error construction and preserve the existing successful update
flow.
- Around line 378-401: Update the search-query handling around ftsMatchTerm so
user-provided boolean words cannot be interpreted as FTS5 operators by MATCH.
Build the FTS term as a quoted literal phrase, removing any wildcard characters
before quoting while preserving the intended trailing prefix wildcard; keep the
existing creator and campaign ID matching branches unchanged.

In `@backend/src/validation/schemas.ts`:
- Around line 1-3: Remove the duplicate import declarations in schemas.ts for z,
extendZodWithOpenApi, and config, preserving one import binding for each symbol
and leaving their existing module sources unchanged.
- Around line 111-119: Update createCampaignPayloadSchema.description to retain
only one minimum-length validation, removing the duplicate .min(10, ...) rule
while preserving the intended minimum requirement. Add a boundary test covering
the retained minimum length and verify inputs below it are rejected.
- Around line 111-119: Update the title validation schema to enforce the
100-character limit after sanitizeInput transforms the value, adding a
post-transform length refinement while preserving the existing minimum and
maximum checks; cover titles whose sanitized output is exactly 100 characters
and exceeds 100.

---

Nitpick comments:
In `@backend/src/index.ts`:
- Around line 105-106: Update the Express app initialization at app to import
Helmet and register helmet() as early middleware immediately after express() is
created, before application routes or other request handling, so standard
security headers are applied.

In `@backend/src/services/campaignStore.ts`:
- Around line 769-780: Extract the duplicated token-matching logic from
addPledge and reconcileOnChainPledge into a shared
isTokenAcceptedByCampaign(campaign, tokenId, assetCode) helper. Preserve the
existing direct canonical match, legacy asset-code fallback, and
asset-code-prefix behavior, then replace both inline checks with calls to the
helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 9d967429-1cd1-4670-90be-5418c9241239

📥 Commits

Reviewing files that changed from the base of the PR and between 103233e and 6fca2d1.

📒 Files selected for processing (5)
  • README.md
  • backend/package.json
  • backend/src/index.ts
  • backend/src/services/campaignStore.ts
  • backend/src/validation/schemas.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/package.json

@sarah-obasi-analytics

Copy link
Copy Markdown
Author

@ritik4ever please merge PR, If there isnt any issue...

@sarah-obasi-analytics

Copy link
Copy Markdown
Author

@ritik4ever

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.

[FEATURE] Add pledge receipt endpoint (PDF)

2 participants