Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
import { env } from '@/lib/env';
|
|
|
|
|
import { logger } from '@/lib/logger';
|
|
|
|
|
|
|
|
|
|
const BASE_URL = env.DOCUMENSO_API_URL;
|
|
|
|
|
const API_KEY = env.DOCUMENSO_API_KEY;
|
|
|
|
|
|
|
|
|
|
async function documensoFetch(path: string, options?: RequestInit): Promise<unknown> {
|
|
|
|
|
const res = await fetch(`${BASE_URL}${path}`, {
|
|
|
|
|
...options,
|
|
|
|
|
headers: {
|
|
|
|
|
Authorization: `Bearer ${API_KEY}`,
|
|
|
|
|
'Content-Type': 'application/json',
|
|
|
|
|
...options?.headers,
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
const err = await res.text();
|
|
|
|
|
logger.error({ path, status: res.status, err }, 'Documenso API error');
|
|
|
|
|
throw new Error(`Documenso API error: ${res.status}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return res.json();
|
|
|
|
|
}
|
|
|
|
|
|
2026-04-27 15:25:06 +02:00
|
|
|
// Documenso 2.x renamed top-level `id` → `documentId` and recipient `id` →
|
|
|
|
|
// `recipientId`; v1.13 still uses `id`. Normalize both shapes to the legacy
|
|
|
|
|
// `id` form that this codebase consumes everywhere downstream.
|
|
|
|
|
function normalizeDocument(raw: unknown): DocumensoDocument {
|
|
|
|
|
const r = (raw ?? {}) as Record<string, unknown>;
|
|
|
|
|
const id = String(r.documentId ?? r.id ?? '');
|
|
|
|
|
const status = String(r.status ?? 'PENDING');
|
|
|
|
|
const recipientsRaw = (r.recipients as Array<Record<string, unknown>> | undefined) ?? [];
|
|
|
|
|
const recipients = recipientsRaw.map((rec) => ({
|
|
|
|
|
id: String(rec.recipientId ?? rec.id ?? ''),
|
|
|
|
|
name: String(rec.name ?? ''),
|
|
|
|
|
email: String(rec.email ?? ''),
|
|
|
|
|
role: String(rec.role ?? ''),
|
|
|
|
|
signingOrder: Number(rec.signingOrder ?? 0),
|
|
|
|
|
status: String(rec.signingStatus ?? rec.status ?? 'PENDING'),
|
|
|
|
|
signingUrl: typeof rec.signingUrl === 'string' ? rec.signingUrl : undefined,
|
|
|
|
|
embeddedUrl: typeof rec.embeddedUrl === 'string' ? rec.embeddedUrl : undefined,
|
|
|
|
|
}));
|
|
|
|
|
return { id, status, recipients };
|
|
|
|
|
}
|
|
|
|
|
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
export interface DocumensoRecipient {
|
|
|
|
|
name: string;
|
|
|
|
|
email: string;
|
|
|
|
|
role: string;
|
|
|
|
|
signingOrder: number;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export interface DocumensoDocument {
|
|
|
|
|
id: string;
|
|
|
|
|
status: string;
|
|
|
|
|
recipients: Array<{
|
|
|
|
|
id: string;
|
|
|
|
|
name: string;
|
|
|
|
|
email: string;
|
|
|
|
|
role: string;
|
|
|
|
|
signingOrder: number;
|
|
|
|
|
status: string;
|
|
|
|
|
signingUrl?: string;
|
|
|
|
|
embeddedUrl?: string;
|
|
|
|
|
}>;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function createDocument(
|
|
|
|
|
title: string,
|
|
|
|
|
pdfBase64: string,
|
|
|
|
|
recipients: DocumensoRecipient[],
|
|
|
|
|
): Promise<DocumensoDocument> {
|
|
|
|
|
return documensoFetch('/api/v1/documents', {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
body: JSON.stringify({ title, document: pdfBase64, recipients }),
|
2026-04-27 15:25:06 +02:00
|
|
|
}).then(normalizeDocument);
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
}
|
|
|
|
|
|
2026-04-24 18:43:41 +02:00
|
|
|
export async function generateDocumentFromTemplate(
|
|
|
|
|
templateId: number,
|
|
|
|
|
payload: Record<string, unknown>,
|
|
|
|
|
): Promise<DocumensoDocument> {
|
|
|
|
|
return documensoFetch(`/api/v1/templates/${templateId}/generate-document`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
body: JSON.stringify(payload),
|
2026-04-27 15:25:06 +02:00
|
|
|
}).then(normalizeDocument);
|
2026-04-24 18:43:41 +02:00
|
|
|
}
|
|
|
|
|
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
export async function sendDocument(docId: string): Promise<DocumensoDocument> {
|
|
|
|
|
return documensoFetch(`/api/v1/documents/${docId}/send`, {
|
|
|
|
|
method: 'POST',
|
2026-04-27 15:25:06 +02:00
|
|
|
}).then(normalizeDocument);
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function getDocument(docId: string): Promise<DocumensoDocument> {
|
2026-04-27 15:25:06 +02:00
|
|
|
return documensoFetch(`/api/v1/documents/${docId}`).then(normalizeDocument);
|
Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM,
PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source
files covering clients, berths, interests/pipeline, documents/EOI,
expenses/invoices, email, notifications, dashboard, admin, and
client portal. CI/CD via Gitea Actions with Docker builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 11:52:51 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function sendReminder(docId: string, signerId: string): Promise<void> {
|
|
|
|
|
await documensoFetch(`/api/v1/documents/${docId}/recipients/${signerId}/remind`, {
|
|
|
|
|
method: 'POST',
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function downloadSignedPdf(docId: string): Promise<Buffer> {
|
|
|
|
|
const res = await fetch(`${BASE_URL}/api/v1/documents/${docId}/download`, {
|
|
|
|
|
headers: { Authorization: `Bearer ${API_KEY}` },
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (!res.ok) {
|
|
|
|
|
const err = await res.text();
|
|
|
|
|
logger.error({ docId, status: res.status, err }, 'Documenso download error');
|
|
|
|
|
throw new Error(`Documenso download error: ${res.status}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const arrayBuffer = await res.arrayBuffer();
|
|
|
|
|
return Buffer.from(arrayBuffer);
|
|
|
|
|
}
|