refactor(profile-editor): rebuild backend on web3-adapter for 2-way sync#985
refactor(profile-editor): rebuild backend on web3-adapter for 2-way sync#985sosweetham wants to merge 2 commits into
Conversation
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>
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (9)
💤 Files with no reviewable changes (1)
✅ Files skipped from review due to trivial changes (1)
🚧 Files skipped from review as they are similar to previous changes (6)
📝 WalkthroughWalkthroughThe 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. ChangesProfile Editor API Architecture Refactor
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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 winRevoke the temporary blob previews after each upload.
URL.createObjectURL(file)is never paired withURL.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 tradeoffConsider wrapping saves in a transaction for consistency.
If
this.profiles.save(prof)fails afterthis.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 valueOutdated 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 valueBreaking change:
/api/profiles/:enamenow requires authentication.The AI summary indicates this endpoint was previously public. Adding
authGuardwill 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 valueClarify purpose of
isPrivateif base identity is never gated.The comment on line 13 states "Always public (
isPrivatestays false) — base identity is never gated," but the entity defines anisPrivatecolumn with a default offalse. 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 valueMigration 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
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (44)
.env.exampleplatforms/profile-editor/api/nodemon.jsonplatforms/profile-editor/api/package.jsonplatforms/profile-editor/api/src/aaas.tsplatforms/profile-editor/api/src/controllers/AuthController.tsplatforms/profile-editor/api/src/controllers/DiscoveryController.tsplatforms/profile-editor/api/src/controllers/FileProxyController.tsplatforms/profile-editor/api/src/controllers/ProfileController.tsplatforms/profile-editor/api/src/controllers/WebhookController.tsplatforms/profile-editor/api/src/controllers/auth.tsplatforms/profile-editor/api/src/controllers/discover.tsplatforms/profile-editor/api/src/controllers/files.tsplatforms/profile-editor/api/src/controllers/profile.tsplatforms/profile-editor/api/src/controllers/webhook.tsplatforms/profile-editor/api/src/database/data-source.tsplatforms/profile-editor/api/src/database/entities/Session.tsplatforms/profile-editor/api/src/database/entities/User.tsplatforms/profile-editor/api/src/database/migrations/1773143278029-InitialSchema.tsplatforms/profile-editor/api/src/database/migrations/1775600000000-RenameAvatarBannerColumns.tsplatforms/profile-editor/api/src/db.tsplatforms/profile-editor/api/src/entities/ProfessionalProfile.tsplatforms/profile-editor/api/src/entities/User.tsplatforms/profile-editor/api/src/env.tsplatforms/profile-editor/api/src/index.tsplatforms/profile-editor/api/src/middleware/auth.tsplatforms/profile-editor/api/src/migrations/1700000000000-Init.tsplatforms/profile-editor/api/src/schemas.tsplatforms/profile-editor/api/src/services/EVaultProfileService.tsplatforms/profile-editor/api/src/services/EVaultSyncService.tsplatforms/profile-editor/api/src/services/ProfessionalProfileService.tsplatforms/profile-editor/api/src/services/RegistryService.tsplatforms/profile-editor/api/src/services/UserSearchService.tsplatforms/profile-editor/api/src/services/UserService.tsplatforms/profile-editor/api/src/types/express.d.tsplatforms/profile-editor/api/src/types/profile.tsplatforms/profile-editor/api/src/utils/file-proxy.tsplatforms/profile-editor/api/src/web3adapter/mappings/professional-profile.mapping.jsonplatforms/profile-editor/api/src/web3adapter/mappings/user.mapping.jsonplatforms/profile-editor/api/src/web3adapter/watchers/subscriber.tsplatforms/profile-editor/api/tsconfig.jsonplatforms/profile-editor/client/src/lib/components/profile/DocumentsSection.svelteplatforms/profile-editor/client/src/lib/components/profile/ProfileCard.svelteplatforms/profile-editor/client/src/lib/components/profile/ProfileHeader.svelteplatforms/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
| 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(/\/$/, ""); |
There was a problem hiding this comment.
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.
| const upload = multer({ | ||
| limits: { fileSize: 100 * 1024 * 1024 }, | ||
| storage: multer.memoryStorage(), |
There was a problem hiding this comment.
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.
- 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>
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
users+professional_profiles). The REST API only reads/writes local rows.PostgresSubscriber→toGlobal(outbound);POST /api/webhook→fromGlobal(inbound);lockedIdsprevents echo loops. AaaS is the delivery transport (subscription auto-registers on boot).isPublicgate sync with dreamsync via the Professional Profile ontology. Mappings align with dreamsync/pictique field names (isPrivate,__file(avatarUrl), etc.).__file/EVaultClient.uploadFile(no file-manager, no bytea). Client change is ~20 lines: store/render the returned public URL, drop the asset-proxy route.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./api/webhook→fromGlobal→ localusersrow populated.⛔ 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 (
Deliveryis 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 thefix/max-retries-deadletter-queuebranch (that only handles retry caps).Notes
reference/and is intentionally not committed (deprecated).🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Improvements
Configuration