Spaces:
Running
Running
Download apps/api/src/integrations/integrations.router.ts from Benaah/MicProtocol: direct link, hf CLI and curl.
- Browser
- Download file 22.8 kB
-
https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/api/src/integrations/integrations.router.ts
- Command line
-
hf download hf://spaces/Benaah/MicProtocol/apps/api/src/integrations/integrations.router.ts
-
curl -L -o integrations.router.ts https://huggingface.co/spaces/Benaah/MicProtocol/resolve/main/apps/api/src/integrations/integrations.router.ts
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"), | |
| }); | |
| () | |
| 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!, | |
| ); | |
| }), | |
| }); | |
| } | |
| } | |