first commit

This commit is contained in:
AyrisAI
2026-05-14 01:57:52 +03:00
parent 863a32cd35
commit 4a9196f483
47 changed files with 12043 additions and 102 deletions

View File

@@ -0,0 +1,3 @@
import { handlers } from "@/auth";
export const { GET, POST } = handlers;

View File

@@ -0,0 +1,18 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { deleteDomain } from "@/lib/mailcow";
// DELETE /api/domains/[domain] — super admin only
export async function DELETE(
_req: NextRequest,
{ params }: { params: Promise<{ domain: string }> }
) {
const session = await auth();
if (!session || session.user.role !== "SUPER_ADMIN") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const { domain } = await params;
const result = await deleteDomain(decodeURIComponent(domain));
return NextResponse.json(result.data, { status: result.ok ? 200 : 502 });
}

34
app/api/domains/route.ts Normal file
View File

@@ -0,0 +1,34 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { getDomains, createDomain } from "@/lib/mailcow";
import { canAccessDomain } from "@/lib/users";
// GET /api/domains — list domains (filtered by session)
export async function GET() {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const allDomains = await getDomains();
const userDomains = session.user.domains ?? [];
// Super admin sees all, domain admin only sees their domains
const visible = allDomains.filter((d) => canAccessDomain(userDomains, d.domain_name));
return NextResponse.json(visible);
}
// POST /api/domains — create domain (super admin only)
export async function POST(req: NextRequest) {
const session = await auth();
if (!session || session.user.role !== "SUPER_ADMIN") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const body = await req.json();
const { domain, description, mailboxes, quota, maxquota } = body;
if (!domain) return NextResponse.json({ error: "domain gerekli" }, { status: 400 });
const result = await createDomain({ domain, description, mailboxes, quota, maxquota });
return NextResponse.json(result.data, { status: result.ok ? 200 : 502 });
}

View File

@@ -0,0 +1,49 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { getMailSession, setMailSession, clearMailSession } from "@/lib/mail-session";
import { listFolders } from "@/lib/imap";
// POST /api/mail/auth — login to mailbox (store creds in cookie)
export async function POST(req: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const { email, password } = await req.json();
if (!email || !password) {
return NextResponse.json({ error: "Email ve şifre gerekli" }, { status: 400 });
}
// Test the credentials by listing folders
try {
await listFolders({ email, password });
} catch (err: any) {
return NextResponse.json(
{ error: "IMAP bağlantısı başarısız: " + (err?.message ?? "Bilinmeyen hata") },
{ status: 401 }
);
}
await setMailSession({ email, password });
return NextResponse.json({ success: true, email });
}
// GET /api/mail/auth — check if mail session exists
export async function GET() {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const mailSession = await getMailSession();
if (!mailSession) {
return NextResponse.json({ connected: false });
}
return NextResponse.json({ connected: true, email: mailSession.email });
}
// DELETE /api/mail/auth — logout from mailbox
export async function DELETE() {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
await clearMailSession();
return NextResponse.json({ success: true });
}

View File

@@ -0,0 +1,23 @@
import { NextResponse } from "next/server";
import { auth } from "@/auth";
import { getMailSession } from "@/lib/mail-session";
import { listFolders } from "@/lib/imap";
// GET /api/mail/folders
export async function GET() {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const creds = await getMailSession();
if (!creds) return NextResponse.json({ error: "Mail oturumu yok" }, { status: 401 });
try {
const folders = await listFolders(creds);
return NextResponse.json(folders);
} catch (err: any) {
return NextResponse.json(
{ error: "Klasörler alınamadı: " + (err?.message ?? "") },
{ status: 502 }
);
}
}

View File

@@ -0,0 +1,44 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { getMailSession } from "@/lib/mail-session";
import { getAttachment } from "@/lib/imap";
// GET /api/mail/messages/[uid]/attachments?folder=INBOX&filename=doc.pdf
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ uid: string }> }
) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const creds = await getMailSession();
if (!creds) return NextResponse.json({ error: "Mail oturumu yok" }, { status: 401 });
const { uid } = await params;
const folder = req.nextUrl.searchParams.get("folder") ?? "INBOX";
const filename = req.nextUrl.searchParams.get("filename");
if (!filename) {
return NextResponse.json({ error: "Dosya adı gerekli" }, { status: 400 });
}
try {
const att = await getAttachment(creds, folder, parseInt(uid), filename);
if (!att) {
return NextResponse.json({ error: "Ek bulunamadı" }, { status: 404 });
}
return new NextResponse(att.content, {
headers: {
"Content-Type": att.contentType,
"Content-Disposition": `attachment; filename="${encodeURIComponent(filename)}"`,
"Content-Length": String(att.content.length),
},
});
} catch (err: any) {
return NextResponse.json(
{ error: "Ek indirilemedi: " + (err?.message ?? "") },
{ status: 502 }
);
}
}

View File

@@ -0,0 +1,32 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { getMailSession } from "@/lib/mail-session";
import { getMessage } from "@/lib/imap";
// GET /api/mail/messages/[uid]?folder=INBOX
export async function GET(
req: NextRequest,
{ params }: { params: Promise<{ uid: string }> }
) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const creds = await getMailSession();
if (!creds) return NextResponse.json({ error: "Mail oturumu yok" }, { status: 401 });
const { uid } = await params;
const folder = req.nextUrl.searchParams.get("folder") ?? "INBOX";
try {
const message = await getMessage(creds, folder, parseInt(uid));
if (!message) {
return NextResponse.json({ error: "Mesaj bulunamadı" }, { status: 404 });
}
return NextResponse.json(message);
} catch (err: any) {
return NextResponse.json(
{ error: "Mesaj alınamadı: " + (err?.message ?? "") },
{ status: 502 }
);
}
}

View File

@@ -0,0 +1,59 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { getMailSession } from "@/lib/mail-session";
import { listMessages, deleteMessage, moveMessage, toggleFlag } from "@/lib/imap";
// GET /api/mail/messages?folder=INBOX&page=1
export async function GET(req: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const creds = await getMailSession();
if (!creds) return NextResponse.json({ error: "Mail oturumu yok" }, { status: 401 });
const folder = req.nextUrl.searchParams.get("folder") ?? "INBOX";
const page = parseInt(req.nextUrl.searchParams.get("page") ?? "1");
try {
const result = await listMessages(creds, folder, page, 50);
return NextResponse.json(result);
} catch (err: any) {
return NextResponse.json(
{ error: "Mesajlar alınamadı: " + (err?.message ?? "") },
{ status: 502 }
);
}
}
// POST /api/mail/messages — actions: delete, move, flag
export async function POST(req: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const creds = await getMailSession();
if (!creds) return NextResponse.json({ error: "Mail oturumu yok" }, { status: 401 });
const { action, folder, uid, toFolder, flag, add } = await req.json();
try {
switch (action) {
case "delete":
await deleteMessage(creds, folder, uid);
break;
case "move":
await moveMessage(creds, folder, uid, toFolder);
break;
case "flag":
await toggleFlag(creds, folder, uid, flag, add);
break;
default:
return NextResponse.json({ error: "Bilinmeyen işlem" }, { status: 400 });
}
return NextResponse.json({ success: true });
} catch (err: any) {
return NextResponse.json(
{ error: "İşlem başarısız: " + (err?.message ?? "") },
{ status: 502 }
);
}
}

View File

@@ -0,0 +1,64 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { getMailSession } from "@/lib/mail-session";
import { sendMail } from "@/lib/smtp";
// POST /api/mail/send — supports both JSON and FormData (for attachments)
export async function POST(req: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const creds = await getMailSession();
if (!creds) return NextResponse.json({ error: "Mail oturumu yok" }, { status: 401 });
const contentType = req.headers.get("content-type") ?? "";
let to: string, cc: string | undefined, subject: string, html: string, text: string | undefined;
let attachments: { filename: string; content: Buffer; contentType?: string }[] = [];
if (contentType.includes("multipart/form-data")) {
// FormData — with attachments
const formData = await req.formData();
to = formData.get("to") as string;
cc = (formData.get("cc") as string) || undefined;
subject = formData.get("subject") as string;
html = (formData.get("html") as string) || "";
text = (formData.get("text") as string) || undefined;
const files = formData.getAll("attachments") as File[];
for (const file of files) {
const buffer = Buffer.from(await file.arrayBuffer());
attachments.push({
filename: file.name,
content: buffer,
contentType: file.type || undefined,
});
}
} else {
// JSON — simple message
const body = await req.json();
to = body.to;
cc = body.cc || undefined;
subject = body.subject;
html = body.html || `<p>${body.text || ""}</p>`;
text = body.text || undefined;
}
if (!to || !subject) {
return NextResponse.json({ error: "Alıcı ve konu gerekli" }, { status: 400 });
}
const result = await sendMail(creds, {
to,
cc,
subject,
html,
text,
attachments: attachments.length > 0 ? attachments : undefined,
});
if (result.success) {
return NextResponse.json({ success: true, messageId: result.messageId });
}
return NextResponse.json({ error: result.error }, { status: 502 });
}

View File

@@ -0,0 +1,55 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { deleteMailbox, editMailbox, getMailboxes } from "@/lib/mailcow";
import { canAccessDomain } from "@/lib/users";
async function checkAccess(session: Awaited<ReturnType<typeof auth>>, email: string) {
if (!session) return false;
const domain = email.split("@")[1];
return canAccessDomain(session.user.domains ?? [], domain);
}
// DELETE /api/mailboxes/[email]
export async function DELETE(
_req: NextRequest,
{ params }: { params: Promise<{ email: string }> }
) {
const session = await auth();
const { email } = await params;
const decoded = decodeURIComponent(email);
if (!(await checkAccess(session, decoded))) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const result = await deleteMailbox([decoded]);
return NextResponse.json(result.data, { status: result.ok ? 200 : 502 });
}
// PATCH /api/mailboxes/[email] — password change or toggle active
export async function PATCH(
req: NextRequest,
{ params }: { params: Promise<{ email: string }> }
) {
const session = await auth();
const { email } = await params;
const decoded = decodeURIComponent(email);
if (!(await checkAccess(session, decoded))) {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const body = await req.json();
const attr: Parameters<typeof editMailbox>[1] = {};
if (body.password) {
attr.password = body.password;
attr.password2 = body.password;
}
if (typeof body.active === "number") {
attr.active = body.active;
}
const result = await editMailbox([decoded], attr);
return NextResponse.json(result.data, { status: result.ok ? 200 : 502 });
}

View File

@@ -0,0 +1,40 @@
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/auth";
import { getMailboxes, createMailbox } from "@/lib/mailcow";
import { canAccessDomain } from "@/lib/users";
// GET /api/mailboxes?domain=example.com
export async function GET(req: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const domain = req.nextUrl.searchParams.get("domain");
if (!domain) return NextResponse.json({ error: "domain parametresi gerekli" }, { status: 400 });
if (!canAccessDomain(session.user.domains ?? [], domain)) {
return NextResponse.json({ error: "Bu domaine erişim yetkiniz yok" }, { status: 403 });
}
const mailboxes = await getMailboxes(domain);
return NextResponse.json(mailboxes);
}
// POST /api/mailboxes
export async function POST(req: NextRequest) {
const session = await auth();
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
const body = await req.json();
const { local_part, domain, name, password, quota } = body;
if (!local_part || !domain || !name || !password) {
return NextResponse.json({ error: "Eksik alan" }, { status: 400 });
}
if (!canAccessDomain(session.user.domains ?? [], domain)) {
return NextResponse.json({ error: "Bu domaine erişim yetkiniz yok" }, { status: 403 });
}
const result = await createMailbox({ local_part, domain, name, password, quota });
return NextResponse.json(result.data, { status: result.ok ? 200 : 502 });
}

21
app/api/users/route.ts Normal file
View File

@@ -0,0 +1,21 @@
import { NextResponse } from "next/server";
import { auth } from "@/auth";
import { getUsers } from "@/lib/users";
// GET /api/users — super admin only, lists env-defined users (no passwords)
export async function GET() {
const session = await auth();
if (!session || session.user.role !== "SUPER_ADMIN") {
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
}
const users = getUsers().map(({ id, name, email, role, domains }) => ({
id,
name,
email,
role,
domains,
}));
return NextResponse.json(users);
}