first version
This commit is contained in:
9
app/api/auth/route.ts
Normal file
9
app/api/auth/route.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
const { password } = await req.json();
|
||||
if (password === process.env.APP_PASSWORD) {
|
||||
return NextResponse.json({ success: true });
|
||||
}
|
||||
return NextResponse.json({ error: 'Invalid password' }, { status: 401 });
|
||||
}
|
||||
11
app/api/domains/route.ts
Normal file
11
app/api/domains/route.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { db } from '@/app/db/drizzle';
|
||||
import { domains } from '@/app/db/schema';
|
||||
import { authenticate } from '@/app/lib/utils';
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
if (!authenticate(req)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
const domainList = await db.select({ bucket: domains.bucket, domain: domains.domain }).from(domains);
|
||||
return NextResponse.json(domainList);
|
||||
}
|
||||
92
app/api/email/route.ts
Normal file
92
app/api/email/route.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/app/db/drizzle';
|
||||
import { emails } from '@/app/db/schema';
|
||||
import { authenticate, getBody } from '@/app/lib/utils';
|
||||
import { eq } from 'drizzle-orm';
|
||||
import { CopyObjectCommand, GetObjectCommand, HeadObjectCommand } from '@aws-sdk/client-s3';
|
||||
import { getS3Client } from '@/app/lib/utils';
|
||||
import nodemailer from 'nodemailer';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
if (!authenticate(req)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
const { searchParams } = new URL(req.url);
|
||||
const bucket = searchParams.get('bucket');
|
||||
const key = searchParams.get('key');
|
||||
if (!bucket || !key) return NextResponse.json({ error: 'Missing params' }, { status: 400 });
|
||||
|
||||
const [email] = await db.select().from(emails).where(eq(emails.s3Key, key));
|
||||
if (!email) return NextResponse.json({ error: 'Email not found' }, { status: 404 });
|
||||
|
||||
return NextResponse.json({
|
||||
subject: email.subject,
|
||||
from: email.from,
|
||||
to: email.to?.join(', '),
|
||||
html: email.html,
|
||||
raw: email.raw,
|
||||
processed: email.processed ? 'true' : 'false',
|
||||
});
|
||||
}
|
||||
|
||||
// PUT: Update processed in S3 and DB
|
||||
export async function PUT(req: NextRequest) {
|
||||
if (!authenticate(req)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
const { bucket, key, processed } = await req.json();
|
||||
if (!bucket || !key) return NextResponse.json({ error: 'Missing params' }, { status: 400 });
|
||||
|
||||
const s3 = getS3Client();
|
||||
const head = await s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
|
||||
const newMeta = { ...head.Metadata, [process.env.PROCESSED_META_KEY!]: processed };
|
||||
await s3.send(new CopyObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
CopySource: `${bucket}/${key}`,
|
||||
Metadata: newMeta,
|
||||
MetadataDirective: 'REPLACE'
|
||||
}));
|
||||
|
||||
await db.update(emails).set({ processed: processed === 'true' }).where(eq(emails.s3Key, key));
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
}
|
||||
|
||||
// POST: Resend, update in S3 and DB
|
||||
export async function POST(req: NextRequest) {
|
||||
if (!authenticate(req)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
const { bucket, key } = await req.json();
|
||||
if (!bucket || !key) return NextResponse.json({ error: 'Missing params' }, { status: 400 });
|
||||
|
||||
const s3 = getS3Client();
|
||||
const { Body } = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
|
||||
const raw = await getBody(Body as Readable);
|
||||
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: process.env.SMTP_HOST,
|
||||
port: Number(process.env.SMTP_PORT),
|
||||
secure: false,
|
||||
auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS },
|
||||
tls: { rejectUnauthorized: false }
|
||||
});
|
||||
|
||||
try {
|
||||
await transporter.sendMail({ raw });
|
||||
// Update S3 Metadata
|
||||
const head = await s3.send(new HeadObjectCommand({ Bucket: bucket, Key: key }));
|
||||
const newMeta = { ...head.Metadata, [process.env.PROCESSED_META_KEY!]: process.env.PROCESSED_META_VALUE! };
|
||||
await s3.send(new CopyObjectCommand({
|
||||
Bucket: bucket,
|
||||
Key: key,
|
||||
CopySource: `${bucket}/${key}`,
|
||||
Metadata: newMeta,
|
||||
MetadataDirective: 'REPLACE'
|
||||
}));
|
||||
// Update DB
|
||||
await db.update(emails).set({ processed: true }).where(eq(emails.s3Key, key));
|
||||
return NextResponse.json({ message: 'Resent successfully' });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ error: (error as Error).message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
26
app/api/emails/route.ts
Normal file
26
app/api/emails/route.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/app/db/drizzle';
|
||||
import { domains, emails } from '@/app/db/schema';
|
||||
import { authenticate } from '@/app/lib/utils';
|
||||
import { eq, sql } from 'drizzle-orm';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
if (!authenticate(req)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
const { searchParams } = new URL(req.url);
|
||||
const bucket = searchParams.get('bucket');
|
||||
const mailbox = searchParams.get('mailbox')?.toLowerCase();
|
||||
if (!bucket || !mailbox) return NextResponse.json({ error: 'Missing params' }, { status: 400 });
|
||||
|
||||
const [domain] = await db.select().from(domains).where(eq(domains.bucket, bucket));
|
||||
if (!domain) return NextResponse.json({ error: 'Domain not found' }, { status: 404 });
|
||||
|
||||
const emailList = await db.select({
|
||||
key: emails.s3Key,
|
||||
subject: emails.subject,
|
||||
date: emails.date,
|
||||
processed: emails.processed,
|
||||
}).from(emails).where(sql`${mailbox} = ANY(${emails.to}) AND ${emails.domainId} = ${domain.id}`);
|
||||
|
||||
return NextResponse.json(emailList.map(e => ({ key: e.key, subject: e.subject, date: e.date?.toISOString(), processed: e.processed ? 'true' : 'false' })));
|
||||
}
|
||||
22
app/api/mailboxes/route.ts
Normal file
22
app/api/mailboxes/route.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/app/db/drizzle';
|
||||
import { domains, emails } from '@/app/db/schema';
|
||||
import { authenticate } from '@/app/lib/utils';
|
||||
import { eq, sql } from 'drizzle-orm';
|
||||
|
||||
export async function GET(req: NextRequest) {
|
||||
if (!authenticate(req)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
const { searchParams } = new URL(req.url);
|
||||
const bucket = searchParams.get('bucket');
|
||||
if (!bucket) return NextResponse.json({ error: 'Missing bucket' }, { status: 400 });
|
||||
|
||||
const [domain] = await db.select().from(domains).where(eq(domains.bucket, bucket));
|
||||
if (!domain) return NextResponse.json({ error: 'Domain not found' }, { status: 404 });
|
||||
|
||||
const mailboxData = await db.select({ to: emails.to }).from(emails).where(eq(emails.domainId, domain.id));
|
||||
const uniqueMailboxes = new Set<string>();
|
||||
mailboxData.forEach(em => em.to?.forEach(r => uniqueMailboxes.add(r.toLowerCase())));
|
||||
|
||||
return NextResponse.json(Array.from(uniqueMailboxes));
|
||||
}
|
||||
30
app/api/resend-domain/route.ts
Normal file
30
app/api/resend-domain/route.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
import { db } from '@/app/db/drizzle';
|
||||
import { domains, emails } from '@/app/db/schema';
|
||||
import { authenticate } from '@/app/lib/utils';
|
||||
import { eq } from 'drizzle-orm';
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
if (!authenticate(req)) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
|
||||
|
||||
const { bucket } = await req.json();
|
||||
if (!bucket) return NextResponse.json({ error: 'Missing bucket' }, { status: 400 });
|
||||
|
||||
const [domain] = await db.select().from(domains).where(eq(domains.bucket, bucket));
|
||||
if (!domain) return NextResponse.json({ error: 'Domain not found' }, { status: 404 });
|
||||
|
||||
const unprocessed = await db.select({ s3Key: emails.s3Key }).from(emails).where(eq(emails.processed, false));
|
||||
|
||||
let count = 0;
|
||||
for (const em of unprocessed) {
|
||||
// Call POST /api/email internally for resend (updates DB/S3)
|
||||
await fetch(`${req.headers.get('origin')}/api/email`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json', Authorization: req.headers.get('Authorization')! },
|
||||
body: JSON.stringify({ bucket, key: em.s3Key }),
|
||||
});
|
||||
count++;
|
||||
}
|
||||
|
||||
return NextResponse.json({ message: `Resent ${count} emails` });
|
||||
}
|
||||
Reference in New Issue
Block a user