Projektstart
This commit is contained in:
16
README.md
16
README.md
@@ -49,7 +49,12 @@ docker compose up --build
|
||||
- `GET /admin/exports` (admin)
|
||||
- `GET /admin/exports/:id` (admin)
|
||||
- `GET /admin/exports/:id/download` (admin)
|
||||
- `POST /admin/exports/purge` (admin)
|
||||
- `DELETE /admin/exports/:id` (admin)
|
||||
- `GET /jobs/exports/:id/stream` (auth, SSE)
|
||||
|
||||
Export queue:
|
||||
- ZIP exports are queued via Redis/BullMQ and processed by the worker container.
|
||||
- `DELETE /admin/tenants/:id` (admin)
|
||||
|
||||
OAuth:
|
||||
@@ -70,8 +75,10 @@ UI:
|
||||
```bash
|
||||
cd backend
|
||||
DATABASE_URL=postgresql://mailcleaner:mailcleaner@localhost:5432/mailcleaner \\
|
||||
SEED_EMAIL=admin@simplemailcleaner.local \\
|
||||
SEED_PASSWORD=change-me-now \\
|
||||
SEED_ADMIN_EMAIL=admin@simplemailcleaner.local \\
|
||||
SEED_ADMIN_PASSWORD=change-me-now \\
|
||||
SEED_TENANT=Default Tenant \\
|
||||
SEED_TENANT_ID=seed-tenant \\
|
||||
npm run prisma:seed
|
||||
```
|
||||
- DSGVO: data storage is designed for tenant isolation; encryption at rest will be added.
|
||||
@@ -82,3 +89,8 @@ npm run prisma:seed
|
||||
Export settings:
|
||||
- `EXPORT_DIR` (default `/tmp/mailcleaner-exports`)
|
||||
- `EXPORT_TTL_HOURS` (default `24`)
|
||||
|
||||
Proxy settings (Nginx Proxy Manager):
|
||||
- `TRUST_PROXY=true`
|
||||
- `VITE_API_URL=https://your-domain.tld`
|
||||
- `GOOGLE_REDIRECT_URI=https://your-domain.tld/oauth/gmail/callback`
|
||||
|
||||
14
backend/.env
Normal file
14
backend/.env
Normal file
@@ -0,0 +1,14 @@
|
||||
NODE_ENV=development
|
||||
PORT=8000
|
||||
DATABASE_URL=postgresql://mailcleaner:mailcleaner@localhost:5432/mailcleaner
|
||||
REDIS_URL=redis://localhost:6379
|
||||
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
|
||||
SEED_ADMIN_EMAIL=admin@simplemailcleaner.local
|
||||
SEED_ADMIN_PASSWORD=change-me-now
|
||||
SEED_TENANT=Default Tenant
|
||||
SEED_TENANT_ID=seed-tenant
|
||||
@@ -8,3 +8,9 @@ GOOGLE_CLIENT_SECRET=
|
||||
GOOGLE_REDIRECT_URI=http://localhost:8000/oauth/gmail/callback
|
||||
EXPORT_DIR=/tmp/mailcleaner-exports
|
||||
EXPORT_TTL_HOURS=24
|
||||
TRUST_PROXY=false
|
||||
VITE_API_URL=https://example.com
|
||||
SEED_ADMIN_EMAIL=admin@simplemailcleaner.local
|
||||
SEED_ADMIN_PASSWORD=change-me-now
|
||||
SEED_TENANT=Default Tenant
|
||||
SEED_TENANT_ID=seed-tenant
|
||||
|
||||
9
backend/node_modules/.prisma/client/edge.js
generated
vendored
9
backend/node_modules/.prisma/client/edge.js
generated
vendored
File diff suppressed because one or more lines are too long
3
backend/node_modules/.prisma/client/index-browser.js
generated
vendored
3
backend/node_modules/.prisma/client/index-browser.js
generated
vendored
@@ -136,6 +136,7 @@ exports.Prisma.ExportJobScalarFieldEnum = {
|
||||
status: 'status',
|
||||
format: 'format',
|
||||
scope: 'scope',
|
||||
progress: 'progress',
|
||||
filePath: 'filePath',
|
||||
error: 'error',
|
||||
expiresAt: 'expiresAt',
|
||||
@@ -171,6 +172,8 @@ exports.Prisma.MailboxAccountScalarFieldEnum = {
|
||||
oauthAccessToken: 'oauthAccessToken',
|
||||
oauthExpiresAt: 'oauthExpiresAt',
|
||||
providerUserId: 'providerUserId',
|
||||
oauthLastCheckedAt: 'oauthLastCheckedAt',
|
||||
oauthLastErrorCode: 'oauthLastErrorCode',
|
||||
appPassword: 'appPassword',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
|
||||
317
backend/node_modules/.prisma/client/index.d.ts
generated
vendored
317
backend/node_modules/.prisma/client/index.d.ts
generated
vendored
@@ -3144,16 +3144,27 @@ export namespace Prisma {
|
||||
|
||||
export type AggregateExportJob = {
|
||||
_count: ExportJobCountAggregateOutputType | null
|
||||
_avg: ExportJobAvgAggregateOutputType | null
|
||||
_sum: ExportJobSumAggregateOutputType | null
|
||||
_min: ExportJobMinAggregateOutputType | null
|
||||
_max: ExportJobMaxAggregateOutputType | null
|
||||
}
|
||||
|
||||
export type ExportJobAvgAggregateOutputType = {
|
||||
progress: number | null
|
||||
}
|
||||
|
||||
export type ExportJobSumAggregateOutputType = {
|
||||
progress: number | null
|
||||
}
|
||||
|
||||
export type ExportJobMinAggregateOutputType = {
|
||||
id: string | null
|
||||
tenantId: string | null
|
||||
status: $Enums.ExportStatus | null
|
||||
format: string | null
|
||||
scope: string | null
|
||||
progress: number | null
|
||||
filePath: string | null
|
||||
error: string | null
|
||||
expiresAt: Date | null
|
||||
@@ -3167,6 +3178,7 @@ export namespace Prisma {
|
||||
status: $Enums.ExportStatus | null
|
||||
format: string | null
|
||||
scope: string | null
|
||||
progress: number | null
|
||||
filePath: string | null
|
||||
error: string | null
|
||||
expiresAt: Date | null
|
||||
@@ -3180,6 +3192,7 @@ export namespace Prisma {
|
||||
status: number
|
||||
format: number
|
||||
scope: number
|
||||
progress: number
|
||||
filePath: number
|
||||
error: number
|
||||
expiresAt: number
|
||||
@@ -3189,12 +3202,21 @@ export namespace Prisma {
|
||||
}
|
||||
|
||||
|
||||
export type ExportJobAvgAggregateInputType = {
|
||||
progress?: true
|
||||
}
|
||||
|
||||
export type ExportJobSumAggregateInputType = {
|
||||
progress?: true
|
||||
}
|
||||
|
||||
export type ExportJobMinAggregateInputType = {
|
||||
id?: true
|
||||
tenantId?: true
|
||||
status?: true
|
||||
format?: true
|
||||
scope?: true
|
||||
progress?: true
|
||||
filePath?: true
|
||||
error?: true
|
||||
expiresAt?: true
|
||||
@@ -3208,6 +3230,7 @@ export namespace Prisma {
|
||||
status?: true
|
||||
format?: true
|
||||
scope?: true
|
||||
progress?: true
|
||||
filePath?: true
|
||||
error?: true
|
||||
expiresAt?: true
|
||||
@@ -3221,6 +3244,7 @@ export namespace Prisma {
|
||||
status?: true
|
||||
format?: true
|
||||
scope?: true
|
||||
progress?: true
|
||||
filePath?: true
|
||||
error?: true
|
||||
expiresAt?: true
|
||||
@@ -3264,6 +3288,18 @@ export namespace Prisma {
|
||||
* Count returned ExportJobs
|
||||
**/
|
||||
_count?: true | ExportJobCountAggregateInputType
|
||||
/**
|
||||
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}
|
||||
*
|
||||
* Select which fields to average
|
||||
**/
|
||||
_avg?: ExportJobAvgAggregateInputType
|
||||
/**
|
||||
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}
|
||||
*
|
||||
* Select which fields to sum
|
||||
**/
|
||||
_sum?: ExportJobSumAggregateInputType
|
||||
/**
|
||||
* {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs}
|
||||
*
|
||||
@@ -3297,6 +3333,8 @@ export namespace Prisma {
|
||||
take?: number
|
||||
skip?: number
|
||||
_count?: ExportJobCountAggregateInputType | true
|
||||
_avg?: ExportJobAvgAggregateInputType
|
||||
_sum?: ExportJobSumAggregateInputType
|
||||
_min?: ExportJobMinAggregateInputType
|
||||
_max?: ExportJobMaxAggregateInputType
|
||||
}
|
||||
@@ -3307,12 +3345,15 @@ export namespace Prisma {
|
||||
status: $Enums.ExportStatus
|
||||
format: string
|
||||
scope: string
|
||||
progress: number
|
||||
filePath: string | null
|
||||
error: string | null
|
||||
expiresAt: Date | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
_count: ExportJobCountAggregateOutputType | null
|
||||
_avg: ExportJobAvgAggregateOutputType | null
|
||||
_sum: ExportJobSumAggregateOutputType | null
|
||||
_min: ExportJobMinAggregateOutputType | null
|
||||
_max: ExportJobMaxAggregateOutputType | null
|
||||
}
|
||||
@@ -3337,6 +3378,7 @@ export namespace Prisma {
|
||||
status?: boolean
|
||||
format?: boolean
|
||||
scope?: boolean
|
||||
progress?: boolean
|
||||
filePath?: boolean
|
||||
error?: boolean
|
||||
expiresAt?: boolean
|
||||
@@ -3351,6 +3393,7 @@ export namespace Prisma {
|
||||
status?: boolean
|
||||
format?: boolean
|
||||
scope?: boolean
|
||||
progress?: boolean
|
||||
filePath?: boolean
|
||||
error?: boolean
|
||||
expiresAt?: boolean
|
||||
@@ -3365,6 +3408,7 @@ export namespace Prisma {
|
||||
status?: boolean
|
||||
format?: boolean
|
||||
scope?: boolean
|
||||
progress?: boolean
|
||||
filePath?: boolean
|
||||
error?: boolean
|
||||
expiresAt?: boolean
|
||||
@@ -3390,6 +3434,7 @@ export namespace Prisma {
|
||||
status: $Enums.ExportStatus
|
||||
format: string
|
||||
scope: string
|
||||
progress: number
|
||||
filePath: string | null
|
||||
error: string | null
|
||||
expiresAt: Date | null
|
||||
@@ -3794,6 +3839,7 @@ export namespace Prisma {
|
||||
readonly status: FieldRef<"ExportJob", 'ExportStatus'>
|
||||
readonly format: FieldRef<"ExportJob", 'String'>
|
||||
readonly scope: FieldRef<"ExportJob", 'String'>
|
||||
readonly progress: FieldRef<"ExportJob", 'Int'>
|
||||
readonly filePath: FieldRef<"ExportJob", 'String'>
|
||||
readonly error: FieldRef<"ExportJob", 'String'>
|
||||
readonly expiresAt: FieldRef<"ExportJob", 'DateTime'>
|
||||
@@ -5139,6 +5185,8 @@ export namespace Prisma {
|
||||
oauthAccessToken: string | null
|
||||
oauthExpiresAt: Date | null
|
||||
providerUserId: string | null
|
||||
oauthLastCheckedAt: Date | null
|
||||
oauthLastErrorCode: string | null
|
||||
appPassword: string | null
|
||||
createdAt: Date | null
|
||||
updatedAt: Date | null
|
||||
@@ -5161,6 +5209,8 @@ export namespace Prisma {
|
||||
oauthAccessToken: string | null
|
||||
oauthExpiresAt: Date | null
|
||||
providerUserId: string | null
|
||||
oauthLastCheckedAt: Date | null
|
||||
oauthLastErrorCode: string | null
|
||||
appPassword: string | null
|
||||
createdAt: Date | null
|
||||
updatedAt: Date | null
|
||||
@@ -5183,6 +5233,8 @@ export namespace Prisma {
|
||||
oauthAccessToken: number
|
||||
oauthExpiresAt: number
|
||||
providerUserId: number
|
||||
oauthLastCheckedAt: number
|
||||
oauthLastErrorCode: number
|
||||
appPassword: number
|
||||
createdAt: number
|
||||
updatedAt: number
|
||||
@@ -5217,6 +5269,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: true
|
||||
oauthExpiresAt?: true
|
||||
providerUserId?: true
|
||||
oauthLastCheckedAt?: true
|
||||
oauthLastErrorCode?: true
|
||||
appPassword?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
@@ -5239,6 +5293,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: true
|
||||
oauthExpiresAt?: true
|
||||
providerUserId?: true
|
||||
oauthLastCheckedAt?: true
|
||||
oauthLastErrorCode?: true
|
||||
appPassword?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
@@ -5261,6 +5317,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: true
|
||||
oauthExpiresAt?: true
|
||||
providerUserId?: true
|
||||
oauthLastCheckedAt?: true
|
||||
oauthLastErrorCode?: true
|
||||
appPassword?: true
|
||||
createdAt?: true
|
||||
updatedAt?: true
|
||||
@@ -5370,6 +5428,8 @@ export namespace Prisma {
|
||||
oauthAccessToken: string | null
|
||||
oauthExpiresAt: Date | null
|
||||
providerUserId: string | null
|
||||
oauthLastCheckedAt: Date | null
|
||||
oauthLastErrorCode: string | null
|
||||
appPassword: string | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
@@ -5411,6 +5471,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: boolean
|
||||
oauthExpiresAt?: boolean
|
||||
providerUserId?: boolean
|
||||
oauthLastCheckedAt?: boolean
|
||||
oauthLastErrorCode?: boolean
|
||||
appPassword?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
@@ -5437,6 +5499,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: boolean
|
||||
oauthExpiresAt?: boolean
|
||||
providerUserId?: boolean
|
||||
oauthLastCheckedAt?: boolean
|
||||
oauthLastErrorCode?: boolean
|
||||
appPassword?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
@@ -5460,6 +5524,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: boolean
|
||||
oauthExpiresAt?: boolean
|
||||
providerUserId?: boolean
|
||||
oauthLastCheckedAt?: boolean
|
||||
oauthLastErrorCode?: boolean
|
||||
appPassword?: boolean
|
||||
createdAt?: boolean
|
||||
updatedAt?: boolean
|
||||
@@ -5499,6 +5565,8 @@ export namespace Prisma {
|
||||
oauthAccessToken: string | null
|
||||
oauthExpiresAt: Date | null
|
||||
providerUserId: string | null
|
||||
oauthLastCheckedAt: Date | null
|
||||
oauthLastErrorCode: string | null
|
||||
appPassword: string | null
|
||||
createdAt: Date
|
||||
updatedAt: Date
|
||||
@@ -5914,6 +5982,8 @@ export namespace Prisma {
|
||||
readonly oauthAccessToken: FieldRef<"MailboxAccount", 'String'>
|
||||
readonly oauthExpiresAt: FieldRef<"MailboxAccount", 'DateTime'>
|
||||
readonly providerUserId: FieldRef<"MailboxAccount", 'String'>
|
||||
readonly oauthLastCheckedAt: FieldRef<"MailboxAccount", 'DateTime'>
|
||||
readonly oauthLastErrorCode: FieldRef<"MailboxAccount", 'String'>
|
||||
readonly appPassword: FieldRef<"MailboxAccount", 'String'>
|
||||
readonly createdAt: FieldRef<"MailboxAccount", 'DateTime'>
|
||||
readonly updatedAt: FieldRef<"MailboxAccount", 'DateTime'>
|
||||
@@ -14118,6 +14188,7 @@ export namespace Prisma {
|
||||
status: 'status',
|
||||
format: 'format',
|
||||
scope: 'scope',
|
||||
progress: 'progress',
|
||||
filePath: 'filePath',
|
||||
error: 'error',
|
||||
expiresAt: 'expiresAt',
|
||||
@@ -14159,6 +14230,8 @@ export namespace Prisma {
|
||||
oauthAccessToken: 'oauthAccessToken',
|
||||
oauthExpiresAt: 'oauthExpiresAt',
|
||||
providerUserId: 'providerUserId',
|
||||
oauthLastCheckedAt: 'oauthLastCheckedAt',
|
||||
oauthLastErrorCode: 'oauthLastErrorCode',
|
||||
appPassword: 'appPassword',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
@@ -14347,6 +14420,20 @@ export namespace Prisma {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'Int'
|
||||
*/
|
||||
export type IntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Int'>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'Int[]'
|
||||
*/
|
||||
export type ListIntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Int[]'>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'UserRole'
|
||||
*/
|
||||
@@ -14375,20 +14462,6 @@ export namespace Prisma {
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'Int'
|
||||
*/
|
||||
export type IntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Int'>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'Int[]'
|
||||
*/
|
||||
export type ListIntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Int[]'>
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Reference to a field of type 'RuleConditionType'
|
||||
*/
|
||||
@@ -14524,6 +14597,7 @@ export namespace Prisma {
|
||||
status?: EnumExportStatusFilter<"ExportJob"> | $Enums.ExportStatus
|
||||
format?: StringFilter<"ExportJob"> | string
|
||||
scope?: StringFilter<"ExportJob"> | string
|
||||
progress?: IntFilter<"ExportJob"> | number
|
||||
filePath?: StringNullableFilter<"ExportJob"> | string | null
|
||||
error?: StringNullableFilter<"ExportJob"> | string | null
|
||||
expiresAt?: DateTimeNullableFilter<"ExportJob"> | Date | string | null
|
||||
@@ -14538,6 +14612,7 @@ export namespace Prisma {
|
||||
status?: SortOrder
|
||||
format?: SortOrder
|
||||
scope?: SortOrder
|
||||
progress?: SortOrder
|
||||
filePath?: SortOrderInput | SortOrder
|
||||
error?: SortOrderInput | SortOrder
|
||||
expiresAt?: SortOrderInput | SortOrder
|
||||
@@ -14555,6 +14630,7 @@ export namespace Prisma {
|
||||
status?: EnumExportStatusFilter<"ExportJob"> | $Enums.ExportStatus
|
||||
format?: StringFilter<"ExportJob"> | string
|
||||
scope?: StringFilter<"ExportJob"> | string
|
||||
progress?: IntFilter<"ExportJob"> | number
|
||||
filePath?: StringNullableFilter<"ExportJob"> | string | null
|
||||
error?: StringNullableFilter<"ExportJob"> | string | null
|
||||
expiresAt?: DateTimeNullableFilter<"ExportJob"> | Date | string | null
|
||||
@@ -14569,14 +14645,17 @@ export namespace Prisma {
|
||||
status?: SortOrder
|
||||
format?: SortOrder
|
||||
scope?: SortOrder
|
||||
progress?: SortOrder
|
||||
filePath?: SortOrderInput | SortOrder
|
||||
error?: SortOrderInput | SortOrder
|
||||
expiresAt?: SortOrderInput | SortOrder
|
||||
createdAt?: SortOrder
|
||||
updatedAt?: SortOrder
|
||||
_count?: ExportJobCountOrderByAggregateInput
|
||||
_avg?: ExportJobAvgOrderByAggregateInput
|
||||
_max?: ExportJobMaxOrderByAggregateInput
|
||||
_min?: ExportJobMinOrderByAggregateInput
|
||||
_sum?: ExportJobSumOrderByAggregateInput
|
||||
}
|
||||
|
||||
export type ExportJobScalarWhereWithAggregatesInput = {
|
||||
@@ -14588,6 +14667,7 @@ export namespace Prisma {
|
||||
status?: EnumExportStatusWithAggregatesFilter<"ExportJob"> | $Enums.ExportStatus
|
||||
format?: StringWithAggregatesFilter<"ExportJob"> | string
|
||||
scope?: StringWithAggregatesFilter<"ExportJob"> | string
|
||||
progress?: IntWithAggregatesFilter<"ExportJob"> | number
|
||||
filePath?: StringNullableWithAggregatesFilter<"ExportJob"> | string | null
|
||||
error?: StringNullableWithAggregatesFilter<"ExportJob"> | string | null
|
||||
expiresAt?: DateTimeNullableWithAggregatesFilter<"ExportJob"> | Date | string | null
|
||||
@@ -14685,6 +14765,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: StringNullableFilter<"MailboxAccount"> | string | null
|
||||
oauthExpiresAt?: DateTimeNullableFilter<"MailboxAccount"> | Date | string | null
|
||||
providerUserId?: StringNullableFilter<"MailboxAccount"> | string | null
|
||||
oauthLastCheckedAt?: DateTimeNullableFilter<"MailboxAccount"> | Date | string | null
|
||||
oauthLastErrorCode?: StringNullableFilter<"MailboxAccount"> | string | null
|
||||
appPassword?: StringNullableFilter<"MailboxAccount"> | string | null
|
||||
createdAt?: DateTimeFilter<"MailboxAccount"> | Date | string
|
||||
updatedAt?: DateTimeFilter<"MailboxAccount"> | Date | string
|
||||
@@ -14710,6 +14792,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: SortOrderInput | SortOrder
|
||||
oauthExpiresAt?: SortOrderInput | SortOrder
|
||||
providerUserId?: SortOrderInput | SortOrder
|
||||
oauthLastCheckedAt?: SortOrderInput | SortOrder
|
||||
oauthLastErrorCode?: SortOrderInput | SortOrder
|
||||
appPassword?: SortOrderInput | SortOrder
|
||||
createdAt?: SortOrder
|
||||
updatedAt?: SortOrder
|
||||
@@ -14738,6 +14822,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: StringNullableFilter<"MailboxAccount"> | string | null
|
||||
oauthExpiresAt?: DateTimeNullableFilter<"MailboxAccount"> | Date | string | null
|
||||
providerUserId?: StringNullableFilter<"MailboxAccount"> | string | null
|
||||
oauthLastCheckedAt?: DateTimeNullableFilter<"MailboxAccount"> | Date | string | null
|
||||
oauthLastErrorCode?: StringNullableFilter<"MailboxAccount"> | string | null
|
||||
appPassword?: StringNullableFilter<"MailboxAccount"> | string | null
|
||||
createdAt?: DateTimeFilter<"MailboxAccount"> | Date | string
|
||||
updatedAt?: DateTimeFilter<"MailboxAccount"> | Date | string
|
||||
@@ -14763,6 +14849,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: SortOrderInput | SortOrder
|
||||
oauthExpiresAt?: SortOrderInput | SortOrder
|
||||
providerUserId?: SortOrderInput | SortOrder
|
||||
oauthLastCheckedAt?: SortOrderInput | SortOrder
|
||||
oauthLastErrorCode?: SortOrderInput | SortOrder
|
||||
appPassword?: SortOrderInput | SortOrder
|
||||
createdAt?: SortOrder
|
||||
updatedAt?: SortOrder
|
||||
@@ -14793,6 +14881,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: StringNullableWithAggregatesFilter<"MailboxAccount"> | string | null
|
||||
oauthExpiresAt?: DateTimeNullableWithAggregatesFilter<"MailboxAccount"> | Date | string | null
|
||||
providerUserId?: StringNullableWithAggregatesFilter<"MailboxAccount"> | string | null
|
||||
oauthLastCheckedAt?: DateTimeNullableWithAggregatesFilter<"MailboxAccount"> | Date | string | null
|
||||
oauthLastErrorCode?: StringNullableWithAggregatesFilter<"MailboxAccount"> | string | null
|
||||
appPassword?: StringNullableWithAggregatesFilter<"MailboxAccount"> | string | null
|
||||
createdAt?: DateTimeWithAggregatesFilter<"MailboxAccount"> | Date | string
|
||||
updatedAt?: DateTimeWithAggregatesFilter<"MailboxAccount"> | Date | string
|
||||
@@ -15409,6 +15499,7 @@ export namespace Prisma {
|
||||
status?: $Enums.ExportStatus
|
||||
format: string
|
||||
scope: string
|
||||
progress?: number
|
||||
filePath?: string | null
|
||||
error?: string | null
|
||||
expiresAt?: Date | string | null
|
||||
@@ -15423,6 +15514,7 @@ export namespace Prisma {
|
||||
status?: $Enums.ExportStatus
|
||||
format: string
|
||||
scope: string
|
||||
progress?: number
|
||||
filePath?: string | null
|
||||
error?: string | null
|
||||
expiresAt?: Date | string | null
|
||||
@@ -15435,6 +15527,7 @@ export namespace Prisma {
|
||||
status?: EnumExportStatusFieldUpdateOperationsInput | $Enums.ExportStatus
|
||||
format?: StringFieldUpdateOperationsInput | string
|
||||
scope?: StringFieldUpdateOperationsInput | string
|
||||
progress?: IntFieldUpdateOperationsInput | number
|
||||
filePath?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
error?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
@@ -15449,6 +15542,7 @@ export namespace Prisma {
|
||||
status?: EnumExportStatusFieldUpdateOperationsInput | $Enums.ExportStatus
|
||||
format?: StringFieldUpdateOperationsInput | string
|
||||
scope?: StringFieldUpdateOperationsInput | string
|
||||
progress?: IntFieldUpdateOperationsInput | number
|
||||
filePath?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
error?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
@@ -15462,6 +15556,7 @@ export namespace Prisma {
|
||||
status?: $Enums.ExportStatus
|
||||
format: string
|
||||
scope: string
|
||||
progress?: number
|
||||
filePath?: string | null
|
||||
error?: string | null
|
||||
expiresAt?: Date | string | null
|
||||
@@ -15474,6 +15569,7 @@ export namespace Prisma {
|
||||
status?: EnumExportStatusFieldUpdateOperationsInput | $Enums.ExportStatus
|
||||
format?: StringFieldUpdateOperationsInput | string
|
||||
scope?: StringFieldUpdateOperationsInput | string
|
||||
progress?: IntFieldUpdateOperationsInput | number
|
||||
filePath?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
error?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
@@ -15487,6 +15583,7 @@ export namespace Prisma {
|
||||
status?: EnumExportStatusFieldUpdateOperationsInput | $Enums.ExportStatus
|
||||
format?: StringFieldUpdateOperationsInput | string
|
||||
scope?: StringFieldUpdateOperationsInput | string
|
||||
progress?: IntFieldUpdateOperationsInput | number
|
||||
filePath?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
error?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
@@ -15586,6 +15683,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: string | null
|
||||
oauthExpiresAt?: Date | string | null
|
||||
providerUserId?: string | null
|
||||
oauthLastCheckedAt?: Date | string | null
|
||||
oauthLastErrorCode?: string | null
|
||||
appPassword?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
@@ -15611,6 +15710,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: string | null
|
||||
oauthExpiresAt?: Date | string | null
|
||||
providerUserId?: string | null
|
||||
oauthLastCheckedAt?: Date | string | null
|
||||
oauthLastErrorCode?: string | null
|
||||
appPassword?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
@@ -15634,6 +15735,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
providerUserId?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthLastCheckedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
oauthLastErrorCode?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
appPassword?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -15659,6 +15762,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
providerUserId?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthLastCheckedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
oauthLastErrorCode?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
appPassword?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -15683,6 +15788,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: string | null
|
||||
oauthExpiresAt?: Date | string | null
|
||||
providerUserId?: string | null
|
||||
oauthLastCheckedAt?: Date | string | null
|
||||
oauthLastErrorCode?: string | null
|
||||
appPassword?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
@@ -15704,6 +15811,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
providerUserId?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthLastCheckedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
oauthLastErrorCode?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
appPassword?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -15726,6 +15835,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
providerUserId?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthLastCheckedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
oauthLastErrorCode?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
appPassword?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -16440,6 +16551,17 @@ export namespace Prisma {
|
||||
not?: NestedEnumExportStatusFilter<$PrismaModel> | $Enums.ExportStatus
|
||||
}
|
||||
|
||||
export type IntFilter<$PrismaModel = never> = {
|
||||
equals?: number | IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | IntFieldRefInput<$PrismaModel>
|
||||
not?: NestedIntFilter<$PrismaModel> | number
|
||||
}
|
||||
|
||||
export type StringNullableFilter<$PrismaModel = never> = {
|
||||
equals?: string | StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | ListStringFieldRefInput<$PrismaModel> | null
|
||||
@@ -16482,6 +16604,7 @@ export namespace Prisma {
|
||||
status?: SortOrder
|
||||
format?: SortOrder
|
||||
scope?: SortOrder
|
||||
progress?: SortOrder
|
||||
filePath?: SortOrder
|
||||
error?: SortOrder
|
||||
expiresAt?: SortOrder
|
||||
@@ -16489,12 +16612,17 @@ export namespace Prisma {
|
||||
updatedAt?: SortOrder
|
||||
}
|
||||
|
||||
export type ExportJobAvgOrderByAggregateInput = {
|
||||
progress?: SortOrder
|
||||
}
|
||||
|
||||
export type ExportJobMaxOrderByAggregateInput = {
|
||||
id?: SortOrder
|
||||
tenantId?: SortOrder
|
||||
status?: SortOrder
|
||||
format?: SortOrder
|
||||
scope?: SortOrder
|
||||
progress?: SortOrder
|
||||
filePath?: SortOrder
|
||||
error?: SortOrder
|
||||
expiresAt?: SortOrder
|
||||
@@ -16508,6 +16636,7 @@ export namespace Prisma {
|
||||
status?: SortOrder
|
||||
format?: SortOrder
|
||||
scope?: SortOrder
|
||||
progress?: SortOrder
|
||||
filePath?: SortOrder
|
||||
error?: SortOrder
|
||||
expiresAt?: SortOrder
|
||||
@@ -16515,6 +16644,10 @@ export namespace Prisma {
|
||||
updatedAt?: SortOrder
|
||||
}
|
||||
|
||||
export type ExportJobSumOrderByAggregateInput = {
|
||||
progress?: SortOrder
|
||||
}
|
||||
|
||||
export type EnumExportStatusWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: $Enums.ExportStatus | EnumExportStatusFieldRefInput<$PrismaModel>
|
||||
in?: $Enums.ExportStatus[] | ListEnumExportStatusFieldRefInput<$PrismaModel>
|
||||
@@ -16525,6 +16658,22 @@ export namespace Prisma {
|
||||
_max?: NestedEnumExportStatusFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type IntWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | IntFieldRefInput<$PrismaModel>
|
||||
not?: NestedIntWithAggregatesFilter<$PrismaModel> | number
|
||||
_count?: NestedIntFilter<$PrismaModel>
|
||||
_avg?: NestedFloatFilter<$PrismaModel>
|
||||
_sum?: NestedIntFilter<$PrismaModel>
|
||||
_min?: NestedIntFilter<$PrismaModel>
|
||||
_max?: NestedIntFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type StringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | ListStringFieldRefInput<$PrismaModel> | null
|
||||
@@ -16614,17 +16763,6 @@ export namespace Prisma {
|
||||
not?: NestedEnumMailProviderFilter<$PrismaModel> | $Enums.MailProvider
|
||||
}
|
||||
|
||||
export type IntFilter<$PrismaModel = never> = {
|
||||
equals?: number | IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | IntFieldRefInput<$PrismaModel>
|
||||
not?: NestedIntFilter<$PrismaModel> | number
|
||||
}
|
||||
|
||||
export type IntNullableFilter<$PrismaModel = never> = {
|
||||
equals?: number | IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | ListIntFieldRefInput<$PrismaModel> | null
|
||||
@@ -16668,6 +16806,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: SortOrder
|
||||
oauthExpiresAt?: SortOrder
|
||||
providerUserId?: SortOrder
|
||||
oauthLastCheckedAt?: SortOrder
|
||||
oauthLastErrorCode?: SortOrder
|
||||
appPassword?: SortOrder
|
||||
createdAt?: SortOrder
|
||||
updatedAt?: SortOrder
|
||||
@@ -16695,6 +16835,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: SortOrder
|
||||
oauthExpiresAt?: SortOrder
|
||||
providerUserId?: SortOrder
|
||||
oauthLastCheckedAt?: SortOrder
|
||||
oauthLastErrorCode?: SortOrder
|
||||
appPassword?: SortOrder
|
||||
createdAt?: SortOrder
|
||||
updatedAt?: SortOrder
|
||||
@@ -16717,6 +16859,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: SortOrder
|
||||
oauthExpiresAt?: SortOrder
|
||||
providerUserId?: SortOrder
|
||||
oauthLastCheckedAt?: SortOrder
|
||||
oauthLastErrorCode?: SortOrder
|
||||
appPassword?: SortOrder
|
||||
createdAt?: SortOrder
|
||||
updatedAt?: SortOrder
|
||||
@@ -16737,22 +16881,6 @@ export namespace Prisma {
|
||||
_max?: NestedEnumMailProviderFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type IntWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | IntFieldRefInput<$PrismaModel>
|
||||
not?: NestedIntWithAggregatesFilter<$PrismaModel> | number
|
||||
_count?: NestedIntFilter<$PrismaModel>
|
||||
_avg?: NestedFloatFilter<$PrismaModel>
|
||||
_sum?: NestedIntFilter<$PrismaModel>
|
||||
_min?: NestedIntFilter<$PrismaModel>
|
||||
_max?: NestedIntFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type IntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | ListIntFieldRefInput<$PrismaModel> | null
|
||||
@@ -17372,6 +17500,14 @@ export namespace Prisma {
|
||||
set?: $Enums.ExportStatus
|
||||
}
|
||||
|
||||
export type IntFieldUpdateOperationsInput = {
|
||||
set?: number
|
||||
increment?: number
|
||||
decrement?: number
|
||||
multiply?: number
|
||||
divide?: number
|
||||
}
|
||||
|
||||
export type NullableStringFieldUpdateOperationsInput = {
|
||||
set?: string | null
|
||||
}
|
||||
@@ -17444,14 +17580,6 @@ export namespace Prisma {
|
||||
set?: $Enums.MailProvider
|
||||
}
|
||||
|
||||
export type IntFieldUpdateOperationsInput = {
|
||||
set?: number
|
||||
increment?: number
|
||||
decrement?: number
|
||||
multiply?: number
|
||||
divide?: number
|
||||
}
|
||||
|
||||
export type NullableIntFieldUpdateOperationsInput = {
|
||||
set?: number | null
|
||||
increment?: number
|
||||
@@ -17998,6 +18126,33 @@ export namespace Prisma {
|
||||
_max?: NestedEnumExportStatusFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedIntWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | IntFieldRefInput<$PrismaModel>
|
||||
not?: NestedIntWithAggregatesFilter<$PrismaModel> | number
|
||||
_count?: NestedIntFilter<$PrismaModel>
|
||||
_avg?: NestedFloatFilter<$PrismaModel>
|
||||
_sum?: NestedIntFilter<$PrismaModel>
|
||||
_min?: NestedIntFilter<$PrismaModel>
|
||||
_max?: NestedIntFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedFloatFilter<$PrismaModel = never> = {
|
||||
equals?: number | FloatFieldRefInput<$PrismaModel>
|
||||
in?: number[] | ListFloatFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | ListFloatFieldRefInput<$PrismaModel>
|
||||
lt?: number | FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | FloatFieldRefInput<$PrismaModel>
|
||||
not?: NestedFloatFilter<$PrismaModel> | number
|
||||
}
|
||||
|
||||
export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: string | StringFieldRefInput<$PrismaModel> | null
|
||||
in?: string[] | ListStringFieldRefInput<$PrismaModel> | null
|
||||
@@ -18079,33 +18234,6 @@ export namespace Prisma {
|
||||
_max?: NestedEnumMailProviderFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedIntWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | IntFieldRefInput<$PrismaModel>
|
||||
in?: number[] | ListIntFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | ListIntFieldRefInput<$PrismaModel>
|
||||
lt?: number | IntFieldRefInput<$PrismaModel>
|
||||
lte?: number | IntFieldRefInput<$PrismaModel>
|
||||
gt?: number | IntFieldRefInput<$PrismaModel>
|
||||
gte?: number | IntFieldRefInput<$PrismaModel>
|
||||
not?: NestedIntWithAggregatesFilter<$PrismaModel> | number
|
||||
_count?: NestedIntFilter<$PrismaModel>
|
||||
_avg?: NestedFloatFilter<$PrismaModel>
|
||||
_sum?: NestedIntFilter<$PrismaModel>
|
||||
_min?: NestedIntFilter<$PrismaModel>
|
||||
_max?: NestedIntFilter<$PrismaModel>
|
||||
}
|
||||
|
||||
export type NestedFloatFilter<$PrismaModel = never> = {
|
||||
equals?: number | FloatFieldRefInput<$PrismaModel>
|
||||
in?: number[] | ListFloatFieldRefInput<$PrismaModel>
|
||||
notIn?: number[] | ListFloatFieldRefInput<$PrismaModel>
|
||||
lt?: number | FloatFieldRefInput<$PrismaModel>
|
||||
lte?: number | FloatFieldRefInput<$PrismaModel>
|
||||
gt?: number | FloatFieldRefInput<$PrismaModel>
|
||||
gte?: number | FloatFieldRefInput<$PrismaModel>
|
||||
not?: NestedFloatFilter<$PrismaModel> | number
|
||||
}
|
||||
|
||||
export type NestedIntNullableWithAggregatesFilter<$PrismaModel = never> = {
|
||||
equals?: number | IntFieldRefInput<$PrismaModel> | null
|
||||
in?: number[] | ListIntFieldRefInput<$PrismaModel> | null
|
||||
@@ -18197,6 +18325,7 @@ export namespace Prisma {
|
||||
status?: $Enums.ExportStatus
|
||||
format: string
|
||||
scope: string
|
||||
progress?: number
|
||||
filePath?: string | null
|
||||
error?: string | null
|
||||
expiresAt?: Date | string | null
|
||||
@@ -18209,6 +18338,7 @@ export namespace Prisma {
|
||||
status?: $Enums.ExportStatus
|
||||
format: string
|
||||
scope: string
|
||||
progress?: number
|
||||
filePath?: string | null
|
||||
error?: string | null
|
||||
expiresAt?: Date | string | null
|
||||
@@ -18272,6 +18402,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: string | null
|
||||
oauthExpiresAt?: Date | string | null
|
||||
providerUserId?: string | null
|
||||
oauthLastCheckedAt?: Date | string | null
|
||||
oauthLastErrorCode?: string | null
|
||||
appPassword?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
@@ -18295,6 +18427,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: string | null
|
||||
oauthExpiresAt?: Date | string | null
|
||||
providerUserId?: string | null
|
||||
oauthLastCheckedAt?: Date | string | null
|
||||
oauthLastErrorCode?: string | null
|
||||
appPassword?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
@@ -18407,6 +18541,7 @@ export namespace Prisma {
|
||||
status?: EnumExportStatusFilter<"ExportJob"> | $Enums.ExportStatus
|
||||
format?: StringFilter<"ExportJob"> | string
|
||||
scope?: StringFilter<"ExportJob"> | string
|
||||
progress?: IntFilter<"ExportJob"> | number
|
||||
filePath?: StringNullableFilter<"ExportJob"> | string | null
|
||||
error?: StringNullableFilter<"ExportJob"> | string | null
|
||||
expiresAt?: DateTimeNullableFilter<"ExportJob"> | Date | string | null
|
||||
@@ -18480,6 +18615,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: StringNullableFilter<"MailboxAccount"> | string | null
|
||||
oauthExpiresAt?: DateTimeNullableFilter<"MailboxAccount"> | Date | string | null
|
||||
providerUserId?: StringNullableFilter<"MailboxAccount"> | string | null
|
||||
oauthLastCheckedAt?: DateTimeNullableFilter<"MailboxAccount"> | Date | string | null
|
||||
oauthLastErrorCode?: StringNullableFilter<"MailboxAccount"> | string | null
|
||||
appPassword?: StringNullableFilter<"MailboxAccount"> | string | null
|
||||
createdAt?: DateTimeFilter<"MailboxAccount"> | Date | string
|
||||
updatedAt?: DateTimeFilter<"MailboxAccount"> | Date | string
|
||||
@@ -18866,6 +19003,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: string | null
|
||||
oauthExpiresAt?: Date | string | null
|
||||
providerUserId?: string | null
|
||||
oauthLastCheckedAt?: Date | string | null
|
||||
oauthLastErrorCode?: string | null
|
||||
appPassword?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
@@ -18890,6 +19029,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: string | null
|
||||
oauthExpiresAt?: Date | string | null
|
||||
providerUserId?: string | null
|
||||
oauthLastCheckedAt?: Date | string | null
|
||||
oauthLastErrorCode?: string | null
|
||||
appPassword?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
@@ -18962,6 +19103,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
providerUserId?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthLastCheckedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
oauthLastErrorCode?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
appPassword?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -18986,6 +19129,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
providerUserId?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthLastCheckedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
oauthLastErrorCode?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
appPassword?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -19393,6 +19538,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: string | null
|
||||
oauthExpiresAt?: Date | string | null
|
||||
providerUserId?: string | null
|
||||
oauthLastCheckedAt?: Date | string | null
|
||||
oauthLastErrorCode?: string | null
|
||||
appPassword?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
@@ -19417,6 +19564,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: string | null
|
||||
oauthExpiresAt?: Date | string | null
|
||||
providerUserId?: string | null
|
||||
oauthLastCheckedAt?: Date | string | null
|
||||
oauthLastErrorCode?: string | null
|
||||
appPassword?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
@@ -19544,6 +19693,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
providerUserId?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthLastCheckedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
oauthLastErrorCode?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
appPassword?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -19568,6 +19719,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
providerUserId?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthLastCheckedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
oauthLastErrorCode?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
appPassword?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -19788,6 +19941,7 @@ export namespace Prisma {
|
||||
status?: $Enums.ExportStatus
|
||||
format: string
|
||||
scope: string
|
||||
progress?: number
|
||||
filePath?: string | null
|
||||
error?: string | null
|
||||
expiresAt?: Date | string | null
|
||||
@@ -19821,6 +19975,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: string | null
|
||||
oauthExpiresAt?: Date | string | null
|
||||
providerUserId?: string | null
|
||||
oauthLastCheckedAt?: Date | string | null
|
||||
oauthLastErrorCode?: string | null
|
||||
appPassword?: string | null
|
||||
createdAt?: Date | string
|
||||
updatedAt?: Date | string
|
||||
@@ -19852,6 +20008,7 @@ export namespace Prisma {
|
||||
status?: EnumExportStatusFieldUpdateOperationsInput | $Enums.ExportStatus
|
||||
format?: StringFieldUpdateOperationsInput | string
|
||||
scope?: StringFieldUpdateOperationsInput | string
|
||||
progress?: IntFieldUpdateOperationsInput | number
|
||||
filePath?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
error?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
@@ -19864,6 +20021,7 @@ export namespace Prisma {
|
||||
status?: EnumExportStatusFieldUpdateOperationsInput | $Enums.ExportStatus
|
||||
format?: StringFieldUpdateOperationsInput | string
|
||||
scope?: StringFieldUpdateOperationsInput | string
|
||||
progress?: IntFieldUpdateOperationsInput | number
|
||||
filePath?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
error?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
@@ -19876,6 +20034,7 @@ export namespace Prisma {
|
||||
status?: EnumExportStatusFieldUpdateOperationsInput | $Enums.ExportStatus
|
||||
format?: StringFieldUpdateOperationsInput | string
|
||||
scope?: StringFieldUpdateOperationsInput | string
|
||||
progress?: IntFieldUpdateOperationsInput | number
|
||||
filePath?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
error?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
expiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
@@ -19929,6 +20088,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
providerUserId?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthLastCheckedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
oauthLastErrorCode?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
appPassword?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -19952,6 +20113,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
providerUserId?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthLastCheckedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
oauthLastErrorCode?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
appPassword?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
@@ -19975,6 +20138,8 @@ export namespace Prisma {
|
||||
oauthAccessToken?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthExpiresAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
providerUserId?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
oauthLastCheckedAt?: NullableDateTimeFieldUpdateOperationsInput | Date | string | null
|
||||
oauthLastErrorCode?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
appPassword?: NullableStringFieldUpdateOperationsInput | string | null
|
||||
createdAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
updatedAt?: DateTimeFieldUpdateOperationsInput | Date | string
|
||||
|
||||
9
backend/node_modules/.prisma/client/index.js
generated
vendored
9
backend/node_modules/.prisma/client/index.js
generated
vendored
File diff suppressed because one or more lines are too long
2
backend/node_modules/.prisma/client/package.json
generated
vendored
2
backend/node_modules/.prisma/client/package.json
generated
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"name": "prisma-client-e73c910a086b88f8fcb9eefa67d9d9c4f84c5390da0c21a4dde9378c21e5e10d",
|
||||
"name": "prisma-client-6c67176f7022092b3b8f46007c6286b76456763ea6fcd4c80a580e5070b636e6",
|
||||
"main": "index.js",
|
||||
"types": "index.d.ts",
|
||||
"browser": "index-browser.js",
|
||||
|
||||
41
backend/node_modules/.prisma/client/schema.prisma
generated
vendored
41
backend/node_modules/.prisma/client/schema.prisma
generated
vendored
@@ -68,6 +68,7 @@ model ExportJob {
|
||||
status ExportStatus @default(QUEUED)
|
||||
format String
|
||||
scope String
|
||||
progress Int @default(0)
|
||||
filePath String?
|
||||
error String?
|
||||
expiresAt DateTime?
|
||||
@@ -93,25 +94,27 @@ model User {
|
||||
}
|
||||
|
||||
model MailboxAccount {
|
||||
id String @id @default(cuid())
|
||||
tenantId String
|
||||
email String
|
||||
provider MailProvider
|
||||
isActive Boolean @default(true)
|
||||
imapHost String
|
||||
imapPort Int
|
||||
imapTLS Boolean
|
||||
smtpHost String?
|
||||
smtpPort Int?
|
||||
smtpTLS Boolean?
|
||||
oauthToken String?
|
||||
oauthRefreshToken String?
|
||||
oauthAccessToken String?
|
||||
oauthExpiresAt DateTime?
|
||||
providerUserId String?
|
||||
appPassword String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
id String @id @default(cuid())
|
||||
tenantId String
|
||||
email String
|
||||
provider MailProvider
|
||||
isActive Boolean @default(true)
|
||||
imapHost String
|
||||
imapPort Int
|
||||
imapTLS Boolean
|
||||
smtpHost String?
|
||||
smtpPort Int?
|
||||
smtpTLS Boolean?
|
||||
oauthToken String?
|
||||
oauthRefreshToken String?
|
||||
oauthAccessToken String?
|
||||
oauthExpiresAt DateTime?
|
||||
providerUserId String?
|
||||
oauthLastCheckedAt DateTime?
|
||||
oauthLastErrorCode String?
|
||||
appPassword String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
tenant Tenant @relation(fields: [tenantId], references: [id])
|
||||
folders MailboxFolder[]
|
||||
|
||||
3
backend/node_modules/.prisma/client/wasm.js
generated
vendored
3
backend/node_modules/.prisma/client/wasm.js
generated
vendored
@@ -136,6 +136,7 @@ exports.Prisma.ExportJobScalarFieldEnum = {
|
||||
status: 'status',
|
||||
format: 'format',
|
||||
scope: 'scope',
|
||||
progress: 'progress',
|
||||
filePath: 'filePath',
|
||||
error: 'error',
|
||||
expiresAt: 'expiresAt',
|
||||
@@ -171,6 +172,8 @@ exports.Prisma.MailboxAccountScalarFieldEnum = {
|
||||
oauthAccessToken: 'oauthAccessToken',
|
||||
oauthExpiresAt: 'oauthExpiresAt',
|
||||
providerUserId: 'providerUserId',
|
||||
oauthLastCheckedAt: 'oauthLastCheckedAt',
|
||||
oauthLastErrorCode: 'oauthLastErrorCode',
|
||||
appPassword: 'appPassword',
|
||||
createdAt: 'createdAt',
|
||||
updatedAt: 'updatedAt'
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "ExportJob" ADD COLUMN "progress" INTEGER NOT NULL DEFAULT 0;
|
||||
|
||||
-- AlterTable
|
||||
ALTER TABLE "MailboxAccount" ADD COLUMN "oauthLastCheckedAt" TIMESTAMP(3),
|
||||
ADD COLUMN "oauthLastErrorCode" TEXT;
|
||||
@@ -68,6 +68,7 @@ model ExportJob {
|
||||
status ExportStatus @default(QUEUED)
|
||||
format String
|
||||
scope String
|
||||
progress Int @default(0)
|
||||
filePath String?
|
||||
error String?
|
||||
expiresAt DateTime?
|
||||
@@ -109,6 +110,8 @@ model MailboxAccount {
|
||||
oauthAccessToken String?
|
||||
oauthExpiresAt DateTime?
|
||||
providerUserId String?
|
||||
oauthLastCheckedAt DateTime?
|
||||
oauthLastErrorCode String?
|
||||
appPassword String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@ -4,27 +4,34 @@ import { PrismaClient, UserRole } from "@prisma/client";
|
||||
const prisma = new PrismaClient();
|
||||
|
||||
const tenantName = process.env.SEED_TENANT ?? "Default Tenant";
|
||||
const email = process.env.SEED_EMAIL ?? "admin@example.com";
|
||||
const password = process.env.SEED_PASSWORD ?? "change-me-please";
|
||||
const email = process.env.SEED_ADMIN_EMAIL ?? process.env.SEED_EMAIL ?? "admin@example.com";
|
||||
const password = process.env.SEED_ADMIN_PASSWORD ?? process.env.SEED_PASSWORD ?? "change-me-please";
|
||||
const role = (process.env.SEED_ROLE as UserRole | undefined) ?? "ADMIN";
|
||||
|
||||
const main = async () => {
|
||||
const hash = await argon2.hash(password);
|
||||
const tenantId = process.env.SEED_TENANT_ID ?? "seed-tenant";
|
||||
const tenant = await prisma.tenant.upsert({
|
||||
where: { id: tenantId },
|
||||
update: { name: tenantName },
|
||||
create: { id: tenantId, name: tenantName }
|
||||
});
|
||||
|
||||
const existing = await prisma.user.findUnique({ where: { email } });
|
||||
if (existing) {
|
||||
process.stdout.write("Seed user already exists.\n");
|
||||
await prisma.user.update({
|
||||
where: { email },
|
||||
data: { password: hash, role, tenantId: tenant.id }
|
||||
});
|
||||
process.stdout.write("Seed admin updated.\n");
|
||||
return;
|
||||
}
|
||||
|
||||
const hash = await argon2.hash(password);
|
||||
const tenant = await prisma.tenant.create({
|
||||
data: { name: tenantName }
|
||||
});
|
||||
|
||||
await prisma.user.create({
|
||||
data: { tenantId: tenant.id, email, password: hash, role }
|
||||
});
|
||||
|
||||
process.stdout.write("Seeded tenant and user.\n");
|
||||
process.stdout.write("Seeded admin user.\n");
|
||||
};
|
||||
|
||||
main()
|
||||
|
||||
@@ -8,7 +8,7 @@ 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) => {
|
||||
export const buildExport = async (jobId: string) => {
|
||||
const job = await prisma.exportJob.findUnique({ where: { id: jobId }, include: { tenant: true } });
|
||||
if (!job) throw new Error("Export job not found");
|
||||
|
||||
@@ -23,6 +23,8 @@ const buildExport = async (jobId: string) => {
|
||||
});
|
||||
if (!tenant) throw new Error("Tenant not found");
|
||||
|
||||
await prisma.exportJob.update({ where: { id: job.id }, data: { progress: 20 } });
|
||||
|
||||
const scope = job.scope;
|
||||
const payload: Record<string, unknown> = {};
|
||||
if (scope === "all" || scope === "users") payload.users = tenant.users;
|
||||
@@ -31,6 +33,8 @@ const buildExport = async (jobId: string) => {
|
||||
if (scope === "all" || scope === "rules") payload.rules = tenant.rules;
|
||||
if (scope === "all") payload.tenant = { id: tenant.id, name: tenant.name };
|
||||
|
||||
await prisma.exportJob.update({ where: { id: job.id }, data: { progress: 50 } });
|
||||
|
||||
await mkdir(EXPORT_DIR, { recursive: true });
|
||||
const fileName = `${job.id}.${job.format === "zip" ? "zip" : "json"}`;
|
||||
const filePath = `${EXPORT_DIR}/${fileName}`;
|
||||
@@ -47,32 +51,21 @@ const buildExport = async (jobId: string) => {
|
||||
await writeFile(filePath, JSON.stringify(payload, null, 2));
|
||||
}
|
||||
|
||||
await prisma.exportJob.update({ where: { id: job.id }, data: { progress: 90 } });
|
||||
|
||||
await prisma.exportJob.update({
|
||||
where: { id: job.id },
|
||||
data: { status: "DONE", filePath, expiresAt: new Date(Date.now() + EXPORT_TTL_HOURS * 3600 * 1000) }
|
||||
data: { status: "DONE", filePath, progress: 100, expiresAt: new Date(Date.now() + EXPORT_TTL_HOURS * 3600 * 1000) }
|
||||
});
|
||||
};
|
||||
|
||||
export const processExportQueue = async () => {
|
||||
while (true) {
|
||||
const job = await prisma.exportJob.findFirst({
|
||||
where: { status: "QUEUED" },
|
||||
orderBy: { createdAt: "asc" }
|
||||
});
|
||||
if (!job) {
|
||||
await cleanupExpiredExports();
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
continue;
|
||||
}
|
||||
|
||||
await prisma.exportJob.update({ where: { id: job.id }, data: { status: "RUNNING" } });
|
||||
try {
|
||||
await buildExport(job.id);
|
||||
} catch (err) {
|
||||
await prisma.exportJob.update({
|
||||
where: { id: job.id },
|
||||
data: { status: "FAILED", error: err instanceof Error ? err.message : String(err) }
|
||||
});
|
||||
}
|
||||
}
|
||||
export const runExportJob = async (jobId: string) => {
|
||||
await prisma.exportJob.update({ where: { id: jobId }, data: { status: "RUNNING", progress: 5 } });
|
||||
await buildExport(jobId);
|
||||
};
|
||||
|
||||
export const startExportCleanupLoop = () => {
|
||||
setInterval(() => {
|
||||
cleanupExpiredExports().catch(() => undefined);
|
||||
}, 60000);
|
||||
};
|
||||
|
||||
@@ -2,9 +2,10 @@ 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";
|
||||
import { queueCleanupJob, removeQueueJob, queueExportJob } from "../queue/queue.js";
|
||||
import { createReadStream } from "node:fs";
|
||||
import { access } from "node:fs/promises";
|
||||
import { access, unlink } from "node:fs/promises";
|
||||
import { cleanupExpiredExports } from "./exportCleanup.js";
|
||||
|
||||
const roleSchema = z.object({
|
||||
role: z.enum(["USER", "ADMIN"])
|
||||
@@ -74,6 +75,7 @@ export async function adminRoutes(app: FastifyInstance) {
|
||||
scope: scope
|
||||
}
|
||||
});
|
||||
await queueExportJob(exportJob.id);
|
||||
return { jobId: exportJob.id };
|
||||
}
|
||||
|
||||
@@ -111,8 +113,10 @@ export async function adminRoutes(app: FastifyInstance) {
|
||||
return reply.send(createReadStream(job.filePath));
|
||||
});
|
||||
|
||||
app.get("/exports", async () => {
|
||||
app.get("/exports", async (request) => {
|
||||
const query = request.query as { status?: string };
|
||||
const exports = await prisma.exportJob.findMany({
|
||||
where: query.status ? { status: query.status as "QUEUED" | "RUNNING" | "DONE" | "FAILED" } : undefined,
|
||||
orderBy: { createdAt: "desc" }
|
||||
});
|
||||
const sanitized = exports.map((job) => ({
|
||||
@@ -120,12 +124,33 @@ export async function adminRoutes(app: FastifyInstance) {
|
||||
status: job.status,
|
||||
format: job.format,
|
||||
scope: job.scope,
|
||||
progress: job.progress,
|
||||
expiresAt: job.expiresAt,
|
||||
createdAt: job.createdAt
|
||||
}));
|
||||
return { exports: sanitized };
|
||||
});
|
||||
|
||||
app.post("/exports/purge", async () => {
|
||||
await cleanupExpiredExports();
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
app.delete("/exports/:id", async (request, reply) => {
|
||||
const params = request.params as { id: string };
|
||||
const job = await prisma.exportJob.findUnique({ where: { id: params.id } });
|
||||
if (!job) return reply.code(404).send({ message: "Export job not found" });
|
||||
if (job.filePath) {
|
||||
try {
|
||||
await unlink(job.filePath);
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
await prisma.exportJob.delete({ where: { id: job.id } });
|
||||
return { success: true };
|
||||
});
|
||||
|
||||
app.delete("/tenants/:id", async (request, reply) => {
|
||||
const params = request.params as { id: string };
|
||||
const tenant = await prisma.tenant.findUnique({ where: { id: params.id } });
|
||||
@@ -241,7 +266,11 @@ export async function adminRoutes(app: FastifyInstance) {
|
||||
tenant: account.tenant ? { id: account.tenant.id, name: account.tenant.name } : null,
|
||||
imapHost: account.imapHost,
|
||||
imapPort: account.imapPort,
|
||||
imapTLS: account.imapTLS
|
||||
imapTLS: account.imapTLS,
|
||||
oauthExpiresAt: account.oauthExpiresAt,
|
||||
oauthLastCheckedAt: account.oauthLastCheckedAt,
|
||||
oauthLastErrorCode: account.oauthLastErrorCode,
|
||||
hasOauth: Boolean(account.oauthRefreshToken || account.oauthAccessToken)
|
||||
}));
|
||||
|
||||
return { accounts: sanitized };
|
||||
|
||||
@@ -8,7 +8,8 @@ const envSchema = z.object({
|
||||
JWT_SECRET: z.string().min(12),
|
||||
GOOGLE_CLIENT_ID: z.string().optional(),
|
||||
GOOGLE_CLIENT_SECRET: z.string().optional(),
|
||||
GOOGLE_REDIRECT_URI: z.string().optional()
|
||||
GOOGLE_REDIRECT_URI: z.string().optional(),
|
||||
TRUST_PROXY: z.coerce.boolean().default(false)
|
||||
});
|
||||
|
||||
export type AppConfig = z.infer<typeof envSchema>;
|
||||
@@ -21,5 +22,6 @@ export const config = envSchema.parse({
|
||||
JWT_SECRET: process.env.JWT_SECRET,
|
||||
GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID,
|
||||
GOOGLE_CLIENT_SECRET: process.env.GOOGLE_CLIENT_SECRET,
|
||||
GOOGLE_REDIRECT_URI: process.env.GOOGLE_REDIRECT_URI
|
||||
GOOGLE_REDIRECT_URI: process.env.GOOGLE_REDIRECT_URI,
|
||||
TRUST_PROXY: process.env.TRUST_PROXY
|
||||
});
|
||||
|
||||
@@ -77,9 +77,17 @@ export async function oauthRoutes(app: FastifyInstance) {
|
||||
try {
|
||||
const refreshed = await refreshGmailTokens(account);
|
||||
await pingGmail(account);
|
||||
await prisma.mailboxAccount.update({
|
||||
where: { id: account.id },
|
||||
data: { oauthLastCheckedAt: new Date(), oauthLastErrorCode: null }
|
||||
});
|
||||
return { connected: true, healthy: true, expiresAt: refreshed.expiresAt ?? account.oauthExpiresAt };
|
||||
} catch (err) {
|
||||
const error = mapGmailError(err);
|
||||
await prisma.mailboxAccount.update({
|
||||
where: { id: account.id },
|
||||
data: { oauthLastCheckedAt: new Date(), oauthLastErrorCode: error.code }
|
||||
});
|
||||
return { connected: true, healthy: false, expiresAt: account.oauthExpiresAt, error };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -22,7 +22,8 @@ const app = Fastify({
|
||||
target: "pino-pretty",
|
||||
options: { colorize: true }
|
||||
}
|
||||
}
|
||||
},
|
||||
trustProxy: config.TRUST_PROXY
|
||||
});
|
||||
|
||||
await app.register(cors, { origin: true });
|
||||
|
||||
@@ -3,6 +3,7 @@ import IORedis from "ioredis";
|
||||
import { config } from "../config.js";
|
||||
|
||||
let cleanupQueue: Queue | null = null;
|
||||
let exportQueue: Queue | null = null;
|
||||
|
||||
const getConnection = () => new IORedis(config.REDIS_URL, { maxRetriesPerRequest: null });
|
||||
|
||||
@@ -15,6 +16,15 @@ export const getCleanupQueue = () => {
|
||||
return cleanupQueue;
|
||||
};
|
||||
|
||||
export const getExportQueue = () => {
|
||||
if (!exportQueue) {
|
||||
exportQueue = new Queue("export", {
|
||||
connection: getConnection()
|
||||
});
|
||||
}
|
||||
return exportQueue;
|
||||
};
|
||||
|
||||
export const queueCleanupJob = async (cleanupJobId: string, mailboxAccountId: string) => {
|
||||
const queue = getCleanupQueue();
|
||||
await queue.add(
|
||||
@@ -24,6 +34,15 @@ export const queueCleanupJob = async (cleanupJobId: string, mailboxAccountId: st
|
||||
);
|
||||
};
|
||||
|
||||
export const queueExportJob = async (exportJobId: string) => {
|
||||
const queue = getExportQueue();
|
||||
await queue.add(
|
||||
"export",
|
||||
{ exportJobId },
|
||||
{ jobId: exportJobId }
|
||||
);
|
||||
};
|
||||
|
||||
export const getQueueJob = async (jobId: string) => {
|
||||
const queue = getCleanupQueue();
|
||||
return queue.getJob(jobId);
|
||||
|
||||
@@ -100,12 +100,14 @@ export async function queueRoutes(app: FastifyInstance) {
|
||||
});
|
||||
|
||||
let lastStatus = "";
|
||||
let lastProgress = -1;
|
||||
const timer = setInterval(async () => {
|
||||
const latest = await prisma.exportJob.findUnique({ where: { id: params.id } });
|
||||
if (!latest) return;
|
||||
if (latest.status !== lastStatus) {
|
||||
if (latest.status !== lastStatus || latest.progress !== lastProgress) {
|
||||
reply.raw.write(`data: ${JSON.stringify(latest)}\n\n`);
|
||||
lastStatus = latest.status;
|
||||
lastProgress = latest.progress ?? 0;
|
||||
}
|
||||
if (latest.status === "DONE" || latest.status === "FAILED") {
|
||||
clearInterval(timer);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { prisma } from "./db.js";
|
||||
import { config } from "./config.js";
|
||||
import { runCleanup } from "./mail/cleanup.js";
|
||||
import { logJobEvent } from "./queue/jobEvents.js";
|
||||
import { processExportQueue } from "./admin/exportWorker.js";
|
||||
import { runExportJob, startExportCleanupLoop } from "./admin/exportWorker.js";
|
||||
|
||||
const connection = new IORedis(config.REDIS_URL, { maxRetriesPerRequest: null });
|
||||
|
||||
@@ -32,6 +32,26 @@ const worker = new Worker(
|
||||
{ connection }
|
||||
);
|
||||
|
||||
const exportWorker = new Worker(
|
||||
"export",
|
||||
async (job) => {
|
||||
const { exportJobId } = job.data as { exportJobId: string };
|
||||
await runExportJob(exportJobId);
|
||||
return { ok: true };
|
||||
},
|
||||
{ connection }
|
||||
);
|
||||
|
||||
exportWorker.on("failed", async (job, err) => {
|
||||
if (!job) return;
|
||||
const exportJobId = job.data?.exportJobId as string | undefined;
|
||||
if (!exportJobId) return;
|
||||
await prisma.exportJob.update({
|
||||
where: { id: exportJobId },
|
||||
data: { status: "FAILED", error: err.message }
|
||||
});
|
||||
});
|
||||
|
||||
worker.on("failed", async (job, err) => {
|
||||
if (!job) return;
|
||||
const cleanupJobId = job.data?.cleanupJobId as string | undefined;
|
||||
@@ -47,6 +67,4 @@ worker.on("failed", async (job, err) => {
|
||||
});
|
||||
|
||||
process.stdout.write("[worker] cleanup worker ready\n");
|
||||
processExportQueue().catch((err) => {
|
||||
process.stderr.write(`[worker] export worker failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
||||
});
|
||||
startExportCleanupLoop();
|
||||
|
||||
@@ -32,6 +32,11 @@ services:
|
||||
GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-http://localhost:8000/oauth/gmail/callback}
|
||||
EXPORT_DIR: /tmp/mailcleaner-exports
|
||||
EXPORT_TTL_HOURS: 24
|
||||
TRUST_PROXY: ${TRUST_PROXY:-false}
|
||||
SEED_ADMIN_EMAIL: ${SEED_ADMIN_EMAIL:-admin@simplemailcleaner.local}
|
||||
SEED_ADMIN_PASSWORD: ${SEED_ADMIN_PASSWORD:-change-me-now}
|
||||
SEED_TENANT: ${SEED_TENANT:-Default Tenant}
|
||||
SEED_TENANT_ID: ${SEED_TENANT_ID:-seed-tenant}
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
@@ -54,6 +59,11 @@ services:
|
||||
GOOGLE_REDIRECT_URI: ${GOOGLE_REDIRECT_URI:-http://localhost:8000/oauth/gmail/callback}
|
||||
EXPORT_DIR: /tmp/mailcleaner-exports
|
||||
EXPORT_TTL_HOURS: 24
|
||||
TRUST_PROXY: ${TRUST_PROXY:-false}
|
||||
SEED_ADMIN_EMAIL: ${SEED_ADMIN_EMAIL:-admin@simplemailcleaner.local}
|
||||
SEED_ADMIN_PASSWORD: ${SEED_ADMIN_PASSWORD:-change-me-now}
|
||||
SEED_TENANT: ${SEED_TENANT:-Default Tenant}
|
||||
SEED_TENANT_ID: ${SEED_TENANT_ID:-seed-tenant}
|
||||
depends_on:
|
||||
- postgres
|
||||
- redis
|
||||
@@ -67,7 +77,7 @@ services:
|
||||
dockerfile: Dockerfile
|
||||
environment:
|
||||
PORT: 3000
|
||||
VITE_API_URL: http://localhost:8000
|
||||
VITE_API_URL: ${VITE_API_URL:-http://localhost:8000}
|
||||
depends_on:
|
||||
- api
|
||||
ports:
|
||||
|
||||
@@ -24,6 +24,10 @@ type Account = {
|
||||
email: string;
|
||||
provider: string;
|
||||
isActive: boolean;
|
||||
hasOauth?: boolean;
|
||||
oauthExpiresAt?: string | null;
|
||||
oauthLastCheckedAt?: string | null;
|
||||
oauthLastErrorCode?: string | null;
|
||||
tenant?: { id: string; name: string } | null;
|
||||
};
|
||||
|
||||
@@ -52,7 +56,8 @@ export default function AdminPanel({ token, onImpersonate }: Props) {
|
||||
const [exportTenantId, setExportTenantId] = useState<string | null>(null);
|
||||
const [exportStatus, setExportStatus] = useState<"idle" | "loading" | "done" | "failed">("idle");
|
||||
const [exportJobId, setExportJobId] = useState<string | null>(null);
|
||||
const [exportHistory, setExportHistory] = useState<{ id: string; status: string; expiresAt?: string | null; createdAt?: string }[]>([]);
|
||||
const [exportHistory, setExportHistory] = useState<{ id: string; status: string; expiresAt?: string | null; createdAt?: string; progress?: number }[]>([]);
|
||||
const [exportFilter, setExportFilter] = useState<"all" | "active" | "done" | "failed" | "expired">("all");
|
||||
const [exportScope, setExportScope] = useState<"all" | "users" | "accounts" | "jobs" | "rules">("all");
|
||||
const [exportFormat, setExportFormat] = useState<"json" | "csv" | "zip">("json");
|
||||
const [tenantSort, setTenantSort] = useState<"recent" | "oldest" | "name">("recent");
|
||||
@@ -72,8 +77,8 @@ export default function AdminPanel({ token, onImpersonate }: Props) {
|
||||
|
||||
const jobsData = await apiFetch("/admin/jobs", {}, token);
|
||||
setJobs(jobsData.jobs ?? []);
|
||||
const exportsData = await apiFetch("/admin/exports", {}, token);
|
||||
setExportHistory(exportsData.exports ?? []);
|
||||
const exportsData = await apiFetch("/admin/exports", {}, token);
|
||||
setExportHistory(exportsData.exports ?? []);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
@@ -107,7 +112,7 @@ export default function AdminPanel({ token, onImpersonate }: Props) {
|
||||
source.onmessage = async (event) => {
|
||||
const data = JSON.parse(event.data);
|
||||
setExportHistory((prev) =>
|
||||
prev.map((item) => (item.id === data.id ? { ...item, status: data.status, expiresAt: data.expiresAt } : item))
|
||||
prev.map((item) => (item.id === data.id ? { ...item, status: data.status, expiresAt: data.expiresAt, progress: data.progress } : item))
|
||||
);
|
||||
if (data.status === "DONE") {
|
||||
const response = await downloadExport(token, result.jobId);
|
||||
@@ -213,6 +218,14 @@ export default function AdminPanel({ token, onImpersonate }: Props) {
|
||||
const usersSorted = sortBy(users, userSort, (u) => u.email);
|
||||
const accountsSorted = sortBy(accounts, accountSort, (a) => a.email);
|
||||
const jobsSorted = sortBy(jobs, jobSort, (j) => j.status);
|
||||
const exportsFiltered = exportHistory.filter((item) => {
|
||||
const expired = item.expiresAt ? new Date(item.expiresAt) < new Date() : false;
|
||||
if (exportFilter === "expired") return expired;
|
||||
if (exportFilter === "active") return item.status === "QUEUED" || item.status === "RUNNING";
|
||||
if (exportFilter === "done") return item.status === "DONE";
|
||||
if (exportFilter === "failed") return item.status === "FAILED";
|
||||
return true;
|
||||
});
|
||||
const mapJobStatus = (status: string) => {
|
||||
switch (status) {
|
||||
case "RUNNING":
|
||||
@@ -317,7 +330,28 @@ export default function AdminPanel({ token, onImpersonate }: Props) {
|
||||
{exportHistory.length > 0 && (
|
||||
<div className="export-history">
|
||||
<h4>{t("exportHistory")}</h4>
|
||||
{exportHistory.map((item) => (
|
||||
<div className="filter-row">
|
||||
<label>
|
||||
{t("adminSortLabel")}
|
||||
<select value={exportFilter} onChange={(event) => setExportFilter(event.target.value as typeof exportFilter)}>
|
||||
<option value="all">{t("adminExportAll")}</option>
|
||||
<option value="active">{t("exportStatusRunning")}</option>
|
||||
<option value="done">{t("exportStatusDone")}</option>
|
||||
<option value="failed">{t("exportStatusFailed")}</option>
|
||||
<option value="expired">{t("exportStatusExpired")}</option>
|
||||
</select>
|
||||
</label>
|
||||
<button
|
||||
className="ghost"
|
||||
onClick={async () => {
|
||||
await apiFetch("/admin/exports/purge", { method: "POST" }, token);
|
||||
loadAll().catch(() => undefined);
|
||||
}}
|
||||
>
|
||||
{t("adminExportPurge")}
|
||||
</button>
|
||||
</div>
|
||||
{exportsFiltered.map((item) => (
|
||||
<div key={item.id} className="list-item">
|
||||
<div>
|
||||
<strong>{item.id.slice(0, 6)}</strong>
|
||||
@@ -332,6 +366,9 @@ export default function AdminPanel({ token, onImpersonate }: Props) {
|
||||
? t("exportStatusDone")
|
||||
: t("exportStatusFailed")}
|
||||
</p>
|
||||
{item.progress !== undefined && (
|
||||
<p>{t("exportProgress", { progress: item.progress })}</p>
|
||||
)}
|
||||
</div>
|
||||
<div className="inline-actions">
|
||||
<span>
|
||||
@@ -351,6 +388,15 @@ export default function AdminPanel({ token, onImpersonate }: Props) {
|
||||
>
|
||||
{t("exportDownload")}
|
||||
</button>
|
||||
<button
|
||||
className="ghost"
|
||||
onClick={async () => {
|
||||
await apiFetch(`/admin/exports/${item.id}`, { method: "DELETE" }, token);
|
||||
loadAll().catch(() => undefined);
|
||||
}}
|
||||
>
|
||||
{t("delete")}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@@ -442,6 +488,22 @@ export default function AdminPanel({ token, onImpersonate }: Props) {
|
||||
</span>
|
||||
</div>
|
||||
<p>{account.provider} · {account.tenant?.name ?? "-"}</p>
|
||||
{account.hasOauth && (
|
||||
<p className="status-note">
|
||||
{t("statusLabel")}: {account.oauthLastErrorCode ? t("badgeUnhealthy") : t("badgeHealthy")} ·{" "}
|
||||
{t("adminExpiresAt")}: {account.oauthExpiresAt ? new Date(account.oauthExpiresAt).toLocaleString() : t("oauthStatusUnknown")} ·{" "}
|
||||
{t("adminLastConnected")}: {account.oauthLastCheckedAt ? new Date(account.oauthLastCheckedAt).toLocaleString() : t("oauthStatusUnknown")}
|
||||
</p>
|
||||
)}
|
||||
{account.oauthLastErrorCode && (
|
||||
<p className="status-note">
|
||||
{account.oauthLastErrorCode === "invalid_grant"
|
||||
? t("oauthErrorInvalidGrant")
|
||||
: account.oauthLastErrorCode === "token_expired"
|
||||
? t("oauthErrorExpired")
|
||||
: t("oauthErrorUnknown")}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
<button className="ghost" onClick={() => toggleAccount(account)}>
|
||||
{account.isActive ? t("adminDisable") : t("adminEnable")}
|
||||
|
||||
@@ -151,5 +151,7 @@
|
||||
"exportStatusRunning": "Läuft",
|
||||
"exportStatusDone": "Fertig",
|
||||
"exportStatusFailed": "Fehlgeschlagen",
|
||||
"exportStatusExpired": "Abgelaufen"
|
||||
"exportStatusExpired": "Abgelaufen",
|
||||
"adminExportPurge": "Abgelaufene löschen",
|
||||
"exportProgress": "Fortschritt {{progress}}%"
|
||||
}
|
||||
|
||||
@@ -151,5 +151,7 @@
|
||||
"exportStatusRunning": "Running",
|
||||
"exportStatusDone": "Done",
|
||||
"exportStatusFailed": "Failed",
|
||||
"exportStatusExpired": "Expired"
|
||||
"exportStatusExpired": "Expired",
|
||||
"adminExportPurge": "Purge expired",
|
||||
"exportProgress": "Progress {{progress}}%"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user