fix: singing bowls ambient + Next.js 16 async params/cookies
Ambient:
- Replace oscillator drone/melody with singing bowl synthesis
- Each bowl: instant attack + exponential decay (7–12s) = real physical sound
- Two detuned copies per strike for natural shimmer beat
- A-minor pentatonic (A3 C4 D4 E4 G4 A4 C5 E5), weighted toward warm lows
- 30% chance of soft harmonic fifth companion tone per strike
- Random gaps 3–8s between strikes so it breathes naturally
- Two long hall reverb tails (2.4s / 4.8s) for warmth and space
- Graceful 2.5s fade-out on stop
Next.js 16 compatibility (breaking changes from v14→v16):
- Dynamic route params now Promise<{...}> → await params in all handlers
- cookies() now returns Promise → isAdmin() made async, await cookies()
- Files: [...path], memories/[id], media/[id], auth, upload all updated
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -9,7 +9,7 @@ function getExpectedToken() {
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const cookieStore = cookies()
|
||||
const cookieStore = await cookies()
|
||||
const token = cookieStore.get('admin_auth')?.value
|
||||
return NextResponse.json({ authed: token === getExpectedToken() })
|
||||
}
|
||||
|
||||
@@ -25,9 +25,10 @@ const MIME: Record<string, string> = {
|
||||
|
||||
export async function GET(
|
||||
request: NextRequest,
|
||||
{ params }: { params: { path: string[] } }
|
||||
{ params }: { params: Promise<{ path: string[] }> }
|
||||
) {
|
||||
const filePath = path.join(DATA_DIR, 'uploads', ...params.path)
|
||||
const { path: segments } = await params
|
||||
const filePath = path.join(DATA_DIR, 'uploads', ...segments)
|
||||
|
||||
// Prevent path traversal
|
||||
const uploadsBase = path.join(DATA_DIR, 'uploads')
|
||||
|
||||
@@ -6,8 +6,9 @@ import { unlink } from 'fs/promises'
|
||||
import { existsSync } from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
function isAdmin() {
|
||||
const token = cookies().get('admin_auth')?.value
|
||||
async function isAdmin() {
|
||||
const cookieStore = await cookies()
|
||||
const token = cookieStore.get('admin_auth')?.value
|
||||
const expected = createHash('sha256')
|
||||
.update(process.env.ADMIN_PASSWORD || 'change-me')
|
||||
.digest('hex')
|
||||
@@ -18,14 +19,15 @@ const DATA_DIR = process.env.DATA_DIR || path.join(process.cwd(), 'data')
|
||||
|
||||
export async function DELETE(
|
||||
_req: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
if (!isAdmin()) {
|
||||
if (!await isAdmin()) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
const db = getDb()
|
||||
const media = db.prepare('SELECT * FROM media WHERE id = ?').get(params.id) as
|
||||
const media = db.prepare('SELECT * FROM media WHERE id = ?').get(id) as
|
||||
| { filename: string }
|
||||
| undefined
|
||||
|
||||
@@ -33,12 +35,11 @@ export async function DELETE(
|
||||
return NextResponse.json({ error: 'Not found' }, { status: 404 })
|
||||
}
|
||||
|
||||
// Delete file from disk
|
||||
const filePath = path.join(DATA_DIR, 'uploads', media.filename)
|
||||
if (existsSync(filePath)) {
|
||||
await unlink(filePath)
|
||||
}
|
||||
|
||||
db.prepare('DELETE FROM media WHERE id = ?').run(params.id)
|
||||
db.prepare('DELETE FROM media WHERE id = ?').run(id)
|
||||
return NextResponse.json({ success: true })
|
||||
}
|
||||
|
||||
@@ -3,8 +3,9 @@ import { getDb } from '@/lib/db'
|
||||
import { cookies } from 'next/headers'
|
||||
import { createHash } from 'crypto'
|
||||
|
||||
function isAdmin() {
|
||||
const token = cookies().get('admin_auth')?.value
|
||||
async function isAdmin() {
|
||||
const cookieStore = await cookies()
|
||||
const token = cookieStore.get('admin_auth')?.value
|
||||
const expected = createHash('sha256')
|
||||
.update(process.env.ADMIN_PASSWORD || 'change-me')
|
||||
.digest('hex')
|
||||
@@ -13,30 +14,32 @@ function isAdmin() {
|
||||
|
||||
export async function PUT(
|
||||
req: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
if (!isAdmin()) {
|
||||
if (!await isAdmin()) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
const { title, content } = await req.json()
|
||||
const db = getDb()
|
||||
db.prepare(
|
||||
"UPDATE memories SET title = ?, content = ?, updated_at = datetime('now') WHERE id = ?"
|
||||
).run(title, content, params.id)
|
||||
const memory = db.prepare('SELECT * FROM memories WHERE id = ?').get(params.id)
|
||||
).run(title, content, id)
|
||||
const memory = db.prepare('SELECT * FROM memories WHERE id = ?').get(id)
|
||||
return NextResponse.json(memory)
|
||||
}
|
||||
|
||||
export async function DELETE(
|
||||
_req: NextRequest,
|
||||
{ params }: { params: { id: string } }
|
||||
{ params }: { params: Promise<{ id: string }> }
|
||||
) {
|
||||
if (!isAdmin()) {
|
||||
if (!await isAdmin()) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
const { id } = await params
|
||||
const db = getDb()
|
||||
db.prepare('DELETE FROM memories WHERE id = ?').run(params.id)
|
||||
db.prepare('DELETE FROM memories WHERE id = ?').run(id)
|
||||
return NextResponse.json({ success: true })
|
||||
}
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import { getDb } from '@/lib/db'
|
||||
import { cookies } from 'next/headers'
|
||||
import { createHash } from 'crypto'
|
||||
|
||||
function isAdmin() {
|
||||
const token = cookies().get('admin_auth')?.value
|
||||
const expected = createHash('sha256')
|
||||
.update(process.env.ADMIN_PASSWORD || 'change-me')
|
||||
.digest('hex')
|
||||
return token === expected
|
||||
}
|
||||
|
||||
export async function GET() {
|
||||
const db = getDb()
|
||||
|
||||
@@ -8,8 +8,9 @@ import { getDb } from '@/lib/db'
|
||||
export const runtime = 'nodejs'
|
||||
export const maxDuration = 60
|
||||
|
||||
function isAdmin() {
|
||||
const token = cookies().get('admin_auth')?.value
|
||||
async function isAdmin() {
|
||||
const cookieStore = await cookies()
|
||||
const token = cookieStore.get('admin_auth')?.value
|
||||
const expected = createHash('sha256')
|
||||
.update(process.env.ADMIN_PASSWORD || 'change-me')
|
||||
.digest('hex')
|
||||
@@ -46,7 +47,7 @@ const FOLDER_TO_TYPE: Record<string, 'photo' | 'video' | 'music'> = {
|
||||
}
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
if (!isAdmin()) {
|
||||
if (!await isAdmin()) {
|
||||
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
}
|
||||
|
||||
@@ -58,11 +59,9 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: 'Keine Datei' }, { status: 400 })
|
||||
}
|
||||
|
||||
// Try to detect type from mime or extension
|
||||
let mimeType = file.type?.toLowerCase() || ''
|
||||
const ext = path.extname(file.name).toLowerCase()
|
||||
|
||||
// iOS HEIC fallback
|
||||
if (!mimeType && (ext === '.heic' || ext === '.heif')) {
|
||||
mimeType = 'image/heic'
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user