MicProtocol / apps /api /src /integrations /integrations.router.ts
Benaah's picture
Upload folder using huggingface_hub
6abfd9d verified
Raw History Blame Contribute Delete
22.8 kB
import { Injectable } from "@nestjs/common";
import { z } from "zod";
import {
router,
protectedProcedure,
workspaceProcedure,
requireFeature,
} from "../trpc/trpc.init";
import { SubscriptionFeature } from "../billing/subscription-features";
import { IntegrationsService, type IntegrationProviderName } from "./integrations.service";
import { IntegrationHealthService } from "./integration-health.service";
import {
calComBookingSchema, type CalComBookingInput,
documensoSendDocumentSchema, type DocumensoSendDocumentInput,
documensoDocumentStatusSchema, type DocumensoDocumentStatusInput,
} from "@micprotocol/shared-types";
const ALL_PROVIDERS = [
"google_calendar",
"google_mail",
"outlook",
"riverside",
"zoom",
"cloudflare_r2",
// Self-hosted open-source integration providers
"calcom",
"chatwoot",
"documenso",
"formbricks",
"listmonk",
"pretix",
] as const;
const SELF_HOSTED_PROVIDERS = [
"calcom",
"chatwoot",
"documenso",
"formbricks",
"listmonk",
"pretix",
] as const;
const workspaceIdInput = z.object({ workspaceId: z.string().uuid() });
const providerInput = z.object({
workspaceId: z.string().uuid(),
provider: z.enum(ALL_PROVIDERS),
});
const selfHostedCredentialsSchema = z.object({
workspaceId: z.string().uuid(),
provider: z.enum(["calcom", "chatwoot", "documenso", "formbricks", "listmonk", "pretix"]),
apiKey: z.string().min(1, "API key is required"),
baseUrl: z.string().url().optional(),
webhookSecret: z.string().optional(),
});
const r2CredentialsSchema = z.object({
workspaceId: z.string().uuid(),
accessKeyId: z.string().min(1, "Access Key ID is required"),
secretAccessKey: z.string().min(1, "Secret Access Key is required"),
accountId: z.string().optional(),
});
const syncCalendarSchema = z.object({
workspaceId: z.string().uuid(),
provider: z.enum(["google_calendar", "outlook"]),
});
const calendarEventSchema = z.object({
workspaceId: z.string().uuid(),
provider: z.enum(["google_calendar", "outlook"]),
eventData: z.object({
title: z.string().min(1).max(500),
description: z.string().max(5000).optional(),
startTime: z.string().datetime(),
endTime: z.string().datetime(),
timezone: z.string().optional(),
location: z.string().max(500).optional(),
meetLink: z.string().url().optional(),
attendees: z
.array(
z.object({
email: z.string().email(),
name: z.string().optional(),
}),
)
.optional(),
}),
});
const getEventsSchema = z.object({
workspaceId: z.string().uuid(),
provider: z.enum(["google_calendar", "outlook"]),
timeMin: z.string().datetime().optional(),
timeMax: z.string().datetime().optional(),
});
const chatwootConversationSchema = z.object({
workspaceId: z.string().uuid(),
conversationId: z.number(),
});
const chatwootMessageSchema = z.object({
workspaceId: z.string().uuid(),
conversationId: z.number(),
content: z.string().min(1).max(5000),
});
const chatwootCreateConversationSchema = z.object({
workspaceId: z.string().uuid(),
inboxId: z.number(),
contactName: z.string().min(1),
contactEmail: z.string().email(),
contactPhone: z.string().optional(),
message: z.string().min(1).max(5000),
customAttributes: z.record(z.string(), z.any()).optional(),
});
const webhookSecretSchema = z.object({
workspaceId: z.string().uuid(),
provider: z.enum(["calcom", "chatwoot", "documenso", "formbricks", "listmonk", "pretix"]),
secret: z.string().min(1, "Webhook secret is required"),
});
const pretixEventsSchema = z.object({
workspaceId: z.string().uuid(),
organizer: z.string().min(1, "Organizer slug is required"),
});
const pretixItemsSchema = z.object({
workspaceId: z.string().uuid(),
organizer: z.string().min(1, "Organizer slug is required"),
event: z.string().min(1, "Event slug is required"),
});
const pretixOrdersSchema = z.object({
workspaceId: z.string().uuid(),
organizer: z.string().min(1, "Organizer slug is required"),
event: z.string().min(1, "Event slug is required"),
});
const pretixOrderPositionsSchema = z.object({
workspaceId: z.string().uuid(),
organizer: z.string().min(1, "Organizer slug is required"),
event: z.string().min(1, "Event slug is required"),
order: z.string().min(1, "Order code is required"),
});
@Injectable()
export class IntegrationsRouter {
constructor(
private readonly integrationsService: IntegrationsService,
private readonly healthService: IntegrationHealthService,
) {}
get router() {
return router({
// ═══ Provider Metadata ════════════════════════
getAllProviders: protectedProcedure.query(async () => {
return this.integrationsService.getAllProviders();
}),
// ═══ Integration CRUD ═══════════════════════
list: workspaceProcedure
.input(workspaceIdInput)
.query(async ({ ctx, input }) => {
return this.integrationsService.listIntegrations(
input.workspaceId,
ctx.userId!,
);
}),
get: workspaceProcedure
.input(providerInput)
.query(async ({ ctx, input }) => {
return this.integrationsService.getIntegration(
input.workspaceId,
input.provider as IntegrationProviderName,
ctx.userId!,
);
}),
disconnect: workspaceProcedure
.input(providerInput)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.disconnectIntegration(
input.workspaceId,
input.provider as IntegrationProviderName,
ctx.userId!,
);
}),
// ═══ OAuth Flow ═════════════════════════════
getAuthUrl: workspaceProcedure
.input(providerInput)
.query(async ({ ctx, input }) => {
return this.integrationsService.getAuthUrl(
input.workspaceId,
input.provider as IntegrationProviderName,
ctx.userId!,
);
}),
handleCallback: protectedProcedure
.input(
z.object({
code: z.string(),
state: z.string(),
}),
)
.mutation(async ({ input }) => {
return this.integrationsService.handleOAuthCallback(
input.code,
input.state,
);
}),
// ═══ API Key Integrations ═════════════════════
// R2 API key β€” Studio plan feature
updateR2Integration: requireFeature(SubscriptionFeature.API_WEBHOOKS)
.input(r2CredentialsSchema)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.updateApiKeyIntegration(
input.workspaceId,
"cloudflare_r2",
ctx.userId!,
{
accessKeyId: input.accessKeyId,
secretAccessKey: input.secretAccessKey,
accountId: input.accountId,
},
);
}),
// Self-hosted integrations (Cal.com, Chatwoot, Documenso, Formbricks, Listmonk)
updateSelfHostedIntegration: requireFeature(SubscriptionFeature.SELF_HOSTED_INTEGRATIONS)
.input(selfHostedCredentialsSchema)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.updateApiKeyIntegration(
input.workspaceId,
input.provider,
ctx.userId!,
{
apiKey: input.apiKey,
baseUrl: input.baseUrl,
webhookSecret: input.webhookSecret,
},
);
}),
// Check health of a self-hosted integration by hitting its health endpoint
checkHealth: workspaceProcedure
.input(providerInput)
.query(async ({ ctx, input }) => {
const integration = await this.integrationsService.getIntegration(
input.workspaceId,
input.provider as IntegrationProviderName,
ctx.userId!,
);
if (!integration) {
return { alive: false, error: "Not connected" };
}
const selfHosted = (integration as any).metadata?.baseUrl;
if (!selfHosted) {
return { alive: false, error: "No endpoint configured" };
}
try {
const res = await fetch(`${selfHosted}/health`, {
signal: AbortSignal.timeout(5000),
});
return { alive: res.ok, status: res.status };
} catch (e: any) {
return { alive: false, error: e.message };
}
}),
// ═══ Calendar Sync ═══════════════════════════
syncCalendar: workspaceProcedure
.input(syncCalendarSchema)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.syncCalendarEvents(
input.workspaceId,
ctx.userId!,
input.provider,
);
}),
getSyncedEvents: workspaceProcedure
.input(getEventsSchema)
.query(async ({ ctx, input }) => {
return this.integrationsService.getSyncedEvents(
input.workspaceId,
input.provider,
ctx.userId!,
input.timeMin,
input.timeMax,
);
}),
createCalendarEvent: workspaceProcedure
.input(calendarEventSchema)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.createCalendarEvent(
input.workspaceId,
ctx.userId!,
input.provider,
input.eventData,
);
}),
// ═══ Cal.com Scheduling ══════════════════════════════════════
listCalComEventTypes: workspaceProcedure
.input(workspaceIdInput)
.query(async ({ ctx, input }) => {
return this.integrationsService.listCalComEventTypes(
input.workspaceId,
ctx.userId!,
);
}),
createCalComBooking: workspaceProcedure
.input(calComBookingSchema)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.createCalComBooking(
input.workspaceId,
ctx.userId!,
{
eventTypeId: input.eventTypeId,
start: input.start,
attendee: input.attendee,
meetingUrl: input.meetingUrl,
},
);
}),
// ═══ Documenso E-Signatures ══════════════════════════════════════════════════
sendDocumensoDocument: workspaceProcedure
.input(documensoSendDocumentSchema)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.sendDocument(
input.workspaceId,
{
title: input.document.title,
recipients: (input.document.recipients ?? []).map((r: { email: string; name?: string }) => ({
email: r.email,
name: r.name ?? "",
})),
},
);
}),
getDocumensoDocumentStatus: workspaceProcedure
.input(documensoDocumentStatusSchema)
.query(async ({ ctx, input }) => {
return this.integrationsService.getDocumensoDocumentStatus(
input.workspaceId,
Number(input.documentId),
);
}),
// ═══ Listmonk Newsletters ══════════════════════════════════════════════════════
listListmonkLists: workspaceProcedure
.input(workspaceIdInput)
.query(async ({ ctx, input }) => {
return this.integrationsService.listListmonkLists(input.workspaceId);
}),
createListmonkList: workspaceProcedure
.input(
z.object({
workspaceId: z.string().uuid(),
name: z.string().min(1),
type: z.enum(["public", "private", "optin"]).optional(),
}),
)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.createListmonkList(input.workspaceId, {
name: input.name,
type: input.type ?? "public",
});
}),
subscribeListmonkSubscriber: workspaceProcedure
.input(
z.object({
workspaceId: z.string().uuid(),
listId: z.number(),
email: z.string().email(),
name: z.string().optional(),
attrs: z.record(z.string(), z.any()).optional(),
}),
)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.subscribeListmonkSubscriber(input.workspaceId, {
listId: input.listId,
email: input.email,
name: input.name,
attrs: input.attrs,
});
}),
sendListmonkTransactional: workspaceProcedure
.input(
z.object({
workspaceId: z.string().uuid(),
to: z.string().email(),
subject: z.string().min(1),
body: z.string().min(1),
type: z.enum(["html", "plain"]).optional(),
}),
)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.sendListmonkEmail(input.workspaceId, {
to: input.to,
subject: input.subject,
body: input.body,
type: input.type,
});
}),
// ═══ Chatwoot Customer Support ═════════════════════════════════════
getChatwootConversations: workspaceProcedure
.input(workspaceIdInput)
.query(async ({ ctx, input }) => {
return this.integrationsService.getChatwootConversations(
input.workspaceId,
ctx.userId!,
);
}),
getChatwootMessages: workspaceProcedure
.input(chatwootConversationSchema)
.query(async ({ ctx, input }) => {
return this.integrationsService.getChatwootMessages(
input.workspaceId,
ctx.userId!,
input.conversationId,
);
}),
sendChatwootMessage: workspaceProcedure
.input(chatwootMessageSchema)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.sendChatwootMessage(
input.workspaceId,
ctx.userId!,
{
conversationId: input.conversationId,
content: input.content,
},
);
}),
createChatwootConversation: workspaceProcedure
.input(chatwootCreateConversationSchema)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.createChatwootConversation(
input.workspaceId,
ctx.userId!,
{
inboxId: input.inboxId,
contactName: input.contactName,
contactEmail: input.contactEmail,
contactPhone: input.contactPhone,
message: input.message,
customAttributes: input.customAttributes,
},
);
}),
// ═══ Pretix Ticketing ═══════════════════════════════════════════════════════════
listPretixEvents: workspaceProcedure
.input(pretixEventsSchema)
.query(async ({ ctx, input }) => {
return this.integrationsService.listPretixEvents(
input.workspaceId,
ctx.userId!,
input.organizer,
);
}),
listPretixItems: workspaceProcedure
.input(pretixItemsSchema)
.query(async ({ ctx, input }) => {
return this.integrationsService.listPretixItems(
input.workspaceId,
ctx.userId!,
input.organizer,
input.event,
);
}),
listPretixOrders: workspaceProcedure
.input(pretixOrdersSchema)
.query(async ({ ctx, input }) => {
return this.integrationsService.listPretixOrders(
input.workspaceId,
ctx.userId!,
input.organizer,
input.event,
);
}),
listPretixOrderPositions: workspaceProcedure
.input(pretixOrderPositionsSchema)
.query(async ({ ctx, input }) => {
return this.integrationsService.listPretixOrderPositions(
input.workspaceId,
ctx.userId!,
input.organizer,
input.event,
input.order,
);
}),
// ═══ Monitoring ═══════════════════════
getHealth: protectedProcedure.query(async () => {
return this.healthService.getHealthResults();
}),
getWebhookDeliveries: protectedProcedure
.input(z.object({ limit: z.number().int().min(1).max(500).optional() }))
.query(async ({ input }) => {
return this.healthService.getWebhookDeliveries(input.limit ?? 50);
}),
// ═══ Self-Hosted Integration Management ═══
listSelfHosted: workspaceProcedure
.input(workspaceIdInput)
.query(async ({ ctx, input }) => {
return this.integrationsService.listSelfHostedIntegrations(
input.workspaceId,
ctx.userId!,
);
}),
updateWebhookSecret: workspaceProcedure
.input(webhookSecretSchema)
.mutation(async ({ ctx, input }) => {
return this.integrationsService.updateWebhookSecret(
input.workspaceId,
input.provider,
input.secret,
ctx.userId!,
);
}),
// ═══ Provider Metrics ═══════════════════
getMetrics: protectedProcedure.query(async () => {
return this.integrationsService.getProviderMetrics();
}),
// ═══ Stats ═══════════════════════════════════
getStats: workspaceProcedure
.input(workspaceIdInput)
.query(async ({ ctx, input }) => {
return this.integrationsService.getIntegrationStats(
input.workspaceId,
ctx.userId!,
);
}),
});
}
}