chore: Move initial avatar upload to background worker (#3727)
* chore: Async user avatar upload processor * chore: Async team avatar upload * Refactor to task for retries * Docs Include avatarUrl in task props to prevent race condition Remove transaction around upload fetch request
This commit is contained in:
40
server/queues/processors/AvatarProcessor.ts
Normal file
40
server/queues/processors/AvatarProcessor.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { Team, User } from "@server/models";
|
||||
import { Event, TeamEvent, UserEvent } from "@server/types";
|
||||
import UploadTeamAvatarTask from "../tasks/UploadTeamAvatarTask";
|
||||
import UploadUserAvatarTask from "../tasks/UploadUserAvatarTask";
|
||||
import BaseProcessor from "./BaseProcessor";
|
||||
|
||||
export default class AvatarProcessor extends BaseProcessor {
|
||||
static applicableEvents: Event["name"][] = ["users.create", "teams.create"];
|
||||
|
||||
async perform(event: UserEvent | TeamEvent) {
|
||||
// The uploads are performed in a separate task to allow for retrying in the
|
||||
// case of failures as it involves network calls to third party services.
|
||||
|
||||
if (event.name === "users.create") {
|
||||
const user = await User.findByPk(event.userId, {
|
||||
rejectOnEmpty: true,
|
||||
});
|
||||
|
||||
if (user.avatarUrl) {
|
||||
await UploadUserAvatarTask.schedule({
|
||||
userId: event.userId,
|
||||
avatarUrl: user.avatarUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (event.name === "teams.create") {
|
||||
const team = await Team.findByPk(event.teamId, {
|
||||
rejectOnEmpty: true,
|
||||
});
|
||||
|
||||
if (team.avatarUrl) {
|
||||
await UploadTeamAvatarTask.schedule({
|
||||
teamId: event.teamId,
|
||||
avatarUrl: team.avatarUrl,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -157,6 +157,9 @@ export default class DeliverWebhookTask extends BaseTask<Props> {
|
||||
case "integrations.update":
|
||||
await this.handleIntegrationEvent(subscription, event);
|
||||
return;
|
||||
case "teams.create":
|
||||
// Ignored
|
||||
return;
|
||||
case "teams.update":
|
||||
await this.handleTeamEvent(subscription, event);
|
||||
return;
|
||||
|
||||
40
server/queues/tasks/UploadTeamAvatarTask.ts
Normal file
40
server/queues/tasks/UploadTeamAvatarTask.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { Team } from "@server/models";
|
||||
import { uploadToS3FromUrl } from "@server/utils/s3";
|
||||
import BaseTask, { TaskPriority } from "./BaseTask";
|
||||
|
||||
type Props = {
|
||||
/* The teamId to operate on */
|
||||
teamId: string;
|
||||
/* The original avatarUrl from the SSO provider */
|
||||
avatarUrl: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A task that uploads the provided avatarUrl to S3 storage and updates the
|
||||
* team's record with the new url.
|
||||
*/
|
||||
export default class UploadTeamAvatarTask extends BaseTask<Props> {
|
||||
public async perform(props: Props) {
|
||||
const team = await Team.findByPk(props.teamId, {
|
||||
rejectOnEmpty: true,
|
||||
});
|
||||
|
||||
const avatarUrl = await uploadToS3FromUrl(
|
||||
props.avatarUrl,
|
||||
`avatars/${team.id}/${uuidv4()}`,
|
||||
"public-read"
|
||||
);
|
||||
|
||||
if (avatarUrl) {
|
||||
await team.update({ avatarUrl });
|
||||
}
|
||||
}
|
||||
|
||||
public get options() {
|
||||
return {
|
||||
attempts: 3,
|
||||
priority: TaskPriority.Normal,
|
||||
};
|
||||
}
|
||||
}
|
||||
40
server/queues/tasks/UploadUserAvatarTask.ts
Normal file
40
server/queues/tasks/UploadUserAvatarTask.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { User } from "@server/models";
|
||||
import { uploadToS3FromUrl } from "@server/utils/s3";
|
||||
import BaseTask, { TaskPriority } from "./BaseTask";
|
||||
|
||||
type Props = {
|
||||
/* The userId to operate on */
|
||||
userId: string;
|
||||
/* The original avatarUrl from the SSO provider */
|
||||
avatarUrl: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* A task that uploads the provided avatarUrl to S3 storage and updates the
|
||||
* user's record with the new url.
|
||||
*/
|
||||
export default class UploadUserAvatarTask extends BaseTask<Props> {
|
||||
public async perform(props: Props) {
|
||||
const user = await User.findByPk(props.userId, {
|
||||
rejectOnEmpty: true,
|
||||
});
|
||||
|
||||
const avatarUrl = await uploadToS3FromUrl(
|
||||
props.avatarUrl,
|
||||
`avatars/${user.id}/${uuidv4()}`,
|
||||
"public-read"
|
||||
);
|
||||
|
||||
if (avatarUrl) {
|
||||
await user.update({ avatarUrl });
|
||||
}
|
||||
}
|
||||
|
||||
public get options() {
|
||||
return {
|
||||
attempts: 3,
|
||||
priority: TaskPriority.Normal,
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user