Skip to content

feat: anonymous ereferences#927

Open
sosweetham wants to merge 2 commits intomainfrom
feat/anon-erefs
Open

feat: anonymous ereferences#927
sosweetham wants to merge 2 commits intomainfrom
feat/anon-erefs

Conversation

@sosweetham
Copy link
Member

@sosweetham sosweetham commented Mar 17, 2026

Description of change

allows users to make anonymous ereferences

we still know that the references are being provided by real people so it has no affect on score calculation

we only obfuscate the details on the transport from backend to client, the backend still knows who provided the reference to maintain data integrity

Issue Number

closes #873

Type of change

  • New (a change which implements a new feature)

How the change has been tested

manual

Change checklist

  • I have ensured that the CI Checks pass locally
  • I have removed any unnecessary logic
  • My code is well documented
  • I have signed my commits
  • My code follows the pattern of the application
  • I have self reviewed my code

SS

image image image

Summary by CodeRabbit

Release Notes

  • New Features

    • Anonymous reference posting: Users can now toggle anonymity when creating references to hide their identity from reference recipients
    • Anonymous badge displays on reference cards, tables, and activity feeds to indicate anonymously posted references
  • Bug Fixes

    • Enhanced anonymous reference handling to prevent author information leakage in dashboard activities and reference views
  • Chores

    • Database migration to support anonymous reference tracking

@sosweetham sosweetham requested a review from coodos as a code owner March 17, 2026 03:43
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Mar 17, 2026

📝 Walkthrough

Walkthrough

Adds anonymous eReference support: new anonymous column and migration, backend plumbing to store and redact author data when anonymous, frontend UI to toggle anonymity and display Anonymous badges, plus an updated migration-generation script and a JWT error message fix.

Changes

Cohort / File(s) Summary
Database Schema
platforms/ereputation/api/src/database/entities/Reference.ts, platforms/ereputation/api/src/database/migrations/1773718262631-anon-erefs.ts
Add anonymous: boolean column (default false) to Reference entity and a TypeORM migration to add/remove the column.
Backend Controllers
platforms/ereputation/api/src/controllers/ReferenceController.ts, platforms/ereputation/api/src/controllers/DashboardController.ts
Accept and propagate optional anonymous flag; redact or null author fields in API responses and set display name to "Anonymous" for anonymous references.
Backend Services
platforms/ereputation/api/src/services/ReferenceService.ts, platforms/ereputation/api/src/services/CalculationService.ts, platforms/ereputation/api/src/services/VotingReputationService.ts
Persist anonymous on create; map reference author/display name to "Anonymous" when flag is true, otherwise fall back to author fields or "Unknown".
Utils / Config
platforms/ereputation/api/package.json, platforms/ereputation/api/src/utils/jwt.ts
Change migration-generation script to an interactive bash wrapper that prompts for migration name and writes to src/database/migrations/; update JWT secret env var reference in runtime error message.
Frontend Components
platforms/ereputation/client/client/src/components/modals/reference-modal.tsx, platforms/ereputation/client/client/src/components/modals/reference-view-modal.tsx
Add anonymous toggle in reference creation modal; include anonymous in submission and reset behavior; show "Anonymous" badge/owner line in reference view modal when applicable.
Frontend Pages
platforms/ereputation/client/client/src/pages/dashboard.tsx, platforms/ereputation/client/client/src/pages/references.tsx
Compute and display "Anonymous" as forFrom when reference.anonymous is true; render inline Anonymous badges in activity and reference lists; include anonymous flag in modal payloads.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Frontend as UI
    participant Controller as ReferenceController
    participant Service as ReferenceService
    participant DB as Database

    Note over Client,DB: Create anonymous reference flow
    Client->>Frontend: user submits reference (content, anonymous=true)
    Frontend->>Controller: POST /api/references {..., anonymous: true}
    Controller->>Service: createReference(data)
    Service->>DB: INSERT reference (anonymous=true, authorId)
    DB-->>Service: inserted Reference
    Service-->>Controller: Reference (author omitted/null)
    Controller-->>Frontend: 201 Created (anonymous=true, author=null)
    Frontend-->>Client: show confirmation

    Note over Client,DB: Retrieve references (anonymous redaction)
    Client->>Frontend: GET /api/references
    Frontend->>Controller: GET /api/references
    Controller->>Service: getAllReferences()
    Service->>DB: SELECT references
    DB-->>Service: references (with anonymous flags)
    Service-->>Controller: mapped references (author null when anonymous)
    Controller-->>Frontend: 200 OK (forFrom="Anonymous" where applicable)
    Frontend-->>Client: render list with Anonymous badges
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • coodos
  • xPathin

Poem

"I hop through refs both near and far, 🐇
A secret tucked beneath my star.
'Anonymous' whispers soft and light,
Badges gleam in morning light.
Hooray — no names, just gentle sight."

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: anonymous ereferences' clearly and concisely summarizes the main feature being added in the changeset.
Description check ✅ Passed The PR description covers all required template sections: issue number, type of change, testing approach, and completed checklist items with supporting evidence.
Linked Issues check ✅ Passed The PR successfully implements anonymous eReferences as specified in issue #873, with backend retaining authorship knowledge while obfuscating sender details in client transport.
Out of Scope Changes check ✅ Passed All changes are directly related to anonymous eReferences feature. The JWT secret reference update in jwt.ts appears to be an incidental fix maintaining consistency but is minimal and unrelated to the primary feature.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/anon-erefs
📝 Coding Plan
  • Generate coding plan for human review comments

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

Caution

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

⚠️ Outside diff range comments (1)
platforms/ereputation/api/src/controllers/ReferenceController.ts (1)

17-37: ⚠️ Potential issue | 🟡 Minor

Validate anonymous as a boolean.

req.body.anonymous is forwarded as-is. Non-boolean payloads can silently change or break the stored privacy flag depending on downstream coercion, so this should reject anything except true/false.

🛡️ Suggested fix
             if (!targetType || !targetId || !targetName || !content) {
                 return res.status(400).json({ error: "Missing required fields" });
             }

+            if (anonymous !== undefined && typeof anonymous !== "boolean") {
+                return res.status(400).json({ error: "anonymous must be a boolean" });
+            }
+
             if (numericScore && (numericScore < 1 || numericScore > 5)) {
                 return res.status(400).json({ error: "Numeric score must be between 1 and 5" });
             }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platforms/ereputation/api/src/controllers/ReferenceController.ts` around
lines 17 - 37, The request handler in ReferenceController reads
req.body.anonymous and passes it into createReference without type-checking, so
validate that anonymous is strictly a boolean (true or false) before using it:
check req.body.anonymous is either === true or === false (or absent/undefined)
and if present but not a boolean return res.status(400).json({ error: "anonymous
must be a boolean" }); then pass anonymous: anonymous ?? false into
this.referenceService.createReference; reference the req.body.anonymous
parameter, the anonymous local used in the createReference call, and the
createReference method to locate where to add the check.
🧹 Nitpick comments (3)
platforms/ereputation/api/package.json (1)

11-11: Quote the variable to handle edge cases.

The $name variable is unquoted, which could cause issues if someone enters a migration name with spaces (though uncommon). Additionally, the script relies on bash-specific features (read -p), which won't work on Windows without WSL or Git Bash.

🛠️ Suggested fix
-"migration:generate": "bash -c 'read -p \"Migration name: \" name && npx typeorm-ts-node-commonjs migration:generate src/database/migrations/$name -d src/database/data-source.ts'",
+"migration:generate": "bash -c 'read -p \"Migration name: \" name && npx typeorm-ts-node-commonjs migration:generate \"src/database/migrations/$name\" -d src/database/data-source.ts'",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platforms/ereputation/api/package.json` at line 11, The migration script in
package.json ("migration:generate") uses an unquoted $name and a bash-only read
-p, which breaks on Windows and when names contain spaces; update the script to
quote the variable (use "$name") and replace the bash prompt with a
cross-platform input method (for example a short node one-liner or an npm CLI
prompt) that reads the migration name and then calls npx
typeorm-ts-node-commonjs migration:generate with the quoted name; reference the
"migration:generate" script and the $name variable when making the change.
platforms/ereputation/api/src/controllers/DashboardController.ts (1)

87-94: Prefer an allow-list DTO for anonymous activities.

This works today, but redacting author and authorId after spreading the whole entity makes this endpoint easy to regress the next time Reference grows another identity-bearing field. For a privacy-sensitive response, it would be safer to serialize only the dashboard fields you intend to expose.

💡 Example direction
-                    const refData = ref.anonymous
-                        ? { ...ref, author: undefined, authorId: undefined }
-                        : ref;
+                    const refData = {
+                        id: ref.id,
+                        targetType: ref.targetType,
+                        targetId: ref.targetId,
+                        targetName: ref.targetName,
+                        content: ref.content,
+                        referenceType: ref.referenceType,
+                        numericScore: ref.numericScore,
+                        status: ref.status,
+                        createdAt: ref.createdAt,
+                        anonymous: ref.anonymous ?? false,
+                        author: ref.anonymous ? undefined : ref.author,
+                    };
...
-                        data: { ...refData, anonymous: ref.anonymous ?? false }
+                        data: refData

Also applies to: 103-103

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

In `@platforms/ereputation/api/src/controllers/DashboardController.ts` around
lines 87 - 94, The current logic in DashboardController that builds authorName
and refData by spreading the full Reference (symbols: ref, authorName, refData)
risks leaking future identity fields; replace the spread-based response with an
explicit allow-list DTO: construct a new object containing only the
dashboard-safe fields you intend to expose (e.g., id, type, content, createdAt,
authorName, any non-identifying metadata) and, when ref.anonymous is true, omit
or null out all identity properties (author, authorId, handle, ename, etc.);
update both places where refData is created so the endpoint serializes only
those listed fields instead of {...ref}.
platforms/ereputation/client/client/src/pages/dashboard.tsx (1)

122-165: Remove the dead second reference branch.

Lines 124-142 already return for every reference activity, so the later reference check can never execute. Keeping the anonymous modal mapping duplicated in unreachable code makes future changes easy to drift.

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

In `@platforms/ereputation/client/client/src/pages/dashboard.tsx` around lines 122
- 165, The second duplicated reference-check branch inside handleViewActivity is
dead code because the first reference branch always returns; remove the entire
redundant if-block (the repeated "if (activity.type === 'reference' ||
activity.activity === 'Reference Provided' || activity.activity === 'Reference
Received')" block along with its inner mapping and setReferenceViewModal call)
so only the first reference-handling logic remains, leaving the rest of
handleViewActivity (calculation/activity handling) intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@platforms/ereputation/api/src/utils/jwt.ts`:
- Around line 3-5: Update the stale comment above the environment check to
reference the actual env var name used in code: change the comment that
currently says "Fail fast if JWT_SECRET is missing" so it matches the checked
symbol process.env.EREPUTATION_JWT_SECRET and the thrown error message; ensure
the comment explicitly names EREPUTATION_JWT_SECRET to avoid mismatch with the
code in jwt.ts.

In
`@platforms/ereputation/client/client/src/components/modals/reference-modal.tsx`:
- Line 65: The modal's anonymous state (anonymous / setAnonymous) is only reset
inside resetForm(), but the normal close code path(s) do not invoke resetForm(),
so canceling and reopening preserves the previous privacy choice; update the
modal close handlers referenced in this file (the normal close path and the
other close/cancel path around the second block noted) to call resetForm() (or
at minimum call setAnonymous(false) and any other cleanup done by resetForm())
before closing so the anonymous toggle is cleared on close.

---

Outside diff comments:
In `@platforms/ereputation/api/src/controllers/ReferenceController.ts`:
- Around line 17-37: The request handler in ReferenceController reads
req.body.anonymous and passes it into createReference without type-checking, so
validate that anonymous is strictly a boolean (true or false) before using it:
check req.body.anonymous is either === true or === false (or absent/undefined)
and if present but not a boolean return res.status(400).json({ error: "anonymous
must be a boolean" }); then pass anonymous: anonymous ?? false into
this.referenceService.createReference; reference the req.body.anonymous
parameter, the anonymous local used in the createReference call, and the
createReference method to locate where to add the check.

---

Nitpick comments:
In `@platforms/ereputation/api/package.json`:
- Line 11: The migration script in package.json ("migration:generate") uses an
unquoted $name and a bash-only read -p, which breaks on Windows and when names
contain spaces; update the script to quote the variable (use "$name") and
replace the bash prompt with a cross-platform input method (for example a short
node one-liner or an npm CLI prompt) that reads the migration name and then
calls npx typeorm-ts-node-commonjs migration:generate with the quoted name;
reference the "migration:generate" script and the $name variable when making the
change.

In `@platforms/ereputation/api/src/controllers/DashboardController.ts`:
- Around line 87-94: The current logic in DashboardController that builds
authorName and refData by spreading the full Reference (symbols: ref,
authorName, refData) risks leaking future identity fields; replace the
spread-based response with an explicit allow-list DTO: construct a new object
containing only the dashboard-safe fields you intend to expose (e.g., id, type,
content, createdAt, authorName, any non-identifying metadata) and, when
ref.anonymous is true, omit or null out all identity properties (author,
authorId, handle, ename, etc.); update both places where refData is created so
the endpoint serializes only those listed fields instead of {...ref}.

In `@platforms/ereputation/client/client/src/pages/dashboard.tsx`:
- Around line 122-165: The second duplicated reference-check branch inside
handleViewActivity is dead code because the first reference branch always
returns; remove the entire redundant if-block (the repeated "if (activity.type
=== 'reference' || activity.activity === 'Reference Provided' ||
activity.activity === 'Reference Received')" block along with its inner mapping
and setReferenceViewModal call) so only the first reference-handling logic
remains, leaving the rest of handleViewActivity (calculation/activity handling)
intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b6479698-4373-4b84-a011-b7f2d7ccc141

📥 Commits

Reviewing files that changed from the base of the PR and between d2efd8b and 4b3914c.

📒 Files selected for processing (13)
  • platforms/ereputation/api/package.json
  • platforms/ereputation/api/src/controllers/DashboardController.ts
  • platforms/ereputation/api/src/controllers/ReferenceController.ts
  • platforms/ereputation/api/src/database/entities/Reference.ts
  • platforms/ereputation/api/src/database/migrations/1773718262631-anon-erefs.ts
  • platforms/ereputation/api/src/services/CalculationService.ts
  • platforms/ereputation/api/src/services/ReferenceService.ts
  • platforms/ereputation/api/src/services/VotingReputationService.ts
  • platforms/ereputation/api/src/utils/jwt.ts
  • platforms/ereputation/client/client/src/components/modals/reference-modal.tsx
  • platforms/ereputation/client/client/src/components/modals/reference-view-modal.tsx
  • platforms/ereputation/client/client/src/pages/dashboard.tsx
  • platforms/ereputation/client/client/src/pages/references.tsx

Comment on lines +3 to +5
// Fail fast if JWT_SECRET is missing
if (!process.env.EREPUTATION_JWT_SECRET) {
throw new Error("JWT_SECRET environment variable is required but was not provided. Please set JWT_SECRET in your environment configuration.");
throw new Error("EREPUTATION_JWT_SECRET environment variable is required but was not provided. Please set EREPUTATION_JWT_SECRET in your environment configuration.");
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Comment on line 3 is stale.

The comment references JWT_SECRET but the actual check and error message reference EREPUTATION_JWT_SECRET. Consider updating the comment to match:

-// Fail fast if JWT_SECRET is missing
+// Fail fast if EREPUTATION_JWT_SECRET is missing
📝 Committable suggestion

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

Suggested change
// Fail fast if JWT_SECRET is missing
if (!process.env.EREPUTATION_JWT_SECRET) {
throw new Error("JWT_SECRET environment variable is required but was not provided. Please set JWT_SECRET in your environment configuration.");
throw new Error("EREPUTATION_JWT_SECRET environment variable is required but was not provided. Please set EREPUTATION_JWT_SECRET in your environment configuration.");
// Fail fast if EREPUTATION_JWT_SECRET is missing
if (!process.env.EREPUTATION_JWT_SECRET) {
throw new Error("EREPUTATION_JWT_SECRET environment variable is required but was not provided. Please set EREPUTATION_JWT_SECRET in your environment configuration.");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@platforms/ereputation/api/src/utils/jwt.ts` around lines 3 - 5, Update the
stale comment above the environment check to reference the actual env var name
used in code: change the comment that currently says "Fail fast if JWT_SECRET is
missing" so it matches the checked symbol process.env.EREPUTATION_JWT_SECRET and
the thrown error message; ensure the comment explicitly names
EREPUTATION_JWT_SECRET to avoid mismatch with the code in jwt.ts.

Copy link
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.

♻️ Duplicate comments (1)
platforms/ereputation/client/client/src/components/modals/reference-modal.tsx (1)

239-244: ⚠️ Potential issue | 🟠 Major

Add eventSource to the close-reset effect dependencies to prevent stale SSE connections.

If a form submission completes after the modal closes, the onSuccess handler will create a new EventSource via startSSEConnection() after the close-reset effect has already run. Since the effect depends only on open, it won't re-execute to clean up the newly created EventSource, leaving an active SSE connection while the modal is closed.

🔧 Proposed fix
   useEffect(() => {
     if (!open) {
       resetForm();
     }
-  }, [open]);
+  }, [open, eventSource]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@platforms/ereputation/client/client/src/components/modals/reference-modal.tsx`
around lines 239 - 244, The effect that resets the form when the modal closes
currently depends only on open and can miss cleaning up EventSource instances
created after close; update the useEffect that calls resetForm() to also list
eventSource in its dependency array and ensure it closes any active SSE by
checking and calling eventSource.close() (or invoking the existing SSE cleanup
helper) before or when resetForm() runs; reference the useEffect surrounding
resetForm(), the eventSource variable/state, and startSSEConnection()/onSuccess
handler so the effect will re-run and properly tear down stale SSE connections.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In
`@platforms/ereputation/client/client/src/components/modals/reference-modal.tsx`:
- Around line 239-244: The effect that resets the form when the modal closes
currently depends only on open and can miss cleaning up EventSource instances
created after close; update the useEffect that calls resetForm() to also list
eventSource in its dependency array and ensure it closes any active SSE by
checking and calling eventSource.close() (or invoking the existing SSE cleanup
helper) before or when resetForm() runs; reference the useEffect surrounding
resetForm(), the eventSource variable/state, and startSSEConnection()/onSuccess
handler so the effect will re-run and properly tear down stale SSE connections.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1b0c6adb-6826-4f13-a889-f09394ea7214

📥 Commits

Reviewing files that changed from the base of the PR and between 4b3914c and 0ca307a.

📒 Files selected for processing (2)
  • platforms/ereputation/api/src/utils/jwt.ts
  • platforms/ereputation/client/client/src/components/modals/reference-modal.tsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • platforms/ereputation/api/src/utils/jwt.ts

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] (eReputation): Anonymous eReference

1 participant