feat: anonymous ereferences - #927

Merged
coodos merged 2 commits into
mainfrom
feat/anon-erefs
Mar 30, 2026
Merged

feat: anonymous ereferences#927
coodos merged 2 commits into
mainfrom
feat/anon-erefs

Conversation

@sosweetham

@sosweethamsosweetham commented Mar 17, 2026

Copy link
Copy Markdown
Member

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

imageimageimage

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 ownerMarch 17, 2026 03:43
@coderabbitai

coderabbitaiBot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title 'feat: anonymous ereferences' clearly and concisely summarizes the main feature being added in the changeset.
Description check✅ PassedThe PR description covers all required template sections: issue number, type of change, testing approach, and completed checklist items with supporting evidence.
Linked Issues check✅ PassedThe 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✅ PassedAll 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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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){
thrownewError("JWT_SECRET environment variable is required but was not provided. Please set JWT_SECRET in your environment configuration.");
thrownewError("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){
thrownewError("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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@coodos
coodos merged commit f9c0f83 into mainMar 30, 2026
4 checks passed
@coodos
coodos deleted the feat/anon-erefs branch March 30, 2026 10:42
@coderabbitaicoderabbitaiBot mentioned this pull request Apr 2, 2026
6 tasks
Sign up for freeto 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

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat: anonymous ereferences - #927

Merged
coodos merged 2 commits into
mainfrom
feat/anon-erefs
Mar 30, 2026
Merged

feat: anonymous ereferences#927
coodos merged 2 commits into
mainfrom
feat/anon-erefs

Conversation

@sosweetham

@sosweethamsosweetham commented Mar 17, 2026

Copy link
Copy Markdown
Member

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

imageimageimage

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 ownerMarch 17, 2026 03:43
@coderabbitai

coderabbitaiBot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title 'feat: anonymous ereferences' clearly and concisely summarizes the main feature being added in the changeset.
Description check✅ PassedThe PR description covers all required template sections: issue number, type of change, testing approach, and completed checklist items with supporting evidence.
Linked Issues check✅ PassedThe 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✅ PassedAll 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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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){
thrownewError("JWT_SECRET environment variable is required but was not provided. Please set JWT_SECRET in your environment configuration.");
thrownewError("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){
thrownewError("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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@coodos
coodos merged commit f9c0f83 into mainMar 30, 2026
4 checks passed
@coodos
coodos deleted the feat/anon-erefs branch March 30, 2026 10:42
@coderabbitaicoderabbitaiBot mentioned this pull request Apr 2, 2026
6 tasks
Sign up for freeto 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

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: anonymous ereferences - #927

Merged
coodos merged 2 commits into
mainfrom
feat/anon-erefs
Mar 30, 2026
Merged

feat: anonymous ereferences#927
coodos merged 2 commits into
mainfrom
feat/anon-erefs

Conversation

@sosweetham

@sosweethamsosweetham commented Mar 17, 2026

Copy link
Copy Markdown
Member

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

imageimageimage

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 ownerMarch 17, 2026 03:43
@coderabbitai

coderabbitaiBot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title 'feat: anonymous ereferences' clearly and concisely summarizes the main feature being added in the changeset.
Description check✅ PassedThe PR description covers all required template sections: issue number, type of change, testing approach, and completed checklist items with supporting evidence.
Linked Issues check✅ PassedThe 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✅ PassedAll 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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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){
thrownewError("JWT_SECRET environment variable is required but was not provided. Please set JWT_SECRET in your environment configuration.");
thrownewError("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){
thrownewError("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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@coodos
coodos merged commit f9c0f83 into mainMar 30, 2026
4 checks passed
@coodos
coodos deleted the feat/anon-erefs branch March 30, 2026 10:42
@coderabbitaicoderabbitaiBot mentioned this pull request Apr 2, 2026
6 tasks
Sign up for freeto 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

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: anonymous ereferences - #927

Merged
coodos merged 2 commits into
mainfrom
feat/anon-erefs
Mar 30, 2026
Merged

feat: anonymous ereferences#927
coodos merged 2 commits into
mainfrom
feat/anon-erefs

Conversation

@sosweetham

@sosweethamsosweetham commented Mar 17, 2026

Copy link
Copy Markdown
Member

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

imageimageimage

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 ownerMarch 17, 2026 03:43
@coderabbitai

coderabbitaiBot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title 'feat: anonymous ereferences' clearly and concisely summarizes the main feature being added in the changeset.
Description check✅ PassedThe PR description covers all required template sections: issue number, type of change, testing approach, and completed checklist items with supporting evidence.
Linked Issues check✅ PassedThe 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✅ PassedAll 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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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){
thrownewError("JWT_SECRET environment variable is required but was not provided. Please set JWT_SECRET in your environment configuration.");
thrownewError("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){
thrownewError("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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@coodos
coodos merged commit f9c0f83 into mainMar 30, 2026
4 checks passed
@coodos
coodos deleted the feat/anon-erefs branch March 30, 2026 10:42
@coderabbitaicoderabbitaiBot mentioned this pull request Apr 2, 2026
6 tasks
Sign up for freeto 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

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat: anonymous ereferences - #927

Merged
coodos merged 2 commits into
mainfrom
feat/anon-erefs
Mar 30, 2026
Merged

feat: anonymous ereferences#927
coodos merged 2 commits into
mainfrom
feat/anon-erefs

Conversation

@sosweetham

@sosweethamsosweetham commented Mar 17, 2026

Copy link
Copy Markdown
Member

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

imageimageimage

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 ownerMarch 17, 2026 03:43
@coderabbitai

coderabbitaiBot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title 'feat: anonymous ereferences' clearly and concisely summarizes the main feature being added in the changeset.
Description check✅ PassedThe PR description covers all required template sections: issue number, type of change, testing approach, and completed checklist items with supporting evidence.
Linked Issues check✅ PassedThe 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✅ PassedAll 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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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){
thrownewError("JWT_SECRET environment variable is required but was not provided. Please set JWT_SECRET in your environment configuration.");
thrownewError("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){
thrownewError("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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@coodos
coodos merged commit f9c0f83 into mainMar 30, 2026
4 checks passed
@coodos
coodos deleted the feat/anon-erefs branch March 30, 2026 10:42
@coderabbitaicoderabbitaiBot mentioned this pull request Apr 2, 2026
6 tasks
Sign up for freeto 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

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: anonymous ereferences - #927

Merged
coodos merged 2 commits into
mainfrom
feat/anon-erefs
Mar 30, 2026
Merged

feat: anonymous ereferences#927
coodos merged 2 commits into
mainfrom
feat/anon-erefs

Conversation

@sosweetham

@sosweethamsosweetham commented Mar 17, 2026

Copy link
Copy Markdown
Member

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

imageimageimage

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 ownerMarch 17, 2026 03:43
@coderabbitai

coderabbitaiBot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title 'feat: anonymous ereferences' clearly and concisely summarizes the main feature being added in the changeset.
Description check✅ PassedThe PR description covers all required template sections: issue number, type of change, testing approach, and completed checklist items with supporting evidence.
Linked Issues check✅ PassedThe 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✅ PassedAll 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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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){
thrownewError("JWT_SECRET environment variable is required but was not provided. Please set JWT_SECRET in your environment configuration.");
thrownewError("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){
thrownewError("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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@coodos
coodos merged commit f9c0f83 into mainMar 30, 2026
4 checks passed
@coodos
coodos deleted the feat/anon-erefs branch March 30, 2026 10:42
@coderabbitaicoderabbitaiBot mentioned this pull request Apr 2, 2026
6 tasks
Sign up for freeto 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

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat: anonymous ereferences - #927

Merged
coodos merged 2 commits into
mainfrom
feat/anon-erefs
Mar 30, 2026
Merged

feat: anonymous ereferences#927
coodos merged 2 commits into
mainfrom
feat/anon-erefs

Conversation

@sosweetham

@sosweethamsosweetham commented Mar 17, 2026

Copy link
Copy Markdown
Member

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

imageimageimage

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 ownerMarch 17, 2026 03:43
@coderabbitai

coderabbitaiBot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title 'feat: anonymous ereferences' clearly and concisely summarizes the main feature being added in the changeset.
Description check✅ PassedThe PR description covers all required template sections: issue number, type of change, testing approach, and completed checklist items with supporting evidence.
Linked Issues check✅ PassedThe 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✅ PassedAll 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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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){
thrownewError("JWT_SECRET environment variable is required but was not provided. Please set JWT_SECRET in your environment configuration.");
thrownewError("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){
thrownewError("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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@coodos
coodos merged commit f9c0f83 into mainMar 30, 2026
4 checks passed
@coodos
coodos deleted the feat/anon-erefs branch March 30, 2026 10:42
@coderabbitaicoderabbitaiBot mentioned this pull request Apr 2, 2026
6 tasks
Sign up for freeto 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

2 participants

@sosweetham@coodos
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat: anonymous ereferences - #927

Merged
coodos merged 2 commits into
mainfrom
feat/anon-erefs
Mar 30, 2026
Merged

feat: anonymous ereferences#927
coodos merged 2 commits into
mainfrom
feat/anon-erefs

Conversation

@sosweetham

@sosweethamsosweetham commented Mar 17, 2026

Copy link
Copy Markdown
Member

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

imageimageimage

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 ownerMarch 17, 2026 03:43
@coderabbitai

coderabbitaiBot commented Mar 17, 2026

Copy link
Copy Markdown
Contributor
📝 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 nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring 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 nameStatusExplanation
Title check✅ PassedThe title 'feat: anonymous ereferences' clearly and concisely summarizes the main feature being added in the changeset.
Description check✅ PassedThe PR description covers all required template sections: issue number, type of change, testing approach, and completed checklist items with supporting evidence.
Linked Issues check✅ PassedThe 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✅ PassedAll 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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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){
thrownewError("JWT_SECRET environment variable is required but was not provided. Please set JWT_SECRET in your environment configuration.");
thrownewError("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){
thrownewError("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.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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

@coodos
coodos merged commit f9c0f83 into mainMar 30, 2026
4 checks passed
@coodos
coodos deleted the feat/anon-erefs branch March 30, 2026 10:42
@coderabbitaicoderabbitaiBot mentioned this pull request Apr 2, 2026
6 tasks
Sign up for freeto 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

2 participants

@sosweetham@coodos