Skip to content

Add user profile image support across backend and frontend#553

Open
nasagong wants to merge 5 commits intoyorkie-team:mainfrom
nasagong:feat/show-profile-image
Open

Add user profile image support across backend and frontend#553
nasagong wants to merge 5 commits intoyorkie-team:mainfrom
nasagong:feat/show-profile-image

Conversation

@nasagong
Copy link
Copy Markdown

@nasagong nasagong commented Nov 27, 2025

What this PR does / why we need it:

Implemented user profile image support across both the backend and frontend.

Which issue(s) this PR fixes:

Fixes #348

Special notes for your reviewer:

  • Added profileIcon field in User model
  • Conditional logic was used to avoid unnecessary queries during profile image updates, so a unique composite key was not added

Does this PR introduce a user-facing change?:

Users can now view their own and others' GitHub profile images across headers, document cards, and table tabs.
header
document_card
table
image

Additional documentation:


Checklist:

  • Added relevant tests or not required
  • Didn't break anything

Summary by CodeRabbit

Release Notes

  • New Features
    • User profile icons from social authentication accounts are now captured and displayed throughout the application, including in headers, presence indicators, and user collaboration features.

@CLAassistant
Copy link
Copy Markdown

CLAassistant commented Nov 27, 2025

CLA assistant check
All committers have signed the CLA.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Nov 27, 2025

📝 Walkthrough

Walkthrough

This PR adds profile icon support throughout the application, capturing user profile images from social login providers (GitHub), persisting them in the database, and displaying them across various UI components and real-time collaborative features.

Changes

Cohort / File(s) Summary
Database Schema
packages/backend/prisma/schema.prisma
Added nullable profileIcon field to User model with MongoDB field mapping.
GitHub Authentication
packages/backend/src/auth/github.strategy.ts, packages/backend/src/auth/types/login-request.type.ts
GitHub strategy now extracts profile icon from profile.photos and includes it in LoginUserInfo type as profileIcon: string | null.
Auth Service
packages/backend/src/auth/auth.service.ts
Updated loginWithSocialProvider to pass profileIcon from GitHub strategy to user creation flow.
User Service & Types
packages/backend/src/users/users.service.ts, packages/backend/src/users/types/user-domain.type.ts
findOne now selects profileIcon in queries; findOrCreate accepts profileIcon parameter and conditionally updates existing users; added profileIcon and lastWorkspaceSlug optional fields to UserDomain type.
Workspace User Models
packages/backend/src/workspace-users/types/workspace-user.domain.ts, packages/backend/src/workspace-users/workspace-users.service.ts
Added profileIcon optional property to WorkspaceUserDomain type; updated findMany to include profileIcon in user record selection.
Real-time Presence
packages/codemirror/src/plugins/yorkie/yorkieSync.ts, packages/frontend/src/features/editor/hooks/useYorkieDocument.ts
Extended YorkiePresenceType with profileIcon: string | null field; updated useYorkieDocument hook to include profileIcon in initial presence and adjust dependencies accordingly.
Frontend Avatar Rendering
packages/frontend/src/components/cards/DocumentCard.tsx, packages/frontend/src/components/headers/SettingHeader.tsx, packages/frontend/src/components/headers/UserPresenceList.tsx, packages/frontend/src/components/headers/WorkspaceHeader.tsx, packages/frontend/src/components/workspace/TableTab.tsx
Updated avatar components across multiple locations to accept and render profileIcon via src attribute with quoted-string normalization; maintains initial-letter fallback when icon unavailable.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Poem

🐰 A profile pic now graces the display,
From GitHub's vault, shown bright as day,
In avatars across the screen,
The prettiest faces ever seen,
As Yorkie syncs with presence true,
Each collaborator shines in view!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add user profile image support across backend and frontend' directly and clearly summarizes the main change—adding profile image functionality across the entire application stack.
Linked Issues check ✅ Passed The PR implements all coding objectives from issue #348: captures profile icons from social providers (GitHub in github.strategy.ts), stores profileIcon in the User model, persists it through findOrCreate logic, and displays it across multiple UI components (headers, document cards, tables, presence lists).
Out of Scope Changes check ✅ Passed All changes are directly scoped to implementing profile image support across backend and frontend as specified in issue #348; no unrelated modifications were introduced.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

❤️ Share

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

Copy link
Copy Markdown
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: 0

🧹 Nitpick comments (4)
backend/src/users/types/user-domain.type.ts (1)

8-11: Expose profileIcon in domain model looks good; consider fixing nearby typo

The new optional profileIcon with ApiProperty aligns with the schema and API surface. While you’re in this block, you might also correct the description typo "Last worksace slug of user""Last workspace slug of user".

frontend/src/hooks/useYorkieDocument.ts (1)

70-87: Profile icon is wired into Yorkie presence; verify behavior on mid‑session changes

Including profileIcon in initialPresence and keying createYorkieDocument off userStore.data?.profileIcon makes sense. Note, though, that the effect which calls createYorkieDocument bails out once doc and client are set, so subsequent profileIcon changes during a session won’t propagate into presence. If you expect avatars to update live when a user’s profile image changes, you may want to either (a) gate initialization on userStore.data being ready, or (b) explicitly update presence when profileIcon changes.

frontend/src/pages/settings/profile/Index.tsx (1)

56-65: Prefer undefined over empty string for Avatar src fallback

To avoid emitting an <img src=""> when there’s no profile image, consider:

- src={userStore.data?.profileIcon || ""}
+ src={userStore.data?.profileIcon ?? undefined}

MUI will still fall back to the initials, but without an unnecessary empty‑URL image load.

frontend/src/components/workspace/TableTab.tsx (1)

23-37: Consider consistent quote-stripping logic for all fields.

The profileIcon field uses conditional logic before stripping quotes (line 34), while color and name always strip quotes (lines 31-32). This creates inconsistent handling: an empty string profileIcon would become null, but empty string color or name would remain as "" after quote stripping.

Apply this diff for consistency:

 	return {
 		color: data.color.replace(/^"|"$/g, ""),
 		name: data.name.replace(/^"|"$/g, ""),
 		cursor: data.cursor,
-		profileIcon: data.profileIcon ? data.profileIcon.replace(/^"|"$/g, "") : null,
+		profileIcon: data.profileIcon?.replace(/^"|"$/g, "") ?? null,
 		selection: data.selection,
 	};

This ensures that:

  • Undefined/null → null
  • Empty string → empty string (consistent with color/name)
  • Quoted strings → unquoted strings
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b05495f and c846cd8.

📒 Files selected for processing (19)
  • backend/prisma/schema.prisma (1 hunks)
  • backend/src/auth/auth.service.ts (1 hunks)
  • backend/src/auth/github.strategy.ts (1 hunks)
  • backend/src/auth/types/login-request.type.ts (1 hunks)
  • backend/src/users/types/user-domain.type.ts (1 hunks)
  • backend/src/users/users.service.ts (3 hunks)
  • backend/src/workspace-users/types/workspace-user.domain.ts (1 hunks)
  • backend/src/workspace-users/workspace-users.service.ts (1 hunks)
  • frontend/src/components/cards/DocumentCard.tsx (2 hunks)
  • frontend/src/components/headers/SettingHeader.tsx (1 hunks)
  • frontend/src/components/headers/UserPresenceList.tsx (2 hunks)
  • frontend/src/components/headers/WorkspaceHeader.tsx (1 hunks)
  • frontend/src/components/workspace/TableTab.tsx (2 hunks)
  • frontend/src/hooks/api/types/document.d.ts (1 hunks)
  • frontend/src/hooks/api/types/user.d.ts (1 hunks)
  • frontend/src/hooks/useYorkieDocument.ts (1 hunks)
  • frontend/src/pages/settings/profile/Index.tsx (1 hunks)
  • frontend/src/store/userSlice.ts (2 hunks)
  • frontend/src/utils/yorkie/yorkieSync.ts (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
backend/src/users/users.service.ts (2)
frontend/src/hooks/api/types/user.d.ts (1)
  • User (1-8)
frontend/src/store/userSlice.ts (1)
  • User (5-12)
🔇 Additional comments (21)
backend/prisma/schema.prisma (1)

18-18: New profileIcon field is consistent and backward‑compatible

Optional String mapped to profile_icon follows the existing schema conventions and cleanly supports nullable profile image URLs.

backend/src/workspace-users/workspace-users.service.ts (1)

35-43: Selecting profileIcon in workspace user query is appropriate

Including profileIcon in the user.findMany select cleanly surfaces profile images to callers and stays consistent with the new workspace‑user domain shape.

backend/src/auth/auth.service.ts (1)

18-22: Passing profileIcon into findOrCreate keeps user data in sync

Forwarding req.user.profileIcon to usersService.findOrCreate is a straightforward way to persist social‑provider avatars without impacting the existing token structure.

frontend/src/store/userSlice.ts (1)

5-12: Extending User with profileIcon is consistent with store design

Adding profileIcon: string | null and documenting it in the slice comment matches how other optional user attributes are modeled and gives components a single source of truth for avatar URLs.

Also applies to: 36-46

frontend/src/hooks/api/types/document.d.ts (1)

11-21: Presence type now correctly carries optional profileIcon

Extending Document.presences[].data with profileIcon?: string | null matches the new presence shape and gives callers a typed way to render avatars with images when available.

frontend/src/hooks/api/types/user.d.ts (1)

4-4: LGTM!

The profileIcon field is correctly typed as optional with a null union, consistent with the backend type definitions and other optional user fields.

frontend/src/components/headers/SettingHeader.tsx (1)

36-38: LGTM!

The Avatar correctly uses the profile icon when available and falls back to the user's initial. MUI's Avatar component handles image loading failures gracefully by falling back to the children content.

backend/src/auth/types/login-request.type.ts (1)

7-7: LGTM!

The profileIcon field is correctly added as a required field with null as a valid value, matching the return type from the GitHub strategy.

frontend/src/components/headers/WorkspaceHeader.tsx (1)

74-76: LGTM!

Consistent implementation with SettingHeader - the Avatar correctly displays the profile icon when available and falls back to initials.

frontend/src/utils/yorkie/yorkieSync.ts (1)

13-13: LGTM!

The profileIcon field correctly extends the Yorkie presence type to support profile images in collaborative editing sessions.

frontend/src/components/headers/UserPresenceList.tsx (2)

55-55: LGTM!

Profile icons are correctly displayed for users in the presence list, with appropriate fallback to initials.


94-94: LGTM!

Consistent implementation - the popover list also displays profile icons when available.

frontend/src/components/workspace/TableTab.tsx (1)

91-91: LGTM!

Avatar correctly uses the profile icon with appropriate fallback.

backend/src/auth/github.strategy.ts (1)

30-30: Verification confirmed: Implementation is correct.

The profile.photos?.[0]?.value correctly accesses the avatar URL from passport-github's normalized Profile type. The photos property is indeed an array of objects with a value property containing the URL string, and the optional chaining syntax appropriately handles cases where photos may be undefined.

profileIcon: profile.photos?.[0]?.value || null,

No changes needed.

frontend/src/components/cards/DocumentCard.tsx (2)

29-29: LGTM! Consistent type and parsing logic.

The profileIcon field is properly typed as optional and the quote-stripping logic mirrors the pattern used for other presence fields.

Also applies to: 36-36


111-118: LGTM! Proper Avatar image handling with fallback.

The Avatar correctly uses profileIcon as the image source with an appropriate empty string fallback. When the image is unavailable, MUI Avatar will display the initials as children, providing a good user experience.

backend/src/workspace-users/types/workspace-user.domain.ts (1)

8-9: LGTM! Well-documented optional field.

The profileIcon field is properly defined with clear API documentation and appropriate typing.

backend/src/users/users.service.ts (4)

36-36: LGTM! Proper field selection.

The profileIcon field is correctly added to the select clause, ensuring it's included in the query results.


64-69: Verify the intended behavior when profileIcon is null.

The current logic only updates the profile icon when a non-null value is provided that differs from the stored value. If profileIcon is null (e.g., a user removes their GitHub profile picture), the existing stored icon will persist unchanged.

Is this the intended behavior? Consider whether removing a profile picture from the OAuth provider should clear it in your application or preserve the last known value.

If clearing the icon is desired, consider this alternative:

 if (foundUser) {
-    if (profileIcon && foundUser.profileIcon !== profileIcon) {
+    if (foundUser.profileIcon !== profileIcon) {
         return await this.prismaService.user.update({
             where: { id: foundUser.id },
             data: { profileIcon },
         });
     }
     return foundUser;
 }

This would update the database whenever the profileIcon value differs, including when it becomes null.


77-77: LGTM! Proper handling of optional field during user creation.

The profileIcon is correctly set to the provided value or null, which aligns with the nullable database schema.


51-55: Verify all callers have been updated for the new profileIcon parameter.

The method signature now requires a third parameter (profileIcon: string | null). Ensure all call sites in the codebase have been updated to pass this new parameter. A search of the codebase was unable to complete due to access constraints; manual verification is recommended to confirm all invocations of findOrCreate include the new parameter.

@krapie krapie requested a review from kokodak January 16, 2026 11:58
@krapie krapie added the enhancement 🌟 New feature or request label Jan 16, 2026
Comment on lines +64 to +69
if (profileIcon && foundUser.profileIcon !== profileIcon) {
return await this.prismaService.user.update({
where: { id: foundUser.id },
data: { profileIcon },
});
}
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This part could potentially cause issues later when the user profile image change feature is added.

Let's assume the following scenario:

  1. User creates an account or login via social login.
  2. User changes profile image on CodePair.
  3. The user logs in again.

Even if a user wants to change to a different image, it will keep reverting to the image from social login info.

Maybe we can ignore this for now and just add a comment, or it would be good to discuss when the user image should be set.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Sorry for the late response 😓. I added a TODO comment for now since there's no clear policy on profile image updates yet. If you'd prefer I can also make it so the profile icon is only set on the first login. Let me know!

- Add the profileIcon field to the User model in the Prisma schema

- Prepare for storing and retrieving users' profile icons across the application
- Modified github strategy to request and retrieve the user's profile image

- Updated the LoginUserInfo type to include the avatar URL field
- Added logic to save the user's profile image URL to the database during the updated login flow

- Added profile icon support to workspace user information

- Ensures user and workspace contexts have consistent access to profile images
- Added user profile icon to DocumentCard, headers, and TableTab
- Updated type definitions for the profileIcon field
@nasagong nasagong force-pushed the feat/show-profile-image branch from c846cd8 to 025ae2e Compare March 28, 2026 14:32
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
packages/frontend/src/features/editor/hooks/useYorkieDocument.ts (1)

79-86: Consider: Profile icon changes won't update presence on already-attached documents.

The dependency on userStore.data?.profileIcon recreates the callback, but the useEffect at line 100 guards against re-initialization when doc or client already exist. If the user's profile icon changes mid-session, the presence won't update until they re-open the document.

This is likely acceptable for the initial implementation since profile icons rarely change during a session. If real-time presence icon updates become a requirement, you'd need to call doc.update() on presence changes separately.

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

In `@packages/frontend/src/features/editor/hooks/useYorkieDocument.ts` around
lines 79 - 86, The current callback recreation on userStore.data?.profileIcon
doesn't update presence for already-attached documents because the useEffect
that creates the doc/client guards against re-initialization; add a separate
effect that watches userStore.data?.profileIcon and, when it changes and doc is
attached, call the Yorkie document presence update (e.g., invoke doc.update(...)
or the appropriate doc presence update API) to set presence.profileIcon so
existing sessions reflect the new icon; reference the existing symbols
useYorkieDocument state variables doc and client and the
userStore.data?.profileIcon dependency to locate where to add this effect.
packages/backend/src/users/users.service.ts (1)

32-36: Return type could be Promise<User> instead of Promise<User | null>.

The function always returns a User object—either from finding an existing user, updating one, or creating a new one. The | null in the return type appears unnecessary and could mislead callers into adding unnecessary null checks.

♻️ Suggested fix
 async findOrCreate(
   socialProvider: string,
   socialUid: string,
   profileIcon: string | null
-): Promise<User | null> {
+): Promise<User> {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/backend/src/users/users.service.ts` around lines 32 - 36, The
findOrCreate function currently types its return as Promise<User | null> though
all code paths return a User; update the signature of findOrCreate to return
Promise<User> (replace Promise<User | null> with Promise<User>), ensure any
internal returns still return a User instance (no nulls introduced), and update
any callers or related type annotations that depended on the nullable return to
expect a User instead of null.
packages/frontend/src/components/workspace/TableTab.tsx (1)

23-37: Consider extracting parsePresenceData to a shared utility.

The parsePresenceData function is duplicated between TableTab.tsx and DocumentCard.tsx with identical logic. Both handle the same presence data structure from document.presences.

♻️ Suggested approach

Create a shared utility in a common location (e.g., utils/presence.ts):

export interface RawPresenceData {
    color: string;
    name: string;
    cursor: string | null;
    profileIcon?: string | null;
    selection: string | null;
}

export interface ParsedPresenceData {
    color: string;
    name: string;
    cursor: string | null;
    profileIcon: string | null;
    selection: string | null;
}

export const parsePresenceData = (data: RawPresenceData): ParsedPresenceData => ({
    color: data.color.replace(/^"|"$/g, ""),
    name: data.name.replace(/^"|"$/g, ""),
    cursor: data.cursor,
    profileIcon: data.profileIcon ? data.profileIcon.replace(/^"|"$/g, "") : null,
    selection: data.selection,
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/frontend/src/components/workspace/TableTab.tsx` around lines 23 -
37, Extract the duplicated parsePresenceData logic into a shared utility (e.g.,
utils/presence.ts) and replace the local implementations in TableTab.tsx and
DocumentCard.tsx with a single exported parsePresenceData function; ensure the
exported function accepts the RawPresenceData shape and returns the normalized
ParsedPresenceData (stripping surrounding quotes from color, name, and
profileIcon and preserving cursor/selection), then update both TableTab.tsx and
DocumentCard.tsx to import parsePresenceData from the new module and remove the
local copies.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/backend/src/users/users.service.ts`:
- Around line 32-36: The findOrCreate function currently types its return as
Promise<User | null> though all code paths return a User; update the signature
of findOrCreate to return Promise<User> (replace Promise<User | null> with
Promise<User>), ensure any internal returns still return a User instance (no
nulls introduced), and update any callers or related type annotations that
depended on the nullable return to expect a User instead of null.

In `@packages/frontend/src/components/workspace/TableTab.tsx`:
- Around line 23-37: Extract the duplicated parsePresenceData logic into a
shared utility (e.g., utils/presence.ts) and replace the local implementations
in TableTab.tsx and DocumentCard.tsx with a single exported parsePresenceData
function; ensure the exported function accepts the RawPresenceData shape and
returns the normalized ParsedPresenceData (stripping surrounding quotes from
color, name, and profileIcon and preserving cursor/selection), then update both
TableTab.tsx and DocumentCard.tsx to import parsePresenceData from the new
module and remove the local copies.

In `@packages/frontend/src/features/editor/hooks/useYorkieDocument.ts`:
- Around line 79-86: The current callback recreation on
userStore.data?.profileIcon doesn't update presence for already-attached
documents because the useEffect that creates the doc/client guards against
re-initialization; add a separate effect that watches
userStore.data?.profileIcon and, when it changes and doc is attached, call the
Yorkie document presence update (e.g., invoke doc.update(...) or the appropriate
doc presence update API) to set presence.profileIcon so existing sessions
reflect the new icon; reference the existing symbols useYorkieDocument state
variables doc and client and the userStore.data?.profileIcon dependency to
locate where to add this effect.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3c4e6044-5f61-4cc9-af71-51a1576a5769

📥 Commits

Reviewing files that changed from the base of the PR and between c846cd8 and 025ae2e.

📒 Files selected for processing (15)
  • packages/backend/prisma/schema.prisma
  • packages/backend/src/auth/auth.service.ts
  • packages/backend/src/auth/github.strategy.ts
  • packages/backend/src/auth/types/login-request.type.ts
  • packages/backend/src/users/types/user-domain.type.ts
  • packages/backend/src/users/users.service.ts
  • packages/backend/src/workspace-users/types/workspace-user.domain.ts
  • packages/backend/src/workspace-users/workspace-users.service.ts
  • packages/codemirror/src/plugins/yorkie/yorkieSync.ts
  • packages/frontend/src/components/cards/DocumentCard.tsx
  • packages/frontend/src/components/headers/SettingHeader.tsx
  • packages/frontend/src/components/headers/UserPresenceList.tsx
  • packages/frontend/src/components/headers/WorkspaceHeader.tsx
  • packages/frontend/src/components/workspace/TableTab.tsx
  • packages/frontend/src/features/editor/hooks/useYorkieDocument.ts
✅ Files skipped from review due to trivial changes (4)
  • packages/backend/src/workspace-users/types/workspace-user.domain.ts
  • packages/backend/prisma/schema.prisma
  • packages/codemirror/src/plugins/yorkie/yorkieSync.ts
  • packages/backend/src/users/types/user-domain.type.ts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement 🌟 New feature or request

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Show Profile Image After Social Login

4 participants