feat(pitch-deck): data room — file sharing and investor uploads
Build pitch-deck / build-push-deploy (push) Successful in 1m21s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 32s
Build pitch-deck / build-push-deploy (push) Successful in 1m21s
CI / go-lint (push) Has been skipped
CI / python-lint (push) Has been skipped
CI / nodejs-lint (push) Has been skipped
CI / test-go-consent (push) Successful in 31s
CI / test-python-voice (push) Successful in 33s
CI / test-bqas (push) Successful in 32s
- lib/dataroom-storage.ts: local volume storage (DATAROOM_PATH env var, default /data/dataroom) replacing NextCloud WebDAV - Admin API: upload documents, rename, delete, manage per-investor releases - Investor API: list released documents, stream download with audit log, upload own documents (max DATAROOM_MAX_UPLOAD_MB, default 50MB) - /pitch-admin/dataroom: document list + release toggles + investor uploads tab - /dataroom: investor-facing document library + upload section - All reads and writes logged to pitch_audit_logs - Migration 005: dataroom_documents, dataroom_releases, dataroom_investor_uploads - AdminShell: Data Room nav link (FolderOpen icon) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import pool from '@/lib/db'
|
||||
import { streamFile } from '@/lib/dataroom-storage'
|
||||
import { logAudit } from '@/lib/auth'
|
||||
import path from 'path'
|
||||
|
||||
interface Ctx { params: Promise<{ id: string }> }
|
||||
|
||||
export async function GET(request: NextRequest, ctx: Ctx) {
|
||||
const investorId = request.headers.get('x-investor-id')
|
||||
const sessionId = request.headers.get('x-session-id')
|
||||
if (!investorId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
|
||||
const { id } = await ctx.params
|
||||
|
||||
// Verify investor has a release for this document
|
||||
const { rows } = await pool.query(
|
||||
`SELECT d.file_path, d.filename, d.mime_type, d.display_name
|
||||
FROM dataroom_releases r
|
||||
JOIN dataroom_documents d ON d.id = r.document_id
|
||||
WHERE r.investor_id = $1 AND d.id = $2`,
|
||||
[investorId, id],
|
||||
)
|
||||
if (rows.length === 0) return NextResponse.json({ error: 'Not found' }, { status: 404 })
|
||||
|
||||
const doc = rows[0]
|
||||
|
||||
await logAudit(investorId, 'dataroom_document_downloaded', { document_id: id, filename: doc.filename }, request, undefined, sessionId ?? undefined)
|
||||
|
||||
const { stream, size } = await streamFile(doc.file_path)
|
||||
const disposition = request.nextUrl.searchParams.get('preview') === '1' ? 'inline' : 'attachment'
|
||||
|
||||
return new Response(stream, {
|
||||
headers: {
|
||||
'Content-Type': doc.mime_type || 'application/octet-stream',
|
||||
'Content-Disposition': `${disposition}; filename="${path.basename(doc.filename)}"`,
|
||||
'Content-Length': String(size),
|
||||
},
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
import { NextRequest, NextResponse } from 'next/server'
|
||||
import pool from '@/lib/db'
|
||||
|
||||
export const dynamic = 'force-dynamic'
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const investorId = request.headers.get('x-investor-id')
|
||||
if (!investorId) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
|
||||
|
||||
const { rows } = await pool.query(
|
||||
`SELECT d.id, d.filename, d.display_name, d.mime_type, d.file_size, r.released_at
|
||||
FROM dataroom_releases r
|
||||
JOIN dataroom_documents d ON d.id = r.document_id
|
||||
WHERE r.investor_id = $1
|
||||
ORDER BY r.released_at DESC`,
|
||||
[investorId],
|
||||
)
|
||||
return NextResponse.json({ documents: rows })
|
||||
}
|
||||
Reference in New Issue
Block a user