Projektstart

This commit is contained in:
2026-01-22 16:04:42 +01:00
parent 57e5f652f8
commit 5174b88af9
2716 changed files with 4225555 additions and 128 deletions

View File

@@ -1,6 +1,8 @@
import { FastifyInstance } from "fastify";
import { z } from "zod";
import { prisma } from "../db.js";
import { logJobEvent } from "../queue/jobEvents.js";
import { queueCleanupJob, removeQueueJob } from "../queue/queue.js";
const roleSchema = z.object({
role: z.enum(["USER", "ADMIN"])
@@ -25,6 +27,74 @@ export async function adminRoutes(app: FastifyInstance) {
return { tenants };
});
app.get("/tenants/:id/export", async (request, reply) => {
const params = request.params as { id: string };
const query = request.query as { format?: string };
const tenant = await prisma.tenant.findUnique({
where: { id: params.id },
include: {
users: true,
mailboxAccounts: true,
rules: { include: { conditions: true, actions: true } },
jobs: { include: { unsubscribeAttempts: true, events: true } }
}
});
if (!tenant) return reply.code(404).send({ message: "Tenant not found" });
const scope = query.scope ?? "all";
if (query.format === "csv") {
const rows = [
["type", "id", "email", "name", "createdAt"].join(",")
];
if (scope === "all" || scope === "users") {
for (const user of tenant.users) {
rows.push(["user", user.id, user.email, tenant.name, user.createdAt.toISOString()].join(","));
}
}
if (scope === "all" || scope === "accounts") {
for (const account of tenant.mailboxAccounts) {
rows.push(["account", account.id, account.email, account.provider, account.createdAt.toISOString()].join(","));
}
}
if (scope === "all" || scope === "jobs") {
for (const job of tenant.jobs) {
rows.push(["job", job.id, "", job.status, job.createdAt.toISOString()].join(","));
}
}
reply.header("Content-Type", "text/csv");
return reply.send(rows.join("\n"));
}
if (scope === "users") return { users: tenant.users };
if (scope === "accounts") return { accounts: tenant.mailboxAccounts };
if (scope === "jobs") return { jobs: tenant.jobs };
if (scope === "rules") return { rules: tenant.rules };
return { tenant };
});
app.delete("/tenants/:id", async (request, reply) => {
const params = request.params as { id: string };
const tenant = await prisma.tenant.findUnique({ where: { id: params.id } });
if (!tenant) return reply.code(404).send({ message: "Tenant not found" });
await prisma.$transaction(async (tx) => {
const jobs = await tx.cleanupJob.findMany({ where: { tenantId: tenant.id } });
const jobIds = jobs.map((job) => job.id);
await tx.cleanupJobEvent.deleteMany({ where: { jobId: { in: jobIds } } });
await tx.unsubscribeAttempt.deleteMany({ where: { jobId: { in: jobIds } } });
await tx.cleanupJob.deleteMany({ where: { tenantId: tenant.id } });
await tx.ruleAction.deleteMany({ where: { rule: { tenantId: tenant.id } } });
await tx.ruleCondition.deleteMany({ where: { rule: { tenantId: tenant.id } } });
await tx.rule.deleteMany({ where: { tenantId: tenant.id } });
await tx.mailItem.deleteMany({ where: { folder: { mailboxAccount: { tenantId: tenant.id } } } });
await tx.mailboxFolder.deleteMany({ where: { mailboxAccount: { tenantId: tenant.id } } });
await tx.mailboxAccount.deleteMany({ where: { tenantId: tenant.id } });
await tx.user.deleteMany({ where: { tenantId: tenant.id } });
await tx.tenant.delete({ where: { id: tenant.id } });
});
return { success: true };
});
app.put("/tenants/:id", async (request, reply) => {
const params = request.params as { id: string };
const input = activeSchema.parse(request.body);
@@ -99,7 +169,7 @@ export async function adminRoutes(app: FastifyInstance) {
data: { password: hashed }
});
return { user: updated };
return { success: true };
});
app.get("/accounts", async () => {
@@ -163,4 +233,43 @@ export async function adminRoutes(app: FastifyInstance) {
});
return { events };
});
app.post("/jobs/:id/cancel", async (request, reply) => {
const params = request.params as { id: string };
const job = await prisma.cleanupJob.findUnique({ where: { id: params.id } });
if (!job) return reply.code(404).send({ message: "Job not found" });
await prisma.cleanupJob.update({ where: { id: job.id }, data: { status: "CANCELED" } });
await removeQueueJob(job.id);
await logJobEvent(job.id, "info", "Job canceled by admin", 100);
return { success: true };
});
app.post("/jobs/:id/retry", async (request, reply) => {
const params = request.params as { id: string };
const job = await prisma.cleanupJob.findUnique({ where: { id: params.id } });
if (!job) return reply.code(404).send({ message: "Job not found" });
const newJob = await prisma.cleanupJob.create({
data: {
tenantId: job.tenantId,
mailboxAccountId: job.mailboxAccountId,
dryRun: job.dryRun,
unsubscribeEnabled: job.unsubscribeEnabled,
routingEnabled: job.routingEnabled
}
});
await queueCleanupJob(newJob.id, newJob.mailboxAccountId);
await logJobEvent(newJob.id, "info", "Job queued by admin", 5);
return { jobId: newJob.id };
});
app.post("/impersonate/:userId", async (request, reply) => {
const params = request.params as { userId: string };
const user = await prisma.user.findUnique({ where: { id: params.userId } });
if (!user) return reply.code(404).send({ message: "User not found" });
const token = app.jwt.sign({ sub: user.id, tenantId: user.tenantId });
return { token };
});
}