repositories
loading repo index
repositories
loading repo index
repository
loading code, commits, and activity
The Living OS cockpit
stars
latest
clone command
git clone gitlawb://did:key:z6Mku78K...XywC/living-os-cockp...git clone gitlawb://did:key:z6Mku78K.../living-os-cockp...59751530feat: surface worker supervisor health in live work8h ago| #1 | import { promises as fs } from 'fs'; |
| #2 | import path from 'path'; |
| #3 | import { NextRequest, NextResponse } from 'next/server'; |
| #4 | import { authErrorResponse, getUserContext } from '@/lib/user-context'; |
| #5 | |
| #6 | export const runtime = 'nodejs'; |
| #7 | |
| #8 | const DOCUMENT_STORE_ROOT = '/home/kingbau/.local/share/living-os-cockpit/documents'; |
| #9 | |
| #10 | type StoredDocument = { |
| #11 | id: string; |
| #12 | templateId: string; |
| #13 | title: string; |
| #14 | trustName: string; |
| #15 | trustId: string; |
| #16 | displayName: string; |
| #17 | registryNumber: string; |
| #18 | generatedAt: string; |
| #19 | createdAt: string; |
| #20 | sourceContent?: string; |
| #21 | }; |
| #22 | |
| #23 | function storePath(userId: string) { |
| #24 | return path.join(DOCUMENT_STORE_ROOT, `${userId}.json`); |
| #25 | } |
| #26 | |
| #27 | async function readDocuments(userId: string): Promise<StoredDocument[]> { |
| #28 | try { |
| #29 | const data = JSON.parse(await fs.readFile(storePath(userId), 'utf8')) as StoredDocument[]; |
| #30 | return Array.isArray(data) ? data : []; |
| #31 | } catch { |
| #32 | return []; |
| #33 | } |
| #34 | } |
| #35 | |
| #36 | async function writeDocuments(userId: string, docs: StoredDocument[]) { |
| #37 | await fs.mkdir(DOCUMENT_STORE_ROOT, { recursive: true }); |
| #38 | const filePath = storePath(userId); |
| #39 | await fs.writeFile(filePath, `${JSON.stringify(docs, null, 2)}\n`, 'utf8'); |
| #40 | await fs.chmod(filePath, 0o600); |
| #41 | } |
| #42 | |
| #43 | function cleanText(value: unknown, fallback = '', max = 160) { |
| #44 | if (typeof value !== 'string') return fallback; |
| #45 | const cleaned = value.replace(/\s+/g, ' ').trim(); |
| #46 | return cleaned ? cleaned.slice(0, max) : fallback; |
| #47 | } |
| #48 | |
| #49 | function cleanId(value: unknown) { |
| #50 | return cleanText(value, '', 80).replace(/[^a-zA-Z0-9._-]/g, '_'); |
| #51 | } |
| #52 | |
| #53 | function pdfEscape(value: string) { |
| #54 | return value.replace(/\\/g, '\\\\').replace(/\(/g, '\\(').replace(/\)/g, '\\)'); |
| #55 | } |
| #56 | |
| #57 | function pdfBuffer(doc: StoredDocument) { |
| #58 | const lines = [ |
| #59 | doc.title, |
| #60 | doc.trustName, |
| #61 | `Registry: ${doc.registryNumber}`, |
| #62 | `Trust ID: ${doc.trustId}`, |
| #63 | `Authorized Representative: ${doc.displayName}`, |
| #64 | `Generated: ${doc.generatedAt}`, |
| #65 | '', |
| #66 | 'This cockpit-generated instrument record is retained in the member document library.', |
| #67 | '', |
| #68 | ...(doc.sourceContent ? doc.sourceContent.split(/\r?\n/).slice(0, 28) : []), |
| #69 | ]; |
| #70 | const stream = [ |
| #71 | 'BT', |
| #72 | '/F1 16 Tf', |
| #73 | '72 760 Td', |
| #74 | ...lines.flatMap((line, index) => [ |
| #75 | index === 0 ? `(${pdfEscape(line)}) Tj` : `0 -24 Td (${pdfEscape(line)}) Tj`, |
| #76 | index === 0 ? '/F1 11 Tf' : '', |
| #77 | ]).filter(Boolean), |
| #78 | 'ET', |
| #79 | ].join('\n'); |
| #80 | |
| #81 | const objects = [ |
| #82 | '<< /Type /Catalog /Pages 2 0 R >>', |
| #83 | '<< /Type /Pages /Kids [3 0 R] /Count 1 >>', |
| #84 | '<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] /Resources << /Font << /F1 4 0 R >> >> /Contents 5 0 R >>', |
| #85 | '<< /Type /Font /Subtype /Type1 /BaseFont /Helvetica >>', |
| #86 | `<< /Length ${Buffer.byteLength(stream)} >>\nstream\n${stream}\nendstream`, |
| #87 | ]; |
| #88 | let body = '%PDF-1.4\n'; |
| #89 | const offsets = [0]; |
| #90 | objects.forEach((object, index) => { |
| #91 | offsets.push(Buffer.byteLength(body)); |
| #92 | body += `${index + 1} 0 obj\n${object}\nendobj\n`; |
| #93 | }); |
| #94 | const xrefOffset = Buffer.byteLength(body); |
| #95 | body += `xref\n0 ${objects.length + 1}\n0000000000 65535 f \n`; |
| #96 | for (let index = 1; index <= objects.length; index += 1) { |
| #97 | body += `${String(offsets[index]).padStart(10, '0')} 00000 n \n`; |
| #98 | } |
| #99 | body += `trailer\n<< /Size ${objects.length + 1} /Root 1 0 R >>\nstartxref\n${xrefOffset}\n%%EOF\n`; |
| #100 | return Buffer.from(body, 'utf8'); |
| #101 | } |
| #102 | |
| #103 | function filenameFor(doc: StoredDocument) { |
| #104 | return `${doc.registryNumber || doc.id}.pdf`.replace(/[^a-zA-Z0-9._-]/g, '_'); |
| #105 | } |
| #106 | |
| #107 | export async function GET(req: NextRequest) { |
| #108 | try { |
| #109 | const ctx = await getUserContext(); |
| #110 | const docs = await readDocuments(ctx.userId); |
| #111 | const id = req.nextUrl.searchParams.get('download'); |
| #112 | if (id) { |
| #113 | const doc = docs.find(item => item.id === id); |
| #114 | if (!doc) return NextResponse.json({ error: 'document_not_found' }, { status: 404 }); |
| #115 | return new Response(pdfBuffer(doc), { |
| #116 | status: 200, |
| #117 | headers: { |
| #118 | 'Content-Type': 'application/pdf', |
| #119 | 'Content-Disposition': `attachment; filename="${filenameFor(doc)}"`, |
| #120 | 'Cache-Control': 'no-store', |
| #121 | }, |
| #122 | }); |
| #123 | } |
| #124 | return NextResponse.json({ documents: docs }); |
| #125 | } catch (error) { |
| #126 | return authErrorResponse(error); |
| #127 | } |
| #128 | } |
| #129 | |
| #130 | export async function POST(req: NextRequest) { |
| #131 | try { |
| #132 | const ctx = await getUserContext(); |
| #133 | const payload = await req.json().catch(() => null); |
| #134 | const source = payload && typeof payload === 'object' ? payload as Record<string, unknown> : {}; |
| #135 | const now = new Date().toISOString(); |
| #136 | const doc: StoredDocument = { |
| #137 | id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, |
| #138 | templateId: cleanId(source.templateId) || 'instrument', |
| #139 | title: cleanText(source.title, 'Sovereign Instrument'), |
| #140 | trustName: cleanText(source.trustName, 'Trust Estate Pending'), |
| #141 | trustId: cleanText(source.trustId, 'TRUST-PENDING'), |
| #142 | displayName: cleanText(source.displayName, ctx.displayName), |
| #143 | registryNumber: cleanText(source.registryNumber, `DOC-${Date.now()}`, 120), |
| #144 | generatedAt: cleanText(source.generatedAt, now, 120), |
| #145 | createdAt: now, |
| #146 | sourceContent: typeof source.sourceContent === 'string' ? source.sourceContent.slice(0, 50000) : undefined, |
| #147 | }; |
| #148 | const docs = [doc, ...(await readDocuments(ctx.userId))].slice(0, 200); |
| #149 | await writeDocuments(ctx.userId, docs); |
| #150 | return NextResponse.json({ document: doc, documents: docs }, { status: 201 }); |
| #151 | } catch (error) { |
| #152 | return authErrorResponse(error); |
| #153 | } |
| #154 | } |
| #155 | |
| #156 | export async function DELETE(req: NextRequest) { |
| #157 | try { |
| #158 | const ctx = await getUserContext(); |
| #159 | const id = req.nextUrl.searchParams.get('id'); |
| #160 | if (!id) return NextResponse.json({ error: 'id_required' }, { status: 400 }); |
| #161 | const docs = await readDocuments(ctx.userId); |
| #162 | const next = docs.filter(item => item.id !== id); |
| #163 | await writeDocuments(ctx.userId, next); |
| #164 | return NextResponse.json({ deleted: next.length !== docs.length, documents: next }); |
| #165 | } catch (error) { |
| #166 | return authErrorResponse(error); |
| #167 | } |
| #168 | } |
| #169 |