UNPKG

@smartsamurai/krapi-sdk

Version:

KRAPI TypeScript SDK - Easy-to-use client SDK for connecting to self-hosted KRAPI servers (like Appwrite SDK)

637 lines (606 loc) 22 kB
/** * Email Adapter * * Unifies EmailHttpClient and EmailService behind a common interface. */ import { EmailService, EmailConfig as ServiceEmailConfig, EmailTemplate as ServiceEmailTemplate, EmailRequest as ServiceEmailRequest, } from "../../email-service"; import { EmailHttpClient } from "../../http-clients/email-http-client"; import { EmailConfig, EmailTemplate, SendEmailRequest } from "../../types"; import { createAdapterInitError } from "./error-handler"; type Mode = "client" | "server"; /** * Transform service EmailConfig (smtp_username) to types EmailConfig (smtp_user) */ function transformServiceEmailConfigToTypes( serviceConfig: ServiceEmailConfig | null ): EmailConfig { if (!serviceConfig) { return { smtp_host: "", smtp_port: 587, smtp_user: "", smtp_password: "", smtp_secure: false, from_email: "", from_name: "", enabled: true, }; } return { smtp_host: serviceConfig.smtp_host, smtp_port: serviceConfig.smtp_port, smtp_user: serviceConfig.smtp_username, smtp_password: serviceConfig.smtp_password, smtp_secure: serviceConfig.smtp_secure, from_email: serviceConfig.from_email, from_name: serviceConfig.from_name, enabled: true, }; } /** * Transform service EmailTemplate to types EmailTemplate */ function transformServiceEmailTemplateToTypes( serviceTemplate: ServiceEmailTemplate ): EmailTemplate { return { id: serviceTemplate.id, name: serviceTemplate.name, subject: serviceTemplate.subject, body: serviceTemplate.body, type: "custom", // Default type since service doesn't have it project_id: serviceTemplate.project_id, variables: serviceTemplate.variables || [], created_at: serviceTemplate.created_at, updated_at: serviceTemplate.updated_at, }; } /** * Transform types SendEmailRequest to service EmailRequest */ function transformTypesEmailRequestToService( emailData: SendEmailRequest, projectId: string ): ServiceEmailRequest { const request: ServiceEmailRequest = { project_id: projectId, to: emailData.to, subject: emailData.subject, body: emailData.body || "", text: emailData.body || "", }; if (emailData.reply_to) { request.replyTo = emailData.reply_to; } if (emailData.attachments) { request.attachments = emailData.attachments.map((att) => { const attachment: { filename?: string; content?: string | Buffer; path?: string; contentType?: string; } = {}; if (att.filename) attachment.filename = att.filename; if (att.content) attachment.content = att.content; if (att.content_type) attachment.contentType = att.content_type; return attachment; }); } return request; } /** * Transform types SendEmailRequest to EmailRequest (for HTTP client) */ function transformTypesToHttpEmailRequest( emailData: SendEmailRequest, projectId: string ): { project_id: string; to: string | string[]; subject: string; body: string; replyTo?: string; template_id?: string; variables?: Record<string, unknown>; from?: string; attachments?: Array<{ filename: string; content: string | Buffer; content_type?: string; }>; } { // For HTTP client, we need to create a compatible format // Since EmailRequest and SendEmailRequest have different structures, // we'll build an object that matches what the HTTP client expects const request: { project_id: string; to: string | string[]; subject: string; body: string; replyTo?: string; template_id?: string; variables?: Record<string, unknown>; from?: string; attachments?: Array<{ filename: string; content: string | Buffer; content_type?: string; }>; } = { project_id: projectId, to: emailData.to, subject: emailData.subject, body: emailData.body || "", }; if (emailData.reply_to) { request.replyTo = emailData.reply_to; } if (emailData.template_id) { request.template_id = emailData.template_id; } if (emailData.variables) { request.variables = emailData.variables; } if (emailData.from) { request.from = emailData.from; } if (emailData.attachments) { request.attachments = emailData.attachments; } return request; } export class EmailAdapter { private mode: Mode; private httpClient: EmailHttpClient | undefined; private service: EmailService | undefined; constructor(mode: Mode, httpClient?: EmailHttpClient, service?: EmailService) { this.mode = mode; this.httpClient = httpClient; this.service = service; } async getConfig(projectId: string): Promise<EmailConfig> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.getConfig(projectId); return (response.data as unknown as EmailConfig) || ({} as EmailConfig); } else { if (!this.service) { throw createAdapterInitError("Email service", this.mode); } const config = await this.service.getConfig(projectId); return transformServiceEmailConfigToTypes(config); } } async updateConfig(projectId: string, config: { provider: string; settings?: Record<string, unknown>; }): Promise<EmailConfig> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } // Transform interface format to EmailConfig format for HTTP client // Ensure smtp_host and smtp_port are always set from the request body // Support multiple field name formats: host/hostname, port, smtp_host/smtpHost, smtp_port/smtpPort // SAFE: Check that config.settings exists before accessing nested properties const settings = config.settings || {}; const smtpHost = (typeof settings.smtp_host === "string" && settings.smtp_host) ? settings.smtp_host : (typeof settings.smtpHost === "string" && settings.smtpHost) ? settings.smtpHost : (typeof settings.host === "string" && settings.host) ? settings.host : (typeof settings.hostname === "string" && settings.hostname) ? settings.hostname : ""; const smtpPort = (typeof settings.smtp_port === "number" && settings.smtp_port) ? settings.smtp_port : (typeof settings.smtpPort === "number" && settings.smtpPort) ? settings.smtpPort : (typeof settings.port === "number" && settings.port) ? settings.port : 587; const emailConfig: Partial<EmailConfig> = { smtp_host: smtpHost, smtp_port: smtpPort, smtp_user: (typeof settings.smtp_user === "string" && settings.smtp_user) ? settings.smtp_user : (typeof settings.smtpUser === "string" && settings.smtpUser) ? settings.smtpUser : (typeof settings.smtp_username === "string" && settings.smtp_username) ? settings.smtp_username : (typeof settings.smtpUsername === "string" && settings.smtpUsername) ? settings.smtpUsername : "", smtp_password: (typeof settings.smtp_password === "string" && settings.smtp_password) ? settings.smtp_password : (typeof settings.smtpPassword === "string" && settings.smtpPassword) ? settings.smtpPassword : "", smtp_secure: (typeof settings.smtp_secure === "boolean") ? settings.smtp_secure : (typeof settings.smtpSecure === "boolean") ? settings.smtpSecure : false, from_email: (typeof settings.from_email === "string" && settings.from_email) ? settings.from_email : (typeof settings.fromEmail === "string" && settings.fromEmail) ? settings.fromEmail : "", from_name: (typeof settings.from_name === "string" && settings.from_name) ? settings.from_name : (typeof settings.fromName === "string" && settings.fromName) ? settings.fromName : "", enabled: (typeof settings.enabled === "boolean") ? settings.enabled : true, }; const response = await this.httpClient.updateConfig(projectId, emailConfig); // Response interceptor returns response.data, so response is already unwrapped // Handle both formats: EmailConfig directly or { success: true, data: EmailConfig } if (response && typeof response === "object") { // If response has 'data' field, extract it if ("data" in response && response.data) { return (response.data as unknown as EmailConfig) || ({} as EmailConfig); } // If response is EmailConfig directly (has smtp_host, etc.) if ("smtp_host" in response || "smtpHost" in response) { return response as unknown as EmailConfig; } } return {} as EmailConfig; } else { if (!this.service) { throw createAdapterInitError("Email service", this.mode); } // Transform interface format to service EmailConfig format // Ensure smtp_host and smtp_port are always set from the request body // Support multiple field name formats: host/hostname, port, smtp_host/smtpHost, smtp_port/smtpPort // SAFE: Check that config.settings exists before accessing nested properties const settings = config.settings || {}; const smtpHost = (typeof settings.smtp_host === "string" && settings.smtp_host) ? settings.smtp_host : (typeof settings.smtpHost === "string" && settings.smtpHost) ? settings.smtpHost : (typeof settings.host === "string" && settings.host) ? settings.host : (typeof settings.hostname === "string" && settings.hostname) ? settings.hostname : ""; const smtpPort = (typeof settings.smtp_port === "number" && settings.smtp_port) ? settings.smtp_port : (typeof settings.smtpPort === "number" && settings.smtpPort) ? settings.smtpPort : (typeof settings.port === "number" && settings.port) ? settings.port : 587; const serviceConfig: ServiceEmailConfig = { smtp_host: smtpHost, smtp_port: smtpPort, smtp_username: (typeof settings.smtp_username === "string" && settings.smtp_username) ? settings.smtp_username : (typeof settings.smtpUsername === "string" && settings.smtpUsername) ? settings.smtpUsername : (typeof settings.smtp_user === "string" && settings.smtp_user) ? settings.smtp_user : (typeof settings.smtpUser === "string" && settings.smtpUser) ? settings.smtpUser : "", smtp_password: (typeof settings.smtp_password === "string" && settings.smtp_password) ? settings.smtp_password : (typeof settings.smtpPassword === "string" && settings.smtpPassword) ? settings.smtpPassword : "", smtp_secure: (typeof settings.smtp_secure === "boolean") ? settings.smtp_secure : (typeof settings.smtpSecure === "boolean") ? settings.smtpSecure : false, from_email: (typeof settings.from_email === "string" && settings.from_email) ? settings.from_email : (typeof settings.fromEmail === "string" && settings.fromEmail) ? settings.fromEmail : "", from_name: (typeof settings.from_name === "string" && settings.from_name) ? settings.from_name : (typeof settings.fromName === "string" && settings.fromName) ? settings.fromName : "", }; const updated = await this.service.updateConfig(projectId, serviceConfig); // If service returns null, return a config with the values we extracted from request if (!updated) { return { smtp_host: smtpHost, smtp_port: smtpPort, smtp_user: serviceConfig.smtp_username, smtp_password: serviceConfig.smtp_password, smtp_secure: serviceConfig.smtp_secure, from_email: serviceConfig.from_email, from_name: serviceConfig.from_name, enabled: true, }; } return transformServiceEmailConfigToTypes(updated); } } async testConfig(projectId: string, testEmail: string): Promise<{ success: boolean; message?: string }> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.testConfig(projectId, testEmail); return response.data || { success: false }; } else { if (!this.service) { throw createAdapterInitError("Email service", this.mode); } const result = await this.service.testConfig(projectId); return { success: result.success || false, message: result.message, }; } } async getTemplates(projectId: string, options?: { limit?: number; offset?: number; search?: string; }): Promise<EmailTemplate[]> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.getTemplates(projectId, options); const data = response.data; if (data && "data" in data) { return (data.data as EmailTemplate[]) || []; } return (data as unknown as EmailTemplate[]) || []; } else { if (!this.service) { throw createAdapterInitError("Email service", this.mode); } const templates = await this.service.getTemplates(projectId); // Transform service EmailTemplate to types EmailTemplate format return templates.map(transformServiceEmailTemplateToTypes); } } async getTemplate(projectId: string, templateId: string): Promise<EmailTemplate> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.getTemplate(projectId, templateId); return (response.data as unknown as EmailTemplate) || ({} as EmailTemplate); } else { if (!this.service) { throw createAdapterInitError("Email service", this.mode); } const template = await this.service.getTemplate(templateId); if (!template) { return {} as EmailTemplate; } return transformServiceEmailTemplateToTypes(template); } } async createTemplate(projectId: string, template: { name: string; subject: string; body: string; variables: string[]; type?: string; }): Promise<EmailTemplate> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.createTemplate(projectId, template); return (response.data as unknown as EmailTemplate) || ({} as EmailTemplate); } else { if (!this.service) { throw createAdapterInitError("Email service", this.mode); } const serviceTemplate: { name: string; subject: string; body: string; variables: string[]; project_id: string; } = { name: template.name, subject: template.subject, body: template.body, variables: template.variables, project_id: projectId, }; const created = await this.service.createTemplate(serviceTemplate); return transformServiceEmailTemplateToTypes(created); } } async updateTemplate(projectId: string, templateId: string, updates: Partial<EmailTemplate>): Promise<EmailTemplate> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.updateTemplate(projectId, templateId, updates); return (response.data as unknown as EmailTemplate) || ({} as EmailTemplate); } else { if (!this.service) { throw createAdapterInitError("Email service", this.mode); } const serviceUpdates: Partial<ServiceEmailTemplate> = {}; if (updates.name !== undefined) serviceUpdates.name = updates.name; if (updates.subject !== undefined) serviceUpdates.subject = updates.subject; if (updates.body !== undefined) serviceUpdates.body = updates.body; if (updates.variables !== undefined) serviceUpdates.variables = updates.variables; const updated = await this.service.updateTemplate(templateId, serviceUpdates); if (!updated) { return {} as EmailTemplate; } return transformServiceEmailTemplateToTypes(updated); } } async deleteTemplate(projectId: string, templateId: string): Promise<{ success: boolean }> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const response = await this.httpClient.deleteTemplate(projectId, templateId); return response.data || { success: false }; } else { if (!this.service) { throw createAdapterInitError("Email service", this.mode); } const success = await this.service.deleteTemplate(templateId); return { success }; } } async send(projectId: string, emailData: SendEmailRequest): Promise<{ success: boolean; message_id?: string; error?: string; }> { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } const httpEmailData = transformTypesToHttpEmailRequest(emailData, projectId); const response = await this.httpClient.sendEmail(projectId, httpEmailData); const data = response.data; const result: { success: boolean; message_id?: string; error?: string; } = { success: data?.success || false, }; if (data?.message_id) { result.message_id = data.message_id; } return result; } else { if (!this.service) { throw createAdapterInitError("Email service", this.mode); } const emailRequest = transformTypesEmailRequestToService(emailData, projectId); const result = await this.service.sendEmail(emailRequest); const response: { success: boolean; message_id?: string; error?: string; } = { success: result.success || false, }; if (result.messageId) { response.message_id = result.messageId; } if (result.message) { response.error = result.message; } return response; } } async getHistory( projectId: string, options?: { limit?: number; offset?: number; status?: string; start_date?: string; end_date?: string; } ): Promise< { id: string; to: string; subject: string; status: string; sent_at: string; }[] > { if (this.mode === "client") { if (!this.httpClient) { throw createAdapterInitError("HTTP client", this.mode); } // Transform options to match HTTP client interface const httpOptions: { limit?: number; offset?: number; status?: "sent" | "failed" | "bounced" | "delivered"; sent_after?: string; sent_before?: string; } = {}; if (options?.limit) httpOptions.limit = options.limit; if (options?.offset) httpOptions.offset = options.offset; if (options?.status) { const statusMap: Record<string, "sent" | "failed" | "bounced" | "delivered"> = { sent: "sent", failed: "failed", bounced: "bounced", delivered: "delivered", }; httpOptions.status = statusMap[options.status] || "sent"; } if (options?.start_date) httpOptions.sent_after = options.start_date; if (options?.end_date) httpOptions.sent_before = options.end_date; const response = await this.httpClient.getEmailHistory(projectId, httpOptions); const data = response.data; if (data && "data" in data) { return (data.data as Array<{ id: string; to: string; subject: string; status: string; sent_at: string; }>) || []; } return (data as unknown as Array<{ id: string; to: string; subject: string; status: string; sent_at: string; }>) || []; } else { if (!this.service) { throw createAdapterInitError("Email service", this.mode); } // Transform options to match service interface const serviceOptions: { limit?: number; offset?: number; status?: "sent" | "failed" | "bounced" | "delivered"; sent_after?: string; sent_before?: string; } = {}; if (options?.limit) serviceOptions.limit = options.limit; if (options?.offset) serviceOptions.offset = options.offset; if (options?.status) { const statusMap: Record<string, "sent" | "failed" | "bounced" | "delivered"> = { sent: "sent", failed: "failed", bounced: "bounced", delivered: "delivered", }; serviceOptions.status = statusMap[options.status] || "sent"; } if (options?.start_date) serviceOptions.sent_after = options.start_date; if (options?.end_date) serviceOptions.sent_before = options.end_date; const history = await this.service.getEmailHistory(projectId, serviceOptions); return (history.data as unknown as Array<{ id: string; to: string; subject: string; status: string; sent_at: string; }>) || []; } } }