forked from BRETT/BRETT
Initial Commit
This commit is contained in:
@@ -0,0 +1,158 @@
|
||||
import { mkdir, writeFile } from 'node:fs/promises'
|
||||
import { basename, extname, join } from 'node:path'
|
||||
import { z } from 'zod'
|
||||
import { uploads } from '../../db/schema'
|
||||
|
||||
const MAX_SIZE = MAX_UPLOAD_SIZE
|
||||
|
||||
const MIME_EXT: Record<string, string> = {
|
||||
'image/jpeg': '.jpg',
|
||||
'image/png': '.png',
|
||||
'image/gif': '.gif',
|
||||
'image/webp': '.webp',
|
||||
'image/avif': '.avif',
|
||||
'video/mp4': '.mp4',
|
||||
'video/webm': '.webm',
|
||||
'video/quicktime': '.mov',
|
||||
'audio/mpeg': '.mp3',
|
||||
'audio/mp3': '.mp3',
|
||||
'audio/flac': '.flac',
|
||||
'audio/x-flac': '.flac',
|
||||
}
|
||||
|
||||
// Fallback, wenn der Browser keinen MIME-Type mitschickt
|
||||
const EXT_MIME: Record<string, string> = {
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp',
|
||||
'.avif': 'image/avif',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.mp4': 'video/mp4',
|
||||
'.webm': 'video/webm',
|
||||
'.mov': 'video/quicktime',
|
||||
'.mp3': 'audio/mpeg',
|
||||
'.flac': 'audio/flac',
|
||||
}
|
||||
|
||||
const urlSchema = z.object({
|
||||
url: z.url().max(2048),
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const user = await requireUser(event)
|
||||
const db = useDb()
|
||||
|
||||
if ((getRequestHeader(event, 'content-type') ?? '').includes('application/json')) {
|
||||
const body = await readValidatedBody(event, urlSchema.parse)
|
||||
if (!/^https?:\/\//i.test(body.url)) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Nur http(s)-URLs erlaubt' })
|
||||
}
|
||||
const type = classifyUrl(body.url)
|
||||
const id = await newUploadId(db)
|
||||
|
||||
// Direkte Bild-/Video-URLs werden heruntergeladen und lokal gespeichert,
|
||||
// als wäre die Datei direkt hochgeladen worden
|
||||
if (type === 'image' || type === 'video' || type === 'audio') {
|
||||
const media = await downloadExternalMedia(body.url)
|
||||
const urlExt = extname(new URL(body.url).pathname).toLowerCase().replace(/[^a-z0-9.]/g, '').slice(0, 10)
|
||||
const ext = MIME_EXT[media.mimeType] ?? urlExt
|
||||
const fileName = `${id}${ext}`
|
||||
await mkdir(uploadDir(), { recursive: true })
|
||||
await writeFile(join(uploadDir(), fileName), media.data)
|
||||
const audioMeta = media.type === 'audio'
|
||||
? await extractAudioMeta(id, media.data, media.mimeType)
|
||||
: null
|
||||
await db.insert(uploads).values({
|
||||
id,
|
||||
userId: user.id,
|
||||
type: media.type,
|
||||
fileName,
|
||||
originalName: decodeURIComponent(basename(new URL(body.url).pathname) || body.url).slice(0, 255),
|
||||
mimeType: media.mimeType.slice(0, 100),
|
||||
size: media.data.length,
|
||||
previewImage: audioMeta?.cover ?? null,
|
||||
audioMeta: audioMeta?.meta ?? null,
|
||||
})
|
||||
await ensureThreadSubscription(id, user.id)
|
||||
return { id, type: media.type, url: `/uploads/${fileName}` }
|
||||
}
|
||||
|
||||
const twitchTarget = type === 'twitch' ? getTwitchTarget(body.url) : null
|
||||
const [preview, twitchPreview] = await Promise.all([
|
||||
type === 'link' || type === 'twitch' ? fetchLinkPreview(body.url) : null,
|
||||
twitchTarget ? fetchTwitchPreview(twitchTarget) : null,
|
||||
])
|
||||
const remotePreviewImage = twitchPreview?.image ?? preview?.image ?? null
|
||||
const previewImage = remotePreviewImage
|
||||
? (await savePreviewImage(id, remotePreviewImage)) ?? remotePreviewImage
|
||||
: null
|
||||
await db.insert(uploads).values({
|
||||
id,
|
||||
userId: user.id,
|
||||
type,
|
||||
externalUrl: body.url,
|
||||
originalName: body.url.slice(0, 255),
|
||||
previewImage,
|
||||
previewTitle: twitchPreview?.title ?? preview?.title ?? null,
|
||||
})
|
||||
await ensureThreadSubscription(id, user.id)
|
||||
return { id, type, url: body.url }
|
||||
}
|
||||
|
||||
const parts = await readMultipartFormData(event)
|
||||
const file = parts?.find(p => p.name === 'file')
|
||||
|
||||
if (!file?.data?.length) {
|
||||
throw createError({ statusCode: 400, statusMessage: 'Keine Datei hochgeladen' })
|
||||
}
|
||||
|
||||
// Manche Formate kommen ohne MIME-Type (z. B. .flac) — dann per Endung klassifizieren
|
||||
const fileExt = extname(file.filename ?? '').toLowerCase()
|
||||
const rawMime = file.type ?? ''
|
||||
const mimeType = rawMime && rawMime !== 'application/octet-stream' ? rawMime : (EXT_MIME[fileExt] ?? rawMime)
|
||||
const type = mimeType.startsWith('image/') ? 'image'
|
||||
: mimeType.startsWith('video/') ? 'video'
|
||||
: mimeType.startsWith('audio/') ? 'audio'
|
||||
: IMAGE_EXT.test(fileExt) ? 'image'
|
||||
: VIDEO_EXT.test(fileExt) ? 'video'
|
||||
: AUDIO_EXT.test(fileExt) ? 'audio'
|
||||
: null
|
||||
if (!type) {
|
||||
throw createError({ statusCode: 415, statusMessage: 'Nur Bilder, Videos und Audiodateien sind erlaubt' })
|
||||
}
|
||||
if (file.data.length > MAX_SIZE) {
|
||||
throw createError({ statusCode: 413, statusMessage: 'Datei ist zu groß (max. 200 MB)' })
|
||||
}
|
||||
|
||||
const ext = MIME_EXT[mimeType]
|
||||
?? fileExt.replace(/[^a-z0-9.]/g, '').slice(0, 10)
|
||||
?? ''
|
||||
const id = await newUploadId(db)
|
||||
const fileName = `${id}${ext}`
|
||||
|
||||
await mkdir(uploadDir(), { recursive: true })
|
||||
await writeFile(join(uploadDir(), fileName), file.data)
|
||||
const audioMeta = type === 'audio'
|
||||
? await extractAudioMeta(id, file.data, mimeType)
|
||||
: null
|
||||
await db.insert(uploads).values({
|
||||
id,
|
||||
userId: user.id,
|
||||
type,
|
||||
fileName,
|
||||
originalName: (file.filename ?? fileName).slice(0, 255),
|
||||
mimeType: (mimeType || 'application/octet-stream').slice(0, 100),
|
||||
size: file.data.length,
|
||||
previewImage: audioMeta?.cover ?? null,
|
||||
audioMeta: audioMeta?.meta ?? null,
|
||||
})
|
||||
|
||||
await ensureThreadSubscription(id, user.id)
|
||||
return {
|
||||
id,
|
||||
type,
|
||||
url: `/uploads/${fileName}`,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user