Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/backend/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ model User {
socialProvider String @map("social_provider")
socialUid String @unique @map("social_uid")
nickname String?
profileIcon String? @map("profile_icon")
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
userWorkspaceList UserWorkspace[]
Expand Down
3 changes: 2 additions & 1 deletion packages/backend/src/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ export class AuthService {
async loginWithSocialProvider(req: LoginRequest): Promise<LoginResponse> {
const user = await this.usersService.findOrCreate(
req.user.socialProvider,
req.user.socialUid
req.user.socialUid,
req.user.profileIcon
);

const accessToken = this.jwtAccessService.sign({ sub: user.id, nickname: user.nickname });
Expand Down
1 change: 1 addition & 0 deletions packages/backend/src/auth/github.strategy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export class GithubStrategy extends PassportStrategy(Strategy, "github") {
socialProvider: "github",
socialUid: profile.id,
nickname: profile.username,
profileIcon: profile.photos?.[0]?.value || null,
};
}
}
1 change: 1 addition & 0 deletions packages/backend/src/auth/types/login-request.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export interface LoginUserInfo {
socialProvider: SocialProvider;
socialUid: string;
nickname: string;
profileIcon: string | null;
}

export type LoginRequest = Request & { user: LoginUserInfo };
4 changes: 4 additions & 0 deletions packages/backend/src/users/types/user-domain.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ export class UserDomain {
id: string;
@ApiProperty({ type: String, description: "Nickname of user", required: false })
nickname?: string;
@ApiProperty({ type: String, description: "Profile icon of user", required: false })
profileIcon?: string;
@ApiProperty({ type: String, description: "Last workspace slug of user", required: false })
lastWorkspaceSlug?: string;
@ApiProperty({ type: Date, description: "Created date of user" })
createdAt: Date;
@ApiProperty({ type: Date, description: "Updated date of user" })
Expand Down
16 changes: 15 additions & 1 deletion packages/backend/src/users/users.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export class UsersService {
select: {
id: true,
nickname: true,
profileIcon: true,
createdAt: true,
updatedAt: true,
},
Expand All @@ -28,7 +29,11 @@ export class UsersService {
return foundUser;
}

async findOrCreate(socialProvider: string, socialUid: string): Promise<User | null> {
async findOrCreate(
socialProvider: string,
socialUid: string,
profileIcon: string | null
): Promise<User | null> {
const foundUser = await this.prismaService.user.findFirst({
where: {
socialProvider,
Expand All @@ -37,13 +42,22 @@ export class UsersService {
});

if (foundUser) {
// TODO: When user profile image change feature is added,
// this should be revisited to avoid overwriting user-uploaded images on re-login.
if (profileIcon && foundUser.profileIcon !== profileIcon) {
return await this.prismaService.user.update({
where: { id: foundUser.id },
data: { profileIcon },
});
}
Comment on lines +47 to +52
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!

return foundUser;
}

const user = await this.prismaService.user.create({
data: {
socialProvider,
socialUid,
profileIcon: profileIcon || null,
},
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ export class WorkspaceUserDomain {
id: string;
@ApiProperty({ type: String, description: "Nickname of the user" })
nickname: string;
@ApiProperty({ type: String, description: "Profile icon of the user", required: false })
profileIcon?: string;
@ApiProperty({ type: Date, description: "Created date of the user" })
createdAt: Date;
@ApiProperty({ type: Date, description: "Updated date of the user" })
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class WorkspaceUsersService {
select: {
id: true,
nickname: true,
profileIcon: true,
updatedAt: true,
createdAt: true,
},
Expand Down
1 change: 1 addition & 0 deletions packages/codemirror/src/plugins/yorkie/yorkieSync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export type YorkiePresenceType = {
color: string;
name: string;
selection: yorkie.TextPosStructRange | null;
profileIcon: string | null;
cursor: [number, number] | null;
};

Expand Down
9 changes: 8 additions & 1 deletion packages/frontend/src/components/cards/DocumentCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,14 @@ function DocumentCard(props: DocumentCardProps) {
color: string;
name: string;
cursor: string | null;
profileIcon?: string | null;
selection: string | null;
}) => {
return {
color: data.color.replace(/^"|"$/g, ""),
name: data.name.replace(/^"|"$/g, ""),
cursor: data.cursor,
profileIcon: data.profileIcon ? data.profileIcon.replace(/^"|"$/g, "") : null,
selection: data.selection,
};
};
Expand Down Expand Up @@ -106,7 +108,12 @@ function DocumentCard(props: DocumentCardProps) {
}}
>
{presenceList.map((presence, index) => (
<Avatar key={index} sx={{ bgcolor: presence.color }} alt={presence.name}>
<Avatar
key={index}
src={presence.profileIcon || ""}
sx={{ bgcolor: presence.color }}
alt={presence.name}
>
{presence.name[0]}
</Avatar>
))}
Expand Down
4 changes: 3 additions & 1 deletion packages/frontend/src/components/headers/SettingHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,9 @@ function SettingHeader() {
alignItems="center"
>
<IconButton onClick={handleOpenProfilePopover}>
<Avatar>{userStore.data?.nickname?.charAt(0)}</Avatar>
<Avatar src={userStore.data?.profileIcon || ""}>
{userStore.data?.nickname?.charAt(0)}
</Avatar>
</IconButton>
<IconButton onClick={handleToWorkspace}>
<CodePairIcon />
Expand Down
2 changes: 2 additions & 0 deletions packages/frontend/src/components/headers/UserPresenceList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ function UserPresenceList(props: UserPresenceListProps) {
<Avatar
onClick={() => handleScrollToUserLocation(presence)}
alt={presence.presence.name}
src={presence.presence.profileIcon || ""}
sx={{ bgcolor: presence.presence.color }}
>
{presence.presence.name[0]}
Expand Down Expand Up @@ -80,6 +81,7 @@ function UserPresenceList(props: UserPresenceListProps) {
>
<ListItemAvatar>
<Avatar
src={presence.presence.profileIcon || ""}
sx={{
bgcolor: presence.presence.color,
width: 24,
Expand Down
4 changes: 3 additions & 1 deletion packages/frontend/src/components/headers/WorkspaceHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,9 @@ function WorkspaceHeader() {
width={DRAWER_WIDTH}
/>
<IconButton onClick={handleOpenProfilePopover}>
<Avatar>{userStore.data?.nickname?.charAt(0)}</Avatar>
<Avatar src={userStore.data?.profileIcon || ""}>
{userStore.data?.nickname?.charAt(0)}
</Avatar>
</IconButton>
</Stack>
</Toolbar>
Expand Down
3 changes: 3 additions & 0 deletions packages/frontend/src/components/workspace/TableTab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,12 +24,14 @@ export default function TableContent({ documents }: { documents: Document[] }) {
color: string;
name: string;
cursor: string | null;
profileIcon?: string | null;
selection: string | null;
}) => {
return {
color: data.color.replace(/^"|"$/g, ""),
name: data.name.replace(/^"|"$/g, ""),
cursor: data.cursor,
profileIcon: data.profileIcon ? data.profileIcon.replace(/^"|"$/g, "") : null,
selection: data.selection,
};
};
Expand Down Expand Up @@ -84,6 +86,7 @@ export default function TableContent({ documents }: { documents: Document[] }) {
{presenceList.map((presence, index) => (
<Avatar
key={index}
src={presence.profileIcon || ""}
sx={{ bgcolor: presence.color }}
alt={presence.name}
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,13 @@ export const useYorkieDocument = (
initialPresence: {
name: presenceName,
color: Color(randomColor()).fade(0.15).toString(),
profileIcon: userStore.data?.profileIcon || null,
selection: null,
cursor: null,
},
});
},
[]
[userStore.data?.profileIcon]
);

const cleanUpYorkieDocument = useCallback(async () => {
Expand Down
4 changes: 4 additions & 0 deletions packages/frontend/src/features/user/store/userSlice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { RootState } from "../../../store/store";
export interface User {
id: string;
nickname: string | null;
profileIcon: string | null;
lastWorkspaceSlug: string;
updatedAt: Date;
createdAt: Date;
}
Expand Down Expand Up @@ -38,6 +40,8 @@ export const selectUser = (state: RootState) => state.user;
* - Storing user data, including:
* - `id`: Unique identifier for the user.
* - `nickname`: User's nickname, or `null` if not set.
* - `profileIcon`: User's profile icon URL, or `null` if not set.
* - `lastWorkspaceSlug`: The last accessed workspace's slug.
* - `updatedAt`: Timestamp of the last user update.
* - `createdAt`: Timestamp of when the user was created.
*/
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/hooks/api/types/document.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export class Document {
color: string;
cursor: string | null;
name: string;
profileIcon?: string | null;
selection: string | null;
};
}
Expand Down
2 changes: 2 additions & 0 deletions packages/frontend/src/hooks/api/types/user.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
export class User {
id: string;
nickname?: string | null;
profileIcon?: string | null;
lastWorkspaceSlug?: string | null;
createdAt: Date;
updatedAt: Date;
}
Expand Down
1 change: 1 addition & 0 deletions packages/frontend/src/pages/settings/profile/Index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function ProfileIndex() {
<Container sx={{ height: "calc(100vh - 88px)", width: "100%" }}>
<Stack alignItems="center" justifyContent="center" gap={6} sx={{ height: 1 }}>
<Avatar
src={userStore.data?.profileIcon || ""}
sx={{
width: avatarSize,
height: avatarSize,
Expand Down
Loading