Build the agent-native compute CRM platform
CI / verify (push) Successful in 3m6s

This commit is contained in:
2026-08-13 01:39:01 -07:00
parent bfd2f8d95a
commit 853bde2265
160 changed files with 61812 additions and 483 deletions
+125
View File
@@ -0,0 +1,125 @@
import { IMPORT_ENTITIES, IMPORT_ENTITY_DEFINITIONS } from '@pig/core';
import type { Database } from '@pig/db';
import { Hono } from 'hono';
import { z } from 'zod';
import { requireCapability } from '../lib/auth';
import type { ApiEnv, MutationDefinition } from '../lib/mutation';
import { MutationError, mutation } from '../lib/mutation';
import {
ImportService,
type ImportCommitResult,
type ImportTransaction,
} from '../services/imports';
import {
MAX_IMPORT_CELL_CHARS,
MAX_IMPORT_COLUMNS,
MAX_IMPORT_FILE_BYTES,
MAX_IMPORT_ROWS,
parseTabularFile,
} from '../services/tabular-import';
const planSchema = z.object({
entity: z.enum(IMPORT_ENTITIES),
sourceName: z.string().trim().min(1).max(255),
headers: z.array(z.string().min(1).max(255)).min(1).max(MAX_IMPORT_COLUMNS),
rows: z.array(
z.array(z.string().max(MAX_IMPORT_CELL_CHARS)).max(MAX_IMPORT_COLUMNS),
).min(1).max(MAX_IMPORT_ROWS),
mapping: z.record(z.string().min(1).max(255)),
keySourceColumn: z.string().min(1).max(255),
}).strict();
const commitSchema = planSchema.extend({
previewDigest: z.string().regex(/^[0-9a-f]{64}$/),
}).strict();
const parseSchema = z.object({
fileName: z.string().trim().min(1).max(255),
mimeType: z.string().max(255).optional(),
base64: z.string().min(1).max(Math.ceil(MAX_IMPORT_FILE_BYTES * 4 / 3) + 16),
}).strict();
interface ImportCommitOperations {
commit(
input: z.infer<typeof commitSchema>,
principal: Parameters<MutationDefinition<typeof commitSchema, ImportCommitResult>['mutate']>[0]['principal'],
now: Date,
): Promise<ImportCommitResult>;
}
type ServiceFactory = (tx: ImportTransaction) => ImportCommitOperations;
const service: ServiceFactory = (tx) => new ImportService(tx);
export function createImportCommitMutationDefinition(
makeService: ServiceFactory = service,
): MutationDefinition<typeof commitSchema, ImportCommitResult> {
return {
schema: commitSchema,
permission: { authorize: (principal) => requireCapability(principal, 'data:import') },
invalidMessage: 'Invalid import commit.',
async mutate({ input, principal, tx, now }) {
const result = await makeService(tx).commit(input, principal, now);
const entityLabel = IMPORT_ENTITY_DEFINITIONS[input.entity].label.toLocaleLowerCase();
return {
data: result,
activity: {
type: 'note',
subject: `Imported ${result.total} ${entityLabel}`,
meta: {
action: 'import',
entity: input.entity,
sourceName: input.sourceName,
keySourceColumn: input.keySourceColumn,
created: result.created,
updated: result.updated,
},
},
};
},
};
}
export function createImportRoutes(db: Database): Hono<ApiEnv> {
const routes = new Hono<ApiEnv>();
routes.use('/api/imports/*', async (context, next) => {
requireCapability(context.get('principal'), 'data:import');
await next();
});
routes.get('/api/imports/config', (context) => context.json({
entities: IMPORT_ENTITIES,
definitions: IMPORT_ENTITY_DEFINITIONS,
limits: {
fileBytes: MAX_IMPORT_FILE_BYTES,
rows: MAX_IMPORT_ROWS,
columns: MAX_IMPORT_COLUMNS,
cellCharacters: MAX_IMPORT_CELL_CHARS,
},
}));
routes.post('/api/imports/parse', async (context) => {
const parsed = parseSchema.safeParse(await context.req.json());
if (!parsed.success) throw new MutationError('invalid_import_file', 'Invalid import file upload.', 400, parsed.error.issues);
let bytes: Uint8Array;
try {
bytes = Buffer.from(parsed.data.base64, 'base64');
} catch {
throw new MutationError('invalid_import_file', 'The import file encoding is invalid.', 400);
}
try {
return context.json(parseTabularFile({
fileName: parsed.data.fileName,
mimeType: parsed.data.mimeType,
bytes,
}));
} catch (error) {
throw new MutationError(
'invalid_import_file',
error instanceof Error ? error.message : 'The import file could not be parsed.',
400,
);
}
});
routes.post('/api/imports/preview', async (context) => {
const parsed = planSchema.safeParse(await context.req.json());
if (!parsed.success) throw new MutationError('invalid_import', 'Invalid import plan.', 400, parsed.error.issues);
return context.json(await new ImportService(db).preview(parsed.data));
});
routes.post('/api/imports/commit', mutation(db, createImportCommitMutationDefinition()));
return routes;
}