Initial commit: Port Nimara CRM (Layers 0-4)
Some checks failed
Build & Push Docker Images / build-and-push (push) Has been cancelled
Build & Push Docker Images / deploy (push) Has been cancelled
Build & Push Docker Images / lint (push) Has been cancelled

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>
This commit is contained in:
2026-03-26 11:52:51 +01:00
commit 67d7e6e3d5
572 changed files with 86496 additions and 0 deletions

View File

@@ -0,0 +1,73 @@
import { getQueue } from '@/lib/queue';
// ─── Types ────────────────────────────────────────────────────────────────────
export interface DraftRequest {
interestId: string;
clientId: string;
portId: string;
context: 'follow_up' | 'introduction' | 'stage_update' | 'general';
additionalInstructions?: string;
}
export interface DraftResult {
subject: string;
body: string;
generatedAt: Date;
}
// ─── Request draft (enqueues job) ─────────────────────────────────────────────
/**
* Request an AI-generated email draft.
* Enqueues a job on the 'ai' queue. Returns jobId for polling.
* Job payload contains ONLY entity IDs (no PII).
*/
export async function requestEmailDraft(
userId: string,
request: DraftRequest,
): Promise<{ jobId: string }> {
const aiQueue = getQueue('ai');
const job = await aiQueue.add('generate-email-draft', {
// No PII — only IDs and context parameters
interestId: request.interestId,
clientId: request.clientId,
portId: request.portId,
context: request.context,
additionalInstructions: request.additionalInstructions,
requestedBy: userId,
});
return { jobId: job.id! };
}
// ─── Poll for result ──────────────────────────────────────────────────────────
/**
* Get the result of an email draft generation job.
* Returns null if still processing.
*/
export async function getEmailDraftResult(jobId: string): Promise<DraftResult | null> {
const aiQueue = getQueue('ai');
const job = await aiQueue.getJob(jobId);
if (!job) return null;
const state = await job.getState();
if (state !== 'completed') return null;
const returnValue = job.returnvalue as
| { subject: string; body: string; generatedAt: string }
| undefined
| null;
if (!returnValue) return null;
return {
subject: returnValue.subject,
body: returnValue.body,
generatedAt: new Date(returnValue.generatedAt),
};
}