diff --git a/server/commands/userProvisioner.ts b/server/commands/userProvisioner.ts index 37ce027a44..333c165ad4 100644 --- a/server/commands/userProvisioner.ts +++ b/server/commands/userProvisioner.ts @@ -8,8 +8,7 @@ import { InviteRequiredError, } from "@server/errors"; import Logger from "@server/logging/Logger"; -import { Team, User, UserAuthentication, UserFlag } from "@server/models"; -import UploadUserAvatarTask from "@server/queues/tasks/UploadUserAvatarTask"; +import { Team, User, UserAuthentication } from "@server/models"; import { sequelize } from "@server/storage/database"; import { APIContext } from "@server/types"; @@ -162,15 +161,6 @@ export default async function userProvisioner( ); }); - // Schedule avatar sync task if user has an avatar URL and hasn't manually changed it - if (avatarUrl && !existingUser.getFlag(UserFlag.AvatarChanged)) { - await new UploadUserAvatarTask().schedule({ - userId: existingUser.id, - avatarUrl, - isSync: true, - }); - } - if (isInvite) { const inviter = await existingUser.$get("invitedBy"); if (inviter) { diff --git a/server/queues/processors/AvatarSyncProcessor.ts b/server/queues/processors/AvatarSyncProcessor.ts new file mode 100644 index 0000000000..ec8878e63e --- /dev/null +++ b/server/queues/processors/AvatarSyncProcessor.ts @@ -0,0 +1,37 @@ +import { User } from "@server/models"; +import { UserEvent } from "@server/types"; +import UploadUserAvatarTask from "../tasks/UploadUserAvatarTask"; +import BaseProcessor from "./BaseProcessor"; + +/** + * Processor to handle avatar synchronization on user signin events. + * This checks if the user's avatar from their identity provider has changed + * and updates it if necessary, unless the user has manually changed their avatar. + */ +export default class AvatarSyncProcessor extends BaseProcessor { + static applicableEvents: UserEvent["name"][] = ["users.signin"]; + + async perform(event: UserEvent) { + switch (event.name) { + case "users.signin": { + const user = await User.findByPk(event.userId, { + rejectOnEmpty: true, + }); + + // Only sync if user has an avatar URL + if (!user.avatarUrl) { + return; + } + + // Schedule the avatar upload task which will check if update is needed + await new UploadUserAvatarTask().schedule({ + userId: user.id, + avatarUrl: user.avatarUrl, + }); + + break; + } + default: + } + } +} diff --git a/server/queues/tasks/UploadUserAvatarTask.ts b/server/queues/tasks/UploadUserAvatarTask.ts index 08fd397470..3c3d012d83 100644 --- a/server/queues/tasks/UploadUserAvatarTask.ts +++ b/server/queues/tasks/UploadUserAvatarTask.ts @@ -12,8 +12,6 @@ type Props = { userId: string; /** The original avatarUrl from the SSO provider */ avatarUrl: string; - /** Whether this is a sync operation (should check for changes) */ - isSync?: boolean; }; /** @@ -26,17 +24,14 @@ export default class UploadUserAvatarTask extends BaseTask { rejectOnEmpty: true, }); - // If this is a sync operation, check if we need to update - if (props.isSync) { - // Check if user has manually changed their avatar - if (user.getFlag(UserFlag.AvatarChanged)) { - return; // Don't override user's manual avatar choice - } + // Check if user has manually changed their avatar + if (user.getFlag(UserFlag.AvatarChanged)) { + return; // Don't override user's manual avatar choice + } - // Check if the avatar has actually changed - if (!shouldUpdateAvatar(user.avatarUrl, props.avatarUrl)) { - return; // No change needed - } + // Check if the avatar has actually changed + if (!shouldUpdateAvatar(user.avatarUrl, props.avatarUrl)) { + return; // No change needed } // Use deterministic filename for change detection