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 <noreply@paperclip.ing>
This commit is contained in:
2026-04-27 14:35:07 -04:00
parent 9e3a54f508
commit 9583c06473
4 changed files with 243 additions and 0 deletions

View File

@@ -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;

155
server/trpc/mail-router.ts Normal file
View File

@@ -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();
}),
});

2
src/lib/mail/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export { ProtonMailClient, protonMail } from './protonmail-client';
export type { ProtonMailMessage, ProtonMailAttachment, ProtonMailContact } from './protonmail-client';

View File

@@ -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<ProtonMailMessage[]> {
const result = await trpc.mail.messages.query({ folder });
return result;
}
async getMessage(messageId: string): Promise<ProtonMailMessage> {
const result = await trpc.mail.message.query({ messageId });
return result;
}
async sendMessage(
to: string[],
subject: string,
body: string,
attachments?: ProtonMailAttachment[]
): Promise<ProtonMailMessage> {
const result = await trpc.mail.send.mutate({
to,
subject,
body,
attachments,
});
return result;
}
async getContact(email: string): Promise<ProtonMailContact | null> {
const result = await trpc.mail.contact.query({ email });
return result;
}
async getContacts(): Promise<ProtonMailContact[]> {
const result = await trpc.mail.contacts.query({});
return result;
}
async addContact(contact: Omit<ProtonMailContact, 'id'>): Promise<ProtonMailContact> {
const result = await trpc.mail.addContact.mutate(contact);
return result;
}
async getAttachment(attachmentId: string): Promise<ProtonMailAttachment> {
const result = await trpc.mail.attachment.query({ attachmentId });
return result;
}
async downloadAttachment(attachmentId: string): Promise<Blob> {
const result = await trpc.mail.attachmentDownload.query({ attachmentId });
return result;
}
}
export const protonMail = new ProtonMailClient();