Files
homelab/apps/platform-dash/src/app/api/s3/route.ts
Julia McGhee 970154769c
Some checks failed
CI / lint-and-test (push) Failing after 30s
CI / build (push) Has been skipped
Deploy Production / deploy (push) Failing after 1m31s
Add platform-dash: S3 artifact browser for Garage object store
Lightweight Next.js app for browsing, uploading, and downloading
artifacts from the cluster-local Garage S3 bucket. Uses the harness
design system. Features:
- File/folder browser with breadcrumb navigation
- Drag-and-drop upload
- Download and delete
- Ingress at platform.coreworlds.io (internal-only)

Also adds platform-dash to CI/deploy workflows.
2026-03-22 10:34:14 +00:00

91 lines
2.5 KiB
TypeScript

import { NextRequest, NextResponse } from "next/server";
import {
S3Client,
ListObjectsV2Command,
PutObjectCommand,
DeleteObjectCommand,
} from "@aws-sdk/client-s3";
const BUCKET = process.env.S3_BUCKET || "artifacts";
function getClient() {
return new S3Client({
endpoint: process.env.S3_ENDPOINT || "http://garage.platform.svc:3900",
region: process.env.S3_REGION || "garage",
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY_ID || "",
secretAccessKey: process.env.S3_SECRET_ACCESS_KEY || "",
},
forcePathStyle: true,
});
}
// GET — list objects
export async function GET(request: NextRequest) {
const prefix = request.nextUrl.searchParams.get("prefix") || "";
const client = getClient();
try {
const result = await client.send(
new ListObjectsV2Command({ Bucket: BUCKET, Prefix: prefix || undefined })
);
const objects = (result.Contents || []).map((obj) => ({
key: obj.Key,
size: obj.Size,
lastModified: obj.LastModified?.toISOString(),
}));
return NextResponse.json({ objects });
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : "S3 error";
return NextResponse.json({ error: msg }, { status: 500 });
}
}
// PUT — upload object
export async function PUT(request: NextRequest) {
const key = request.nextUrl.searchParams.get("key");
if (!key) {
return NextResponse.json({ error: "key required" }, { status: 400 });
}
const body = await request.arrayBuffer();
const client = getClient();
try {
await client.send(
new PutObjectCommand({
Bucket: BUCKET,
Key: key,
Body: Buffer.from(body),
ContentType: request.headers.get("content-type") || "application/octet-stream",
})
);
return NextResponse.json({ ok: true });
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : "Upload failed";
return NextResponse.json({ error: msg }, { status: 500 });
}
}
// DELETE — remove object
export async function DELETE(request: NextRequest) {
const key = request.nextUrl.searchParams.get("key");
if (!key) {
return NextResponse.json({ error: "key required" }, { status: 400 });
}
const client = getClient();
try {
await client.send(
new DeleteObjectCommand({ Bucket: BUCKET, Key: key })
);
return NextResponse.json({ ok: true });
} catch (e: unknown) {
const msg = e instanceof Error ? e.message : "Delete failed";
return NextResponse.json({ error: msg }, { status: 500 });
}
}