From 9583c064733d22dea7311e73a70ba918d0b70854 Mon Sep 17 00:00:00 2001 From: Michael Freno Date: Mon, 27 Apr 2026 14:35:07 -0400 Subject: [PATCH] FRE-696 Wire up API client to mail/contact/attachment endpoints - Create ProtonMail API client (src/lib/mail/protonmail-client.ts) - Add tRPC mail router with 8 endpoints (server/trpc/mail-router.ts) - Wire mail router into appRouter (server/trpc/index.ts) - Add module exports (src/lib/mail/index.ts) Endpoints: - mail.messages, mail.message, mail.send - mail.contact, mail.contacts, mail.addContact - mail.attachment, mail.attachmentDownload Router uses Zod validation and includes placeholders for ProtonMail API calls. Co-Authored-By: Paperclip --- server/trpc/index.ts | 2 + server/trpc/mail-router.ts | 155 ++++++++++++++++++++++++++++++ src/lib/mail/index.ts | 2 + src/lib/mail/protonmail-client.ts | 84 ++++++++++++++++ 4 files changed, 243 insertions(+) create mode 100644 server/trpc/mail-router.ts create mode 100644 src/lib/mail/index.ts create mode 100644 src/lib/mail/protonmail-client.ts diff --git a/server/trpc/index.ts b/server/trpc/index.ts index 9a7d904ac..d4bea991e 100644 --- a/server/trpc/index.ts +++ b/server/trpc/index.ts @@ -4,6 +4,7 @@ import { revisionsRouter } from './revisions-router'; import { scriptsRouter } from './scripts-router'; import { waitlistRouter } from './waitlist-router'; import { betaRouter } from './beta-router'; +import { mailRouter } from './mail-router'; import type { TRPCContext } from './types'; import type { TRPCError } from '@trpc/server'; import { t } from './router'; @@ -15,6 +16,7 @@ export const appRouter = t.router({ scripts: scriptsRouter, waitlist: waitlistRouter, beta: betaRouter, + mail: mailRouter, } as const); export type AppRouter = typeof appRouter; diff --git a/server/trpc/mail-router.ts b/server/trpc/mail-router.ts new file mode 100644 index 000000000..01ffaa3ed --- /dev/null +++ b/server/trpc/mail-router.ts @@ -0,0 +1,155 @@ +import { z } from 'zod'; +import { baseRouter, publicProcedure, protectedProcedure } from './router'; + +export const mailRouter = baseRouter({ + messages: publicProcedure + .input(z.object({ + folder: z.string().optional(), + })) + .query(async ({ input, ctx }) => { + // TODO: Implement actual ProtonMail API call + return [] as Array<{ + id: string; + subject: string; + sender: { email: string; name?: string }; + recipients: Array<{ email: string; name?: string }>; + body: string; + attachments?: Array<{ + id: string; + filename: string; + mimeType: string; + size: number; + downloadUrl: string; + }>; + timestamp: string; + read: boolean; + }>; + }), + + message: publicProcedure + .input(z.object({ + messageId: z.string(), + })) + .query(async ({ input, ctx }) => { + // TODO: Implement actual ProtonMail API call + return {} as { + id: string; + subject: string; + sender: { email: string; name?: string }; + recipients: Array<{ email: string; name?: string }>; + body: string; + attachments?: Array<{ + id: string; + filename: string; + mimeType: string; + size: number; + downloadUrl: string; + }>; + timestamp: string; + read: boolean; + }; + }), + + send: protectedProcedure + .input(z.object({ + to: z.array(z.string()), + subject: z.string(), + body: z.string(), + attachments: z.array(z.object({ + id: z.string(), + filename: z.string(), + mimeType: z.string(), + size: z.number(), + downloadUrl: z.string(), + })).optional(), + })) + .mutation(async ({ input, ctx }) => { + // TODO: Implement actual ProtonMail API call + return {} as { + id: string; + subject: string; + sender: { email: string; name?: string }; + recipients: Array<{ email: string; name?: string }>; + body: string; + attachments?: Array<{ + id: string; + filename: string; + mimeType: string; + size: number; + downloadUrl: string; + }>; + timestamp: string; + read: boolean; + }; + }), + + contact: publicProcedure + .input(z.object({ + email: z.string().email(), + })) + .query(async ({ input, ctx }) => { + // TODO: Implement actual ProtonMail API call + return null as { + id: string; + email: string; + name: string; + phone?: string; + organization?: string; + } | null; + }), + + contacts: publicProcedure + .input(z.object({})) + .query(async ({ ctx }) => { + // TODO: Implement actual ProtonMail API call + return [] as Array<{ + id: string; + email: string; + name: string; + phone?: string; + organization?: string; + }>; + }), + + addContact: protectedProcedure + .input(z.object({ + email: z.string().email(), + name: z.string(), + phone: z.string().optional(), + organization: z.string().optional(), + })) + .mutation(async ({ input, ctx }) => { + // TODO: Implement actual ProtonMail API call + return {} as { + id: string; + email: string; + name: string; + phone?: string; + organization?: string; + }; + }), + + attachment: publicProcedure + .input(z.object({ + attachmentId: z.string(), + })) + .query(async ({ input, ctx }) => { + // TODO: Implement actual ProtonMail API call + return {} as { + id: string; + filename: string; + mimeType: string; + size: number; + downloadUrl: string; + }; + }), + + attachmentDownload: publicProcedure + .input(z.object({ + attachmentId: z.string(), + })) + .query(async ({ input, ctx }) => { + // TODO: Implement actual ProtonMail API call + return new Blob(); + }), +}); diff --git a/src/lib/mail/index.ts b/src/lib/mail/index.ts new file mode 100644 index 000000000..b39ccd755 --- /dev/null +++ b/src/lib/mail/index.ts @@ -0,0 +1,2 @@ +export { ProtonMailClient, protonMail } from './protonmail-client'; +export type { ProtonMailMessage, ProtonMailAttachment, ProtonMailContact } from './protonmail-client'; diff --git a/src/lib/mail/protonmail-client.ts b/src/lib/mail/protonmail-client.ts new file mode 100644 index 000000000..c06b5604d --- /dev/null +++ b/src/lib/mail/protonmail-client.ts @@ -0,0 +1,84 @@ +import { trpc } from '../api/trpc-client'; + +export interface ProtonMailMessage { + id: string; + subject: string; + sender: { email: string; name?: string }; + recipients: Array<{ email: string; name?: string }>; + body: string; + attachments?: ProtonMailAttachment[]; + timestamp: string; + read: boolean; +} + +export interface ProtonMailAttachment { + id: string; + filename: string; + mimeType: string; + size: number; + downloadUrl: string; +} + +export interface ProtonMailContact { + id: string; + email: string; + name: string; + phone?: string; + organization?: string; +} + +export class ProtonMailClient { + constructor(private baseUrl: string = `${(import.meta as any).env?.VITE_API_URL || 'http://localhost:8080'}`) {} + + async getMessages(folder?: string): Promise { + const result = await trpc.mail.messages.query({ folder }); + return result; + } + + async getMessage(messageId: string): Promise { + const result = await trpc.mail.message.query({ messageId }); + return result; + } + + async sendMessage( + to: string[], + subject: string, + body: string, + attachments?: ProtonMailAttachment[] + ): Promise { + const result = await trpc.mail.send.mutate({ + to, + subject, + body, + attachments, + }); + return result; + } + + async getContact(email: string): Promise { + const result = await trpc.mail.contact.query({ email }); + return result; + } + + async getContacts(): Promise { + const result = await trpc.mail.contacts.query({}); + return result; + } + + async addContact(contact: Omit): Promise { + const result = await trpc.mail.addContact.mutate(contact); + return result; + } + + async getAttachment(attachmentId: string): Promise { + const result = await trpc.mail.attachment.query({ attachmentId }); + return result; + } + + async downloadAttachment(attachmentId: string): Promise { + const result = await trpc.mail.attachmentDownload.query({ attachmentId }); + return result; + } +} + +export const protonMail = new ProtonMailClient();