release: v0.16.0

See CHANGELOG.md for the full v0.16.0 entry.

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: ralksta <ralksta@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Ralf <rohlenbo79@gmail.com>
Co-authored-by: Paul Thies <110185070+ImScheinox@users.noreply.github.com>
Co-authored-by: lancetm714 <108419425+lancetm714@users.noreply.github.com>
This commit is contained in:
Ralksta
2026-09-22 14:12:13 +02:00
committed by GitHub
parent c3f92ca4ec
commit a3a6319c29
120 changed files with 6818 additions and 975 deletions

View File

@@ -1,7 +1,8 @@
import { NextResponse } from 'next/server';
import { withAdmin } from '@/lib/admin/withAdmin';
import { promises as fs } from 'fs';
import path from 'path';
import { isAdminAuthenticated, isAdminEnabled } from '@/lib/admin/auth';
import { atomicWrite } from '@/lib/atomicWrite';
const CONTENT_DIR = path.resolve(process.cwd(), 'content');
const DEST = 'favicon.svg';
@@ -16,24 +17,10 @@ const MAX_SIZE = 512 * 1024; // 512 kB
async function writeFavicon(content: Buffer | string): Promise<void> {
await fs.mkdir(CONTENT_DIR, { recursive: true });
const dest = path.join(CONTENT_DIR, DEST);
const tmpPath = `${dest}.${process.pid}.${Date.now()}.tmp`;
try {
await fs.writeFile(tmpPath, content);
await fs.rename(tmpPath, dest);
} catch (err) {
await fs.unlink(tmpPath).catch(() => {});
throw err;
}
await atomicWrite(dest, content);
}
export async function PUT(request: Request) {
if (!isAdminEnabled()) {
return NextResponse.json({ error: 'Admin not enabled' }, { status: 403 });
}
if (!(await isAdminAuthenticated())) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
export const PUT = withAdmin(async (request: Request) => {
let body: FormData;
try {
body = await request.formData();
@@ -88,17 +75,10 @@ export async function PUT(request: Request) {
await writeFavicon(svg);
return NextResponse.json({ success: true, message: 'Favicon updated.' });
}
});
/** Clean up stale favicon files left by a previous upload of a different format. */
export async function DELETE() {
if (!isAdminEnabled()) {
return NextResponse.json({ error: 'Admin not enabled' }, { status: 403 });
}
if (!(await isAdminAuthenticated())) {
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
}
export const DELETE = withAdmin(async () => {
try {
await fs.unlink(path.join(CONTENT_DIR, DEST));
return NextResponse.json({
@@ -108,4 +88,4 @@ export async function DELETE() {
} catch {
return NextResponse.json({ error: 'No custom favicon to remove' }, { status: 404 });
}
}
});