Initial Commit

This commit is contained in:
Anders Böttcher
2026-05-11 23:39:16 +02:00
parent 9dde8d8804
commit 50566f2a22
38 changed files with 5840 additions and 1015 deletions
@@ -0,0 +1,39 @@
import { NextResponse } from 'next/server'
import { getFile, getUploadPath } from '@/lib/store'
import fs from 'fs'
export async function GET(
_request: Request,
{ params }: { params: Promise<{ token: string }> },
) {
const { token } = await params
const record = await getFile(token)
if (!record) {
return NextResponse.json({ error: 'Not found' }, { status: 404 })
}
const filePath = getUploadPath(token, record.originalName)
if (!fs.existsSync(filePath)) {
return NextResponse.json({ error: 'File missing on disk' }, { status: 404 })
}
const stream = fs.createReadStream(filePath)
const webStream = new ReadableStream({
start(controller) {
stream.on('data', (chunk) => controller.enqueue(chunk))
stream.on('end', () => controller.close())
stream.on('error', (err) => controller.error(err))
},
cancel() {
stream.destroy()
},
})
return new Response(webStream, {
headers: {
'Content-Type': 'application/octet-stream',
'Content-Disposition': `attachment; filename="${record.originalName}"`,
'Content-Length': String(record.size),
},
})
}
+14
View File
@@ -0,0 +1,14 @@
import { NextResponse } from 'next/server'
import { getFile } from '@/lib/store'
export async function GET(
_request: Request,
{ params }: { params: Promise<{ token: string }> },
) {
const { token } = await params
const record = await getFile(token)
if (!record) {
return NextResponse.json({ error: 'Not found' }, { status: 404 })
}
return NextResponse.json(record)
}
+7
View File
@@ -0,0 +1,7 @@
import { NextResponse } from 'next/server'
import { readMeta } from '@/lib/store'
export async function GET() {
const records = await readMeta()
return NextResponse.json(records)
}
+70
View File
@@ -0,0 +1,70 @@
import { NextResponse } from 'next/server'
import { nanoid } from 'nanoid'
import { addFile } from '@/lib/store'
import type { FileRecord } from '@/lib/store'
const ALLOWED_EXTENSIONS = ['.backerup', '.json']
const MAX_SIZE_BYTES = 500 * 1024 * 1024 // 500 MB
export async function POST(request: Request) {
let formData: FormData
try {
formData = await request.formData()
} catch {
return NextResponse.json({ error: 'Invalid form data' }, { status: 400 })
}
const file = formData.get('file')
if (!(file instanceof File)) {
return NextResponse.json({ error: 'No file provided' }, { status: 400 })
}
const nameLower = file.name.toLowerCase()
if (!ALLOWED_EXTENSIONS.some((ext) => nameLower.endsWith(ext))) {
return NextResponse.json(
{ error: 'Only .backerup and .json files are accepted' },
{ status: 400 },
)
}
const buffer = Buffer.from(await file.arrayBuffer())
if (buffer.byteLength > MAX_SIZE_BYTES) {
return NextResponse.json({ error: 'File exceeds 500 MB limit' }, { status: 413 })
}
const type: FileRecord['type'] = nameLower.endsWith('.backerup') ? 'backerup' : 'config'
const description = String(formData.get('description') ?? '').trim() || undefined
const displayName = String(formData.get('displayName') ?? '').trim() || undefined
const group = String(formData.get('group') ?? '').trim() || undefined
let containerName: string | undefined
let image: string | undefined
if (type === 'config') {
try {
const raw = JSON.parse(buffer.toString('utf-8')) as Record<string, unknown>
const name = String((raw.Name as string | undefined) ?? '').replace(/^\//, '')
containerName = name || undefined
const cfg = raw.Config as Record<string, unknown> | undefined
image = (cfg?.Image as string | undefined) || undefined
} catch {
// Not valid JSON — still accept the file
}
}
const token = nanoid(10)
const record: FileRecord = {
token,
originalName: file.name,
displayName,
size: buffer.byteLength,
type,
uploadedAt: new Date().toISOString(),
description,
group,
containerName,
image,
}
await addFile(record, buffer)
return NextResponse.json({ token })
}