feat: heic support
This commit is contained in:
@@ -75,6 +75,11 @@ export const de = {
|
||||
success: "Foto erfolgreich hochgeladen.",
|
||||
error: "Das Foto konnte nicht hochgeladen werden. Bitte versuche es erneut.",
|
||||
disabled: "Foto-Uploads sind geschlossen, da die RSVP-Frist abgelaufen ist.",
|
||||
requirements: "JPG, PNG oder WebP. Maximal 10 MB.",
|
||||
requirements: "JPG, JPEG, PNG, WebP, HEIC oder HEIF. Maximal 10 MB.",
|
||||
saved: "Aktuell hochgeladenes Foto",
|
||||
selected: "Ausgewähltes Foto — noch nicht hochgeladen",
|
||||
invalid: "Bitte wähle ein JPG-, JPEG-, PNG-, WebP-, HEIC- oder HEIF-Bild bis 10 MB.",
|
||||
refresh: "Das gespeicherte Foto konnte nicht geladen werden. Bitte aktualisiere die Seite.",
|
||||
previewUnavailable: "Dieses Format kann hier nicht angezeigt werden, wird aber nach dem Hochladen in JPEG umgewandelt.",
|
||||
},
|
||||
} as const
|
||||
|
||||
@@ -75,6 +75,11 @@ export const en = {
|
||||
success: "Photo uploaded successfully.",
|
||||
error: "We could not upload the photo. Please try again.",
|
||||
disabled: "Photo uploads are closed because the RSVP deadline has passed.",
|
||||
requirements: "JPG, PNG, or WebP. Maximum 10 MB.",
|
||||
requirements: "JPG, JPEG, PNG, WebP, HEIC, or HEIF. Maximum 10 MB.",
|
||||
saved: "Currently uploaded photo",
|
||||
selected: "Selected photo — not uploaded yet",
|
||||
invalid: "Please choose a JPG, JPEG, PNG, WebP, HEIC, or HEIF image up to 10 MB.",
|
||||
refresh: "The saved photo could not be loaded. Try refreshing the page.",
|
||||
previewUnavailable: "This format can't be previewed here, but it will be converted to a JPEG once uploaded.",
|
||||
},
|
||||
} as const
|
||||
|
||||
@@ -75,6 +75,11 @@ export const es = {
|
||||
success: "Foto subida correctamente.",
|
||||
error: "No pudimos subir la foto. Inténtalo de nuevo.",
|
||||
disabled: "La subida de fotos está cerrada porque ya pasó la fecha límite.",
|
||||
requirements: "JPG, PNG o WebP. Máximo 10 MB.",
|
||||
requirements: "JPG, JPEG, PNG, WebP, HEIC o HEIF. Máximo 10 MB.",
|
||||
saved: "Foto subida actualmente",
|
||||
selected: "Foto seleccionada — todavía no subida",
|
||||
invalid: "Elige una imagen JPG, JPEG, PNG, WebP, HEIC o HEIF de hasta 10 MB.",
|
||||
refresh: "No se pudo cargar la foto guardada. Intenta actualizar la página.",
|
||||
previewUnavailable: "Este formato no se puede previsualizar aquí, pero se convertirá a JPEG al subirlo.",
|
||||
},
|
||||
} as const
|
||||
|
||||
@@ -75,6 +75,11 @@ export const pl = {
|
||||
success: "Zdjęcie zostało przesłane.",
|
||||
error: "Nie udało się przesłać zdjęcia. Spróbuj ponownie.",
|
||||
disabled: "Przesyłanie zdjęć jest zamknięte, ponieważ termin odpowiedzi już minął.",
|
||||
requirements: "JPG, PNG lub WebP. Maksymalnie 10 MB.",
|
||||
requirements: "JPG, JPEG, PNG, WebP, HEIC lub HEIF. Maksymalnie 10 MB.",
|
||||
saved: "Aktualnie przesłane zdjęcie",
|
||||
selected: "Wybrane zdjęcie — jeszcze nieprzesłane",
|
||||
invalid: "Wybierz zdjęcie JPG, JPEG, PNG, WebP, HEIC lub HEIF o rozmiarze do 10 MB.",
|
||||
refresh: "Nie udało się załadować zapisanego zdjęcia. Odśwież stronę.",
|
||||
previewUnavailable: "Tego formatu nie można tu wyświetlić, ale po przesłaniu zostanie przekonwertowany na JPEG.",
|
||||
},
|
||||
} as const
|
||||
|
||||
+53
-1
@@ -1,4 +1,4 @@
|
||||
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"
|
||||
import { DeleteObjectCommand, GetObjectCommand, HeadObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3"
|
||||
import { getSignedUrl } from "@aws-sdk/s3-request-presigner"
|
||||
import { nanoid } from "nanoid"
|
||||
|
||||
@@ -61,6 +61,58 @@ export async function createPresignedDownloadUrl(objectKey: string) {
|
||||
)
|
||||
}
|
||||
|
||||
export async function downloadUploadObject(objectKey: string, maxSizeBytes: number) {
|
||||
const response = await getS3Client().send(new GetObjectCommand({
|
||||
Bucket: getUploadBucket(),
|
||||
Key: objectKey,
|
||||
}))
|
||||
const body = response.Body
|
||||
if (!body) throw new Error("Uploaded object has no body")
|
||||
|
||||
if ("transformToByteArray" in body && typeof body.transformToByteArray === "function") {
|
||||
const bytes = await body.transformToByteArray()
|
||||
if (bytes.byteLength > maxSizeBytes) throw new Error("Uploaded object is too large")
|
||||
return Buffer.from(bytes)
|
||||
}
|
||||
|
||||
const chunks: Buffer[] = []
|
||||
let size = 0
|
||||
for await (const chunk of body as AsyncIterable<Uint8Array | string>) {
|
||||
const buffer = Buffer.from(chunk)
|
||||
size += buffer.length
|
||||
if (size > maxSizeBytes) throw new Error("Uploaded object is too large")
|
||||
chunks.push(buffer)
|
||||
}
|
||||
return Buffer.concat(chunks, size)
|
||||
}
|
||||
|
||||
export async function putUploadObject({
|
||||
objectKey,
|
||||
contentType,
|
||||
body,
|
||||
}: {
|
||||
objectKey: string
|
||||
contentType: string
|
||||
body: Buffer
|
||||
}) {
|
||||
await getS3Client().send(new PutObjectCommand({
|
||||
Bucket: getUploadBucket(),
|
||||
Key: objectKey,
|
||||
Body: body,
|
||||
ContentType: contentType,
|
||||
ContentLength: body.length,
|
||||
}))
|
||||
}
|
||||
|
||||
export async function getUploadedObjectMetadata(objectKey: string) {
|
||||
const response = await getS3Client().send(new HeadObjectCommand({
|
||||
Bucket: getUploadBucket(),
|
||||
Key: objectKey,
|
||||
}))
|
||||
|
||||
return { contentType: response.ContentType, sizeBytes: response.ContentLength }
|
||||
}
|
||||
|
||||
export async function deleteUploadObject(objectKey: string) {
|
||||
const client = getS3Client()
|
||||
const bucket = getUploadBucket()
|
||||
|
||||
@@ -1,8 +1,33 @@
|
||||
import { z } from "zod"
|
||||
|
||||
export const allowedImageContentTypes = ["image/jpeg", "image/png", "image/webp"] as const
|
||||
export const allowedImageContentTypes = [
|
||||
"image/jpeg",
|
||||
"image/png",
|
||||
"image/webp",
|
||||
"image/heic",
|
||||
"image/heif",
|
||||
] as const
|
||||
export const allowedImageExtensions = [".jpg", ".jpeg", ".png", ".webp", ".heic", ".heif"] as const
|
||||
export const maxUploadSizeBytes = 10 * 1024 * 1024
|
||||
|
||||
export type AllowedImageContentType = (typeof allowedImageContentTypes)[number]
|
||||
|
||||
export function getUploadContentType(file: File): AllowedImageContentType | null {
|
||||
const type = file.type.toLowerCase()
|
||||
if (type === "image/jpg") return "image/jpeg"
|
||||
if (allowedImageContentTypes.includes(type as AllowedImageContentType)) {
|
||||
return type as AllowedImageContentType
|
||||
}
|
||||
|
||||
const extension = `.${file.name.split(".").pop()?.toLowerCase() ?? ""}`
|
||||
if ([".jpg", ".jpeg"].includes(extension)) return "image/jpeg"
|
||||
if (extension === ".png") return "image/png"
|
||||
if (extension === ".webp") return "image/webp"
|
||||
if (extension === ".heic") return "image/heic"
|
||||
if (extension === ".heif") return "image/heif"
|
||||
return null
|
||||
}
|
||||
|
||||
export const presignUploadSchema = z.object({
|
||||
token: z.string().min(1),
|
||||
filename: z.string().trim().min(1).max(255),
|
||||
|
||||
Reference in New Issue
Block a user