Skip to content

refactor(profile-editor): rebuild backend on web3-adapter for 2-way sync#985

Open
sosweetham wants to merge 2 commits into
mainfrom
refactor/profile-editor-web3-adapter
Open

refactor(profile-editor): rebuild backend on web3-adapter for 2-way sync#985
sosweetham wants to merge 2 commits into
mainfrom
refactor/profile-editor-web3-adapter

Conversation

@sosweetham
Copy link
Copy Markdown
Member

@sosweetham sosweetham commented May 31, 2026

Refs #980

What

Rebuilds the profile-editor backend on the standard web3-adapter platform pattern, replacing the bespoke eVault-direct implementation. profile-editor was the only platform not using the adapter; this brings it in line with pictique/blabsy/dreamsync and gives it real two-way cross-platform sync.

Why

The previous backend hand-rolled its own eVault sync (direct GraphQL, an in-memory merge cache, a search index, bytea file storage). It was inconsistent with every other platform and the bespoke sync only ever worked one-way — avatar/banner/name changes from other platforms never flowed back in. Adopting the adapter delegates the hard part (bidirectional translation, file handling, id bookkeeping) to the shared package.

How

  • Local Postgres is the source of truth (users + professional_profiles). The REST API only reads/writes local rows.
  • web3-adapter does all eVault sync: PostgresSubscribertoGlobal (outbound); POST /api/webhookfromGlobal (inbound); lockedIds prevents echo loops. AaaS is the delivery transport (subscription auto-registers on boot).
  • Base identity (name / avatar / banner / handle / verified) syncs 2-way with all platforms via the User ontology; professional profile + the isPublic gate sync with dreamsync via the Professional Profile ontology. Mappings align with dreamsync/pictique field names (isPrivate, __file(avatarUrl), etc.).
  • Files → eVault blob storage via __file / EVaultClient.uploadFile (no file-manager, no bytea). Client change is ~20 lines: store/render the returned public URL, drop the asset-proxy route.
  • Retains auth (offer/SSE/login), zod validation, AaaS subscription bootstrap. Net: ~1,290 lines (≈46% smaller than the original), zero bespoke sync logic.

Client contract

Unchanged except the file fields now carry a public URL instead of a proxy file id (platforms/profile-editor/client — 4 files, ~20 lines).

Verification

  • tsc + biome clean; backend boots, AaaS subscription registers.
  • Inbound proven: a real prod User packet replayed through /api/webhookfromGlobal → local users row populated.
  • Outbound proven: an avatar edit reached the eVault (packet carries the DO Spaces CDN URL) and AaaS ingested it.

⛔ Blocked by #984

Full cross-platform update sync does not work until #984 lands. AaaS currently delivers each MetaEnvelope to a subscriber only once — updates to an existing envelope are never re-delivered (Delivery is unique per (subscriptionId, packetId) + orIgnore, and packets are upserted by envelope id). This breaks update propagation for every platform, not just profile-editor; this backend is correct and needs no change once #984 is fixed. Note: #984 is not addressed by the fix/max-retries-deadletter-queue branch (that only handles retry caps).

Notes

  • The old backend is removed here; a local read-only copy was kept under reference/ and is intentionally not committed (deprecated).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Public profile discovery with skills filtering and pagination
    • Real-time webhook sync for profiles with awareness service integration
  • Improvements

    • Structured profile management: work experience, education, skills, social links
    • File uploads now return public CDN URLs for direct rendering/download
    • Stateless JWT-based auth for session handling
    • Tighter public profile visibility controls
  • Configuration

    • New environment variables for profile-editor DB, JWT, mapping DB, and awareness integration

Replaces the bespoke eVault-direct backend with the standard web3-adapter
platform pattern, matching pictique/blabsy/dreamsync.

- Local Postgres is the source of truth (users + professional_profiles).
  PostgresSubscriber pushes local writes to the eVault via toGlobal;
  POST /api/webhook ingests inbound changes via fromGlobal; AaaS is the
  delivery transport. lockedIds prevents echo loops.
- Base identity (name/avatar/banner) syncs 2-way with all platforms via the
  User ontology; professional profile + the isPublic gate sync with dreamsync.
- Files go to eVault blob storage via __file / EVaultClient.uploadFile
  (no file-manager, no bytea); the client stores the returned public URL
  (~20-line change: render the URL directly, drop the asset-proxy route).
- Retains auth (offer/SSE/login), zod validation, and AaaS subscription
  bootstrap. Drops the hand-rolled GraphQL sync, search index, and bytea.

The previous backend is removed; a local read-only copy was kept under
reference/ and is intentionally not committed (deprecated).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@sosweetham sosweetham requested a review from coodos as a code owner May 31, 2026 06:19
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 31, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1ff8a634-b22d-4229-b832-e245352aba4b

📥 Commits

Reviewing files that changed from the base of the PR and between 133cd50 and 17b5d66.

📒 Files selected for processing (9)
  • platforms/profile-editor/api/src/controllers/files.ts
  • platforms/profile-editor/api/src/controllers/profile.ts
  • platforms/profile-editor/api/src/controllers/webhook.ts
  • platforms/profile-editor/api/src/db.ts
  • platforms/profile-editor/api/src/entities/ProfessionalProfile.ts
  • platforms/profile-editor/api/src/entities/User.ts
  • platforms/profile-editor/api/src/env.ts
  • platforms/profile-editor/api/src/web3adapter/mappings/user.mapping.json
  • platforms/profile-editor/api/src/web3adapter/watchers/subscriber.ts
💤 Files with no reviewable changes (1)
  • platforms/profile-editor/api/src/web3adapter/mappings/user.mapping.json
✅ Files skipped from review due to trivial changes (1)
  • platforms/profile-editor/api/src/entities/ProfessionalProfile.ts
🚧 Files skipped from review as they are similar to previous changes (6)
  • platforms/profile-editor/api/src/db.ts
  • platforms/profile-editor/api/src/entities/User.ts
  • platforms/profile-editor/api/src/controllers/webhook.ts
  • platforms/profile-editor/api/src/web3adapter/watchers/subscriber.ts
  • platforms/profile-editor/api/src/controllers/profile.ts
  • platforms/profile-editor/api/src/env.ts

📝 Walkthrough

Walkthrough

The PR refactors profile-editor to use TypeORM/Postgres with new User and ProfessionalProfile entities and migrations, swaps session-backed auth for stateless JWTs, adds Zod validation, reworks controllers (auth/profile/discover/files/webhook), registers AaaS subscriptions on startup, updates web3 adapter mappings and subscriber debounce/refetch logic, and aligns clients to use public file URLs.

Changes

Profile Editor API Architecture Refactor

Layer / File(s) Summary
Environment and configuration
.env.example, platforms/profile-editor/api/package.json, platforms/profile-editor/api/nodemon.json, platforms/profile-editor/api/tsconfig.json
Profile-editor env vars, scripts, and dev tooling updated for TypeORM, migrations, and Zod validation.
Runtime configuration module
platforms/profile-editor/api/src/env.ts
Typed runtime env loader exposes required DB/JWT/mapping/AaaS values and port.
TypeORM entities and migrations
platforms/profile-editor/api/src/entities/User.ts, platforms/profile-editor/api/src/entities/ProfessionalProfile.ts, platforms/profile-editor/api/src/migrations/1700000000000-Init.ts, platforms/profile-editor/api/src/db.ts
Adds users and professional_profiles entities, Init migration creating tables, and new AppDataSource config (old data-source.ts removed).
Core data access services
platforms/profile-editor/api/src/services/UserService.ts, platforms/profile-editor/api/src/services/ProfessionalProfileService.ts
TypeORM-backed services with allowlisted upsert logic for inbound global payloads and basic find/getOrNew/save helpers.
Request validation
platforms/profile-editor/api/src/schemas.ts
Zod schemas and validate(schema) middleware for login and profile update payloads (arrays validated for work/education/skills/social links).
Authentication and stateless middleware
platforms/profile-editor/api/src/controllers/auth.ts, platforms/profile-editor/api/src/middleware/auth.ts
AuthController provides SSE-based offer/stream/login handlers; auth middleware switched to stateless JWT identity extraction using env.jwtSecret.
Profile management endpoints
platforms/profile-editor/api/src/controllers/profile.ts
ProfileController builds FullProfile from User+ProfessionalProfile, exposes authenticated read/update endpoints and per-array update handlers, and returns sanitized public profiles when isPublic is false.
Discovery and search endpoint
platforms/profile-editor/api/src/controllers/discover.ts
DiscoverController queries joined users/professional_profiles with search, skills overlap, pagination, and no-store cache headers.
File upload controller
platforms/profile-editor/api/src/controllers/files.ts
Multer in-memory upload with MIME allowlist and 25MB limit; uploads to eVault adapter and responds with publicUrl and metadata.
Webhook handler for AaaS
platforms/profile-editor/api/src/controllers/webhook.ts
Parses AaaS payloads, maps schemaId to local table, converts global→local fields, upserts via services, stores mappings, and prevents echo-back.
AaaS subscription registration
platforms/profile-editor/api/src/aaas.ts
Startup helper registers /api/webhook with AaaS when configured, skipping or logging errors without blocking startup.
Application bootstrap
platforms/profile-editor/api/src/index.ts
Initializes TypeORM, constructs services/controllers, wires routes with validation and auth, uses fileUpload, calls AaaS registration, and listens on env.port.
Type declarations
platforms/profile-editor/api/src/types/express.d.ts, platforms/profile-editor/api/src/types/profile.ts
Express Request.user now AuthUser; FullProfile.professional requires array fields; deprecated interfaces removed.
Web3 adapter mappings
platforms/profile-editor/api/src/web3adapter/mappings/user.mapping.json, platforms/profile-editor/api/src/web3adapter/mappings/professional-profile.mapping.json
Updated mapping files: user adds file-backed avatar/banner; professional-profile mapping expanded to include contact, education, workExperience, socialLinks and __file(...) for files.
PostgreSQL subscriber refactor
platforms/profile-editor/api/src/web3adapter/watchers/subscriber.ts
Debounces per-row changes 3s, refetches full entity via AppDataSource, prevents echo via mappingDb/lockedIds, converts top-level Dates to ISO, and restricts syncing to users and professional_profiles.
Client-side asset URL updates
platforms/profile-editor/client/src/lib/components/profile/DocumentsSection.svelte, platforms/profile-editor/client/src/lib/components/profile/ProfileCard.svelte, platforms/profile-editor/client/src/lib/components/profile/ProfileHeader.svelte, platforms/profile-editor/client/src/lib/utils/file-manager.ts
Client components and upload utility updated to use backend-returned publicUrl and render assets directly from public CDN URLs (proxy/helpers removed).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

evault-refactor

Suggested reviewers

  • coodos

"🐰
I hopped through schemas, tables, and routes,
Swapped session for stateless, avoided old bloats.
Files now shine bright on a public CDN,
Hooray — profile editor, refreshed once again! ✨"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: rebuilding the profile-editor backend using the web3-adapter pattern for 2-way sync.
Description check ✅ Passed The description covers all required template sections: issue number (Refs #980), type of change (refactoring), testing approach, and includes self-review insights and architectural rationale.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/profile-editor-web3-adapter

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

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

⚠️ Outside diff range comments (1)
platforms/profile-editor/client/src/lib/components/profile/ProfileHeader.svelte (1)

33-74: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Revoke the temporary blob previews after each upload.

URL.createObjectURL(file) is never paired with URL.revokeObjectURL(...), so repeated avatar/banner changes leak blob URLs until the page is reloaded. Please revoke the previous preview when replacing/clearing it, and on component teardown.

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

In
`@platforms/profile-editor/client/src/lib/components/profile/ProfileHeader.svelte`
around lines 33 - 74, handleAvatarUpload and handleBannerUpload currently call
URL.createObjectURL(file) but never revoke previous blob URLs; update both
functions to revoke any existing preview URL (revoke avatarPreview before
assigning a new URL and before clearing it on success/error) and do the same for
bannerPreview in handleBannerUpload, and add a component teardown using
onDestroy to revoke whichever of avatarPreview or bannerPreview remain; ensure
you reference the avatarPreview and bannerPreview variables and revoke with
URL.revokeObjectURL(...) whenever you replace or clear them.
🧹 Nitpick comments (5)
platforms/profile-editor/api/src/controllers/profile.ts (1)

110-111: ⚖️ Poor tradeoff

Consider wrapping saves in a transaction for consistency.

If this.profiles.save(prof) fails after this.users.save(user) succeeds, the user record will have updated fields while the professional profile remains stale. For profile updates this may be acceptable, but a transaction would ensure atomic updates.

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

In `@platforms/profile-editor/api/src/controllers/profile.ts` around lines 110 -
111, The two consecutive saves (this.users.save(user) and
this.profiles.save(prof)) should be executed inside a single atomic transaction
so that if profiles.save fails the users change is rolled back; wrap both
operations in your ORM's transaction API (e.g., use EntityManager.transaction or
a QueryRunner) and perform the saves via the transaction's manager
(manager.save(user) and manager.save(prof)), catch errors to rollback/propagate
and ensure you rethrow or return the appropriate error response from the
controller.
platforms/profile-editor/api/src/types/profile.ts (1)

30-30: 💤 Low value

Outdated comment: eVault reference should be updated.

The comment references "eVault PROFESSIONAL envelope" but per the PR objectives, this data is now stored in local PostgreSQL tables, not eVault.

📝 Suggested fix
-/** The full professional-profile payload stored in the eVault PROFESSIONAL envelope. */
+/** The full professional-profile payload stored in the local PostgreSQL table. */
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@platforms/profile-editor/api/src/types/profile.ts` at line 30, Update the
outdated JSDoc comment that reads "The full professional-profile payload stored
in the eVault PROFESSIONAL envelope." to reflect the current storage location
(local PostgreSQL tables) and remove any eVault wording; locate the comment
above the professional profile type definition in profile.ts (the comment
referencing "eVault PROFESSIONAL envelope") and replace it with a concise
description like "The full professional-profile payload stored in local
PostgreSQL tables" so documentation matches the new data storage design.
platforms/profile-editor/api/src/index.ts (1)

53-53: 💤 Low value

Breaking change: /api/profiles/:ename now requires authentication.

The AI summary indicates this endpoint was previously public. Adding authGuard will break existing unauthenticated clients. If intentional, consider documenting this in the changelog or migration notes.

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

In `@platforms/profile-editor/api/src/index.ts` at line 53, The route registration
for "/api/profiles/:ename" was made authenticated causing a breaking change;
either restore it as a public endpoint by removing the authGuard from the
app.get call that registers "/api/profiles/:ename" (so profile.getPublicProfile
remains callable without auth) or explicitly add a new public route (e.g.,
app.get("/api/profiles/:ename", profile.getPublicProfile)) and keep the
authenticated variant separate; update any docs/changelog if you intentionally
keep the authGuard.
platforms/profile-editor/api/src/entities/User.ts (1)

44-45: 💤 Low value

Clarify purpose of isPrivate if base identity is never gated.

The comment on line 13 states "Always public (isPrivate stays false) — base identity is never gated," but the entity defines an isPrivate column with a default of false. If this field is never used or always remains false, consider removing it to reduce schema complexity. If there's a future use case or it's required for cross-platform ontology mapping, document that intent.

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

In `@platforms/profile-editor/api/src/entities/User.ts` around lines 44 - 45, The
User entity currently defines an isPrivate column (decorated with `@Column` and
default: false) but comments indicate base identity is always public; decide
whether to remove or document it: either delete the isPrivate property from the
User class and remove any migrations/references to it, or add a clear comment
above the isPrivate declaration explaining its intended future use (e.g.,
cross-platform ontology mapping or planned gating) and keep it only if
referenced elsewhere (search for uses of User.isPrivate, repository queries,
DTOs or migrations). Ensure whichever path you choose updates any related tests,
schema migration files, and API DTOs to stay consistent.
platforms/profile-editor/api/src/migrations/1700000000000-Init.ts (1)

3-4: 💤 Low value

Migration timestamp appears to be a placeholder.

The migration timestamp 1700000000000 (November 15, 2023 00:53:20 GMT) appears to be a rounded placeholder value with all trailing zeros. While this works functionally, using actual timestamps from when migrations are generated (e.g., Date.now() or TypeORM's migration generator) helps with ordering and debugging migration history.

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

In `@platforms/profile-editor/api/src/migrations/1700000000000-Init.ts` around
lines 3 - 4, The migration uses a placeholder timestamp in the
class/filename/name (Init1700000000000) which should be replaced with the real
generation timestamp; update the class name Init1700000000000, the exported
class identifier, and the internal name property to use a unique real timestamp
(e.g., Date.now() value used when the migration was created) so ordering is
correct and rename the migration file accordingly to match the new
timestamp-based class name; ensure the class still implements MigrationInterface
and that any imports/exports referencing Init1700000000000 are updated to the
new identifier.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@platforms/profile-editor/api/src/aaas.ts`:
- Around line 12-24: The registerSubscriptionOnStartup function uses env.baseUrl
and env.awarenessServiceUrl without null/undefined checks which can cause a
TypeError when one is missing; update registerSubscriptionOnStartup to validate
that env.awarenessServiceUrl (and env.baseUrl when awarenessWebhookUrl is not
provided) are defined before calling .replace(), and if they are missing either
log an explicit error/warning and return early or throw a clear error —
reference the symbols env.awarenessServiceUrl, env.baseUrl, and
env.awarenessWebhookUrl and perform the checks near where targetUrl and base are
computed so the function never calls .replace() on undefined.

In `@platforms/profile-editor/api/src/controllers/files.ts`:
- Around line 15-32: The handleUpload flow currently trusts req.file.mimetype
and publishes uploads; add a server-side allowlist check in handleUpload (before
calling adapter.evaultClient.uploadFile) that permits only safe asset MIME types
(e.g., image/png, image/jpeg, image/gif, image/webp, video/mp4, audio/* or
whatever your product allows) and explicitly rejects active/scriptable types
(e.g., text/html, image/svg+xml, application/javascript, application/x-sh, etc.)
by returning a 415 Unsupported Media Type; implement this check using
req.file.mimetype (and optionally verifying file extension/content-type
consistency) and only call adapter.evaultClient.uploadFile when the MIME type is
on the allowlist, otherwise respond with an error and do not create a public
blob.
- Around line 5-7: The current multer setup (variable upload) uses
multer.memoryStorage() with a 100 MB limit and later calls toString("base64"),
which buffers the file twice in RAM; change the upload pipeline to avoid
in-memory buffering by replacing multer.memoryStorage() with either
multer.diskStorage() (write to a temporary file and reduce the memory limit) or
use a streaming uploader that pipes the incoming file directly to eVault, and
lower the multer limits to a safer size; update the code that reads the buffer
and calls toString("base64") to instead stream the temp file (or pipeline) into
the eVault client upload/streaming API and ensure temporary files are cleaned up
after upload.

In `@platforms/profile-editor/api/src/controllers/webhook.ts`:
- Around line 50-52: Move the lock operations so the local row is locked before
awaiting persistence: call this.adapter.addToLockedIds(localId) (and likewise
this.adapter.addToLockedIds(globalId) if desired) before awaiting
this.adapter.mappingDb.storeMapping({ localId, globalId }) in the
upsertFromGlobal / webhook flow, so the local write emitted earlier cannot be
re-processed while storeMapping is still pending.

In `@platforms/profile-editor/api/src/db.ts`:
- Line 17: The current ssl config disables certificate validation by setting
rejectUnauthorized: false; change it so certificate validation is enabled: when
env.dbCaCert is present set ssl to { rejectUnauthorized: true, ca: env.dbCaCert
} and do not globally disable validation; for local development allow a fallback
only when env.nodeEnv === 'development' (e.g. ssl: false or rejectUnauthorized:
false) but in non-development environments enforce rejectUnauthorized: true.
Update the ssl assignment referencing ssl, env.dbCaCert, env.nodeEnv, and
rejectUnauthorized accordingly.

In `@platforms/profile-editor/api/src/entities/ProfessionalProfile.ts`:
- Around line 22-65: The entity's nullable columns currently use non-nullable TS
types; update each property decorated with Column({ nullable: true }) or
Column(..., nullable: true) to include "| null" in its type so runtime nulls
match TypeScript types—for example change ename, name, headline, bio, location,
cvFileId, videoIntroFileId, email, phone, website to string | null; skills to
string[] | null; and workExperience, education, socialLinks to WorkExperience[]
| null, Education[] | null, SocialLink[] | null respectively; leave
non-nullable/defaulted fields like isPublic as-is.

In `@platforms/profile-editor/api/src/entities/User.ts`:
- Around line 20-39: The nullable column properties in the User entity (ename,
handle, name, bio, avatarUrl, bannerUrl, location) are currently typed as
non-nullable strings; update each property type to include | null (e.g., ename:
string | null) so TypeScript reflects the database NULLability while keeping the
definite-assignment assertion if desired (e.g., ename!: string | null); this
ensures callers must handle null values when reading fields like User.name,
User.bio, User.avatarUrl, etc.

In `@platforms/profile-editor/api/src/env.ts`:
- Around line 4-5: The comment above the config({ path: path.resolve(__dirname,
"../../../../.env") }) call incorrectly says "five levels up"; update the
comment to reflect the correct depth ("four levels up") or rephrase to avoid
counting (e.g., "load repo-root .env from four levels up"). Locate the comment
in env.ts near the config/path.resolve call and change the text to match the
actual traversal from platforms/profile-editor/api/src to the repo root.

In `@platforms/profile-editor/api/src/web3adapter/mappings/user.mapping.json`:
- Line 14: The User ontology export currently maps users.isPrivate ("isPrivate"
in user.mapping.json) which conflicts with the agreed source of truth
professional_profiles.isPublic; remove the "isPrivate": "isPrivate" entry from
user.mapping.json (the User ontology mapping) so visibility is only synced from
professional_profiles.isPublic, and verify any consumers reference
professional_profiles.isPublic instead of users.isPrivate.

In `@platforms/profile-editor/api/src/web3adapter/watchers/subscriber.ts`:
- Around line 62-65: The code only checks adapter.lockedIds for the resolved
globalId (from adapter.mappingDb.getGlobalId(id)), so when storeMapping() hasn't
run the webhook's localId lock is missed; change the check to honor both the
original local id and the resolved globalId by testing
adapter.lockedIds.includes(id) || (globalId &&
adapter.lockedIds.includes(globalId)) and return if either is true; reference:
adapter.mappingDb.getGlobalId, adapter.lockedIds, localId (the incoming id), and
globalId.

---

Outside diff comments:
In
`@platforms/profile-editor/client/src/lib/components/profile/ProfileHeader.svelte`:
- Around line 33-74: handleAvatarUpload and handleBannerUpload currently call
URL.createObjectURL(file) but never revoke previous blob URLs; update both
functions to revoke any existing preview URL (revoke avatarPreview before
assigning a new URL and before clearing it on success/error) and do the same for
bannerPreview in handleBannerUpload, and add a component teardown using
onDestroy to revoke whichever of avatarPreview or bannerPreview remain; ensure
you reference the avatarPreview and bannerPreview variables and revoke with
URL.revokeObjectURL(...) whenever you replace or clear them.

---

Nitpick comments:
In `@platforms/profile-editor/api/src/controllers/profile.ts`:
- Around line 110-111: The two consecutive saves (this.users.save(user) and
this.profiles.save(prof)) should be executed inside a single atomic transaction
so that if profiles.save fails the users change is rolled back; wrap both
operations in your ORM's transaction API (e.g., use EntityManager.transaction or
a QueryRunner) and perform the saves via the transaction's manager
(manager.save(user) and manager.save(prof)), catch errors to rollback/propagate
and ensure you rethrow or return the appropriate error response from the
controller.

In `@platforms/profile-editor/api/src/entities/User.ts`:
- Around line 44-45: The User entity currently defines an isPrivate column
(decorated with `@Column` and default: false) but comments indicate base identity
is always public; decide whether to remove or document it: either delete the
isPrivate property from the User class and remove any migrations/references to
it, or add a clear comment above the isPrivate declaration explaining its
intended future use (e.g., cross-platform ontology mapping or planned gating)
and keep it only if referenced elsewhere (search for uses of User.isPrivate,
repository queries, DTOs or migrations). Ensure whichever path you choose
updates any related tests, schema migration files, and API DTOs to stay
consistent.

In `@platforms/profile-editor/api/src/index.ts`:
- Line 53: The route registration for "/api/profiles/:ename" was made
authenticated causing a breaking change; either restore it as a public endpoint
by removing the authGuard from the app.get call that registers
"/api/profiles/:ename" (so profile.getPublicProfile remains callable without
auth) or explicitly add a new public route (e.g.,
app.get("/api/profiles/:ename", profile.getPublicProfile)) and keep the
authenticated variant separate; update any docs/changelog if you intentionally
keep the authGuard.

In `@platforms/profile-editor/api/src/migrations/1700000000000-Init.ts`:
- Around line 3-4: The migration uses a placeholder timestamp in the
class/filename/name (Init1700000000000) which should be replaced with the real
generation timestamp; update the class name Init1700000000000, the exported
class identifier, and the internal name property to use a unique real timestamp
(e.g., Date.now() value used when the migration was created) so ordering is
correct and rename the migration file accordingly to match the new
timestamp-based class name; ensure the class still implements MigrationInterface
and that any imports/exports referencing Init1700000000000 are updated to the
new identifier.

In `@platforms/profile-editor/api/src/types/profile.ts`:
- Line 30: Update the outdated JSDoc comment that reads "The full
professional-profile payload stored in the eVault PROFESSIONAL envelope." to
reflect the current storage location (local PostgreSQL tables) and remove any
eVault wording; locate the comment above the professional profile type
definition in profile.ts (the comment referencing "eVault PROFESSIONAL
envelope") and replace it with a concise description like "The full
professional-profile payload stored in local PostgreSQL tables" so documentation
matches the new data storage design.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

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

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 055f0d6a-eeec-47d9-bbf6-7a5e514bdb77

📥 Commits

Reviewing files that changed from the base of the PR and between 35a8c6d and 133cd50.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (44)
  • .env.example
  • platforms/profile-editor/api/nodemon.json
  • platforms/profile-editor/api/package.json
  • platforms/profile-editor/api/src/aaas.ts
  • platforms/profile-editor/api/src/controllers/AuthController.ts
  • platforms/profile-editor/api/src/controllers/DiscoveryController.ts
  • platforms/profile-editor/api/src/controllers/FileProxyController.ts
  • platforms/profile-editor/api/src/controllers/ProfileController.ts
  • platforms/profile-editor/api/src/controllers/WebhookController.ts
  • platforms/profile-editor/api/src/controllers/auth.ts
  • platforms/profile-editor/api/src/controllers/discover.ts
  • platforms/profile-editor/api/src/controllers/files.ts
  • platforms/profile-editor/api/src/controllers/profile.ts
  • platforms/profile-editor/api/src/controllers/webhook.ts
  • platforms/profile-editor/api/src/database/data-source.ts
  • platforms/profile-editor/api/src/database/entities/Session.ts
  • platforms/profile-editor/api/src/database/entities/User.ts
  • platforms/profile-editor/api/src/database/migrations/1773143278029-InitialSchema.ts
  • platforms/profile-editor/api/src/database/migrations/1775600000000-RenameAvatarBannerColumns.ts
  • platforms/profile-editor/api/src/db.ts
  • platforms/profile-editor/api/src/entities/ProfessionalProfile.ts
  • platforms/profile-editor/api/src/entities/User.ts
  • platforms/profile-editor/api/src/env.ts
  • platforms/profile-editor/api/src/index.ts
  • platforms/profile-editor/api/src/middleware/auth.ts
  • platforms/profile-editor/api/src/migrations/1700000000000-Init.ts
  • platforms/profile-editor/api/src/schemas.ts
  • platforms/profile-editor/api/src/services/EVaultProfileService.ts
  • platforms/profile-editor/api/src/services/EVaultSyncService.ts
  • platforms/profile-editor/api/src/services/ProfessionalProfileService.ts
  • platforms/profile-editor/api/src/services/RegistryService.ts
  • platforms/profile-editor/api/src/services/UserSearchService.ts
  • platforms/profile-editor/api/src/services/UserService.ts
  • platforms/profile-editor/api/src/types/express.d.ts
  • platforms/profile-editor/api/src/types/profile.ts
  • platforms/profile-editor/api/src/utils/file-proxy.ts
  • platforms/profile-editor/api/src/web3adapter/mappings/professional-profile.mapping.json
  • platforms/profile-editor/api/src/web3adapter/mappings/user.mapping.json
  • platforms/profile-editor/api/src/web3adapter/watchers/subscriber.ts
  • platforms/profile-editor/api/tsconfig.json
  • platforms/profile-editor/client/src/lib/components/profile/DocumentsSection.svelte
  • platforms/profile-editor/client/src/lib/components/profile/ProfileCard.svelte
  • platforms/profile-editor/client/src/lib/components/profile/ProfileHeader.svelte
  • platforms/profile-editor/client/src/lib/utils/file-manager.ts
💤 Files with no reviewable changes (15)
  • platforms/profile-editor/api/src/database/migrations/1775600000000-RenameAvatarBannerColumns.ts
  • platforms/profile-editor/api/src/services/EVaultSyncService.ts
  • platforms/profile-editor/api/src/database/entities/Session.ts
  • platforms/profile-editor/api/src/database/data-source.ts
  • platforms/profile-editor/api/src/services/RegistryService.ts
  • platforms/profile-editor/api/src/database/migrations/1773143278029-InitialSchema.ts
  • platforms/profile-editor/api/src/services/EVaultProfileService.ts
  • platforms/profile-editor/api/src/controllers/AuthController.ts
  • platforms/profile-editor/api/src/controllers/ProfileController.ts
  • platforms/profile-editor/api/src/database/entities/User.ts
  • platforms/profile-editor/api/src/utils/file-proxy.ts
  • platforms/profile-editor/api/src/services/UserSearchService.ts
  • platforms/profile-editor/api/src/controllers/WebhookController.ts
  • platforms/profile-editor/api/src/controllers/FileProxyController.ts
  • platforms/profile-editor/api/src/controllers/DiscoveryController.ts

Comment on lines +12 to +24
export async function registerSubscriptionOnStartup(): Promise<void> {
if (!env.awarenessApiKey) {
console.warn(
"[aaas] AWARENESS_API_KEY not set — skipping subscription registration",
);
return;
}

const targetUrl =
env.awarenessWebhookUrl ||
`${env.baseUrl.replace(/\/$/, "")}/api/webhook`;
const headers = { Authorization: `Bearer ${env.awarenessApiKey}` };
const base = env.awarenessServiceUrl.replace(/\/$/, "");
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing null check for env.awarenessServiceUrl may cause runtime TypeError.

The function checks env.awarenessApiKey on line 13, but proceeds to use env.awarenessServiceUrl.replace() on line 24 without verifying it's defined. If the API key is set but the service URL is not, this will throw a TypeError: Cannot read properties of undefined (reading 'replace'), bypassing the graceful error handling.

Similarly, env.baseUrl is used on line 22 without a guard when env.awarenessWebhookUrl is not set.

🛡️ Proposed fix to validate required config
 export async function registerSubscriptionOnStartup(): Promise<void> {
-    if (!env.awarenessApiKey) {
+    if (!env.awarenessApiKey || !env.awarenessServiceUrl) {
         console.warn(
-            "[aaas] AWARENESS_API_KEY not set — skipping subscription registration",
+            "[aaas] AWARENESS_API_KEY or AWARENESS_SERVICE_URL not set — skipping subscription registration",
         );
         return;
     }
 
+    if (!env.awarenessWebhookUrl && !env.baseUrl) {
+        console.warn(
+            "[aaas] Neither AWARENESS_WEBHOOK_URL nor BASE_URL set — skipping subscription registration",
+        );
+        return;
+    }
+
     const targetUrl =
         env.awarenessWebhookUrl ||
         `${env.baseUrl.replace(/\/$/, "")}/api/webhook`;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@platforms/profile-editor/api/src/aaas.ts` around lines 12 - 24, The
registerSubscriptionOnStartup function uses env.baseUrl and
env.awarenessServiceUrl without null/undefined checks which can cause a
TypeError when one is missing; update registerSubscriptionOnStartup to validate
that env.awarenessServiceUrl (and env.baseUrl when awarenessWebhookUrl is not
provided) are defined before calling .replace(), and if they are missing either
log an explicit error/warning and return early or throw a clear error —
reference the symbols env.awarenessServiceUrl, env.baseUrl, and
env.awarenessWebhookUrl and perform the checks near where targetUrl and base are
computed so the function never calls .replace() on undefined.

Comment on lines +5 to +7
const upload = multer({
limits: { fileSize: 100 * 1024 * 1024 },
storage: multer.memoryStorage(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Avoid buffering 100 MB uploads twice in process memory.

memoryStorage() keeps the whole file in RAM, and toString("base64") allocates another large copy on top of it. With the current 100 MB limit, a couple of concurrent uploads can drive hundreds of MB of transient memory in a single Node process. Streaming to eVault, or at minimum lowering the limit to something the API can safely absorb, would make this path much more resilient.

Also applies to: 27-30

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

In `@platforms/profile-editor/api/src/controllers/files.ts` around lines 5 - 7,
The current multer setup (variable upload) uses multer.memoryStorage() with a
100 MB limit and later calls toString("base64"), which buffers the file twice in
RAM; change the upload pipeline to avoid in-memory buffering by replacing
multer.memoryStorage() with either multer.diskStorage() (write to a temporary
file and reduce the memory limit) or use a streaming uploader that pipes the
incoming file directly to eVault, and lower the multer limits to a safer size;
update the code that reads the buffer and calls toString("base64") to instead
stream the temp file (or pipeline) into the eVault client upload/streaming API
and ensure temporary files are cleaned up after upload.

Comment thread platforms/profile-editor/api/src/controllers/files.ts
Comment thread platforms/profile-editor/api/src/controllers/webhook.ts Outdated
Comment thread platforms/profile-editor/api/src/db.ts Outdated
Comment thread platforms/profile-editor/api/src/entities/ProfessionalProfile.ts Outdated
Comment thread platforms/profile-editor/api/src/entities/User.ts Outdated
Comment thread platforms/profile-editor/api/src/env.ts Outdated
Comment thread platforms/profile-editor/api/src/web3adapter/mappings/user.mapping.json Outdated
Comment thread platforms/profile-editor/api/src/web3adapter/watchers/subscriber.ts Outdated
- files: enforce server-side MIME allowlist (reject scriptable content
  with 415) and lower upload limit to 25MB to bound in-memory buffering
- db: validate TLS cert (rejectUnauthorized) outside development
- webhook: lock ids before awaiting storeMapping to prevent re-export
- subscriber: honor local-id lock in echo prevention, not just globalId
- entities: type nullable columns as `T | null`
- user.mapping: drop isPrivate so professional_profiles.isPublic is the
  single source of truth for visibility
- env: correct .env path-depth comment

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant