Implement database migration, notification logs, and one-click Mailcow setup
This commit is contained in:
24
app/api/logs/route.ts
Normal file
24
app/api/logs/route.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
// GET /api/logs — list notification logs
|
||||
export async function GET() {
|
||||
const session = await auth();
|
||||
if (!session || session.user.role !== "SUPER_ADMIN") {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const logs = await prisma.notificationLog.findMany({
|
||||
include: { user: true },
|
||||
orderBy: { createdAt: "desc" },
|
||||
take: 100, // Last 100 logs
|
||||
});
|
||||
|
||||
return NextResponse.json(logs);
|
||||
} catch (error: any) {
|
||||
console.error("[API Logs] Error:", error.message);
|
||||
return NextResponse.json({ error: "Tablo bulunamadı veya veritabanı hatası. Migration yapıldığından emin olun." }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
import { getMailboxes, createMailbox } from "@/lib/mailcow";
|
||||
import { getMailboxes, createMailbox, setupMailboxForwarding } from "@/lib/mailcow";
|
||||
import { canAccessDomain } from "@/lib/users";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
// GET /api/mailboxes?domain=example.com
|
||||
export async function GET(req: NextRequest) {
|
||||
// ... existing GET ...
|
||||
const session = await auth();
|
||||
if (!session) return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
||||
|
||||
@@ -26,6 +28,7 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
const body = await req.json();
|
||||
const { local_part, domain, name, password, quota } = body;
|
||||
const fullEmail = `${local_part}@${domain}`;
|
||||
|
||||
if (!local_part || !domain || !name || !password) {
|
||||
return NextResponse.json({ error: "Eksik alan" }, { status: 400 });
|
||||
@@ -35,6 +38,62 @@ export async function POST(req: NextRequest) {
|
||||
return NextResponse.json({ error: "Bu domaine erişim yetkiniz yok" }, { status: 403 });
|
||||
}
|
||||
|
||||
// 1. Create Mailbox in Mailcow
|
||||
const result = await createMailbox({ local_part, domain, name, password, quota });
|
||||
return NextResponse.json(result.data, { status: result.ok ? 200 : 502 });
|
||||
|
||||
if (!result.ok) {
|
||||
// Log failure to SystemLog
|
||||
try {
|
||||
await prisma.systemLog.create({
|
||||
data: {
|
||||
level: "ERROR",
|
||||
message: `Mailbox creation failed: ${fullEmail}`,
|
||||
details: JSON.stringify(result.data),
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("[SystemLog] Failed to log error", e);
|
||||
}
|
||||
return NextResponse.json(result.data, { status: 502 });
|
||||
}
|
||||
|
||||
// 2. Automated "One-Click" Setup: Create Forwarding to Webhook
|
||||
const webhookUrl = `${req.nextUrl.origin}/api/webhooks/mail`;
|
||||
console.log(`[Setup] Setting up auto-forwarding for ${fullEmail} to ${webhookUrl}`);
|
||||
|
||||
const setupResult = await setupMailboxForwarding(fullEmail, webhookUrl);
|
||||
|
||||
if (!setupResult.ok) {
|
||||
console.error(`[Setup] Failed to setup auto-forwarding for ${fullEmail}`);
|
||||
try {
|
||||
await prisma.systemLog.create({
|
||||
data: {
|
||||
level: "WARN",
|
||||
message: `Auto-forwarding setup failed for ${fullEmail}`,
|
||||
details: JSON.stringify(setupResult.data),
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("[SystemLog] Failed to log warning", e);
|
||||
}
|
||||
// We still return success for mailbox creation, but maybe with a warning header/prop
|
||||
return NextResponse.json({
|
||||
...result.data,
|
||||
setup_warning: "Bildirim kurulumu otomatik yapılamadı, lütfen manuel kontrol edin."
|
||||
});
|
||||
}
|
||||
|
||||
// Log success
|
||||
try {
|
||||
await prisma.systemLog.create({
|
||||
data: {
|
||||
level: "INFO",
|
||||
message: `Mailbox created and notification setup completed: ${fullEmail}`,
|
||||
},
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("[SystemLog] Failed to log success", e);
|
||||
}
|
||||
|
||||
return NextResponse.json(result.data);
|
||||
}
|
||||
|
||||
23
app/api/mappings/[id]/route.ts
Normal file
23
app/api/mappings/[id]/route.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
// DELETE /api/mappings/[id] — delete a mapping
|
||||
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await auth();
|
||||
const { id } = await params;
|
||||
|
||||
if (!session || session.user.role !== "SUPER_ADMIN") {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.mailboxMapping.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return NextResponse.json({ status: "ok" });
|
||||
} catch (error: any) {
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
43
app/api/mappings/route.ts
Normal file
43
app/api/mappings/route.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
// GET /api/mappings — list all mappings
|
||||
export async function GET() {
|
||||
const session = await auth();
|
||||
if (!session || session.user.role !== "SUPER_ADMIN") {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
const mappings = await prisma.mailboxMapping.findMany({
|
||||
include: { user: true },
|
||||
orderBy: { createdAt: "desc" },
|
||||
});
|
||||
|
||||
return NextResponse.json(mappings);
|
||||
}
|
||||
|
||||
// POST /api/mappings — create a new mapping
|
||||
export async function POST(req: NextRequest) {
|
||||
const session = await auth();
|
||||
if (!session || session.user.role !== "SUPER_ADMIN") {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { email, userId } = body;
|
||||
|
||||
const mapping = await prisma.mailboxMapping.create({
|
||||
data: {
|
||||
email: email.toLowerCase().trim(),
|
||||
userId,
|
||||
},
|
||||
include: { user: true },
|
||||
});
|
||||
|
||||
return NextResponse.json(mapping);
|
||||
} catch (error: any) {
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
54
app/api/users/[id]/route.ts
Normal file
54
app/api/users/[id]/route.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
// PATCH /api/users/[id] — update a user
|
||||
export async function PATCH(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await auth();
|
||||
const { id } = await params;
|
||||
|
||||
if (!session || session.user.role !== "SUPER_ADMIN") {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { name, email, password, role, domains, telegramId } = body;
|
||||
|
||||
const user = await prisma.user.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name,
|
||||
email: email?.toLowerCase(),
|
||||
password,
|
||||
role,
|
||||
domains,
|
||||
telegramId,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(user);
|
||||
} catch (error: any) {
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
// DELETE /api/users/[id] — delete a user
|
||||
export async function DELETE(req: NextRequest, { params }: { params: Promise<{ id: string }> }) {
|
||||
const session = await auth();
|
||||
const { id } = await params;
|
||||
|
||||
if (!session || session.user.role !== "SUPER_ADMIN") {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
await prisma.user.delete({
|
||||
where: { id },
|
||||
});
|
||||
|
||||
return NextResponse.json({ status: "ok" });
|
||||
} catch (error: any) {
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
@@ -1,22 +1,45 @@
|
||||
import { NextResponse } from "next/server";
|
||||
import { NextRequest, NextResponse } from "next/server";
|
||||
import { auth } from "@/auth";
|
||||
import { getUsers } from "@/lib/users";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
|
||||
// GET /api/users — super admin only, lists env-defined users (no passwords)
|
||||
// GET /api/users — list all users
|
||||
export async function GET() {
|
||||
const session = await auth();
|
||||
if (!session || session.user.role !== "SUPER_ADMIN") {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
const allUsers = await getUsers();
|
||||
const users = allUsers.map(({ id, name, email, role, domains }) => ({
|
||||
id,
|
||||
name,
|
||||
email,
|
||||
role,
|
||||
domains,
|
||||
}));
|
||||
const users = await prisma.user.findMany({
|
||||
orderBy: { createdAt: "asc" },
|
||||
});
|
||||
|
||||
return NextResponse.json(users);
|
||||
}
|
||||
|
||||
// POST /api/users — create a new user
|
||||
export async function POST(req: NextRequest) {
|
||||
const session = await auth();
|
||||
if (!session || session.user.role !== "SUPER_ADMIN") {
|
||||
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
||||
}
|
||||
|
||||
try {
|
||||
const body = await req.json();
|
||||
const { name, email, password, role, domains, telegramId } = body;
|
||||
|
||||
const user = await prisma.user.create({
|
||||
data: {
|
||||
name,
|
||||
email: email.toLowerCase(),
|
||||
password,
|
||||
role: role || "DOMAIN_ADMIN",
|
||||
domains: domains || [],
|
||||
telegramId,
|
||||
},
|
||||
});
|
||||
|
||||
return NextResponse.json(user);
|
||||
} catch (error: any) {
|
||||
return NextResponse.json({ error: error.message }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,15 +9,20 @@ import { prisma } from "@/lib/prisma";
|
||||
*/
|
||||
|
||||
export async function POST(req: NextRequest) {
|
||||
let aliciMail = "Bilinmiyor";
|
||||
let sender = "Bilinmiyor";
|
||||
let subject = "(Konu Yok)";
|
||||
|
||||
try {
|
||||
const data = await req.json();
|
||||
console.log("[Mail Webhook] Gelen Payload:", JSON.stringify(data));
|
||||
|
||||
// Extract basic info from the incoming payload
|
||||
const aliciMail = (data.to || data.rcpt || "").toLowerCase().trim();
|
||||
const sender = data.from || "Bilinmiyor";
|
||||
const subject = data.subject || "(Konu Yok)";
|
||||
// Extract basic info from the incoming payload (Mailcow handles these fields)
|
||||
aliciMail = (data.to || data.rcpt || "").toLowerCase().trim();
|
||||
sender = data.from || "Bilinmiyor";
|
||||
subject = data.subject || "(Konu Yok)";
|
||||
|
||||
console.log(`[Mail Webhook] Yeni mail geldi: ${sender} -> ${aliciMail}`);
|
||||
console.log(`[Mail Webhook] İşleniyor: ${sender} -> ${aliciMail}`);
|
||||
|
||||
// 1. Find mapping in database
|
||||
const mapping = await prisma.mailboxMapping.findUnique({
|
||||
@@ -31,33 +36,89 @@ export async function POST(req: NextRequest) {
|
||||
|
||||
if (targetChatId && process.env.TELEGRAM_BOT_TOKEN) {
|
||||
const message = `🔔 *Yeni Mail Geldi!*\n\n📧 *Alıcı:* ${aliciMail}\n👤 *Gönderen:* ${sender}\n📝 *Konu:* ${subject}`;
|
||||
|
||||
const telegramUrl = `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/sendMessage`;
|
||||
|
||||
const res = await fetch(telegramUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
chat_id: targetChatId,
|
||||
text: message,
|
||||
parse_mode: "Markdown",
|
||||
}),
|
||||
});
|
||||
let status = "SENT";
|
||||
let errorDetail = null;
|
||||
|
||||
if (!res.ok) {
|
||||
const errorText = await res.text();
|
||||
console.error(`[Mail Webhook] Telegram API hatası: ${res.status} ${errorText}`);
|
||||
} else {
|
||||
console.log(`[Webhook] Bildirim ${user.email} kullanıcısına (ID: ${targetChatId}) gönderildi.`);
|
||||
try {
|
||||
const res = await fetch(telegramUrl, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
chat_id: targetChatId,
|
||||
text: message,
|
||||
parse_mode: "Markdown",
|
||||
}),
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
status = "FAILED";
|
||||
errorDetail = `Telegram API Error: ${res.status} ${await res.text()}`;
|
||||
}
|
||||
} catch (err: any) {
|
||||
status = "FAILED";
|
||||
errorDetail = `Network Error: ${err.message}`;
|
||||
}
|
||||
|
||||
// Log successful/failed delivery
|
||||
await prisma.notificationLog.create({
|
||||
data: {
|
||||
mailbox: aliciMail,
|
||||
sender,
|
||||
subject,
|
||||
status,
|
||||
error: errorDetail,
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
// Log that user was found but notification skipped
|
||||
await prisma.notificationLog.create({
|
||||
data: {
|
||||
mailbox: aliciMail,
|
||||
sender,
|
||||
subject,
|
||||
status: "FAILED",
|
||||
error: !process.env.TELEGRAM_BOT_TOKEN ? "Bot token missing" : "User has no Telegram ID",
|
||||
userId: user.id,
|
||||
},
|
||||
});
|
||||
}
|
||||
} else {
|
||||
console.log(`[Webhook] Sahibi bilinmeyen veya eşleşmeyen mail: ${aliciMail}`);
|
||||
|
||||
// Log unmapped mail
|
||||
await prisma.notificationLog.create({
|
||||
data: {
|
||||
mailbox: aliciMail,
|
||||
sender,
|
||||
subject,
|
||||
status: "FAILED",
|
||||
error: "No user mapping found for this email",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
return NextResponse.json({ status: "ok" });
|
||||
} catch (error: any) {
|
||||
console.error(`[Mail Webhook] Hata: ${error.message}`);
|
||||
|
||||
// Attempt to log the fatal error if we have enough info
|
||||
try {
|
||||
await prisma.notificationLog.create({
|
||||
data: {
|
||||
mailbox: aliciMail,
|
||||
sender,
|
||||
subject,
|
||||
status: "FAILED",
|
||||
error: `Fatal Error: ${error.message}`,
|
||||
},
|
||||
});
|
||||
} catch (dbErr) {
|
||||
console.error("[Mail Webhook] Could not even log the error to DB");
|
||||
}
|
||||
|
||||
return NextResponse.json({ error: "İşlem başarısız" }, { status: 500 });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user