Projektstart

This commit is contained in:
2026-01-22 16:26:57 +01:00
parent bc7fbf8ce6
commit 43c83e96bb
21 changed files with 264 additions and 70 deletions

View File

@@ -6,3 +6,5 @@ JWT_SECRET=change-me-super-secret
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GOOGLE_REDIRECT_URI=http://localhost:8000/oauth/gmail/callback
EXPORT_DIR=/tmp/mailcleaner-exports
EXPORT_TTL_HOURS=24

File diff suppressed because one or more lines are too long

View File

@@ -138,6 +138,7 @@ exports.Prisma.ExportJobScalarFieldEnum = {
scope: 'scope',
filePath: 'filePath',
error: 'error',
expiresAt: 'expiresAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};

View File

@@ -3156,6 +3156,7 @@ export namespace Prisma {
scope: string | null
filePath: string | null
error: string | null
expiresAt: Date | null
createdAt: Date | null
updatedAt: Date | null
}
@@ -3168,6 +3169,7 @@ export namespace Prisma {
scope: string | null
filePath: string | null
error: string | null
expiresAt: Date | null
createdAt: Date | null
updatedAt: Date | null
}
@@ -3180,6 +3182,7 @@ export namespace Prisma {
scope: number
filePath: number
error: number
expiresAt: number
createdAt: number
updatedAt: number
_all: number
@@ -3194,6 +3197,7 @@ export namespace Prisma {
scope?: true
filePath?: true
error?: true
expiresAt?: true
createdAt?: true
updatedAt?: true
}
@@ -3206,6 +3210,7 @@ export namespace Prisma {
scope?: true
filePath?: true
error?: true
expiresAt?: true
createdAt?: true
updatedAt?: true
}
@@ -3218,6 +3223,7 @@ export namespace Prisma {
scope?: true
filePath?: true
error?: true
expiresAt?: true
createdAt?: true
updatedAt?: true
_all?: true
@@ -3303,6 +3309,7 @@ export namespace Prisma {
scope: string
filePath: string | null
error: string | null
expiresAt: Date | null
createdAt: Date
updatedAt: Date
_count: ExportJobCountAggregateOutputType | null
@@ -3332,6 +3339,7 @@ export namespace Prisma {
scope?: boolean
filePath?: boolean
error?: boolean
expiresAt?: boolean
createdAt?: boolean
updatedAt?: boolean
tenant?: boolean | TenantDefaultArgs<ExtArgs>
@@ -3345,6 +3353,7 @@ export namespace Prisma {
scope?: boolean
filePath?: boolean
error?: boolean
expiresAt?: boolean
createdAt?: boolean
updatedAt?: boolean
tenant?: boolean | TenantDefaultArgs<ExtArgs>
@@ -3358,6 +3367,7 @@ export namespace Prisma {
scope?: boolean
filePath?: boolean
error?: boolean
expiresAt?: boolean
createdAt?: boolean
updatedAt?: boolean
}
@@ -3382,6 +3392,7 @@ export namespace Prisma {
scope: string
filePath: string | null
error: string | null
expiresAt: Date | null
createdAt: Date
updatedAt: Date
}, ExtArgs["result"]["exportJob"]>
@@ -3785,6 +3796,7 @@ export namespace Prisma {
readonly scope: FieldRef<"ExportJob", 'String'>
readonly filePath: FieldRef<"ExportJob", 'String'>
readonly error: FieldRef<"ExportJob", 'String'>
readonly expiresAt: FieldRef<"ExportJob", 'DateTime'>
readonly createdAt: FieldRef<"ExportJob", 'DateTime'>
readonly updatedAt: FieldRef<"ExportJob", 'DateTime'>
}
@@ -14108,6 +14120,7 @@ export namespace Prisma {
scope: 'scope',
filePath: 'filePath',
error: 'error',
expiresAt: 'expiresAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};
@@ -14513,6 +14526,7 @@ export namespace Prisma {
scope?: StringFilter<"ExportJob"> | string
filePath?: StringNullableFilter<"ExportJob"> | string | null
error?: StringNullableFilter<"ExportJob"> | string | null
expiresAt?: DateTimeNullableFilter<"ExportJob"> | Date | string | null
createdAt?: DateTimeFilter<"ExportJob"> | Date | string
updatedAt?: DateTimeFilter<"ExportJob"> | Date | string
tenant?: XOR<TenantRelationFilter, TenantWhereInput>
@@ -14526,6 +14540,7 @@ export namespace Prisma {
scope?: SortOrder
filePath?: SortOrderInput | SortOrder
error?: SortOrderInput | SortOrder
expiresAt?: SortOrderInput | SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
tenant?: TenantOrderByWithRelationInput
@@ -14542,6 +14557,7 @@ export namespace Prisma {
scope?: StringFilter<"ExportJob"> | string
filePath?: StringNullableFilter<"ExportJob"> | string | null
error?: StringNullableFilter<"ExportJob"> | string | null
expiresAt?: DateTimeNullableFilter<"ExportJob"> | Date | string | null
createdAt?: DateTimeFilter<"ExportJob"> | Date | string
updatedAt?: DateTimeFilter<"ExportJob"> | Date | string
tenant?: XOR<TenantRelationFilter, TenantWhereInput>
@@ -14555,6 +14571,7 @@ export namespace Prisma {
scope?: SortOrder
filePath?: SortOrderInput | SortOrder
error?: SortOrderInput | SortOrder
expiresAt?: SortOrderInput | SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
_count?: ExportJobCountOrderByAggregateInput
@@ -14573,6 +14590,7 @@ export namespace Prisma {
scope?: StringWithAggregatesFilter<"ExportJob"> | string
filePath?: StringNullableWithAggregatesFilter<"ExportJob"> | string | null
error?: StringNullableWithAggregatesFilter<"ExportJob"> | string | null
expiresAt?: DateTimeNullableWithAggregatesFilter<"ExportJob"> | Date | string | null
createdAt?: DateTimeWithAggregatesFilter<"ExportJob"> | Date | string
updatedAt?: DateTimeWithAggregatesFilter<"ExportJob"> | Date | string
}
@@ -15393,6 +15411,7 @@ export namespace Prisma {
scope: string
filePath?: string | null
error?: string | null
expiresAt?: Date | string | null
createdAt?: Date | string
updatedAt?: Date | string
tenant: TenantCreateNestedOneWithoutExportJobsInput
@@ -15406,6 +15425,7 @@ export namespace Prisma {
scope: string
filePath?: string | null
error?: string | null
expiresAt?: Date | string | null
createdAt?: Date | string
updatedAt?: Date | string
}
@@ -15417,6 +15437,7 @@ export namespace Prisma {
scope?: StringFieldUpdateOperationsInput | string
filePath?: NullableStringFieldUpdateOperationsInput | string | null
error?: NullableStringFieldUpdateOperationsInput | string | null
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
tenant?: TenantUpdateOneRequiredWithoutExportJobsNestedInput
@@ -15430,6 +15451,7 @@ export namespace Prisma {
scope?: StringFieldUpdateOperationsInput | string
filePath?: NullableStringFieldUpdateOperationsInput | string | null
error?: NullableStringFieldUpdateOperationsInput | string | null
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -15442,6 +15464,7 @@ export namespace Prisma {
scope: string
filePath?: string | null
error?: string | null
expiresAt?: Date | string | null
createdAt?: Date | string
updatedAt?: Date | string
}
@@ -15453,6 +15476,7 @@ export namespace Prisma {
scope?: StringFieldUpdateOperationsInput | string
filePath?: NullableStringFieldUpdateOperationsInput | string | null
error?: NullableStringFieldUpdateOperationsInput | string | null
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -15465,6 +15489,7 @@ export namespace Prisma {
scope?: StringFieldUpdateOperationsInput | string
filePath?: NullableStringFieldUpdateOperationsInput | string | null
error?: NullableStringFieldUpdateOperationsInput | string | null
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -16430,6 +16455,17 @@ export namespace Prisma {
not?: NestedStringNullableFilter<$PrismaModel> | string | null
}
export type DateTimeNullableFilter<$PrismaModel = never> = {
equals?: Date | string | DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
not?: NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
}
export type TenantRelationFilter = {
is?: TenantWhereInput
isNot?: TenantWhereInput
@@ -16448,6 +16484,7 @@ export namespace Prisma {
scope?: SortOrder
filePath?: SortOrder
error?: SortOrder
expiresAt?: SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
}
@@ -16460,6 +16497,7 @@ export namespace Prisma {
scope?: SortOrder
filePath?: SortOrder
error?: SortOrder
expiresAt?: SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
}
@@ -16472,6 +16510,7 @@ export namespace Prisma {
scope?: SortOrder
filePath?: SortOrder
error?: SortOrder
expiresAt?: SortOrder
createdAt?: SortOrder
updatedAt?: SortOrder
}
@@ -16504,6 +16543,20 @@ export namespace Prisma {
_max?: NestedStringNullableFilter<$PrismaModel>
}
export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
not?: NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
_count?: NestedIntNullableFilter<$PrismaModel>
_min?: NestedDateTimeNullableFilter<$PrismaModel>
_max?: NestedDateTimeNullableFilter<$PrismaModel>
}
export type EnumUserRoleFilter<$PrismaModel = never> = {
equals?: $Enums.UserRole | EnumUserRoleFieldRefInput<$PrismaModel>
in?: $Enums.UserRole[] | ListEnumUserRoleFieldRefInput<$PrismaModel>
@@ -16588,17 +16641,6 @@ export namespace Prisma {
not?: NestedBoolNullableFilter<$PrismaModel> | boolean | null
}
export type DateTimeNullableFilter<$PrismaModel = never> = {
equals?: Date | string | DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
not?: NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
}
export type MailboxFolderListRelationFilter = {
every?: MailboxFolderWhereInput
some?: MailboxFolderWhereInput
@@ -16735,20 +16777,6 @@ export namespace Prisma {
_max?: NestedBoolNullableFilter<$PrismaModel>
}
export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
not?: NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
_count?: NestedIntNullableFilter<$PrismaModel>
_min?: NestedDateTimeNullableFilter<$PrismaModel>
_max?: NestedDateTimeNullableFilter<$PrismaModel>
}
export type MailboxAccountRelationFilter = {
is?: MailboxAccountWhereInput
isNot?: MailboxAccountWhereInput
@@ -17348,6 +17376,10 @@ export namespace Prisma {
set?: string | null
}
export type NullableDateTimeFieldUpdateOperationsInput = {
set?: Date | string | null
}
export type TenantUpdateOneRequiredWithoutExportJobsNestedInput = {
create?: XOR<TenantCreateWithoutExportJobsInput, TenantUncheckedCreateWithoutExportJobsInput>
connectOrCreate?: TenantCreateOrConnectWithoutExportJobsInput
@@ -17432,10 +17464,6 @@ export namespace Prisma {
set?: boolean | null
}
export type NullableDateTimeFieldUpdateOperationsInput = {
set?: Date | string | null
}
export type TenantUpdateOneRequiredWithoutMailboxAccountsNestedInput = {
create?: XOR<TenantCreateWithoutMailboxAccountsInput, TenantUncheckedCreateWithoutMailboxAccountsInput>
connectOrCreate?: TenantCreateOrConnectWithoutMailboxAccountsInput
@@ -17949,6 +17977,17 @@ export namespace Prisma {
not?: NestedStringNullableFilter<$PrismaModel> | string | null
}
export type NestedDateTimeNullableFilter<$PrismaModel = never> = {
equals?: Date | string | DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
not?: NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
}
export type NestedEnumExportStatusWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.ExportStatus | EnumExportStatusFieldRefInput<$PrismaModel>
in?: $Enums.ExportStatus[] | ListEnumExportStatusFieldRefInput<$PrismaModel>
@@ -17987,6 +18026,20 @@ export namespace Prisma {
not?: NestedIntNullableFilter<$PrismaModel> | number | null
}
export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
not?: NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
_count?: NestedIntNullableFilter<$PrismaModel>
_min?: NestedDateTimeNullableFilter<$PrismaModel>
_max?: NestedDateTimeNullableFilter<$PrismaModel>
}
export type NestedEnumUserRoleFilter<$PrismaModel = never> = {
equals?: $Enums.UserRole | EnumUserRoleFieldRefInput<$PrismaModel>
in?: $Enums.UserRole[] | ListEnumUserRoleFieldRefInput<$PrismaModel>
@@ -18016,17 +18069,6 @@ export namespace Prisma {
not?: NestedBoolNullableFilter<$PrismaModel> | boolean | null
}
export type NestedDateTimeNullableFilter<$PrismaModel = never> = {
equals?: Date | string | DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
not?: NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null
}
export type NestedEnumMailProviderWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.MailProvider | EnumMailProviderFieldRefInput<$PrismaModel>
in?: $Enums.MailProvider[] | ListEnumMailProviderFieldRefInput<$PrismaModel>
@@ -18099,20 +18141,6 @@ export namespace Prisma {
_max?: NestedBoolNullableFilter<$PrismaModel>
}
export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = {
equals?: Date | string | DateTimeFieldRefInput<$PrismaModel> | null
in?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
notIn?: Date[] | string[] | ListDateTimeFieldRefInput<$PrismaModel> | null
lt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
lte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gt?: Date | string | DateTimeFieldRefInput<$PrismaModel>
gte?: Date | string | DateTimeFieldRefInput<$PrismaModel>
not?: NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null
_count?: NestedIntNullableFilter<$PrismaModel>
_min?: NestedDateTimeNullableFilter<$PrismaModel>
_max?: NestedDateTimeNullableFilter<$PrismaModel>
}
export type NestedEnumRuleConditionTypeFilter<$PrismaModel = never> = {
equals?: $Enums.RuleConditionType | EnumRuleConditionTypeFieldRefInput<$PrismaModel>
in?: $Enums.RuleConditionType[] | ListEnumRuleConditionTypeFieldRefInput<$PrismaModel>
@@ -18171,6 +18199,7 @@ export namespace Prisma {
scope: string
filePath?: string | null
error?: string | null
expiresAt?: Date | string | null
createdAt?: Date | string
updatedAt?: Date | string
}
@@ -18182,6 +18211,7 @@ export namespace Prisma {
scope: string
filePath?: string | null
error?: string | null
expiresAt?: Date | string | null
createdAt?: Date | string
updatedAt?: Date | string
}
@@ -18379,6 +18409,7 @@ export namespace Prisma {
scope?: StringFilter<"ExportJob"> | string
filePath?: StringNullableFilter<"ExportJob"> | string | null
error?: StringNullableFilter<"ExportJob"> | string | null
expiresAt?: DateTimeNullableFilter<"ExportJob"> | Date | string | null
createdAt?: DateTimeFilter<"ExportJob"> | Date | string
updatedAt?: DateTimeFilter<"ExportJob"> | Date | string
}
@@ -19759,6 +19790,7 @@ export namespace Prisma {
scope: string
filePath?: string | null
error?: string | null
expiresAt?: Date | string | null
createdAt?: Date | string
updatedAt?: Date | string
}
@@ -19822,6 +19854,7 @@ export namespace Prisma {
scope?: StringFieldUpdateOperationsInput | string
filePath?: NullableStringFieldUpdateOperationsInput | string | null
error?: NullableStringFieldUpdateOperationsInput | string | null
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -19833,6 +19866,7 @@ export namespace Prisma {
scope?: StringFieldUpdateOperationsInput | string
filePath?: NullableStringFieldUpdateOperationsInput | string | null
error?: NullableStringFieldUpdateOperationsInput | string | null
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
}
@@ -19844,6 +19878,7 @@ export namespace Prisma {
scope?: StringFieldUpdateOperationsInput | string
filePath?: NullableStringFieldUpdateOperationsInput | string | null
error?: NullableStringFieldUpdateOperationsInput | string | null
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
}

File diff suppressed because one or more lines are too long

View File

@@ -1,5 +1,5 @@
{
"name": "prisma-client-f972eed39fc4466cb85f0349f5e72ee1d81f9351e0ae96befde50b28d520b85a",
"name": "prisma-client-e73c910a086b88f8fcb9eefa67d9d9c4f84c5390da0c21a4dde9378c21e5e10d",
"main": "index.js",
"types": "index.d.ts",
"browser": "index-browser.js",

View File

@@ -70,6 +70,7 @@ model ExportJob {
scope String
filePath String?
error String?
expiresAt DateTime?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt

View File

@@ -138,6 +138,7 @@ exports.Prisma.ExportJobScalarFieldEnum = {
scope: 'scope',
filePath: 'filePath',
error: 'error',
expiresAt: 'expiresAt',
createdAt: 'createdAt',
updatedAt: 'updatedAt'
};

View File

@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "ExportJob" ADD COLUMN "expiresAt" TIMESTAMP(3);

View File

@@ -0,0 +1,20 @@
import { prisma } from "../db.js";
import { unlink } from "node:fs/promises";
export const cleanupExpiredExports = async () => {
const expired = await prisma.exportJob.findMany({
where: { expiresAt: { lt: new Date() } }
});
if (expired.length === 0) return;
for (const job of expired) {
if (job.filePath) {
try {
await unlink(job.filePath);
} catch {
// ignore
}
}
await prisma.exportJob.delete({ where: { id: job.id } });
}
};

View File

@@ -0,0 +1,7 @@
import { prisma } from "../db.js";
export const listExportsForTenant = async () => {
return prisma.exportJob.findMany({
orderBy: { createdAt: "desc" }
});
};

View File

@@ -1,10 +1,12 @@
import { prisma } from "../db.js";
import { createExportArchive } from "./exportZip.js";
import { mkdir, writeFile } from "node:fs/promises";
import { cleanupExpiredExports } from "./exportCleanup.js";
import { createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
const EXPORT_DIR = process.env.EXPORT_DIR ?? "/tmp/mailcleaner-exports";
const EXPORT_TTL_HOURS = Number(process.env.EXPORT_TTL_HOURS ?? 24);
const buildExport = async (jobId: string) => {
const job = await prisma.exportJob.findUnique({ where: { id: jobId }, include: { tenant: true } });
@@ -47,7 +49,7 @@ const buildExport = async (jobId: string) => {
await prisma.exportJob.update({
where: { id: job.id },
data: { status: "DONE", filePath }
data: { status: "DONE", filePath, expiresAt: new Date(Date.now() + EXPORT_TTL_HOURS * 3600 * 1000) }
});
};
@@ -58,6 +60,7 @@ export const processExportQueue = async () => {
orderBy: { createdAt: "asc" }
});
if (!job) {
await cleanupExpiredExports();
await new Promise((resolve) => setTimeout(resolve, 2000));
continue;
}

View File

@@ -3,6 +3,8 @@ import { z } from "zod";
import { prisma } from "../db.js";
import { logJobEvent } from "../queue/jobEvents.js";
import { queueCleanupJob, removeQueueJob } from "../queue/queue.js";
import { createReadStream } from "node:fs";
import { access } from "node:fs/promises";
const roleSchema = z.object({
role: z.enum(["USER", "ADMIN"])
@@ -94,10 +96,34 @@ export async function adminRoutes(app: FastifyInstance) {
const params = request.params as { id: string };
const job = await prisma.exportJob.findUnique({ where: { id: params.id } });
if (!job || !job.filePath) return reply.code(404).send({ message: "Export file not ready" });
if (job.expiresAt && job.expiresAt < new Date()) {
return reply.code(410).send({ message: "Export expired" });
}
try {
await access(job.filePath);
} catch {
return reply.code(404).send({ message: "Export file missing" });
}
reply.header("Content-Type", "application/zip");
reply.header("Content-Disposition", `attachment; filename=export-${job.id}.zip`);
return reply.sendFile(job.filePath);
return reply.send(createReadStream(job.filePath));
});
app.get("/exports", async () => {
const exports = await prisma.exportJob.findMany({
orderBy: { createdAt: "desc" }
});
const sanitized = exports.map((job) => ({
id: job.id,
status: job.status,
format: job.format,
scope: job.scope,
expiresAt: job.expiresAt,
createdAt: job.createdAt
}));
return { exports: sanitized };
});
app.delete("/tenants/:id", async (request, reply) => {

View File

@@ -84,6 +84,10 @@ export async function queueRoutes(app: FastifyInstance) {
app.get("/exports/:id/stream", async (request, reply) => {
const params = request.params as { id: string };
const user = await prisma.user.findUnique({ where: { id: request.user.sub } });
if (!user || user.role !== "ADMIN") {
return reply.code(403).send({ message: "Forbidden" });
}
const exportJob = await prisma.exportJob.findUnique({ where: { id: params.id } });
if (!exportJob) {
return reply.code(404).send({ message: "Export job not found" });