Skip to content

feat: add automated email template testing and preview (#337)#427

Merged
roshankumar0036singh merged 15 commits into
roshankumar0036singh:mainfrom
Shriraj888:feat/add-automated-email-template-testing-and-preview
Jun 1, 2026
Merged

feat: add automated email template testing and preview (#337)#427
roshankumar0036singh merged 15 commits into
roshankumar0036singh:mainfrom
Shriraj888:feat/add-automated-email-template-testing-and-preview

Conversation

@Shriraj888
Copy link
Copy Markdown
Contributor

@Shriraj888 Shriraj888 commented May 30, 2026

Description

This change introduces automated email template testing and preview capabilities to the cloud-functions service in the UniEvent project.

Key modifications include:

Template Rendering Engine

Developed emailTemplateRenderer.ts to:

  • Resolve HTML templates located in docs/EmailJs_Template/
  • Replace placeholder tags using {{variable}} syntax
  • Provide built-in mock/sample data for template previews

Preview Endpoints

Added the following routes in server.ts:

  • GET /email-preview
    Lists all available email templates.

  • GET /email-preview/:templateName
    Renders the selected template directly in the browser using fallback sample data and supports query parameter overrides.

Example:

/email-preview/feedback_email_template?to_name=John

Dry-Run Email Sender

Implemented emailSender.ts to wrap email dispatching logic and support a { dryRun: true } option. When enabled, the service returns the compiled HTML content instead of making external email API calls.

Jest Typing & Build Configuration

Updated tsconfig.json to:

  • Exclude test files from production builds
  • Include Node.js and Jest ambient type definitions

Dependencies

No additional runtime dependencies were required. Changes rely on the existing TypeScript and Jest tooling already present in the project.

Fixes #337


Type of change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • This change requires a documentation update

How Has This Been Tested?

The modifications have been verified through compiler checks and Jest unit tests.

Unit Tests

Created emailTemplateRenderer.test.ts containing 13 unit tests covering:

  • Placeholder injection
  • Fallback sample data behavior
  • Template name resolution
  • Template discovery/listing functionality

Test command:

npm test

Result:

  • 15/15 tests passing (including existing reputation-related tests)

Build Verification

Verified successful TypeScript compilation and build generation.

Build command:

npm run build

Results:

  • No TypeScript errors
  • No compilation warnings

Checklist

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • Any dependent changes have been merged and published in downstream modules

Summary by CodeRabbit

  • New Features

    • Improved certificate sending (attachments or URL delivery), developer email-template preview UI, admin send-certificates endpoint, and new HTML email templates.
  • Bug Fixes

    • Better duplicate-attendance checks, refined 10-minute notification timing, clearer push-delivery/failure reporting, stricter token authentication and rate-limiting, and deleted events excluded from feeds.
  • Chores

    • TypeScript build config and tooling updates, added scheduled cleanup job.
  • Tests

    • New tests for email templating and email sending.

Copilot AI review requested due to automatic review settings May 30, 2026 09:49
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 30, 2026

Review Change Stack

Warning

Review limit reached

@Shriraj888, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 43 minutes and 9 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 52514aa2-a765-4aa8-ace3-223c5d3f9589

📥 Commits

Reviewing files that changed from the base of the PR and between e8d42aa and 928a29b.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (3)
  • cloud-functions/lib/certificateService.js
  • cloud-functions/src/certificateService.ts
  • cloud-functions/src/utils/push.ts
📝 Walkthrough

Walkthrough

Adds template discovery/rendering and preview, a typed sendEmail utility and tests, unified certificate email sending (Buffer|URL) with per-participant outcomes and event-summary persistence, server auth and rate-limit middleware plus template-preview endpoints, batched push processing with failure tracking, reputation nullish-coalescing fixes, templates, and build/lint config updates.

Changes

Cloud Functions Platform Improvements

Layer / File(s) Summary
Email template renderer and sample data
cloud-functions/src/utils/emailTemplateRenderer.ts
Adds TEMPLATES_DIR, in-memory cache, SAMPLE_DATA, getAvailableTemplates(), getSampleData() and renderTemplate() with HTML-escaping and placeholder replacement.
sendEmail utility and tests
cloud-functions/src/utils/emailSender.ts, cloud-functions/src/utils/emailSender.test.ts
Typed sendEmail supports dryRun, renders templates, validates RESEND_API_KEY, sends via Resend, and returns structured results; Jest tests cover dry-run, render errors, missing credentials, success, and provider failures.
HTML email templates
cloud-functions/templates/*_email_template.html
Adds certificate, feedback, and universal email templates with placeholders used by renderer/sender.
Certificate generation & unified email delivery
cloud-functions/src/certificateService.ts, cloud-functions/lib/certificateService.js
Unifies email sending into `sendCertificateEmail(Buffer
Server auth, rate-limiting, and preview endpoints
cloud-functions/src/server.ts, cloud-functions/lib/server.js
Conditional Admin init from env, validateFirebaseIdToken requiring Bearer header, rateLimitMiddleware with structured errors, protected /api/setRole and /api/sendCertificates, and dev-only /email-preview endpoints rendering templates with escaped query overrides (403 in production).
Daily digest & event notifications (push)
cloud-functions/src/dailyDigest.ts, cloud-functions/lib/dailyDigest.js, cloud-functions/src/eventNotifications.ts, cloud-functions/lib/eventNotifications.js, cloud-functions/src/utils/push.ts, cloud-functions/lib/reminders.js
Moves daily-digest user processing into paged processUsersInBatches with per-page push send and failedPushes tracking, replaces direct expo-server-sdk checks with isExpoPushToken, and widens event notification query window to 9.5–11.5 minutes.
Reputation scoring & leaderboard
cloud-functions/src/reputation.ts, cloud-functions/lib/reputation.js
Switches to nullish coalescing (??) to preserve stored zeros and updates leaderboard read mappings to use optional chaining with numeric fallbacks.
Fraud detection & participants mapping
cloud-functions/lib/analyzeAttendance.js, cloud-functions/lib/lib/participants.js
Rewrites timestamp toDate checks with optional chaining in duplicate/multi-event checks and simplifies participant mapping to { id, ...doc.data() }.
App UserFeed minor updates
app/src/screens/UserFeed.js
Skips events where deletedAt != null in snapshot and manual refresh; adds two inline comments.
Build & lint config
cloud-functions/tsconfig.build.json, cloud-functions/tsconfig.json, package.json
Adds tsconfig.build.json, updates cloud-functions tsconfig target/types/exclude, switches build script to use tsconfig.build.json, and scopes lint-staged to project subpaths and explicit Prettier patterns.
Lib exports & scheduled cleanup
cloud-functions/lib/index.js
Exports attendanceStreak and permanentCleanup and adds hourly cleanupRateLimits scheduled function invoking rate-limiter cleanup.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

level:intermediate, type:testing, quality:clean, type:design

Suggested reviewers

  • roshankumar0036singh

Poem

🐰 I found templates in a moonlit trail,
I stitched safe HTML and traced each mail,
I bundled certs and queued the pushes tight,
Auth gates set — previews glow by night.
A tiny rabbit hops — the backend's bright!

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Several out-of-scope changes are present beyond #337's email template preview objectives: refactored certificateService.ts with Resend integration, updated dailyDigest.ts with error handling, modified eventNotifications.ts timing, reputation.js nullish coalescing, analyzeAttendance.js optional chaining, and rate-limiter additions. Scope #337 strictly to email template testing/preview. Move certificate service refactoring, reputation logic changes, push notification updates, and rate-limiter additions to separate issues/PRs for focused review.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ 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 and concisely describes the main feature addition: automated email template testing and preview functionality with the issue number reference.
Linked Issues check ✅ Passed All primary coding objectives from #337 are met: template rendering engine (emailTemplateRenderer.ts), preview endpoints (GET /email-preview and GET /email-preview/:templateName with query overrides), and dry-run email sender with dryRun mode returning HTML.

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

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Adds developer tooling for previewing EmailJS HTML templates and introduces utilities for rendering/sending templated emails, alongside several backend runtime refactors (rate limiting, push delivery, digest paging, certificate sending).

Changes:

  • Add a filesystem-based email template renderer with sample-data defaults and Jest tests.
  • Add developer-facing Express endpoints to list and preview templates with query-param overrides.
  • Update email/certificate/push/digest backend logic (mostly in cloud-functions/lib/**) and adjust TypeScript config for Node/Jest types.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 16 comments.

Show a summary per file
File Description
cloud-functions/tsconfig.json Adds Node/Jest typings and excludes test files from compilation.
cloud-functions/src/utils/emailTemplateRenderer.ts Implements template discovery, sample data, and placeholder replacement.
cloud-functions/src/utils/emailTemplateRenderer.test.ts Adds Jest tests for renderer behavior against real HTML templates.
cloud-functions/src/utils/emailSender.ts Adds EmailJS send/dry-run helper built on template rendering.
cloud-functions/src/server.ts Adds /email-preview and /email-preview/:templateName developer preview endpoints.
cloud-functions/lib/server.js Adds rate limiting middleware and compiles in email preview endpoints.
cloud-functions/lib/** Various compiled JS changes (push refactor, digest paging, certificate handling, exports).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cloud-functions/src/server.ts
Comment thread cloud-functions/src/server.ts
Comment thread cloud-functions/src/server.ts Outdated
Comment thread cloud-functions/src/server.ts Outdated
Comment thread cloud-functions/src/server.ts Outdated
Comment thread cloud-functions/src/utils/emailSender.ts Outdated
Comment thread cloud-functions/lib/certificateService.js
Comment thread cloud-functions/lib/certificateService.js
Comment thread cloud-functions/lib/dailyDigest.js
Comment thread cloud-functions/lib/dailyDigest.js
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: 10

Caution

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

⚠️ Outside diff range comments (1)
cloud-functions/lib/server.js (1)

319-345: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Honor digestOptIn in the manual digest route too.

cloud-functions/lib/dailyDigest.js now skips users with digestOptIn === false, but this handler still creates notifications and push messages for every user. Admin-triggered digests sent through /api/sendDailyDigest will ignore opt-out preferences unless this path is aligned with the shared implementation.

🤖 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 `@cloud-functions/lib/server.js` around lines 319 - 345, The manual digest loop
currently creates in-app notifications and push messages for every user; update
the usersSnapshot.forEach handler to respect the user's digestOptIn by skipping
users where userData.digestOptIn === false before creating notifRef/batch.set or
pushing to messages; in other words, add a guard using userData.digestOptIn
(treat undefined as opted-in) at the top of the forEach callback so that
notifRef, batch.set, and messages.push only run for users who have not
explicitly opted out.
🧹 Nitpick comments (1)
cloud-functions/lib/certificateService.js (1)

266-289: ⚖️ Poor tradeoff

Per-participant pipeline runs fully sequentially.

Each participant is processed one at a time through PDF generation → storage upload → Firestore write → email send. For events with many participants this serial loop can be slow and risks exceeding the HTTP request/function timeout. Consider processing in bounded-concurrency batches (e.g. p-limit or chunked Promise.allSettled) to improve throughput while protecting Storage/Resend rate limits.

🤖 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 `@cloud-functions/lib/certificateService.js` around lines 266 - 289, The
current for-loop processes participants sequentially (calling processParticipant
and handleExistingCertificateParticipant), causing slow execution and timeout
risk; refactor the participant processing to run with bounded concurrency (e.g.,
use a p-limit instance or chunk participants and run Promise.allSettled per
chunk) so that calls to processParticipant and
handleExistingCertificateParticipant execute in parallel up to a safe limit,
collect outcomes into the same results array, and then preserve the existing
success check that updates events (certificatesSent / certificatesSentAt) only
when all results are successful; ensure concurrency limits respect
Storage/Resend rate limits and maintain idempotency semantics used in the
current loop.
🤖 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 `@cloud-functions/lib/certificateService.js`:
- Around line 109-114: The uploadPdfAndGetUrl function currently requests an
unbounded long-lived signed URL (expires: '2499-12-31'); update the source in
cloud-functions/src/certificateService.ts to generate short-lived V4 signed URLs
instead: call file.getSignedUrl with { version: 'v4', action: 'read', expires:
<boundedExpiry> } where <boundedExpiry> is at most 7 days from now (preferably
much shorter, e.g. 15–60 minutes) or use a Date object/epoch seconds
accordingly; alternatively, change downstream logic to serve the PDF via
authenticated access and remove public signed URLs so participant PII isn’t
exposed. Ensure you modify the uploadPdfAndGetUrl implementation and any callers
that expect the long-lived URL to instead refresh or request short-lived URLs on
demand.

In `@cloud-functions/lib/dailyDigest.js`:
- Around line 106-124: When sendPushNotifications(pageMessages) fails inside the
loop, don't swallow the error — either rethrow it or track failures and return a
non-success result; modify the loop in the daily digest routine that calls
sendPushNotifications so that on catch you increment a failedPushCount (or set
success = false) and include that in the final return (e.g., return { success:
false, count, processed: processedCount, failedPushes: failedPushCount }), or
simply rethrow the caught error after logging so the caller sees the failure;
update the catch block that currently calls functions.logger.error(...) to
implement one of these behaviors referencing sendPushNotifications,
pageMessages, processedCount, count and usersSnapshot.

In `@cloud-functions/lib/eventNotifications.js`:
- Around line 41-49: getUpcomingEvents currently computes a tight 10–11 minute
ISO range from Date.now(), which can miss events if the scheduler drifts; update
the startRange/endRange computation in getUpcomingEvents to either round
Date.now() to minute boundaries (e.g., floor to the previous minute for start
and ceil for end) or expand the window by a small buffer (e.g., start = now +
9m30s, end = now + 11m30s) and keep the existing dedupe logic using the
notified10Min field; modify the startRange/endRange variables and the query that
uses them accordingly so the query fetches the slightly wider/rounded interval.

In `@cloud-functions/lib/reputation.js`:
- Around line 73-76: The code uses || to derive attendanceCount,
registrationCount, and remindersSet which treats 0 as missing; update the
fallbacks to use nullish coalescing (??) or explicit undefined checks when
reading from userData.reputation (e.g., compute attendanceCount,
registrationCount, remindersSet by checking userData.reputation?.attendanceCount
?? userData.attendanceCount ?? 0) so that legitimate zero values are preserved
before calling calculatePoints; adjust the three variable assignments
accordingly to ensure calculatePoints receives the correct numeric counters.

In `@cloud-functions/lib/server.js`:
- Around line 230-291: The preview route app.get('/email-preview/:templateName')
currently injects unescaped templateName, varsJson, err.message and rendered
html (from renderTemplate) directly into the response, enabling reflected XSS;
fix by HTML-escaping templateName and any strings produced from req.query
(overrides), varsJson and err.message before interpolation, and stop inlining
raw rendered html — instead serve it inside a sandboxed iframe (use <iframe
sandbox="allow-same-origin"> with the template placed via a sanitized srcdoc or
a server-side sanitization step) or run the rendered html through a strict
sanitizer before embedding; ensure renderTemplate and getSampleData outputs are
treated as untrusted and escaped/sanitized prior to insertion.

In `@cloud-functions/src/server.ts`:
- Around line 222-288: The preview endpoint is vulnerable to reflected XSS
because templateName, query overrides (used via renderTemplate and varsJson),
and err.message are injected into the HTML without escaping; fix by
HTML-escaping or context-encoding all user-controlled values before injecting
into the wrapper and inside renderTemplate: ensure renderTemplate performs
context-aware escaping for text nodes vs attribute/URL contexts (escape
templateName used in <title> and <h2>, escape overrides when building varsJson
or render a safe JSON viewer, and sanitize err.message before sending the 404
body), and additionally add an environment check in the
/email-preview/:templateName route to disable or protect this endpoint in
production (e.g., require an admin flag or NODE_ENV !== 'production').

In `@cloud-functions/src/utils/emailSender.ts`:
- Around line 94-99: Add an AbortController-based timeout around the EmailJS
fetch call: create an AbortController, set a timer (e.g., const timeout =
setTimeout(() => controller.abort(), <ms>)) and pass controller.signal into the
fetch options for the POST to 'https://api.emailjs.com/api/v1.0/email/send' (the
same call that uses payload and returns response), then clearTimeout(timeout) in
a finally block so the timer is always cleared; apply the same pattern to other
EmailJS fetch call sites in this module.
- Around line 83-99: sendEmail currently renders renderedHtml from
docs/EmailJs_Template using templateName but sends only template_id
(process.env.EMAILJS_TEMPLATE_ID or "template_general") to EmailJS, causing
dry-run HTML to diverge and EmailJS rejection if you try to POST raw HTML; fix
by aligning the local dry-run rendering with the actual template_id used in the
live send: either derive template_id from templateName (map templateName ->
template_id) or load the local template that corresponds to
process.env.EMAILJS_TEMPLATE_ID so the renderedHtml matches what EmailJS will
render, and do not attempt to include raw HTML in the POST body (keep using
template_id + template_params for the fetch to
https://api.emailjs.com/api/v1.0/email/send).
- Around line 83-92: The current payload object (payload with service_id:
serviceId, template_id: templateId, user_id: publicKey, template_params: {...})
only sends the public `user_id` and will fail for EmailJS accounts that require
private-key authorization; update the code that builds `payload` to
conditionally include `accessToken` (e.g., from a configured secret like
EMAILJS_ACCESS_TOKEN) when present or when account is set to strict/private-key
mode, and confirm the account has “Allow EmailJS API for non-browser
applications” enabled if you intend to use only `user_id`.

In `@cloud-functions/src/utils/emailTemplateRenderer.ts`:
- Line 8: TEMPLATES_DIR currently points at a repo-root docs folder which is not
packaged into the Firebase Functions deploy, causing getAvailableTemplates() and
renderTemplate() to fail at runtime; fix by moving or copying EmailJs_Template
into the functions bundle and updating the constant: place the templates under
the cloud-functions package (e.g., cloud-functions/templates), update const
TEMPLATES_DIR in emailTemplateRenderer.ts to path.resolve(__dirname,
'./templates') (or the correct relative path inside the deployed bundle), and
ensure your build/CI or firebase.json deploy settings include that templates
directory so the files are present at runtime.

---

Outside diff comments:
In `@cloud-functions/lib/server.js`:
- Around line 319-345: The manual digest loop currently creates in-app
notifications and push messages for every user; update the usersSnapshot.forEach
handler to respect the user's digestOptIn by skipping users where
userData.digestOptIn === false before creating notifRef/batch.set or pushing to
messages; in other words, add a guard using userData.digestOptIn (treat
undefined as opted-in) at the top of the forEach callback so that notifRef,
batch.set, and messages.push only run for users who have not explicitly opted
out.

---

Nitpick comments:
In `@cloud-functions/lib/certificateService.js`:
- Around line 266-289: The current for-loop processes participants sequentially
(calling processParticipant and handleExistingCertificateParticipant), causing
slow execution and timeout risk; refactor the participant processing to run with
bounded concurrency (e.g., use a p-limit instance or chunk participants and run
Promise.allSettled per chunk) so that calls to processParticipant and
handleExistingCertificateParticipant execute in parallel up to a safe limit,
collect outcomes into the same results array, and then preserve the existing
success check that updates events (certificatesSent / certificatesSentAt) only
when all results are successful; ensure concurrency limits respect
Storage/Resend rate limits and maintain idempotency semantics used in the
current loop.
🪄 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: c4404b27-7810-4204-9957-c18f9e82da28

📥 Commits

Reviewing files that changed from the base of the PR and between 8a535b2 and fcf8130.

📒 Files selected for processing (18)
  • cloud-functions/lib/analyzeAttendance.js
  • cloud-functions/lib/certificateService.js
  • cloud-functions/lib/dailyDigest.js
  • cloud-functions/lib/eventNotifications.js
  • cloud-functions/lib/index.js
  • cloud-functions/lib/lib/participants.js
  • cloud-functions/lib/onEventCreate.js
  • cloud-functions/lib/reminders.js
  • cloud-functions/lib/reputation.js
  • cloud-functions/lib/server.js
  • cloud-functions/lib/setRole.js
  • cloud-functions/lib/utils/distance.js
  • cloud-functions/lib/utils/fraudScore.js
  • cloud-functions/src/server.ts
  • cloud-functions/src/utils/emailSender.ts
  • cloud-functions/src/utils/emailTemplateRenderer.test.ts
  • cloud-functions/src/utils/emailTemplateRenderer.ts
  • cloud-functions/tsconfig.json
💤 Files with no reviewable changes (6)
  • cloud-functions/lib/utils/distance.js
  • cloud-functions/lib/setRole.js
  • cloud-functions/lib/analyzeAttendance.js
  • cloud-functions/lib/onEventCreate.js
  • cloud-functions/lib/lib/participants.js
  • cloud-functions/lib/utils/fraudScore.js

Comment thread cloud-functions/lib/certificateService.js
Comment thread cloud-functions/lib/dailyDigest.js
Comment thread cloud-functions/lib/eventNotifications.js
Comment thread cloud-functions/lib/reputation.js Outdated
Comment thread cloud-functions/lib/server.js Outdated
Comment thread cloud-functions/src/server.ts
Comment thread cloud-functions/src/utils/emailSender.ts Outdated
Comment thread cloud-functions/src/utils/emailSender.ts Outdated
Comment thread cloud-functions/src/utils/emailSender.ts Outdated
Comment thread cloud-functions/src/utils/emailTemplateRenderer.ts Outdated
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

♻️ Duplicate comments (1)
cloud-functions/src/utils/emailTemplateRenderer.ts (1)

102-115: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Use context-aware sanitization for URL and CSS placeholders.

escapeHtml() is only safe for text/attribute escaping. These templates also inject placeholders into href="{{cert_url}}", href="{{event_link}}", and style="display: {{...}}", so values like javascript:... or block;position:fixed;... still pass through as active URL/CSS content. This needs per-field allowlists/validators rather than one generic escaper.

🤖 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 `@cloud-functions/src/utils/emailTemplateRenderer.ts` around lines 102 - 115,
The current generic escapeHtml used in the replacement callback doesn't prevent
dangerous URL/CSS values; update the placeholder handling in the replacement
function (the regex replace that references mergedData and escapeHtml) to
perform context-aware sanitization: for URL placeholders like cert_url and
event_link validate/allowlist schemes (e.g., https:, mailto:) and whitelist host
patterns or absolute/relative path formats, rejecting or neutralizing anything
else; for style/display placeholders only allow a small set of safe values
(e.g., "block","inline","none","inline-block") or map booleans to safe styles;
continue to use escapeHtml for plain text placeholders but route known attribute
or style keys through their respective validators before returning the
replacement.
🤖 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 `@cloud-functions/src/certificateService.ts`:
- Around line 120-121: The code currently persists an expiring GCS V4 signed URL
(produced by uploadPdfAndGetUrl()) into participant.certificateUrl and then
reuses it in handleExistingCertificateParticipant(), causing expired links to be
sent; change persistCertificateUrl() to store the durable storage object path
(e.g., "certificates/${eventId}/${participantId}.pdf" or the value of file.name)
instead of the signed URL, keep uploadPdfAndGetUrl() to return the uploaded
file/object reference, and update send/resend logic to call file.getSignedUrl({
action: 'read', expires: ... , version: 'v4' }) at send time to generate a fresh
signed URL instead of reading participant.certificateUrl directly.

In `@cloud-functions/src/dailyDigest.ts`:
- Around line 106-108: The current return uses success: false when failedPushes
> 0 which signals a retryable failure even though notifications were already
persisted; change the return to indicate partial success instead (for example
return { success: true, partial: true, count, processed: processedCount,
failedPushes } or at minimum success: true with failedPushes included) so
callers won't retry and create duplicates; keep the failedPushes and
processedCount fields but flip the success/partial semantics in the failing
branch (without adding idempotency logic).

In `@cloud-functions/src/utils/emailSender.test.ts`:
- Around line 7-20: The test suite replaces the global.fetch with mockFetch and
never restores it; save the original global.fetch before assigning mockFetch
(e.g., const originalFetch = global.fetch) and restore it in the afterAll block
(use global.fetch = originalFetch) so other tests are not affected; update the
file's top-level setup where mockFetch and global.fetch are assigned and modify
the afterAll cleanup to restore both process.env and global.fetch, referencing
mockFetch, global.fetch, beforeEach, and afterAll to locate the code.

In `@cloud-functions/src/utils/emailTemplateRenderer.ts`:
- Around line 98-100: renderTemplate currently always merges SAMPLE_DATA
(getSampleData(templateName)), causing live sendEmail flows to silently include
preview values; make sample-data fallback opt-in by adding an options parameter
(e.g., { useSampleData?: boolean } default false) to renderTemplate and only
merge getSampleData(templateName) when useSampleData is true; update any preview
endpoints to call renderTemplate(..., { useSampleData: true }) and leave
sendEmail() and its callers unchanged so live sends fail on missing fields;
adjust function signatures for renderTemplate and any internal callers (and
add/modify tests) to reflect the new option.

---

Duplicate comments:
In `@cloud-functions/src/utils/emailTemplateRenderer.ts`:
- Around line 102-115: The current generic escapeHtml used in the replacement
callback doesn't prevent dangerous URL/CSS values; update the placeholder
handling in the replacement function (the regex replace that references
mergedData and escapeHtml) to perform context-aware sanitization: for URL
placeholders like cert_url and event_link validate/allowlist schemes (e.g.,
https:, mailto:) and whitelist host patterns or absolute/relative path formats,
rejecting or neutralizing anything else; for style/display placeholders only
allow a small set of safe values (e.g., "block","inline","none","inline-block")
or map booleans to safe styles; continue to use escapeHtml for plain text
placeholders but route known attribute or style keys through their respective
validators before returning the replacement.
🪄 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: 3c7f23c9-d3c7-4a35-9bad-215feda30de8

📥 Commits

Reviewing files that changed from the base of the PR and between fcf8130 and 1a7de54.

📒 Files selected for processing (20)
  • app/src/screens/LocationHeatmapScreen.js
  • app/src/screens/UserFeed.js
  • cloud-functions/lib/certificateService.js
  • cloud-functions/lib/dailyDigest.js
  • cloud-functions/lib/eventNotifications.js
  • cloud-functions/lib/reputation.js
  • cloud-functions/lib/server.js
  • cloud-functions/src/certificateService.ts
  • cloud-functions/src/dailyDigest.ts
  • cloud-functions/src/eventNotifications.ts
  • cloud-functions/src/reputation.ts
  • cloud-functions/src/server.ts
  • cloud-functions/src/utils/emailSender.test.ts
  • cloud-functions/src/utils/emailSender.ts
  • cloud-functions/src/utils/emailTemplateRenderer.test.ts
  • cloud-functions/src/utils/emailTemplateRenderer.ts
  • cloud-functions/templates/certificate_email_template.html
  • cloud-functions/templates/feedback_email_template.html
  • cloud-functions/templates/universal_email_template.html
  • cloud-functions/tsconfig.json
✅ Files skipped from review due to trivial changes (2)
  • cloud-functions/templates/feedback_email_template.html
  • app/src/screens/LocationHeatmapScreen.js
🚧 Files skipped from review as they are similar to previous changes (6)
  • cloud-functions/lib/eventNotifications.js
  • cloud-functions/lib/dailyDigest.js
  • cloud-functions/src/utils/emailTemplateRenderer.test.ts
  • cloud-functions/lib/reputation.js
  • cloud-functions/src/utils/emailSender.ts
  • cloud-functions/lib/server.js

Comment thread cloud-functions/src/certificateService.ts Outdated
Comment thread cloud-functions/src/dailyDigest.ts
Comment thread cloud-functions/src/utils/emailSender.test.ts Outdated
Comment thread cloud-functions/src/utils/emailTemplateRenderer.ts Outdated
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.

🧹 Nitpick comments (1)
cloud-functions/src/utils/emailTemplateRenderer.test.ts (1)

68-128: ⚡ Quick win

Add an escaping regression test.

These cases prove placeholder replacement, but they do not assert that unsafe input is encoded. A future change that weakens escapeHtml() would still pass this suite.

Suggested test
     describe('renderTemplate()', () => {
+        it('escapes HTML in injected values', () => {
+            const html = renderTemplate('feedback_email_template', {
+                to_name: '<script>alert(1)</script>',
+            });
+
+            expect(html).toContain('&lt;script&gt;alert(1)&lt;/script&gt;');
+            expect(html).not.toContain('<script>alert(1)</script>');
+        });
+
         it('should replace placeholders with provided data', () => {
             const html = renderTemplate('feedback_email_template', {
                 to_name: 'TestUser',
🤖 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 `@cloud-functions/src/utils/emailTemplateRenderer.test.ts` around lines 68 -
128, Add an escaping regression test that ensures renderTemplate/escapeHtml
still HTML-encodes unsafe input: call renderTemplate (e.g., with
'feedback_email_template') passing a malicious value like
"<script>alert(1)</script>" or strings with "&" for a field such as to_name,
then assert the returned html contains the escaped form (e.g.,
"&lt;script&gt;alert(1)&lt;/script&gt;" and "&amp;") and does NOT contain the
raw "<", ">", or "&" for that variable; also keep the existing
expectNoPlaceholders check to ensure placeholders are replaced.
🤖 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.

Nitpick comments:
In `@cloud-functions/src/utils/emailTemplateRenderer.test.ts`:
- Around line 68-128: Add an escaping regression test that ensures
renderTemplate/escapeHtml still HTML-encodes unsafe input: call renderTemplate
(e.g., with 'feedback_email_template') passing a malicious value like
"<script>alert(1)</script>" or strings with "&" for a field such as to_name,
then assert the returned html contains the escaped form (e.g.,
"&lt;script&gt;alert(1)&lt;/script&gt;" and "&amp;") and does NOT contain the
raw "<", ">", or "&" for that variable; also keep the existing
expectNoPlaceholders check to ensure placeholders are replaced.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 920dab10-16cf-46e0-a786-26624111c59d

📥 Commits

Reviewing files that changed from the base of the PR and between 1a7de54 and 41c08ac.

📒 Files selected for processing (19)
  • cloud-functions/lib/analyzeAttendance.js
  • cloud-functions/lib/certificateService.js
  • cloud-functions/lib/dailyDigest.js
  • cloud-functions/lib/eventNotifications.js
  • cloud-functions/lib/lib/participants.js
  • cloud-functions/lib/reminders.js
  • cloud-functions/lib/reputation.js
  • cloud-functions/lib/server.js
  • cloud-functions/src/certificateService.ts
  • cloud-functions/src/dailyDigest.ts
  • cloud-functions/src/server.ts
  • cloud-functions/src/utils/emailSender.test.ts
  • cloud-functions/src/utils/emailTemplateRenderer.test.ts
  • cloud-functions/src/utils/emailTemplateRenderer.ts
  • cloud-functions/templates/certificate_email_template.html
  • cloud-functions/templates/feedback_email_template.html
  • cloud-functions/templates/universal_email_template.html
  • cloud-functions/tsconfig.json
  • package.json
🚧 Files skipped from review as they are similar to previous changes (12)
  • cloud-functions/tsconfig.json
  • cloud-functions/templates/universal_email_template.html
  • cloud-functions/templates/feedback_email_template.html
  • cloud-functions/lib/reminders.js
  • cloud-functions/src/dailyDigest.ts
  • cloud-functions/templates/certificate_email_template.html
  • cloud-functions/src/utils/emailSender.test.ts
  • cloud-functions/src/certificateService.ts
  • cloud-functions/lib/reputation.js
  • cloud-functions/lib/eventNotifications.js
  • cloud-functions/lib/dailyDigest.js
  • cloud-functions/lib/certificateService.js

@Shriraj888
Copy link
Copy Markdown
Contributor Author

@roshankumar0036singh Could you please review this

@roshankumar0036singh
Copy link
Copy Markdown
Owner

certificatesSent only marks true if 100% succeed — partial success is silently lost fix them to handle it

@roshankumar0036singh
Copy link
Copy Markdown
Owner

The old expo instance was removed, but Expo is still imported. why though ?

@roshankumar0036singh
Copy link
Copy Markdown
Owner

reputation.js has leftover merge conflict artifacts

@roshankumar0036singh
Copy link
Copy Markdown
Owner

Signed URL expiry is now 7 days — certificates become inaccessible

@roshankumar0036singh
Copy link
Copy Markdown
Owner

emailSender.ts uses EmailJS but certificateService.ts uses Resend
Two different email providers

@roshankumar0036singh
Copy link
Copy Markdown
Owner

dailyDigest.ts exports sendDailyDigest as an onCall function but server.ts duplicates the logic as an Express route

@roshankumar0036singh
Copy link
Copy Markdown
Owner

tsconfig.json excludes *.test.ts from compilation

@roshankumar0036singh
Copy link
Copy Markdown
Owner

niversal_email_template.html missing newline at end of file

@roshankumar0036singh
Copy link
Copy Markdown
Owner

@Shriraj888 fix everything

@Shriraj888
Copy link
Copy Markdown
Contributor Author

@roshankumar0036singh Thanks for the review. I've addressed all the comments and pushed the changes. Could you please review it again when you have a moment?

@roshankumar0036singh
Copy link
Copy Markdown
Owner

conflcit

…email-template-testing-and-preview

# Conflicts:
#	app/src/screens/UserFeed.js
#	cloud-functions/src/certificateService.ts
#	cloud-functions/src/server.ts
#	cloud-functions/tsconfig.json
@Shriraj888
Copy link
Copy Markdown
Contributor Author

@roshankumar0036singh I've resolved the conflicts and pushed the updated changes. Whenever you get a chance, I'd appreciate a final review. Thanks!

@roshankumar0036singh
Copy link
Copy Markdown
Owner

@Shriraj888 just fix this and everything is good tsconfig.build.json excludes tests but tsconfig.json still includes them

@roshankumar0036singh
Copy link
Copy Markdown
Owner

@Shriraj888 solve the conflcit carefully and also there are 5 comment left to reolsve

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

♻️ Duplicate comments (1)
cloud-functions/src/certificateService.ts (1)

120-135: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Stop storing a durable certificate link as a signed URL.

This still writes a bearer URL into participant.certificateUrl, and the resend path reuses that stored value later. That makes certificate access live as long as the stored URL does, instead of being minted per delivery. Persist the storage object path here and generate a fresh short-lived signed URL when emailing/resending.

🤖 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 `@cloud-functions/src/certificateService.ts` around lines 120 - 135, The code
currently persists a long-lived signed URL into participant.certificateUrl;
instead, change persistCertificateUrl (and callers) to save the storage object
path (e.g., the same storagePath used in uploadPdfAndGetUrl) into
participant.certificatePath (or overwrite certificateUrl with the object path)
and the timestamp, and remove storing the signedUrl; update uploadPdfAndGetUrl
to return the storagePath (or ensure caller has it) and leave signed URL
generation to the email/resend flow (generate a short-lived signed URL from the
saved storage path when sending or resending certificates). Ensure references to
participant.certificateUrl in resend/email logic are replaced to fetch the
stored object path and call file.getSignedUrl at send time.
🧹 Nitpick comments (2)
cloud-functions/package.json (2)

4-4: ⚡ Quick win

tsc -p tsconfig.build.json is safe; remove redundant @types/dotenv

  • cloud-functions/tsconfig.build.json exists, so the build script should not fail due to a missing file.
  • @types/dotenv in dependencies is redundant with dotenv v17 (it ships its own TypeScript types); remove it (or move it to devDependencies if you truly need it).
🤖 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 `@cloud-functions/package.json` at line 4, The build script "build": "tsc -p
tsconfig.build.json" is valid because tsconfig.build.json exists, so leave that
script unchanged; remove the redundant "`@types/dotenv`" entry from package.json
dependencies (or move it to devDependencies if you need it only for
type-checking) since dotenv v17 includes its own types—update the "dependencies"
section to delete "`@types/dotenv`" and run npm install to update lockfile.

25-25: 💤 Low value

Remove misplaced @types/dotenv; dotenv@^17.2.3 already ships its own types.

"`@types/dotenv`": "^6.1.1",

@types/dotenv is deprecated and unnecessary here because dotenv@^17.2.3 includes TypeScript declarations. Drop it entirely, or (if you must keep it) move it to devDependencies to avoid redundant/misplaced typing packages.

🤖 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 `@cloud-functions/package.json` at line 25, The package.json currently lists
"`@types/dotenv`": "^6.1.1" as a dependency but dotenv@^17.2.3 already provides
its own TypeScript types; remove the "`@types/dotenv`" entry from dependencies (or
if you insist on keeping it, move it into devDependencies) so you don't ship
deprecated/redundant typing packages; update package.json accordingly and run a
quick npm install to ensure lockfile updates.
🤖 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 `@cloud-functions/src/certificateService.ts`:
- Around line 365-374: The update currently sets certificatesSentAt to null on
partial/failing retries; change the payload so certificatesSentAt is only
written when allSucceeded is true (i.e., include certificatesSentAt:
FieldValue.serverTimestamp() when allSucceeded), and omit certificatesSentAt
from the update when not allSucceeded so the previous successful timestamp is
preserved; still update certificatesSent (set to allSucceeded),
certificatesLastAttemptAt (always set FieldValue.serverTimestamp()), and
certificateSummary as before. Reference: the
admin.firestore().collection('events').doc(eventId).update({...}) call and the
variables allSucceeded, summary, certificatesSentAt, certificatesLastAttemptAt,
certificatesSent.
- Around line 8-10: The getResendClient function uses a default placeholder API
key which hides misconfiguration; change it to read process.env.RESEND_API_KEY,
and if it's missing/empty throw a clear startup error (e.g., new
Error("RESEND_API_KEY is required")) before constructing new Resend(API_KEY) so
the application fails fast; update the Resend construction in getResendClient to
use the validated API key.

In `@cloud-functions/src/utils/push.ts`:
- Around line 15-16: The function sendPushNotifications currently returns early
with undefined when messages.length === 0, while the normal path returns the
tickets array; change the no-op branch in sendPushNotifications to return an
empty array of tickets (e.g., return []) so callers always receive the same
shape; also apply the same fix to the similar early-return at the other no-op
branch referenced around line 36 to consistently return an empty tickets array
instead of undefined.

---

Duplicate comments:
In `@cloud-functions/src/certificateService.ts`:
- Around line 120-135: The code currently persists a long-lived signed URL into
participant.certificateUrl; instead, change persistCertificateUrl (and callers)
to save the storage object path (e.g., the same storagePath used in
uploadPdfAndGetUrl) into participant.certificatePath (or overwrite
certificateUrl with the object path) and the timestamp, and remove storing the
signedUrl; update uploadPdfAndGetUrl to return the storagePath (or ensure caller
has it) and leave signed URL generation to the email/resend flow (generate a
short-lived signed URL from the saved storage path when sending or resending
certificates). Ensure references to participant.certificateUrl in resend/email
logic are replaced to fetch the stored object path and call file.getSignedUrl at
send time.

---

Nitpick comments:
In `@cloud-functions/package.json`:
- Line 4: The build script "build": "tsc -p tsconfig.build.json" is valid
because tsconfig.build.json exists, so leave that script unchanged; remove the
redundant "`@types/dotenv`" entry from package.json dependencies (or move it to
devDependencies if you need it only for type-checking) since dotenv v17 includes
its own types—update the "dependencies" section to delete "`@types/dotenv`" and
run npm install to update lockfile.
- Line 25: The package.json currently lists "`@types/dotenv`": "^6.1.1" as a
dependency but dotenv@^17.2.3 already provides its own TypeScript types; remove
the "`@types/dotenv`" entry from dependencies (or if you insist on keeping it,
move it into devDependencies) so you don't ship deprecated/redundant typing
packages; update package.json accordingly and run a quick npm install to ensure
lockfile updates.
🪄 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: f668bee3-2001-425e-880e-1895545334c5

📥 Commits

Reviewing files that changed from the base of the PR and between 41c08ac and e8d42aa.

📒 Files selected for processing (23)
  • app/src/screens/UserFeed.js
  • cloud-functions/lib/analyzeAttendance.js
  • cloud-functions/lib/certificateService.js
  • cloud-functions/lib/dailyDigest.js
  • cloud-functions/lib/index.js
  • cloud-functions/lib/reminders.js
  • cloud-functions/lib/reputation.js
  • cloud-functions/lib/server.js
  • cloud-functions/package.json
  • cloud-functions/src/certificateService.ts
  • cloud-functions/src/dailyDigest.ts
  • cloud-functions/src/reputation.ts
  • cloud-functions/src/server.ts
  • cloud-functions/src/utils/emailSender.test.ts
  • cloud-functions/src/utils/emailSender.ts
  • cloud-functions/src/utils/emailTemplateRenderer.test.ts
  • cloud-functions/src/utils/push.ts
  • cloud-functions/templates/feedback_email_template.html
  • cloud-functions/templates/universal_email_template.html
  • cloud-functions/tsconfig.build.json
  • cloud-functions/tsconfig.json
  • package.json
  • sonar-project.properties
✅ Files skipped from review due to trivial changes (1)
  • cloud-functions/tsconfig.build.json
🚧 Files skipped from review as they are similar to previous changes (9)
  • app/src/screens/UserFeed.js
  • cloud-functions/templates/universal_email_template.html
  • cloud-functions/src/reputation.ts
  • cloud-functions/templates/feedback_email_template.html
  • cloud-functions/src/dailyDigest.ts
  • cloud-functions/lib/reminders.js
  • package.json
  • cloud-functions/src/utils/emailTemplateRenderer.test.ts
  • cloud-functions/lib/analyzeAttendance.js

Comment thread cloud-functions/src/certificateService.ts
Comment thread cloud-functions/src/certificateService.ts Outdated
Comment thread cloud-functions/src/utils/push.ts Outdated
@roshankumar0036singh
Copy link
Copy Markdown
Owner

@Shriraj888 handle the coderabbit suggestion

@sonarqubecloud
Copy link
Copy Markdown

@Shriraj888
Copy link
Copy Markdown
Contributor Author

@roshankumar0036singh I've addressed the CodeRabbit suggestions and updated the changes

@roshankumar0036singh roshankumar0036singh merged commit d53ed7e into roshankumar0036singh:main Jun 1, 2026
6 checks passed
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.

Add automated email template testing and preview

3 participants