store profile avatars in R2
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
2026-07-08 11:40:50 +09:00
parent 0112956835
commit 19b8e40b72
7 changed files with 171 additions and 7 deletions
+40 -2
View File
@@ -1,5 +1,12 @@
import { config } from '../config'
import { prisma } from '../prisma'
import {
deletePublicFile,
isR2PublicFilesConfigured,
publicFileUrl,
uploadPublicFile,
userAvatarKey
} from '../storage/r2-public-files'
import { hashPassword, verifyPassword } from '../security/password'
import { redis } from '../redis'
import { createOpaqueToken, hashToken } from '../security/tokens'
@@ -385,6 +392,24 @@ export async function changeUserPassword(userId: string, currentPassword: string
}
export async function updateUserProfileImage(userId: string, imageData: Uint8Array, mimeType: string): Promise<void> {
if (isR2PublicFilesConfigured()) {
await uploadPublicFile({
key: userAvatarKey(userId),
body: imageData,
contentType: mimeType
})
await prisma.user.update({
where: { id: userId },
data: {
profileImageData: null,
profileImageMime: mimeType,
profileImageUpdatedAt: new Date()
}
})
return
}
await prisma.user.update({
where: { id: userId },
data: {
@@ -396,6 +421,8 @@ export async function updateUserProfileImage(userId: string, imageData: Uint8Arr
}
export async function deleteUserProfileImage(userId: string): Promise<void> {
await deletePublicFile(userAvatarKey(userId)).catch(() => undefined)
await prisma.user.update({
where: { id: userId },
data: {
@@ -406,7 +433,7 @@ export async function deleteUserProfileImage(userId: string): Promise<void> {
})
}
export async function findUserProfileImage(userId: string): Promise<{ data: Buffer; mimeType: string; updatedAt: Date | null } | null> {
export async function findUserProfileImage(userId: string): Promise<{ data?: Buffer; url?: string; mimeType: string; updatedAt: Date | null } | null> {
const user = await prisma.user.findFirst({
where: { id: userId, isActive: true },
select: {
@@ -416,7 +443,18 @@ export async function findUserProfileImage(userId: string): Promise<{ data: Buff
}
})
if (!user?.profileImageData || !user.profileImageMime) return null
if (!user?.profileImageMime) return null
const storedUrl = publicFileUrl(userAvatarKey(userId))
if (!user.profileImageData && storedUrl) {
return {
url: storedUrl,
mimeType: user.profileImageMime,
updatedAt: user.profileImageUpdatedAt
}
}
if (!user.profileImageData) return null
return {
data: Buffer.from(user.profileImageData),