feat: certificates, landing pages, ai tutor, website update, compliance

This commit is contained in:
Rotimi Best
2026-05-18 03:47:22 +01:00
parent c31ad63955
commit ac3a2c14a7
644 changed files with 56050 additions and 5439 deletions
+39
View File
@@ -0,0 +1,39 @@
import * as z from 'zod';
/**
* Payload for an `emails:send` job that uses a registered `@cio/email`
* template id. Field shape is validated again inside the worker via the
* template's Zod schema, so we keep the on-the-wire schema permissive.
*/
export const ZSendTemplateEmailPayload = z.object({
kind: z.literal('template'),
template: z.string().min(1),
to: z.string().email(),
fields: z.record(z.string(), z.unknown()).default({}),
from: z.string().optional(),
replyTo: z.string().optional()
});
export type TSendTemplateEmailPayload = z.infer<typeof ZSendTemplateEmailPayload>;
/**
* Payload for an `emails:send` job that carries free-form subject + content
* (used by the public mail route and submission notifications that don't have
* a registered template).
*/
export const ZSendRawEmailPayload = z.object({
kind: z.literal('raw'),
to: z.string().email(),
subject: z.string().min(1),
content: z.string().min(1),
from: z.string().optional(),
replyTo: z.string().optional()
});
export type TSendRawEmailPayload = z.infer<typeof ZSendRawEmailPayload>;
/**
* Discriminated union BullMQ workers parse on entry. Adding a new email
* delivery shape is a matter of appending another variant here + a branch in
* the worker.
*/
export const ZSendEmailPayload = z.discriminatedUnion('kind', [ZSendTemplateEmailPayload, ZSendRawEmailPayload]);
export type TSendEmailPayload = z.infer<typeof ZSendEmailPayload>;
+3
View File
@@ -0,0 +1,3 @@
export * from './media';
export * from './emails';
export * from './maintenance';
+18
View File
@@ -0,0 +1,18 @@
import * as z from 'zod';
export const ZRetentionCompactPayload = z.object({
/** Override default retention windows (in days). Optional. */
windowDays: z
.object({
mediaJob: z.number().int().positive().optional(),
jobStep: z.number().int().positive().optional(),
deadLetterJob: z.number().int().positive().optional()
})
.optional()
});
export type TRetentionCompactPayload = z.infer<typeof ZRetentionCompactPayload>;
export const ZDeadLetterCleanupPayload = z.object({
olderThanDays: z.number().int().positive().default(180)
});
export type TDeadLetterCleanupPayload = z.infer<typeof ZDeadLetterCleanupPayload>;
+65
View File
@@ -0,0 +1,65 @@
import * as z from 'zod';
/**
* Payloads for the `media` and `media-transcribe` queues.
*
* Every payload carries the `mediaJobId` (DB run id) and `assetId` so workers
* never need to re-derive context. `actorContext` is snapshotted at enqueue
* time per the plan's authorization-on-resume rule.
*/
export const ZActorContext = z.object({
userId: z.string().uuid().nullable(),
organizationId: z.string().uuid(),
locale: z.string().optional()
});
export type TActorContext = z.infer<typeof ZActorContext>;
export const ZProbeMetadataPayload = z.object({
mediaJobId: z.string().uuid(),
assetId: z.string().uuid(),
storageKey: z.string().min(1),
actorContext: ZActorContext
});
export type TProbeMetadataPayload = z.infer<typeof ZProbeMetadataPayload>;
export const ZGenerateThumbnailPayload = z.object({
mediaJobId: z.string().uuid(),
assetId: z.string().uuid(),
storageKey: z.string().min(1),
actorContext: ZActorContext,
/** Seconds into the video to grab. Defaults to 1.0 in the worker. */
atSeconds: z.number().nonnegative().optional()
});
export type TGenerateThumbnailPayload = z.infer<typeof ZGenerateThumbnailPayload>;
export const ZExtractAudioPayload = z.object({
mediaJobId: z.string().uuid(),
assetId: z.string().uuid(),
storageKey: z.string().min(1),
actorContext: ZActorContext
});
export type TExtractAudioPayload = z.infer<typeof ZExtractAudioPayload>;
export const ZTranscribeAudioPayload = z.object({
mediaJobId: z.string().uuid(),
assetId: z.string().uuid(),
actorContext: ZActorContext,
/** Provider override; defaults to `openai` in the worker. */
provider: z.enum(['openai', 'assemblyai', 'deepgram']).optional()
});
export type TTranscribeAudioPayload = z.infer<typeof ZTranscribeAudioPayload>;
export const ZCompressVideoPayload = z.object({
mediaJobId: z.string().uuid(),
assetId: z.string().uuid(),
storageKey: z.string().min(1),
actorContext: ZActorContext,
options: z
.object({
crf: z.number().int().min(0).max(51).optional(),
scale: z.string().optional()
})
.optional()
});
export type TCompressVideoPayload = z.infer<typeof ZCompressVideoPayload>;