Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 | 6x 6x 6x 7x 1x 6x 6x 1x 5x 5x 1x 4x 1x 3x 1x 2x 6x 8x 8x 1x 1x 7x 7x 7x 7x 1x 1x 1x | import { TRPCError } from "@trpc/server";
import { imageSize } from "image-size";
import { avatarFormSchema } from "~app/utils/validation";
import { MAX_AVATAR_BYTESIZE, MAX_AVATAR_SIDE_SIZE } from "~utils/images";
import { authProcedure } from "~web/handlers/trpc";
import { getS3Client } from "~web/providers/s3";
export const S3_AVATAR_PREFIX = "avatars";
const ALLOWED_FORMATS = new Set(["png", "jpg"]);
const validateImage = (image: Buffer) => {
if (image.byteLength > MAX_AVATAR_BYTESIZE) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Maximum bytesize allowed is ${MAX_AVATAR_BYTESIZE}.`,
});
}
const metadata = imageSize(image);
if (!metadata.type || !ALLOWED_FORMATS.has(metadata.type)) {
throw new TRPCError({
code: "BAD_REQUEST",
/* c8 ignore next */
message: `Format "${metadata.type || "unknown"}" is not allowed.`,
});
}
const maxSizeAllowed = MAX_AVATAR_SIDE_SIZE;
if (metadata.height > maxSizeAllowed) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Maximum height allowed is ${maxSizeAllowed}.`,
});
}
if (metadata.width > maxSizeAllowed) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Maximum width allowed is ${maxSizeAllowed}.`,
});
}
if (metadata.width !== metadata.height) {
throw new TRPCError({
code: "BAD_REQUEST",
message: `Expected to have equal height and width, got ${metadata.width}x${metadata.height}.`,
});
}
return image;
};
export const procedure = authProcedure
.meta({
title: "Change user avatar",
description:
"Uploads and sets a new avatar image for the current user, or clears it if no avatar is provided.",
})
.input(avatarFormSchema)
.mutation(async ({ input, ctx }) => {
const { database } = ctx;
if (!input.avatar) {
await database
.updateTable("users")
.set({ avatarUrl: null })
.where("users.id", "=", ctx.auth.userId)
.executeTakeFirst();
return;
}
const s3Client = getS3Client(ctx);
const validatedImage = validateImage(
Buffer.from(await input.avatar.arrayBuffer()),
);
const avatarKey = [S3_AVATAR_PREFIX, `${ctx.auth.userId}.png`].join("/");
await s3Client.putObject(avatarKey, validatedImage);
const url = `${[s3Client.endpoint, s3Client.bucket, avatarKey].join(
"/",
)}?lastModified=${Temporal.Now.instant().epochMilliseconds}`;
await database
.updateTable("users")
.set({ avatarUrl: url })
.where("users.id", "=", ctx.auth.userId)
.executeTakeFirst();
return { url };
});
|