9888b1b5d7
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>
49 lines
1.6 KiB
TypeScript
49 lines
1.6 KiB
TypeScript
import 'server-only'
|
|
import fs from 'fs'
|
|
import path from 'path'
|
|
|
|
function storageRoot(): string {
|
|
return process.env.DATAROOM_PATH || '/data/dataroom'
|
|
}
|
|
|
|
export function adminDocDir(documentId: string): string {
|
|
return path.join(storageRoot(), 'admin', documentId)
|
|
}
|
|
|
|
export function investorUploadDir(investorId: string, uploadId: string): string {
|
|
return path.join(storageRoot(), 'investors', investorId, uploadId)
|
|
}
|
|
|
|
export async function ensureDir(dir: string): Promise<void> {
|
|
await fs.promises.mkdir(dir, { recursive: true })
|
|
}
|
|
|
|
export async function saveFile(dir: string, filename: string, buffer: Buffer): Promise<string> {
|
|
await ensureDir(dir)
|
|
const filePath = path.join(dir, filename)
|
|
await fs.promises.writeFile(filePath, buffer)
|
|
return filePath
|
|
}
|
|
|
|
export async function removeDir(dir: string): Promise<void> {
|
|
await fs.promises.rm(dir, { recursive: true, force: true })
|
|
}
|
|
|
|
export async function streamFile(filePath: string): Promise<{ stream: ReadableStream; size: number }> {
|
|
const stat = await fs.promises.stat(filePath)
|
|
const nodeStream = fs.createReadStream(filePath)
|
|
const stream = new ReadableStream({
|
|
start(controller) {
|
|
nodeStream.on('data', (chunk) => controller.enqueue(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)))
|
|
nodeStream.on('end', () => controller.close())
|
|
nodeStream.on('error', (err) => controller.error(err))
|
|
},
|
|
cancel() { nodeStream.destroy() },
|
|
})
|
|
return { stream, size: stat.size }
|
|
}
|
|
|
|
export function safeName(original: string): string {
|
|
return original.replace(/[^a-zA-Z0-9._-]/g, '_').slice(0, 200)
|
|
}
|