feat: add S3 avatar upload during user creation from social provider (#28)

* feat: add S3 avatar upload during user creation from social provider

* chore: remove console.log and update storage config for local development

* refactor: move avatar upload to after user creation and use user ID for filename

* feat: update avatar storage path
This commit is contained in:
LovelessCodes
2025-06-06 00:29:56 +02:00
committed by GitHub
parent 56b0161c6f
commit ce2ef1b1d4

View File

@@ -4,6 +4,7 @@ import { createAuthEndpoint, createAuthMiddleware } from "better-auth/api";
import { apiKey } from "better-auth/plugins";
import { magicLink } from "better-auth/plugins/magic-link";
import { env } from "next-runtime-env";
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
import type { dbClient } from "@kan/db/client";
import * as memberRepo from "@kan/db/repository/member.repo";
@@ -87,6 +88,14 @@ export const socialProvidersPlugin = () => ({
},
});
async function downloadImage(url: string): Promise<Buffer> {
const response = await fetch(url);
if (!response.ok) {
throw new Error(`Failed to download image: ${response.statusText}`);
}
return Buffer.from(await response.arrayBuffer());
}
export const initAuth = (db: dbClient) => {
return betterAuth({
secret: process.env.BETTER_AUTH_SECRET!,
@@ -136,6 +145,46 @@ export const initAuth = (db: dbClient) => {
},
}),
],
databaseHooks: {
user: {
create: {
async after(user, _context) {
if (user.image && !user.image.includes(process.env.NEXT_PUBLIC_STORAGE_DOMAIN!)) {
try {
const client = new S3Client({
region: env("S3_REGION") ?? "",
endpoint: env("S3_ENDPOINT") ?? "",
credentials: {
accessKeyId: env("S3_ACCESS_KEY_ID") ?? "",
secretAccessKey: env("S3_SECRET_ACCESS_KEY") ?? "",
},
});
const allowedFileExtensions = ["jpg", "jpeg", "png", "webp"];
const fileExtension = user.image.split('.').pop()?.split('?')[0] || 'jpg';
const key = `${user.id}/avatar.${!allowedFileExtensions.includes(fileExtension) ? 'jpg' : fileExtension}`;
const imageBuffer = await downloadImage(user.image);
await client.send(new PutObjectCommand({
Bucket: env("NEXT_PUBLIC_AVATAR_BUCKET_NAME") ?? "",
Key: key,
Body: imageBuffer,
ContentType: `image/${!allowedFileExtensions.includes(fileExtension) ? 'jpeg' : fileExtension}`,
ACL: 'public-read',
}));
await userRepo.update(db, user.id, {
image: key,
});
} catch (error) {
console.error(error);
}
}
}
}
}
},
hooks: {
after: createAuthMiddleware(async (ctx) => {
if (ctx.path.startsWith("/get-session")) {