From 807722dc3eafe24d280a46537405ee88bc9e8d0e Mon Sep 17 00:00:00 2001 From: Jonny Nguyen Date: Thu, 25 Jun 2026 22:01:25 -0700 Subject: [PATCH] Refactor project structure to support monorepo with workspaces; add API and web applications with respective configurations, dependencies, and scripts. Introduce environment variable validation and initial API setup for email warmup functionality. --- apps/api/package.json | 43 + {src => apps/api/src}/config.ts | 12 +- apps/api/src/generated/prisma/browser.ts | 69 + apps/api/src/generated/prisma/client.ts | 91 + .../src/generated/prisma/commonInputTypes.ts | 708 ++++ apps/api/src/generated/prisma/enums.ts | 89 + .../src/generated/prisma/internal/class.ts | 294 ++ .../prisma/internal/prismaNamespace.ts | 1663 ++++++++ .../prisma/internal/prismaNamespaceBrowser.ts | 270 ++ apps/api/src/generated/prisma/models.ts | 21 + .../prisma/models/CampaignSatellite.ts | 1593 ++++++++ .../generated/prisma/models/DailySchedule.ts | 1673 ++++++++ .../src/generated/prisma/models/DailyStat.ts | 1763 ++++++++ apps/api/src/generated/prisma/models/Inbox.ts | 2969 ++++++++++++++ .../src/generated/prisma/models/JobRunLog.ts | 1151 ++++++ .../src/generated/prisma/models/SendJob.ts | 1494 +++++++ .../src/generated/prisma/models/Setting.ts | 1147 ++++++ .../generated/prisma/models/WarmupCampaign.ts | 3268 +++++++++++++++ .../src/generated/prisma/models/WarmupLog.ts | 2026 ++++++++++ .../src/generated/prisma/models/Workspace.ts | 1411 +++++++ apps/api/src/index.ts | 82 + apps/api/src/jobs/campaign-dispatcher.job.ts | 220 + apps/api/src/jobs/campaign-rollover.job.ts | 24 + {src => apps/api/src}/jobs/dispatcher.job.ts | 12 +- apps/api/src/jobs/job-runner.ts | 17 + apps/api/src/jobs/rescuer.job.ts | 464 +++ {src => apps/api/src}/jobs/scheduler.ts | 27 +- apps/api/src/jobs/stats-rollup.job.ts | 50 + apps/api/src/lib/dns-check.ts | 70 + apps/api/src/lib/domain-age.ts | 47 + {src => apps/api/src}/lib/inboxGuard.ts | 6 +- {src => apps/api/src}/lib/logger.ts | 0 {src => apps/api/src}/lib/prisma.ts | 21 +- apps/api/src/lib/send-window.ts | 56 + .../api/src}/middleware/apiKeyAuth.ts | 0 .../api/src}/middleware/errorHandler.ts | 0 apps/api/src/models/campaign.schemas.ts | 75 + {src => apps/api/src}/models/enums.ts | 9 +- apps/api/src/models/provider-presets.ts | 50 + {src => apps/api/src}/models/types.ts | 0 apps/api/src/models/workspace.constants.ts | 2 + apps/api/src/routes/ai.routes.ts | 46 + apps/api/src/routes/campaigns.routes.ts | 183 + apps/api/src/routes/inboxes.routes.ts | 367 ++ apps/api/src/routes/integrations.routes.ts | 159 + apps/api/src/routes/jobs.routes.ts | 60 + {src => apps/api/src}/routes/logs.routes.ts | 7 +- apps/api/src/routes/recipes.routes.ts | 60 + .../api/src}/routes/settings.routes.ts | 0 apps/api/src/routes/stats.routes.ts | 78 + apps/api/src/services/ai.service.ts | 233 ++ apps/api/src/services/campaign.service.ts | 654 +++ apps/api/src/services/compliance.service.ts | 169 + .../content-validator.service.test.ts | 37 + .../src/services/content-validator.service.ts | 108 + .../api/src}/services/crypto.service.ts | 0 .../api/src}/services/email.service.ts | 40 +- .../api/src}/services/imap.service.ts | 31 +- apps/api/src/services/job-queue.service.ts | 111 + apps/api/src/services/mailbox-auth.service.ts | 14 + .../src/services/microsoft-oauth.service.ts | 121 + apps/api/src/services/postmaster.service.ts | 212 + apps/api/src/services/recipe.service.test.ts | 144 + apps/api/src/services/recipe.service.ts | 251 ++ .../src/services/reply-rate.service.test.ts | 65 + apps/api/src/services/reply-rate.service.ts | 122 + .../api/src}/services/settings.service.ts | 0 apps/api/src/services/spam-folder.service.ts | 46 + .../services/spam-threshold.service.test.ts | 17 + .../src/services/spam-threshold.service.ts | 68 + apps/api/src/services/stats.service.test.ts | 102 + apps/api/src/services/stats.service.ts | 384 ++ apps/api/src/services/workspace.service.ts | 34 + apps/api/src/test/helpers/test-db.ts | 60 + .../campaign-flow.integration.test.ts | 42 + .../concurrent-primaries.integration.test.ts | 111 + .../primary-exclusivity.integration.test.ts | 74 + .../integration/recipes.integration.test.ts | 118 + apps/api/tsconfig.json | 19 + apps/web/index.html | 13 + apps/web/package.json | 35 + apps/web/postcss.config.js | 6 + apps/web/public/vite.svg | 4 + apps/web/src/App.tsx | 36 + .../src/components/ComplianceChecklist.tsx | 68 + apps/web/src/components/EmptyState.tsx | 27 + apps/web/src/components/MetricCard.tsx | 48 + apps/web/src/components/PageHeader.tsx | 24 + apps/web/src/components/StatusBadge.tsx | 44 + apps/web/src/components/layout/AppShell.tsx | 17 + apps/web/src/components/layout/BottomNav.tsx | 36 + apps/web/src/components/layout/Sidebar.tsx | 65 + apps/web/src/components/ui/Alert.tsx | 42 + apps/web/src/components/ui/Badge.tsx | 28 + apps/web/src/components/ui/Button.tsx | 41 + apps/web/src/components/ui/Card.tsx | 48 + apps/web/src/components/ui/Input.tsx | 19 + apps/web/src/components/ui/Label.tsx | 13 + apps/web/src/components/ui/Progress.tsx | 21 + apps/web/src/components/ui/Select.tsx | 20 + apps/web/src/components/ui/Skeleton.tsx | 5 + apps/web/src/components/ui/Tabs.tsx | 100 + apps/web/src/index.css | 17 + apps/web/src/lib/api.ts | 96 + apps/web/src/lib/auth.tsx | 154 + apps/web/src/lib/types.ts | 155 + apps/web/src/lib/utils.ts | 25 + apps/web/src/main.tsx | 28 + apps/web/src/pages/ActivityPage.tsx | 151 + apps/web/src/pages/CampaignDetailPage.tsx | 738 ++++ apps/web/src/pages/CampaignWizardPage.tsx | 400 ++ apps/web/src/pages/CampaignsPage.tsx | 179 + apps/web/src/pages/DashboardPage.tsx | 185 + apps/web/src/pages/MailboxDetailPage.tsx | 519 +++ apps/web/src/pages/MailboxesPage.tsx | 395 ++ apps/web/src/pages/SettingsPage.tsx | 473 +++ apps/web/src/vite-env.d.ts | 1 + apps/web/tailwind.config.js | 27 + apps/web/tsconfig.json | 23 + apps/web/tsconfig.node.json | 19 + apps/web/vite.config.ts | 25 + docs/16-roadmap.md | 117 +- docs/18-open-questions.md | 14 +- docs/README.md | 4 + docs/fillcare-qa-playbook.md | 50 + docs/self-host-hardening.md | 97 + docs/sprints/sprint-1.1.md | 110 + docs/sprints/sprint-1.2-1.3.md | 81 + docs/sprints/sprint-1.4.md | 39 + docs/sprints/sprint-1.5.md | 44 + docs/sprints/sprint-2.0.md | 29 + docs/sprints/sprint-2.1.md | 40 + docs/sprints/sprint-2.2.md | 25 + docs/sprints/sprint-2.3.md | 23 + docs/sprints/sprint-2.4.md | 29 + package-lock.json | 3576 ++++++++++++++++- package.json | 49 +- packages/shared/package.json | 14 + packages/shared/src/index.ts | 115 + packages/shared/tsconfig.json | 13 + .../migration.sql | 155 + .../migration.sql | 2 + .../migration.sql | 1 + .../migration.sql | 27 + .../migration.sql | 35 + .../migration.sql | 39 + prisma/schema.prisma | 218 +- src/index.ts | 37 - src/jobs/rescuer.job.ts | 216 - src/routes/inboxes.routes.ts | 142 - src/services/ai.service.ts | 165 - 151 files changed, 36505 insertions(+), 705 deletions(-) create mode 100644 apps/api/package.json rename {src => apps/api/src}/config.ts (66%) create mode 100644 apps/api/src/generated/prisma/browser.ts create mode 100644 apps/api/src/generated/prisma/client.ts create mode 100644 apps/api/src/generated/prisma/commonInputTypes.ts create mode 100644 apps/api/src/generated/prisma/enums.ts create mode 100644 apps/api/src/generated/prisma/internal/class.ts create mode 100644 apps/api/src/generated/prisma/internal/prismaNamespace.ts create mode 100644 apps/api/src/generated/prisma/internal/prismaNamespaceBrowser.ts create mode 100644 apps/api/src/generated/prisma/models.ts create mode 100644 apps/api/src/generated/prisma/models/CampaignSatellite.ts create mode 100644 apps/api/src/generated/prisma/models/DailySchedule.ts create mode 100644 apps/api/src/generated/prisma/models/DailyStat.ts create mode 100644 apps/api/src/generated/prisma/models/Inbox.ts create mode 100644 apps/api/src/generated/prisma/models/JobRunLog.ts create mode 100644 apps/api/src/generated/prisma/models/SendJob.ts create mode 100644 apps/api/src/generated/prisma/models/Setting.ts create mode 100644 apps/api/src/generated/prisma/models/WarmupCampaign.ts create mode 100644 apps/api/src/generated/prisma/models/WarmupLog.ts create mode 100644 apps/api/src/generated/prisma/models/Workspace.ts create mode 100644 apps/api/src/index.ts create mode 100644 apps/api/src/jobs/campaign-dispatcher.job.ts create mode 100644 apps/api/src/jobs/campaign-rollover.job.ts rename {src => apps/api/src}/jobs/dispatcher.job.ts (90%) create mode 100644 apps/api/src/jobs/job-runner.ts create mode 100644 apps/api/src/jobs/rescuer.job.ts rename {src => apps/api/src}/jobs/scheduler.ts (53%) create mode 100644 apps/api/src/jobs/stats-rollup.job.ts create mode 100644 apps/api/src/lib/dns-check.ts create mode 100644 apps/api/src/lib/domain-age.ts rename {src => apps/api/src}/lib/inboxGuard.ts (79%) rename {src => apps/api/src}/lib/logger.ts (100%) rename {src => apps/api/src}/lib/prisma.ts (68%) create mode 100644 apps/api/src/lib/send-window.ts rename {src => apps/api/src}/middleware/apiKeyAuth.ts (100%) rename {src => apps/api/src}/middleware/errorHandler.ts (100%) create mode 100644 apps/api/src/models/campaign.schemas.ts rename {src => apps/api/src}/models/enums.ts (72%) create mode 100644 apps/api/src/models/provider-presets.ts rename {src => apps/api/src}/models/types.ts (100%) create mode 100644 apps/api/src/models/workspace.constants.ts create mode 100644 apps/api/src/routes/ai.routes.ts create mode 100644 apps/api/src/routes/campaigns.routes.ts create mode 100644 apps/api/src/routes/inboxes.routes.ts create mode 100644 apps/api/src/routes/integrations.routes.ts create mode 100644 apps/api/src/routes/jobs.routes.ts rename {src => apps/api/src}/routes/logs.routes.ts (87%) create mode 100644 apps/api/src/routes/recipes.routes.ts rename {src => apps/api/src}/routes/settings.routes.ts (100%) create mode 100644 apps/api/src/routes/stats.routes.ts create mode 100644 apps/api/src/services/ai.service.ts create mode 100644 apps/api/src/services/campaign.service.ts create mode 100644 apps/api/src/services/compliance.service.ts create mode 100644 apps/api/src/services/content-validator.service.test.ts create mode 100644 apps/api/src/services/content-validator.service.ts rename {src => apps/api/src}/services/crypto.service.ts (100%) rename {src => apps/api/src}/services/email.service.ts (53%) rename {src => apps/api/src}/services/imap.service.ts (54%) create mode 100644 apps/api/src/services/job-queue.service.ts create mode 100644 apps/api/src/services/mailbox-auth.service.ts create mode 100644 apps/api/src/services/microsoft-oauth.service.ts create mode 100644 apps/api/src/services/postmaster.service.ts create mode 100644 apps/api/src/services/recipe.service.test.ts create mode 100644 apps/api/src/services/recipe.service.ts create mode 100644 apps/api/src/services/reply-rate.service.test.ts create mode 100644 apps/api/src/services/reply-rate.service.ts rename {src => apps/api/src}/services/settings.service.ts (100%) create mode 100644 apps/api/src/services/spam-folder.service.ts create mode 100644 apps/api/src/services/spam-threshold.service.test.ts create mode 100644 apps/api/src/services/spam-threshold.service.ts create mode 100644 apps/api/src/services/stats.service.test.ts create mode 100644 apps/api/src/services/stats.service.ts create mode 100644 apps/api/src/services/workspace.service.ts create mode 100644 apps/api/src/test/helpers/test-db.ts create mode 100644 apps/api/src/test/integration/campaign-flow.integration.test.ts create mode 100644 apps/api/src/test/integration/concurrent-primaries.integration.test.ts create mode 100644 apps/api/src/test/integration/primary-exclusivity.integration.test.ts create mode 100644 apps/api/src/test/integration/recipes.integration.test.ts create mode 100644 apps/api/tsconfig.json create mode 100644 apps/web/index.html create mode 100644 apps/web/package.json create mode 100644 apps/web/postcss.config.js create mode 100644 apps/web/public/vite.svg create mode 100644 apps/web/src/App.tsx create mode 100644 apps/web/src/components/ComplianceChecklist.tsx create mode 100644 apps/web/src/components/EmptyState.tsx create mode 100644 apps/web/src/components/MetricCard.tsx create mode 100644 apps/web/src/components/PageHeader.tsx create mode 100644 apps/web/src/components/StatusBadge.tsx create mode 100644 apps/web/src/components/layout/AppShell.tsx create mode 100644 apps/web/src/components/layout/BottomNav.tsx create mode 100644 apps/web/src/components/layout/Sidebar.tsx create mode 100644 apps/web/src/components/ui/Alert.tsx create mode 100644 apps/web/src/components/ui/Badge.tsx create mode 100644 apps/web/src/components/ui/Button.tsx create mode 100644 apps/web/src/components/ui/Card.tsx create mode 100644 apps/web/src/components/ui/Input.tsx create mode 100644 apps/web/src/components/ui/Label.tsx create mode 100644 apps/web/src/components/ui/Progress.tsx create mode 100644 apps/web/src/components/ui/Select.tsx create mode 100644 apps/web/src/components/ui/Skeleton.tsx create mode 100644 apps/web/src/components/ui/Tabs.tsx create mode 100644 apps/web/src/index.css create mode 100644 apps/web/src/lib/api.ts create mode 100644 apps/web/src/lib/auth.tsx create mode 100644 apps/web/src/lib/types.ts create mode 100644 apps/web/src/lib/utils.ts create mode 100644 apps/web/src/main.tsx create mode 100644 apps/web/src/pages/ActivityPage.tsx create mode 100644 apps/web/src/pages/CampaignDetailPage.tsx create mode 100644 apps/web/src/pages/CampaignWizardPage.tsx create mode 100644 apps/web/src/pages/CampaignsPage.tsx create mode 100644 apps/web/src/pages/DashboardPage.tsx create mode 100644 apps/web/src/pages/MailboxDetailPage.tsx create mode 100644 apps/web/src/pages/MailboxesPage.tsx create mode 100644 apps/web/src/pages/SettingsPage.tsx create mode 100644 apps/web/src/vite-env.d.ts create mode 100644 apps/web/tailwind.config.js create mode 100644 apps/web/tsconfig.json create mode 100644 apps/web/tsconfig.node.json create mode 100644 apps/web/vite.config.ts create mode 100644 docs/fillcare-qa-playbook.md create mode 100644 docs/self-host-hardening.md create mode 100644 docs/sprints/sprint-1.1.md create mode 100644 docs/sprints/sprint-1.2-1.3.md create mode 100644 docs/sprints/sprint-1.4.md create mode 100644 docs/sprints/sprint-1.5.md create mode 100644 docs/sprints/sprint-2.0.md create mode 100644 docs/sprints/sprint-2.1.md create mode 100644 docs/sprints/sprint-2.2.md create mode 100644 docs/sprints/sprint-2.3.md create mode 100644 docs/sprints/sprint-2.4.md create mode 100644 packages/shared/package.json create mode 100644 packages/shared/src/index.ts create mode 100644 packages/shared/tsconfig.json create mode 100644 prisma/migrations/20260625054451_sprint_1_1_campaigns/migration.sql create mode 100644 prisma/migrations/20260625060207_sprint_1_2_1_3_dispatcher/migration.sql create mode 100644 prisma/migrations/20260625173756_sprint_1_4_reply_rate/migration.sql create mode 100644 prisma/migrations/20260625173848_sprint_1_5_stats/migration.sql create mode 100644 prisma/migrations/20260625181925_sprint_2_2_2_4/migration.sql create mode 100644 prisma/migrations/20260626035310_mailbox_auth_microsoft/migration.sql delete mode 100644 src/index.ts delete mode 100644 src/jobs/rescuer.job.ts delete mode 100644 src/routes/inboxes.routes.ts delete mode 100644 src/services/ai.service.ts diff --git a/apps/api/package.json b/apps/api/package.json new file mode 100644 index 0000000..1dcf426 --- /dev/null +++ b/apps/api/package.json @@ -0,0 +1,43 @@ +{ + "name": "@warmbox/api", + "version": "1.0.0", + "private": true, + "main": "dist/index.js", + "scripts": { + "dev": "tsx watch src/index.ts", + "build": "tsc", + "start": "node dist/index.js", + "test": "tsx --test src/services/*.test.ts", + "test:integration": "tsx --test --test-concurrency=1 src/test/integration/*.integration.test.ts" + }, + "dependencies": { + "@prisma/adapter-better-sqlite3": "^7.8.0", + "@prisma/client": "^7.8.0", + "@warmbox/shared": "*", + "better-sqlite3": "^11.10.0", + "cors": "^2.8.5", + "dotenv": "^16.5.0", + "express": "^4.21.2", + "imapflow": "^1.0.189", + "luxon": "^3.7.2", + "mailparser": "^3.7.3", + "node-cron": "^3.0.3", + "nodemailer": "^6.10.1", + "openai": "^4.104.0", + "pino": "^9.6.0", + "zod": "^3.25.28" + }, + "devDependencies": { + "@types/better-sqlite3": "^7.6.13", + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/luxon": "^3.7.2", + "@types/mailparser": "^3.4.6", + "@types/node": "^22.15.21", + "@types/node-cron": "^3.0.11", + "@types/nodemailer": "^6.4.17", + "prisma": "^7.8.0", + "tsx": "^4.19.4", + "typescript": "^5.8.3" + } +} diff --git a/src/config.ts b/apps/api/src/config.ts similarity index 66% rename from src/config.ts rename to apps/api/src/config.ts index e584088..1a81bac 100644 --- a/src/config.ts +++ b/apps/api/src/config.ts @@ -1,7 +1,8 @@ import { z } from 'zod'; import dotenv from 'dotenv'; +import path from 'node:path'; -dotenv.config(); +dotenv.config({ path: path.resolve(__dirname, '../../../.env') }); const envSchema = z.object({ DATABASE_URL: z.string().min(1), @@ -13,6 +14,7 @@ const envSchema = z.object({ OPENAI_API_KEY: z.string().optional(), API_KEY: z.string().optional(), PORT: z.coerce.number().default(3000), + PUBLIC_BASE_URL: z.string().url().optional(), NODE_ENV: z.enum(['development', 'production', 'test']).default('development'), }); @@ -25,7 +27,15 @@ if (!parsed.success) { const data = parsed.data; +function resolveDatabaseUrl(url: string): string { + if (!url.startsWith('file:')) return url; + const filePath = url.slice('file:'.length); + if (path.isAbsolute(filePath)) return url; + return `file:${path.resolve(__dirname, '../../../', filePath)}`; +} + export const config = { ...data, + DATABASE_URL: resolveDatabaseUrl(data.DATABASE_URL), AI_API_KEY: data.AI_API_KEY ?? data.OPENAI_API_KEY, }; diff --git a/apps/api/src/generated/prisma/browser.ts b/apps/api/src/generated/prisma/browser.ts new file mode 100644 index 0000000..f7d934b --- /dev/null +++ b/apps/api/src/generated/prisma/browser.ts @@ -0,0 +1,69 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file should be your main import to use Prisma-related types and utilities in a browser. + * Use it to get access to models, enums, and input types. + * + * This file does not contain a `PrismaClient` class, nor several other helpers that are intended as server-side only. + * See `client.ts` for the standard, server-side entry point. + * + * 🟢 You can import this file directly. + */ + +import * as Prisma from './internal/prismaNamespaceBrowser' +export { Prisma } +export * as $Enums from './enums' +export * from './enums'; +/** + * Model Workspace + * + */ +export type Workspace = Prisma.WorkspaceModel +/** + * Model SendJob + * + */ +export type SendJob = Prisma.SendJobModel +/** + * Model JobRunLog + * + */ +export type JobRunLog = Prisma.JobRunLogModel +/** + * Model Inbox + * + */ +export type Inbox = Prisma.InboxModel +/** + * Model WarmupCampaign + * + */ +export type WarmupCampaign = Prisma.WarmupCampaignModel +/** + * Model CampaignSatellite + * + */ +export type CampaignSatellite = Prisma.CampaignSatelliteModel +/** + * Model DailySchedule + * + */ +export type DailySchedule = Prisma.DailyScheduleModel +/** + * Model DailyStat + * + */ +export type DailyStat = Prisma.DailyStatModel +/** + * Model WarmupLog + * + */ +export type WarmupLog = Prisma.WarmupLogModel +/** + * Model Setting + * + */ +export type Setting = Prisma.SettingModel diff --git a/apps/api/src/generated/prisma/client.ts b/apps/api/src/generated/prisma/client.ts new file mode 100644 index 0000000..3cfc1cb --- /dev/null +++ b/apps/api/src/generated/prisma/client.ts @@ -0,0 +1,91 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types. + * If you're looking for something you can import in the client-side of your application, please refer to the `browser.ts` file instead. + * + * 🟢 You can import this file directly. + */ + +import * as process from 'node:process' +import * as path from 'node:path' + +import * as runtime from "@prisma/client/runtime/client" +import * as $Enums from "./enums" +import * as $Class from "./internal/class" +import * as Prisma from "./internal/prismaNamespace" + +export * as $Enums from './enums' +export * from "./enums" +/** + * ## Prisma Client + * + * Type-safe database client for TypeScript + * @example + * ``` + * const prisma = new PrismaClient({ + * adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }) + * }) + * // Fetch zero or more Workspaces + * const workspaces = await prisma.workspace.findMany() + * ``` + * + * Read more in our [docs](https://pris.ly/d/client). + */ +export const PrismaClient = $Class.getPrismaClientClass() +export type PrismaClient = $Class.PrismaClient +export { Prisma } + +/** + * Model Workspace + * + */ +export type Workspace = Prisma.WorkspaceModel +/** + * Model SendJob + * + */ +export type SendJob = Prisma.SendJobModel +/** + * Model JobRunLog + * + */ +export type JobRunLog = Prisma.JobRunLogModel +/** + * Model Inbox + * + */ +export type Inbox = Prisma.InboxModel +/** + * Model WarmupCampaign + * + */ +export type WarmupCampaign = Prisma.WarmupCampaignModel +/** + * Model CampaignSatellite + * + */ +export type CampaignSatellite = Prisma.CampaignSatelliteModel +/** + * Model DailySchedule + * + */ +export type DailySchedule = Prisma.DailyScheduleModel +/** + * Model DailyStat + * + */ +export type DailyStat = Prisma.DailyStatModel +/** + * Model WarmupLog + * + */ +export type WarmupLog = Prisma.WarmupLogModel +/** + * Model Setting + * + */ +export type Setting = Prisma.SettingModel diff --git a/apps/api/src/generated/prisma/commonInputTypes.ts b/apps/api/src/generated/prisma/commonInputTypes.ts new file mode 100644 index 0000000..ad18b04 --- /dev/null +++ b/apps/api/src/generated/prisma/commonInputTypes.ts @@ -0,0 +1,708 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports various common sort, input & filter types that are not directly linked to a particular model. + * + * 🟢 You can import this file directly. + */ + +import type * as runtime from "@prisma/client/runtime/client" +import * as $Enums from "./enums" +import type * as Prisma from "./internal/prismaNamespace" + + +export type StringFilter<$PrismaModel = never> = { + equals?: string | Prisma.StringFieldRefInput<$PrismaModel> + in?: string[] + notIn?: string[] + lt?: string | Prisma.StringFieldRefInput<$PrismaModel> + lte?: string | Prisma.StringFieldRefInput<$PrismaModel> + gt?: string | Prisma.StringFieldRefInput<$PrismaModel> + gte?: string | Prisma.StringFieldRefInput<$PrismaModel> + contains?: string | Prisma.StringFieldRefInput<$PrismaModel> + startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + not?: Prisma.NestedStringFilter<$PrismaModel> | string +} + +export type DateTimeFilter<$PrismaModel = never> = { + equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + in?: Date[] | string[] + notIn?: Date[] | string[] + lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string +} + +export type StringWithAggregatesFilter<$PrismaModel = never> = { + equals?: string | Prisma.StringFieldRefInput<$PrismaModel> + in?: string[] + notIn?: string[] + lt?: string | Prisma.StringFieldRefInput<$PrismaModel> + lte?: string | Prisma.StringFieldRefInput<$PrismaModel> + gt?: string | Prisma.StringFieldRefInput<$PrismaModel> + gte?: string | Prisma.StringFieldRefInput<$PrismaModel> + contains?: string | Prisma.StringFieldRefInput<$PrismaModel> + startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedStringFilter<$PrismaModel> + _max?: Prisma.NestedStringFilter<$PrismaModel> +} + +export type DateTimeWithAggregatesFilter<$PrismaModel = never> = { + equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + in?: Date[] | string[] + notIn?: Date[] | string[] + lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedDateTimeFilter<$PrismaModel> + _max?: Prisma.NestedDateTimeFilter<$PrismaModel> +} + +export type EnumSendJobStatusFilter<$PrismaModel = never> = { + equals?: $Enums.SendJobStatus | Prisma.EnumSendJobStatusFieldRefInput<$PrismaModel> + in?: $Enums.SendJobStatus[] + notIn?: $Enums.SendJobStatus[] + not?: Prisma.NestedEnumSendJobStatusFilter<$PrismaModel> | $Enums.SendJobStatus +} + +export type DateTimeNullableFilter<$PrismaModel = never> = { + equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null + in?: Date[] | string[] | null + notIn?: Date[] | string[] | null + lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null +} + +export type StringNullableFilter<$PrismaModel = never> = { + equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null + in?: string[] | null + notIn?: string[] | null + lt?: string | Prisma.StringFieldRefInput<$PrismaModel> + lte?: string | Prisma.StringFieldRefInput<$PrismaModel> + gt?: string | Prisma.StringFieldRefInput<$PrismaModel> + gte?: string | Prisma.StringFieldRefInput<$PrismaModel> + contains?: string | Prisma.StringFieldRefInput<$PrismaModel> + startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null +} + +export type SortOrderInput = { + sort: Prisma.SortOrder + nulls?: Prisma.NullsOrder +} + +export type EnumSendJobStatusWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.SendJobStatus | Prisma.EnumSendJobStatusFieldRefInput<$PrismaModel> + in?: $Enums.SendJobStatus[] + notIn?: $Enums.SendJobStatus[] + not?: Prisma.NestedEnumSendJobStatusWithAggregatesFilter<$PrismaModel> | $Enums.SendJobStatus + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumSendJobStatusFilter<$PrismaModel> + _max?: Prisma.NestedEnumSendJobStatusFilter<$PrismaModel> +} + +export type DateTimeNullableWithAggregatesFilter<$PrismaModel = never> = { + equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null + in?: Date[] | string[] | null + notIn?: Date[] | string[] | null + lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null + _count?: Prisma.NestedIntNullableFilter<$PrismaModel> + _min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> + _max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> +} + +export type StringNullableWithAggregatesFilter<$PrismaModel = never> = { + equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null + in?: string[] | null + notIn?: string[] | null + lt?: string | Prisma.StringFieldRefInput<$PrismaModel> + lte?: string | Prisma.StringFieldRefInput<$PrismaModel> + gt?: string | Prisma.StringFieldRefInput<$PrismaModel> + gte?: string | Prisma.StringFieldRefInput<$PrismaModel> + contains?: string | Prisma.StringFieldRefInput<$PrismaModel> + startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null + _count?: Prisma.NestedIntNullableFilter<$PrismaModel> + _min?: Prisma.NestedStringNullableFilter<$PrismaModel> + _max?: Prisma.NestedStringNullableFilter<$PrismaModel> +} + +export type BoolFilter<$PrismaModel = never> = { + equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel> + not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean +} + +export type BoolWithAggregatesFilter<$PrismaModel = never> = { + equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel> + not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedBoolFilter<$PrismaModel> + _max?: Prisma.NestedBoolFilter<$PrismaModel> +} + +export type IntFilter<$PrismaModel = never> = { + equals?: number | Prisma.IntFieldRefInput<$PrismaModel> + in?: number[] + notIn?: number[] + lt?: number | Prisma.IntFieldRefInput<$PrismaModel> + lte?: number | Prisma.IntFieldRefInput<$PrismaModel> + gt?: number | Prisma.IntFieldRefInput<$PrismaModel> + gte?: number | Prisma.IntFieldRefInput<$PrismaModel> + not?: Prisma.NestedIntFilter<$PrismaModel> | number +} + +export type EnumMailboxAuthTypeFilter<$PrismaModel = never> = { + equals?: $Enums.MailboxAuthType | Prisma.EnumMailboxAuthTypeFieldRefInput<$PrismaModel> + in?: $Enums.MailboxAuthType[] + notIn?: $Enums.MailboxAuthType[] + not?: Prisma.NestedEnumMailboxAuthTypeFilter<$PrismaModel> | $Enums.MailboxAuthType +} + +export type EnumMailboxRoleFilter<$PrismaModel = never> = { + equals?: $Enums.MailboxRole | Prisma.EnumMailboxRoleFieldRefInput<$PrismaModel> + in?: $Enums.MailboxRole[] + notIn?: $Enums.MailboxRole[] + not?: Prisma.NestedEnumMailboxRoleFilter<$PrismaModel> | $Enums.MailboxRole +} + +export type EnumMailboxProviderFilter<$PrismaModel = never> = { + equals?: $Enums.MailboxProvider | Prisma.EnumMailboxProviderFieldRefInput<$PrismaModel> + in?: $Enums.MailboxProvider[] + notIn?: $Enums.MailboxProvider[] + not?: Prisma.NestedEnumMailboxProviderFilter<$PrismaModel> | $Enums.MailboxProvider +} + +export type EnumInboxStatusFilter<$PrismaModel = never> = { + equals?: $Enums.InboxStatus | Prisma.EnumInboxStatusFieldRefInput<$PrismaModel> + in?: $Enums.InboxStatus[] + notIn?: $Enums.InboxStatus[] + not?: Prisma.NestedEnumInboxStatusFilter<$PrismaModel> | $Enums.InboxStatus +} + +export type IntWithAggregatesFilter<$PrismaModel = never> = { + equals?: number | Prisma.IntFieldRefInput<$PrismaModel> + in?: number[] + notIn?: number[] + lt?: number | Prisma.IntFieldRefInput<$PrismaModel> + lte?: number | Prisma.IntFieldRefInput<$PrismaModel> + gt?: number | Prisma.IntFieldRefInput<$PrismaModel> + gte?: number | Prisma.IntFieldRefInput<$PrismaModel> + not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number + _count?: Prisma.NestedIntFilter<$PrismaModel> + _avg?: Prisma.NestedFloatFilter<$PrismaModel> + _sum?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedIntFilter<$PrismaModel> + _max?: Prisma.NestedIntFilter<$PrismaModel> +} + +export type EnumMailboxAuthTypeWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.MailboxAuthType | Prisma.EnumMailboxAuthTypeFieldRefInput<$PrismaModel> + in?: $Enums.MailboxAuthType[] + notIn?: $Enums.MailboxAuthType[] + not?: Prisma.NestedEnumMailboxAuthTypeWithAggregatesFilter<$PrismaModel> | $Enums.MailboxAuthType + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumMailboxAuthTypeFilter<$PrismaModel> + _max?: Prisma.NestedEnumMailboxAuthTypeFilter<$PrismaModel> +} + +export type EnumMailboxRoleWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.MailboxRole | Prisma.EnumMailboxRoleFieldRefInput<$PrismaModel> + in?: $Enums.MailboxRole[] + notIn?: $Enums.MailboxRole[] + not?: Prisma.NestedEnumMailboxRoleWithAggregatesFilter<$PrismaModel> | $Enums.MailboxRole + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumMailboxRoleFilter<$PrismaModel> + _max?: Prisma.NestedEnumMailboxRoleFilter<$PrismaModel> +} + +export type EnumMailboxProviderWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.MailboxProvider | Prisma.EnumMailboxProviderFieldRefInput<$PrismaModel> + in?: $Enums.MailboxProvider[] + notIn?: $Enums.MailboxProvider[] + not?: Prisma.NestedEnumMailboxProviderWithAggregatesFilter<$PrismaModel> | $Enums.MailboxProvider + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumMailboxProviderFilter<$PrismaModel> + _max?: Prisma.NestedEnumMailboxProviderFilter<$PrismaModel> +} + +export type EnumInboxStatusWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.InboxStatus | Prisma.EnumInboxStatusFieldRefInput<$PrismaModel> + in?: $Enums.InboxStatus[] + notIn?: $Enums.InboxStatus[] + not?: Prisma.NestedEnumInboxStatusWithAggregatesFilter<$PrismaModel> | $Enums.InboxStatus + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumInboxStatusFilter<$PrismaModel> + _max?: Prisma.NestedEnumInboxStatusFilter<$PrismaModel> +} + +export type EnumCampaignRecipeFilter<$PrismaModel = never> = { + equals?: $Enums.CampaignRecipe | Prisma.EnumCampaignRecipeFieldRefInput<$PrismaModel> + in?: $Enums.CampaignRecipe[] + notIn?: $Enums.CampaignRecipe[] + not?: Prisma.NestedEnumCampaignRecipeFilter<$PrismaModel> | $Enums.CampaignRecipe +} + +export type EnumCampaignStatusFilter<$PrismaModel = never> = { + equals?: $Enums.CampaignStatus | Prisma.EnumCampaignStatusFieldRefInput<$PrismaModel> + in?: $Enums.CampaignStatus[] + notIn?: $Enums.CampaignStatus[] + not?: Prisma.NestedEnumCampaignStatusFilter<$PrismaModel> | $Enums.CampaignStatus +} + +export type EnumCampaignStatusNullableFilter<$PrismaModel = never> = { + equals?: $Enums.CampaignStatus | Prisma.EnumCampaignStatusFieldRefInput<$PrismaModel> | null + in?: $Enums.CampaignStatus[] | null + notIn?: $Enums.CampaignStatus[] | null + not?: Prisma.NestedEnumCampaignStatusNullableFilter<$PrismaModel> | $Enums.CampaignStatus | null +} + +export type EnumCampaignRecipeWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.CampaignRecipe | Prisma.EnumCampaignRecipeFieldRefInput<$PrismaModel> + in?: $Enums.CampaignRecipe[] + notIn?: $Enums.CampaignRecipe[] + not?: Prisma.NestedEnumCampaignRecipeWithAggregatesFilter<$PrismaModel> | $Enums.CampaignRecipe + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumCampaignRecipeFilter<$PrismaModel> + _max?: Prisma.NestedEnumCampaignRecipeFilter<$PrismaModel> +} + +export type EnumCampaignStatusWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.CampaignStatus | Prisma.EnumCampaignStatusFieldRefInput<$PrismaModel> + in?: $Enums.CampaignStatus[] + notIn?: $Enums.CampaignStatus[] + not?: Prisma.NestedEnumCampaignStatusWithAggregatesFilter<$PrismaModel> | $Enums.CampaignStatus + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumCampaignStatusFilter<$PrismaModel> + _max?: Prisma.NestedEnumCampaignStatusFilter<$PrismaModel> +} + +export type EnumCampaignStatusNullableWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.CampaignStatus | Prisma.EnumCampaignStatusFieldRefInput<$PrismaModel> | null + in?: $Enums.CampaignStatus[] | null + notIn?: $Enums.CampaignStatus[] | null + not?: Prisma.NestedEnumCampaignStatusNullableWithAggregatesFilter<$PrismaModel> | $Enums.CampaignStatus | null + _count?: Prisma.NestedIntNullableFilter<$PrismaModel> + _min?: Prisma.NestedEnumCampaignStatusNullableFilter<$PrismaModel> + _max?: Prisma.NestedEnumCampaignStatusNullableFilter<$PrismaModel> +} + +export type FloatNullableFilter<$PrismaModel = never> = { + equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null + in?: number[] | null + notIn?: number[] | null + lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null +} + +export type FloatNullableWithAggregatesFilter<$PrismaModel = never> = { + equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null + in?: number[] | null + notIn?: number[] | null + lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + not?: Prisma.NestedFloatNullableWithAggregatesFilter<$PrismaModel> | number | null + _count?: Prisma.NestedIntNullableFilter<$PrismaModel> + _avg?: Prisma.NestedFloatNullableFilter<$PrismaModel> + _sum?: Prisma.NestedFloatNullableFilter<$PrismaModel> + _min?: Prisma.NestedFloatNullableFilter<$PrismaModel> + _max?: Prisma.NestedFloatNullableFilter<$PrismaModel> +} + +export type EnumWarmupStatusFilter<$PrismaModel = never> = { + equals?: $Enums.WarmupStatus | Prisma.EnumWarmupStatusFieldRefInput<$PrismaModel> + in?: $Enums.WarmupStatus[] + notIn?: $Enums.WarmupStatus[] + not?: Prisma.NestedEnumWarmupStatusFilter<$PrismaModel> | $Enums.WarmupStatus +} + +export type EnumWarmupStatusWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.WarmupStatus | Prisma.EnumWarmupStatusFieldRefInput<$PrismaModel> + in?: $Enums.WarmupStatus[] + notIn?: $Enums.WarmupStatus[] + not?: Prisma.NestedEnumWarmupStatusWithAggregatesFilter<$PrismaModel> | $Enums.WarmupStatus + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumWarmupStatusFilter<$PrismaModel> + _max?: Prisma.NestedEnumWarmupStatusFilter<$PrismaModel> +} + +export type NestedStringFilter<$PrismaModel = never> = { + equals?: string | Prisma.StringFieldRefInput<$PrismaModel> + in?: string[] + notIn?: string[] + lt?: string | Prisma.StringFieldRefInput<$PrismaModel> + lte?: string | Prisma.StringFieldRefInput<$PrismaModel> + gt?: string | Prisma.StringFieldRefInput<$PrismaModel> + gte?: string | Prisma.StringFieldRefInput<$PrismaModel> + contains?: string | Prisma.StringFieldRefInput<$PrismaModel> + startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + not?: Prisma.NestedStringFilter<$PrismaModel> | string +} + +export type NestedDateTimeFilter<$PrismaModel = never> = { + equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + in?: Date[] | string[] + notIn?: Date[] | string[] + lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + not?: Prisma.NestedDateTimeFilter<$PrismaModel> | Date | string +} + +export type NestedStringWithAggregatesFilter<$PrismaModel = never> = { + equals?: string | Prisma.StringFieldRefInput<$PrismaModel> + in?: string[] + notIn?: string[] + lt?: string | Prisma.StringFieldRefInput<$PrismaModel> + lte?: string | Prisma.StringFieldRefInput<$PrismaModel> + gt?: string | Prisma.StringFieldRefInput<$PrismaModel> + gte?: string | Prisma.StringFieldRefInput<$PrismaModel> + contains?: string | Prisma.StringFieldRefInput<$PrismaModel> + startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedStringFilter<$PrismaModel> + _max?: Prisma.NestedStringFilter<$PrismaModel> +} + +export type NestedIntFilter<$PrismaModel = never> = { + equals?: number | Prisma.IntFieldRefInput<$PrismaModel> + in?: number[] + notIn?: number[] + lt?: number | Prisma.IntFieldRefInput<$PrismaModel> + lte?: number | Prisma.IntFieldRefInput<$PrismaModel> + gt?: number | Prisma.IntFieldRefInput<$PrismaModel> + gte?: number | Prisma.IntFieldRefInput<$PrismaModel> + not?: Prisma.NestedIntFilter<$PrismaModel> | number +} + +export type NestedDateTimeWithAggregatesFilter<$PrismaModel = never> = { + equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + in?: Date[] | string[] + notIn?: Date[] | string[] + lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + not?: Prisma.NestedDateTimeWithAggregatesFilter<$PrismaModel> | Date | string + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedDateTimeFilter<$PrismaModel> + _max?: Prisma.NestedDateTimeFilter<$PrismaModel> +} + +export type NestedEnumSendJobStatusFilter<$PrismaModel = never> = { + equals?: $Enums.SendJobStatus | Prisma.EnumSendJobStatusFieldRefInput<$PrismaModel> + in?: $Enums.SendJobStatus[] + notIn?: $Enums.SendJobStatus[] + not?: Prisma.NestedEnumSendJobStatusFilter<$PrismaModel> | $Enums.SendJobStatus +} + +export type NestedDateTimeNullableFilter<$PrismaModel = never> = { + equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null + in?: Date[] | string[] | null + notIn?: Date[] | string[] | null + lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + not?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> | Date | string | null +} + +export type NestedStringNullableFilter<$PrismaModel = never> = { + equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null + in?: string[] | null + notIn?: string[] | null + lt?: string | Prisma.StringFieldRefInput<$PrismaModel> + lte?: string | Prisma.StringFieldRefInput<$PrismaModel> + gt?: string | Prisma.StringFieldRefInput<$PrismaModel> + gte?: string | Prisma.StringFieldRefInput<$PrismaModel> + contains?: string | Prisma.StringFieldRefInput<$PrismaModel> + startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + not?: Prisma.NestedStringNullableFilter<$PrismaModel> | string | null +} + +export type NestedEnumSendJobStatusWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.SendJobStatus | Prisma.EnumSendJobStatusFieldRefInput<$PrismaModel> + in?: $Enums.SendJobStatus[] + notIn?: $Enums.SendJobStatus[] + not?: Prisma.NestedEnumSendJobStatusWithAggregatesFilter<$PrismaModel> | $Enums.SendJobStatus + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumSendJobStatusFilter<$PrismaModel> + _max?: Prisma.NestedEnumSendJobStatusFilter<$PrismaModel> +} + +export type NestedDateTimeNullableWithAggregatesFilter<$PrismaModel = never> = { + equals?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> | null + in?: Date[] | string[] | null + notIn?: Date[] | string[] | null + lt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + lte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gt?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + gte?: Date | string | Prisma.DateTimeFieldRefInput<$PrismaModel> + not?: Prisma.NestedDateTimeNullableWithAggregatesFilter<$PrismaModel> | Date | string | null + _count?: Prisma.NestedIntNullableFilter<$PrismaModel> + _min?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> + _max?: Prisma.NestedDateTimeNullableFilter<$PrismaModel> +} + +export type NestedIntNullableFilter<$PrismaModel = never> = { + equals?: number | Prisma.IntFieldRefInput<$PrismaModel> | null + in?: number[] | null + notIn?: number[] | null + lt?: number | Prisma.IntFieldRefInput<$PrismaModel> + lte?: number | Prisma.IntFieldRefInput<$PrismaModel> + gt?: number | Prisma.IntFieldRefInput<$PrismaModel> + gte?: number | Prisma.IntFieldRefInput<$PrismaModel> + not?: Prisma.NestedIntNullableFilter<$PrismaModel> | number | null +} + +export type NestedStringNullableWithAggregatesFilter<$PrismaModel = never> = { + equals?: string | Prisma.StringFieldRefInput<$PrismaModel> | null + in?: string[] | null + notIn?: string[] | null + lt?: string | Prisma.StringFieldRefInput<$PrismaModel> + lte?: string | Prisma.StringFieldRefInput<$PrismaModel> + gt?: string | Prisma.StringFieldRefInput<$PrismaModel> + gte?: string | Prisma.StringFieldRefInput<$PrismaModel> + contains?: string | Prisma.StringFieldRefInput<$PrismaModel> + startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel> + not?: Prisma.NestedStringNullableWithAggregatesFilter<$PrismaModel> | string | null + _count?: Prisma.NestedIntNullableFilter<$PrismaModel> + _min?: Prisma.NestedStringNullableFilter<$PrismaModel> + _max?: Prisma.NestedStringNullableFilter<$PrismaModel> +} + +export type NestedBoolFilter<$PrismaModel = never> = { + equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel> + not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean +} + +export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = { + equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel> + not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedBoolFilter<$PrismaModel> + _max?: Prisma.NestedBoolFilter<$PrismaModel> +} + +export type NestedEnumMailboxAuthTypeFilter<$PrismaModel = never> = { + equals?: $Enums.MailboxAuthType | Prisma.EnumMailboxAuthTypeFieldRefInput<$PrismaModel> + in?: $Enums.MailboxAuthType[] + notIn?: $Enums.MailboxAuthType[] + not?: Prisma.NestedEnumMailboxAuthTypeFilter<$PrismaModel> | $Enums.MailboxAuthType +} + +export type NestedEnumMailboxRoleFilter<$PrismaModel = never> = { + equals?: $Enums.MailboxRole | Prisma.EnumMailboxRoleFieldRefInput<$PrismaModel> + in?: $Enums.MailboxRole[] + notIn?: $Enums.MailboxRole[] + not?: Prisma.NestedEnumMailboxRoleFilter<$PrismaModel> | $Enums.MailboxRole +} + +export type NestedEnumMailboxProviderFilter<$PrismaModel = never> = { + equals?: $Enums.MailboxProvider | Prisma.EnumMailboxProviderFieldRefInput<$PrismaModel> + in?: $Enums.MailboxProvider[] + notIn?: $Enums.MailboxProvider[] + not?: Prisma.NestedEnumMailboxProviderFilter<$PrismaModel> | $Enums.MailboxProvider +} + +export type NestedEnumInboxStatusFilter<$PrismaModel = never> = { + equals?: $Enums.InboxStatus | Prisma.EnumInboxStatusFieldRefInput<$PrismaModel> + in?: $Enums.InboxStatus[] + notIn?: $Enums.InboxStatus[] + not?: Prisma.NestedEnumInboxStatusFilter<$PrismaModel> | $Enums.InboxStatus +} + +export type NestedIntWithAggregatesFilter<$PrismaModel = never> = { + equals?: number | Prisma.IntFieldRefInput<$PrismaModel> + in?: number[] + notIn?: number[] + lt?: number | Prisma.IntFieldRefInput<$PrismaModel> + lte?: number | Prisma.IntFieldRefInput<$PrismaModel> + gt?: number | Prisma.IntFieldRefInput<$PrismaModel> + gte?: number | Prisma.IntFieldRefInput<$PrismaModel> + not?: Prisma.NestedIntWithAggregatesFilter<$PrismaModel> | number + _count?: Prisma.NestedIntFilter<$PrismaModel> + _avg?: Prisma.NestedFloatFilter<$PrismaModel> + _sum?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedIntFilter<$PrismaModel> + _max?: Prisma.NestedIntFilter<$PrismaModel> +} + +export type NestedFloatFilter<$PrismaModel = never> = { + equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> + in?: number[] + notIn?: number[] + lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + not?: Prisma.NestedFloatFilter<$PrismaModel> | number +} + +export type NestedEnumMailboxAuthTypeWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.MailboxAuthType | Prisma.EnumMailboxAuthTypeFieldRefInput<$PrismaModel> + in?: $Enums.MailboxAuthType[] + notIn?: $Enums.MailboxAuthType[] + not?: Prisma.NestedEnumMailboxAuthTypeWithAggregatesFilter<$PrismaModel> | $Enums.MailboxAuthType + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumMailboxAuthTypeFilter<$PrismaModel> + _max?: Prisma.NestedEnumMailboxAuthTypeFilter<$PrismaModel> +} + +export type NestedEnumMailboxRoleWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.MailboxRole | Prisma.EnumMailboxRoleFieldRefInput<$PrismaModel> + in?: $Enums.MailboxRole[] + notIn?: $Enums.MailboxRole[] + not?: Prisma.NestedEnumMailboxRoleWithAggregatesFilter<$PrismaModel> | $Enums.MailboxRole + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumMailboxRoleFilter<$PrismaModel> + _max?: Prisma.NestedEnumMailboxRoleFilter<$PrismaModel> +} + +export type NestedEnumMailboxProviderWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.MailboxProvider | Prisma.EnumMailboxProviderFieldRefInput<$PrismaModel> + in?: $Enums.MailboxProvider[] + notIn?: $Enums.MailboxProvider[] + not?: Prisma.NestedEnumMailboxProviderWithAggregatesFilter<$PrismaModel> | $Enums.MailboxProvider + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumMailboxProviderFilter<$PrismaModel> + _max?: Prisma.NestedEnumMailboxProviderFilter<$PrismaModel> +} + +export type NestedEnumInboxStatusWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.InboxStatus | Prisma.EnumInboxStatusFieldRefInput<$PrismaModel> + in?: $Enums.InboxStatus[] + notIn?: $Enums.InboxStatus[] + not?: Prisma.NestedEnumInboxStatusWithAggregatesFilter<$PrismaModel> | $Enums.InboxStatus + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumInboxStatusFilter<$PrismaModel> + _max?: Prisma.NestedEnumInboxStatusFilter<$PrismaModel> +} + +export type NestedEnumCampaignRecipeFilter<$PrismaModel = never> = { + equals?: $Enums.CampaignRecipe | Prisma.EnumCampaignRecipeFieldRefInput<$PrismaModel> + in?: $Enums.CampaignRecipe[] + notIn?: $Enums.CampaignRecipe[] + not?: Prisma.NestedEnumCampaignRecipeFilter<$PrismaModel> | $Enums.CampaignRecipe +} + +export type NestedEnumCampaignStatusFilter<$PrismaModel = never> = { + equals?: $Enums.CampaignStatus | Prisma.EnumCampaignStatusFieldRefInput<$PrismaModel> + in?: $Enums.CampaignStatus[] + notIn?: $Enums.CampaignStatus[] + not?: Prisma.NestedEnumCampaignStatusFilter<$PrismaModel> | $Enums.CampaignStatus +} + +export type NestedEnumCampaignStatusNullableFilter<$PrismaModel = never> = { + equals?: $Enums.CampaignStatus | Prisma.EnumCampaignStatusFieldRefInput<$PrismaModel> | null + in?: $Enums.CampaignStatus[] | null + notIn?: $Enums.CampaignStatus[] | null + not?: Prisma.NestedEnumCampaignStatusNullableFilter<$PrismaModel> | $Enums.CampaignStatus | null +} + +export type NestedEnumCampaignRecipeWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.CampaignRecipe | Prisma.EnumCampaignRecipeFieldRefInput<$PrismaModel> + in?: $Enums.CampaignRecipe[] + notIn?: $Enums.CampaignRecipe[] + not?: Prisma.NestedEnumCampaignRecipeWithAggregatesFilter<$PrismaModel> | $Enums.CampaignRecipe + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumCampaignRecipeFilter<$PrismaModel> + _max?: Prisma.NestedEnumCampaignRecipeFilter<$PrismaModel> +} + +export type NestedEnumCampaignStatusWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.CampaignStatus | Prisma.EnumCampaignStatusFieldRefInput<$PrismaModel> + in?: $Enums.CampaignStatus[] + notIn?: $Enums.CampaignStatus[] + not?: Prisma.NestedEnumCampaignStatusWithAggregatesFilter<$PrismaModel> | $Enums.CampaignStatus + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumCampaignStatusFilter<$PrismaModel> + _max?: Prisma.NestedEnumCampaignStatusFilter<$PrismaModel> +} + +export type NestedEnumCampaignStatusNullableWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.CampaignStatus | Prisma.EnumCampaignStatusFieldRefInput<$PrismaModel> | null + in?: $Enums.CampaignStatus[] | null + notIn?: $Enums.CampaignStatus[] | null + not?: Prisma.NestedEnumCampaignStatusNullableWithAggregatesFilter<$PrismaModel> | $Enums.CampaignStatus | null + _count?: Prisma.NestedIntNullableFilter<$PrismaModel> + _min?: Prisma.NestedEnumCampaignStatusNullableFilter<$PrismaModel> + _max?: Prisma.NestedEnumCampaignStatusNullableFilter<$PrismaModel> +} + +export type NestedFloatNullableFilter<$PrismaModel = never> = { + equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null + in?: number[] | null + notIn?: number[] | null + lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + not?: Prisma.NestedFloatNullableFilter<$PrismaModel> | number | null +} + +export type NestedFloatNullableWithAggregatesFilter<$PrismaModel = never> = { + equals?: number | Prisma.FloatFieldRefInput<$PrismaModel> | null + in?: number[] | null + notIn?: number[] | null + lt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + lte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gt?: number | Prisma.FloatFieldRefInput<$PrismaModel> + gte?: number | Prisma.FloatFieldRefInput<$PrismaModel> + not?: Prisma.NestedFloatNullableWithAggregatesFilter<$PrismaModel> | number | null + _count?: Prisma.NestedIntNullableFilter<$PrismaModel> + _avg?: Prisma.NestedFloatNullableFilter<$PrismaModel> + _sum?: Prisma.NestedFloatNullableFilter<$PrismaModel> + _min?: Prisma.NestedFloatNullableFilter<$PrismaModel> + _max?: Prisma.NestedFloatNullableFilter<$PrismaModel> +} + +export type NestedEnumWarmupStatusFilter<$PrismaModel = never> = { + equals?: $Enums.WarmupStatus | Prisma.EnumWarmupStatusFieldRefInput<$PrismaModel> + in?: $Enums.WarmupStatus[] + notIn?: $Enums.WarmupStatus[] + not?: Prisma.NestedEnumWarmupStatusFilter<$PrismaModel> | $Enums.WarmupStatus +} + +export type NestedEnumWarmupStatusWithAggregatesFilter<$PrismaModel = never> = { + equals?: $Enums.WarmupStatus | Prisma.EnumWarmupStatusFieldRefInput<$PrismaModel> + in?: $Enums.WarmupStatus[] + notIn?: $Enums.WarmupStatus[] + not?: Prisma.NestedEnumWarmupStatusWithAggregatesFilter<$PrismaModel> | $Enums.WarmupStatus + _count?: Prisma.NestedIntFilter<$PrismaModel> + _min?: Prisma.NestedEnumWarmupStatusFilter<$PrismaModel> + _max?: Prisma.NestedEnumWarmupStatusFilter<$PrismaModel> +} + + diff --git a/apps/api/src/generated/prisma/enums.ts b/apps/api/src/generated/prisma/enums.ts new file mode 100644 index 0000000..ddcbac7 --- /dev/null +++ b/apps/api/src/generated/prisma/enums.ts @@ -0,0 +1,89 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* +* This file exports all enum related types from the schema. +* +* 🟢 You can import this file directly. +*/ + +export const InboxStatus = { + active: 'active', + paused: 'paused', + error: 'error' +} as const + +export type InboxStatus = (typeof InboxStatus)[keyof typeof InboxStatus] + + +export const WarmupStatus = { + sent: 'sent', + rescued_from_spam: 'rescued_from_spam', + engaged_no_reply: 'engaged_no_reply', + replied: 'replied', + complete: 'complete' +} as const + +export type WarmupStatus = (typeof WarmupStatus)[keyof typeof WarmupStatus] + + +export const MailboxRole = { + unassigned: 'unassigned', + primary: 'primary', + satellite: 'satellite' +} as const + +export type MailboxRole = (typeof MailboxRole)[keyof typeof MailboxRole] + + +export const MailboxProvider = { + google_workspace: 'google_workspace', + gmail: 'gmail', + yahoo: 'yahoo', + outlook: 'outlook', + custom: 'custom' +} as const + +export type MailboxProvider = (typeof MailboxProvider)[keyof typeof MailboxProvider] + + +export const MailboxAuthType = { + password: 'password', + microsoft_oauth: 'microsoft_oauth' +} as const + +export type MailboxAuthType = (typeof MailboxAuthType)[keyof typeof MailboxAuthType] + + +export const CampaignRecipe = { + grow: 'grow', + flat: 'flat', + randomized: 'randomized', + custom: 'custom' +} as const + +export type CampaignRecipe = (typeof CampaignRecipe)[keyof typeof CampaignRecipe] + + +export const CampaignStatus = { + draft: 'draft', + active: 'active', + paused: 'paused', + maintenance: 'maintenance', + completed: 'completed', + failed: 'failed' +} as const + +export type CampaignStatus = (typeof CampaignStatus)[keyof typeof CampaignStatus] + + +export const SendJobStatus = { + pending: 'pending', + processing: 'processing', + completed: 'completed', + failed: 'failed' +} as const + +export type SendJobStatus = (typeof SendJobStatus)[keyof typeof SendJobStatus] diff --git a/apps/api/src/generated/prisma/internal/class.ts b/apps/api/src/generated/prisma/internal/class.ts new file mode 100644 index 0000000..5506509 --- /dev/null +++ b/apps/api/src/generated/prisma/internal/class.ts @@ -0,0 +1,294 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * WARNING: This is an internal file that is subject to change! + * + * 🛑 Under no circumstances should you import this file directly! 🛑 + * + * Please import the `PrismaClient` class from the `client.ts` file instead. + */ + +import * as runtime from "@prisma/client/runtime/client" +import type * as Prisma from "./prismaNamespace" + + +const config: runtime.GetPrismaClientConfig = { + "previewFeatures": [], + "clientVersion": "7.8.0", + "engineVersion": "3c6e192761c0362d496ed980de936e2f3cebcd3a", + "activeProvider": "sqlite", + "inlineSchema": "generator client {\n provider = \"prisma-client\"\n output = \"../apps/api/src/generated/prisma\"\n}\n\ndatasource db {\n provider = \"sqlite\"\n}\n\nenum InboxStatus {\n active\n paused\n error\n}\n\nenum WarmupStatus {\n sent\n rescued_from_spam\n engaged_no_reply\n replied\n complete\n}\n\nenum MailboxRole {\n unassigned\n primary\n satellite\n}\n\nenum MailboxProvider {\n google_workspace\n gmail\n yahoo\n outlook\n custom\n}\n\nenum MailboxAuthType {\n password\n microsoft_oauth\n}\n\nenum CampaignRecipe {\n grow\n flat\n randomized\n custom\n}\n\nenum CampaignStatus {\n draft\n active\n paused\n maintenance\n completed\n failed\n}\n\nmodel Workspace {\n id String @id @default(cuid())\n name String @unique\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n inboxes Inbox[]\n campaigns WarmupCampaign[]\n}\n\nenum SendJobStatus {\n pending\n processing\n completed\n failed\n}\n\nmodel SendJob {\n id String @id @default(cuid())\n campaignId String\n scheduledAt DateTime\n status SendJobStatus @default(pending)\n idempotencyKey String @unique\n processedAt DateTime?\n errorMessage String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n campaign WarmupCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)\n\n @@index([campaignId, status, scheduledAt])\n}\n\nmodel JobRunLog {\n id String @id @default(cuid())\n jobName String\n ranAt DateTime @default(now())\n success Boolean @default(true)\n message String?\n\n @@index([jobName, ranAt])\n}\n\nmodel Inbox {\n id String @id @default(cuid())\n workspaceId String\n email String\n smtpHost String\n smtpPort Int\n imapHost String\n imapPort Int\n username String\n password String\n authType MailboxAuthType @default(password)\n oauthRefreshToken String?\n role MailboxRole @default(unassigned)\n provider MailboxProvider @default(custom)\n status InboxStatus @default(active)\n dailyLimit Int @default(40)\n currentRamp Int @default(5)\n industry String @default(\"SaaS\")\n senderName String?\n companyName String?\n lastError String?\n errorAt DateTime?\n spamFolderPath String?\n lastSpamRescuedAt DateTime?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)\n sentLogs WarmupLog[] @relation(\"Sender\")\n receivedLogs WarmupLog[] @relation(\"Receiver\")\n primaryCampaigns WarmupCampaign[] @relation(\"PrimaryMailbox\")\n satelliteCampaigns CampaignSatellite[]\n\n @@unique([workspaceId, email])\n @@index([status])\n @@index([workspaceId, role])\n}\n\nmodel WarmupCampaign {\n id String @id @default(cuid())\n workspaceId String\n primaryMailboxId String\n name String\n recipe CampaignRecipe @default(grow)\n status CampaignStatus @default(draft)\n durationDays Int @default(30)\n minEmailsPerDay Int @default(1)\n maxEmailsPerDay Int @default(40)\n replyRatePercent Int @default(30)\n maintenanceVolumePercent Int @default(20)\n timezone String @default(\"America/Vancouver\")\n sendWindowStart String @default(\"08:00\")\n sendWindowEnd String @default(\"18:00\")\n customSchedule String?\n startedAt DateTime?\n endsAt DateTime?\n currentDay Int @default(0)\n pausedFromStatus CampaignStatus?\n lastSendAt DateTime?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade)\n primaryMailbox Inbox @relation(\"PrimaryMailbox\", fields: [primaryMailboxId], references: [id], onDelete: Cascade)\n satellites CampaignSatellite[]\n dailySchedules DailySchedule[]\n dailyStats DailyStat[]\n sendJobs SendJob[]\n warmupLogs WarmupLog[]\n\n @@index([workspaceId, status])\n @@index([primaryMailboxId, status])\n}\n\nmodel CampaignSatellite {\n id String @id @default(cuid())\n campaignId String\n satelliteMailboxId String\n weight Int @default(1)\n active Boolean @default(true)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n campaign WarmupCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)\n satelliteMailbox Inbox @relation(fields: [satelliteMailboxId], references: [id], onDelete: Cascade)\n\n @@unique([campaignId, satelliteMailboxId])\n @@index([satelliteMailboxId])\n}\n\nmodel DailySchedule {\n id String @id @default(cuid())\n campaignId String\n dayIndex Int\n date DateTime?\n plannedSends Int\n plannedReplies Int\n actualSends Int @default(0)\n actualReplies Int @default(0)\n spamDetected Int @default(0)\n spamRescued Int @default(0)\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n campaign WarmupCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)\n\n @@unique([campaignId, dayIndex])\n @@index([campaignId])\n}\n\nmodel DailyStat {\n id String @id @default(cuid())\n campaignId String\n date DateTime\n dayIndex Int\n plannedSends Int\n plannedReplies Int\n actualSends Int @default(0)\n actualReplies Int @default(0)\n spamDetected Int @default(0)\n spamRescued Int @default(0)\n replyRateActual Float?\n inboxPlacementRate Float?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n campaign WarmupCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade)\n\n @@unique([campaignId, date])\n @@index([campaignId])\n}\n\nmodel WarmupLog {\n id String @id @default(cuid())\n campaignId String?\n senderId String\n receiverId String\n messageId String?\n inReplyTo String?\n subject String\n body String\n status WarmupStatus @default(sent)\n placement String?\n rescuedFromSpam Boolean @default(false)\n validationResult String?\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n\n campaign WarmupCampaign? @relation(fields: [campaignId], references: [id], onDelete: SetNull)\n sender Inbox @relation(\"Sender\", fields: [senderId], references: [id], onDelete: Cascade)\n receiver Inbox @relation(\"Receiver\", fields: [receiverId], references: [id], onDelete: Cascade)\n\n @@index([senderId, status])\n @@index([receiverId, status])\n @@index([messageId])\n @@index([campaignId])\n}\n\nmodel Setting {\n id String @id @default(cuid())\n key String @unique\n value String\n createdAt DateTime @default(now())\n updatedAt DateTime @updatedAt\n}\n", + "runtimeDataModel": { + "models": {}, + "enums": {}, + "types": {} + }, + "parameterizationSchema": { + "strings": [], + "graph": "" + } +} + +config.runtimeDataModel = JSON.parse("{\"models\":{\"Workspace\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"inboxes\",\"kind\":\"object\",\"type\":\"Inbox\",\"relationName\":\"InboxToWorkspace\"},{\"name\":\"campaigns\",\"kind\":\"object\",\"type\":\"WarmupCampaign\",\"relationName\":\"WarmupCampaignToWorkspace\"}],\"dbName\":null},\"SendJob\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"campaignId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"scheduledAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"SendJobStatus\"},{\"name\":\"idempotencyKey\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"processedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"errorMessage\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"campaign\",\"kind\":\"object\",\"type\":\"WarmupCampaign\",\"relationName\":\"SendJobToWarmupCampaign\"}],\"dbName\":null},\"JobRunLog\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"jobName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"ranAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"success\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"message\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null},\"Inbox\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"workspaceId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"email\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"smtpHost\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"smtpPort\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"imapHost\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"imapPort\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"username\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"password\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"authType\",\"kind\":\"enum\",\"type\":\"MailboxAuthType\"},{\"name\":\"oauthRefreshToken\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"role\",\"kind\":\"enum\",\"type\":\"MailboxRole\"},{\"name\":\"provider\",\"kind\":\"enum\",\"type\":\"MailboxProvider\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"InboxStatus\"},{\"name\":\"dailyLimit\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"currentRamp\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"industry\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"senderName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"companyName\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastError\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"errorAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"spamFolderPath\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"lastSpamRescuedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"workspace\",\"kind\":\"object\",\"type\":\"Workspace\",\"relationName\":\"InboxToWorkspace\"},{\"name\":\"sentLogs\",\"kind\":\"object\",\"type\":\"WarmupLog\",\"relationName\":\"Sender\"},{\"name\":\"receivedLogs\",\"kind\":\"object\",\"type\":\"WarmupLog\",\"relationName\":\"Receiver\"},{\"name\":\"primaryCampaigns\",\"kind\":\"object\",\"type\":\"WarmupCampaign\",\"relationName\":\"PrimaryMailbox\"},{\"name\":\"satelliteCampaigns\",\"kind\":\"object\",\"type\":\"CampaignSatellite\",\"relationName\":\"CampaignSatelliteToInbox\"}],\"dbName\":null},\"WarmupCampaign\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"workspaceId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"primaryMailboxId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"name\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"recipe\",\"kind\":\"enum\",\"type\":\"CampaignRecipe\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"CampaignStatus\"},{\"name\":\"durationDays\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"minEmailsPerDay\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"maxEmailsPerDay\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"replyRatePercent\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"maintenanceVolumePercent\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"timezone\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sendWindowStart\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"sendWindowEnd\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"customSchedule\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"startedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"endsAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"currentDay\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"pausedFromStatus\",\"kind\":\"enum\",\"type\":\"CampaignStatus\"},{\"name\":\"lastSendAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"workspace\",\"kind\":\"object\",\"type\":\"Workspace\",\"relationName\":\"WarmupCampaignToWorkspace\"},{\"name\":\"primaryMailbox\",\"kind\":\"object\",\"type\":\"Inbox\",\"relationName\":\"PrimaryMailbox\"},{\"name\":\"satellites\",\"kind\":\"object\",\"type\":\"CampaignSatellite\",\"relationName\":\"CampaignSatelliteToWarmupCampaign\"},{\"name\":\"dailySchedules\",\"kind\":\"object\",\"type\":\"DailySchedule\",\"relationName\":\"DailyScheduleToWarmupCampaign\"},{\"name\":\"dailyStats\",\"kind\":\"object\",\"type\":\"DailyStat\",\"relationName\":\"DailyStatToWarmupCampaign\"},{\"name\":\"sendJobs\",\"kind\":\"object\",\"type\":\"SendJob\",\"relationName\":\"SendJobToWarmupCampaign\"},{\"name\":\"warmupLogs\",\"kind\":\"object\",\"type\":\"WarmupLog\",\"relationName\":\"WarmupCampaignToWarmupLog\"}],\"dbName\":null},\"CampaignSatellite\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"campaignId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"satelliteMailboxId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"weight\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"active\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"campaign\",\"kind\":\"object\",\"type\":\"WarmupCampaign\",\"relationName\":\"CampaignSatelliteToWarmupCampaign\"},{\"name\":\"satelliteMailbox\",\"kind\":\"object\",\"type\":\"Inbox\",\"relationName\":\"CampaignSatelliteToInbox\"}],\"dbName\":null},\"DailySchedule\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"campaignId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"dayIndex\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"plannedSends\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"plannedReplies\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"actualSends\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"actualReplies\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"spamDetected\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"spamRescued\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"campaign\",\"kind\":\"object\",\"type\":\"WarmupCampaign\",\"relationName\":\"DailyScheduleToWarmupCampaign\"}],\"dbName\":null},\"DailyStat\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"campaignId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"date\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"dayIndex\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"plannedSends\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"plannedReplies\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"actualSends\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"actualReplies\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"spamDetected\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"spamRescued\",\"kind\":\"scalar\",\"type\":\"Int\"},{\"name\":\"replyRateActual\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"inboxPlacementRate\",\"kind\":\"scalar\",\"type\":\"Float\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"campaign\",\"kind\":\"object\",\"type\":\"WarmupCampaign\",\"relationName\":\"DailyStatToWarmupCampaign\"}],\"dbName\":null},\"WarmupLog\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"campaignId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"senderId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"receiverId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"messageId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"inReplyTo\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"subject\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"body\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"status\",\"kind\":\"enum\",\"type\":\"WarmupStatus\"},{\"name\":\"placement\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"rescuedFromSpam\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"validationResult\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"campaign\",\"kind\":\"object\",\"type\":\"WarmupCampaign\",\"relationName\":\"WarmupCampaignToWarmupLog\"},{\"name\":\"sender\",\"kind\":\"object\",\"type\":\"Inbox\",\"relationName\":\"Sender\"},{\"name\":\"receiver\",\"kind\":\"object\",\"type\":\"Inbox\",\"relationName\":\"Receiver\"}],\"dbName\":null},\"Setting\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"key\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"value\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"createdAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"},{\"name\":\"updatedAt\",\"kind\":\"scalar\",\"type\":\"DateTime\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}") +config.parameterizationSchema = { + strings: JSON.parse("[\"where\",\"orderBy\",\"cursor\",\"workspace\",\"primaryMailbox\",\"campaign\",\"satelliteMailbox\",\"satellites\",\"dailySchedules\",\"dailyStats\",\"sendJobs\",\"warmupLogs\",\"_count\",\"sender\",\"receiver\",\"sentLogs\",\"receivedLogs\",\"primaryCampaigns\",\"satelliteCampaigns\",\"inboxes\",\"campaigns\",\"Workspace.findUnique\",\"Workspace.findUniqueOrThrow\",\"Workspace.findFirst\",\"Workspace.findFirstOrThrow\",\"Workspace.findMany\",\"data\",\"Workspace.createOne\",\"Workspace.createMany\",\"Workspace.createManyAndReturn\",\"Workspace.updateOne\",\"Workspace.updateMany\",\"Workspace.updateManyAndReturn\",\"create\",\"update\",\"Workspace.upsertOne\",\"Workspace.deleteOne\",\"Workspace.deleteMany\",\"having\",\"_min\",\"_max\",\"Workspace.groupBy\",\"Workspace.aggregate\",\"SendJob.findUnique\",\"SendJob.findUniqueOrThrow\",\"SendJob.findFirst\",\"SendJob.findFirstOrThrow\",\"SendJob.findMany\",\"SendJob.createOne\",\"SendJob.createMany\",\"SendJob.createManyAndReturn\",\"SendJob.updateOne\",\"SendJob.updateMany\",\"SendJob.updateManyAndReturn\",\"SendJob.upsertOne\",\"SendJob.deleteOne\",\"SendJob.deleteMany\",\"SendJob.groupBy\",\"SendJob.aggregate\",\"JobRunLog.findUnique\",\"JobRunLog.findUniqueOrThrow\",\"JobRunLog.findFirst\",\"JobRunLog.findFirstOrThrow\",\"JobRunLog.findMany\",\"JobRunLog.createOne\",\"JobRunLog.createMany\",\"JobRunLog.createManyAndReturn\",\"JobRunLog.updateOne\",\"JobRunLog.updateMany\",\"JobRunLog.updateManyAndReturn\",\"JobRunLog.upsertOne\",\"JobRunLog.deleteOne\",\"JobRunLog.deleteMany\",\"JobRunLog.groupBy\",\"JobRunLog.aggregate\",\"Inbox.findUnique\",\"Inbox.findUniqueOrThrow\",\"Inbox.findFirst\",\"Inbox.findFirstOrThrow\",\"Inbox.findMany\",\"Inbox.createOne\",\"Inbox.createMany\",\"Inbox.createManyAndReturn\",\"Inbox.updateOne\",\"Inbox.updateMany\",\"Inbox.updateManyAndReturn\",\"Inbox.upsertOne\",\"Inbox.deleteOne\",\"Inbox.deleteMany\",\"_avg\",\"_sum\",\"Inbox.groupBy\",\"Inbox.aggregate\",\"WarmupCampaign.findUnique\",\"WarmupCampaign.findUniqueOrThrow\",\"WarmupCampaign.findFirst\",\"WarmupCampaign.findFirstOrThrow\",\"WarmupCampaign.findMany\",\"WarmupCampaign.createOne\",\"WarmupCampaign.createMany\",\"WarmupCampaign.createManyAndReturn\",\"WarmupCampaign.updateOne\",\"WarmupCampaign.updateMany\",\"WarmupCampaign.updateManyAndReturn\",\"WarmupCampaign.upsertOne\",\"WarmupCampaign.deleteOne\",\"WarmupCampaign.deleteMany\",\"WarmupCampaign.groupBy\",\"WarmupCampaign.aggregate\",\"CampaignSatellite.findUnique\",\"CampaignSatellite.findUniqueOrThrow\",\"CampaignSatellite.findFirst\",\"CampaignSatellite.findFirstOrThrow\",\"CampaignSatellite.findMany\",\"CampaignSatellite.createOne\",\"CampaignSatellite.createMany\",\"CampaignSatellite.createManyAndReturn\",\"CampaignSatellite.updateOne\",\"CampaignSatellite.updateMany\",\"CampaignSatellite.updateManyAndReturn\",\"CampaignSatellite.upsertOne\",\"CampaignSatellite.deleteOne\",\"CampaignSatellite.deleteMany\",\"CampaignSatellite.groupBy\",\"CampaignSatellite.aggregate\",\"DailySchedule.findUnique\",\"DailySchedule.findUniqueOrThrow\",\"DailySchedule.findFirst\",\"DailySchedule.findFirstOrThrow\",\"DailySchedule.findMany\",\"DailySchedule.createOne\",\"DailySchedule.createMany\",\"DailySchedule.createManyAndReturn\",\"DailySchedule.updateOne\",\"DailySchedule.updateMany\",\"DailySchedule.updateManyAndReturn\",\"DailySchedule.upsertOne\",\"DailySchedule.deleteOne\",\"DailySchedule.deleteMany\",\"DailySchedule.groupBy\",\"DailySchedule.aggregate\",\"DailyStat.findUnique\",\"DailyStat.findUniqueOrThrow\",\"DailyStat.findFirst\",\"DailyStat.findFirstOrThrow\",\"DailyStat.findMany\",\"DailyStat.createOne\",\"DailyStat.createMany\",\"DailyStat.createManyAndReturn\",\"DailyStat.updateOne\",\"DailyStat.updateMany\",\"DailyStat.updateManyAndReturn\",\"DailyStat.upsertOne\",\"DailyStat.deleteOne\",\"DailyStat.deleteMany\",\"DailyStat.groupBy\",\"DailyStat.aggregate\",\"WarmupLog.findUnique\",\"WarmupLog.findUniqueOrThrow\",\"WarmupLog.findFirst\",\"WarmupLog.findFirstOrThrow\",\"WarmupLog.findMany\",\"WarmupLog.createOne\",\"WarmupLog.createMany\",\"WarmupLog.createManyAndReturn\",\"WarmupLog.updateOne\",\"WarmupLog.updateMany\",\"WarmupLog.updateManyAndReturn\",\"WarmupLog.upsertOne\",\"WarmupLog.deleteOne\",\"WarmupLog.deleteMany\",\"WarmupLog.groupBy\",\"WarmupLog.aggregate\",\"Setting.findUnique\",\"Setting.findUniqueOrThrow\",\"Setting.findFirst\",\"Setting.findFirstOrThrow\",\"Setting.findMany\",\"Setting.createOne\",\"Setting.createMany\",\"Setting.createManyAndReturn\",\"Setting.updateOne\",\"Setting.updateMany\",\"Setting.updateManyAndReturn\",\"Setting.upsertOne\",\"Setting.deleteOne\",\"Setting.deleteMany\",\"Setting.groupBy\",\"Setting.aggregate\",\"AND\",\"OR\",\"NOT\",\"id\",\"key\",\"value\",\"createdAt\",\"updatedAt\",\"equals\",\"in\",\"notIn\",\"lt\",\"lte\",\"gt\",\"gte\",\"not\",\"contains\",\"startsWith\",\"endsWith\",\"campaignId\",\"senderId\",\"receiverId\",\"messageId\",\"inReplyTo\",\"subject\",\"body\",\"WarmupStatus\",\"status\",\"placement\",\"rescuedFromSpam\",\"validationResult\",\"date\",\"dayIndex\",\"plannedSends\",\"plannedReplies\",\"actualSends\",\"actualReplies\",\"spamDetected\",\"spamRescued\",\"replyRateActual\",\"inboxPlacementRate\",\"satelliteMailboxId\",\"weight\",\"active\",\"workspaceId\",\"primaryMailboxId\",\"name\",\"CampaignRecipe\",\"recipe\",\"CampaignStatus\",\"durationDays\",\"minEmailsPerDay\",\"maxEmailsPerDay\",\"replyRatePercent\",\"maintenanceVolumePercent\",\"timezone\",\"sendWindowStart\",\"sendWindowEnd\",\"customSchedule\",\"startedAt\",\"endsAt\",\"currentDay\",\"pausedFromStatus\",\"lastSendAt\",\"email\",\"smtpHost\",\"smtpPort\",\"imapHost\",\"imapPort\",\"username\",\"password\",\"MailboxAuthType\",\"authType\",\"oauthRefreshToken\",\"MailboxRole\",\"role\",\"MailboxProvider\",\"provider\",\"InboxStatus\",\"dailyLimit\",\"currentRamp\",\"industry\",\"senderName\",\"companyName\",\"lastError\",\"errorAt\",\"spamFolderPath\",\"lastSpamRescuedAt\",\"jobName\",\"ranAt\",\"success\",\"message\",\"scheduledAt\",\"SendJobStatus\",\"idempotencyKey\",\"processedAt\",\"errorMessage\",\"every\",\"some\",\"none\",\"campaignId_date\",\"campaignId_dayIndex\",\"campaignId_satelliteMailboxId\",\"workspaceId_email\",\"is\",\"isNot\",\"connectOrCreate\",\"upsert\",\"createMany\",\"set\",\"disconnect\",\"delete\",\"connect\",\"updateMany\",\"deleteMany\",\"increment\",\"decrement\",\"multiply\",\"divide\"]"), + graph: "oQVeoAEJEwAA1wIAIBQAANgCACC9AQAA1gIAML4BAAAwABC_AQAA1gIAMMABAQAAAAHDAUAAngIAIcQBQACeAgAh6wEBAAAAAQEAAAABACAhAwAA3wIAIA8AAOUCACAQAADlAgAgEQAA2AIAIBIAAOECACC9AQAA9AIAML4BAAADABC_AQAA9AIAMMABAQCdAgAhwwFAAJ4CACHEAUAAngIAIdgBAAD4AowCIukBAQCdAgAh_QEBAJ0CACH-AQEAnQIAIf8BAgDcAgAhgAIBAJ0CACGBAgIA3AIAIYICAQCdAgAhgwIBAJ0CACGFAgAA9QKFAiKGAgEA0AIAIYgCAAD2AogCIooCAAD3AooCIowCAgDcAgAhjQICANwCACGOAgEAnQIAIY8CAQDQAgAhkAIBANACACGRAgEA0AIAIZICQADdAgAhkwIBANACACGUAkAA3QIAIQwDAADYBAAgDwAA3gQAIBAAAN4EACARAADXBAAgEgAA2gQAIIYCAAD-AgAgjwIAAP4CACCQAgAA_gIAIJECAAD-AgAgkgIAAP4CACCTAgAA_gIAIJQCAAD-AgAgIgMAAN8CACAPAADlAgAgEAAA5QIAIBEAANgCACASAADhAgAgvQEAAPQCADC-AQAAAwAQvwEAAPQCADDAAQEAAAABwwFAAJ4CACHEAUAAngIAIdgBAAD4AowCIukBAQCdAgAh_QEBAJ0CACH-AQEAnQIAIf8BAgDcAgAhgAIBAJ0CACGBAgIA3AIAIYICAQCdAgAhgwIBAJ0CACGFAgAA9QKFAiKGAgEA0AIAIYgCAAD2AogCIooCAAD3AooCIowCAgDcAgAhjQICANwCACGOAgEAnQIAIY8CAQDQAgAhkAIBANACACGRAgEA0AIAIZICQADdAgAhkwIBANACACGUAkAA3QIAIaQCAADzAgAgAwAAAAMAIAEAAAQAMAIAAAUAIBQFAADyAgAgDQAA4AIAIA4AAOACACC9AQAA8AIAML4BAAAHABC_AQAA8AIAMMABAQCdAgAhwwFAAJ4CACHEAUAAngIAIdABAQDQAgAh0QEBAJ0CACHSAQEAnQIAIdMBAQDQAgAh1AEBANACACHVAQEAnQIAIdYBAQCdAgAh2AEAAPEC2AEi2QEBANACACHaASAAzwIAIdsBAQDQAgAhCAUAAN8EACANAADZBAAgDgAA2QQAINABAAD-AgAg0wEAAP4CACDUAQAA_gIAINkBAAD-AgAg2wEAAP4CACAUBQAA8gIAIA0AAOACACAOAADgAgAgvQEAAPACADC-AQAABwAQvwEAAPACADDAAQEAAAABwwFAAJ4CACHEAUAAngIAIdABAQDQAgAh0QEBAJ0CACHSAQEAnQIAIdMBAQDQAgAh1AEBANACACHVAQEAnQIAIdYBAQCdAgAh2AEAAPEC2AEi2QEBANACACHaASAAzwIAIdsBAQDQAgAhAwAAAAcAIAEAAAgAMAIAAAkAICADAADfAgAgBAAA4AIAIAcAAOECACAIAADiAgAgCQAA4wIAIAoAAOQCACALAADlAgAgvQEAANkCADC-AQAACwAQvwEAANkCADDAAQEAnQIAIcMBQACeAgAhxAFAAJ4CACHYAQAA2wLvASLpAQEAnQIAIeoBAQCdAgAh6wEBAJ0CACHtAQAA2gLtASLvAQIA3AIAIfABAgDcAgAh8QECANwCACHyAQIA3AIAIfMBAgDcAgAh9AEBAJ0CACH1AQEAnQIAIfYBAQCdAgAh9wEBANACACH4AUAA3QIAIfkBQADdAgAh-gECANwCACH7AQAA3gLvASP8AUAA3QIAIQEAAAALACAMBQAA6AIAIAYAAOACACC9AQAA7wIAML4BAAANABC_AQAA7wIAMMABAQCdAgAhwwFAAJ4CACHEAUAAngIAIdABAQCdAgAh5gEBAJ0CACHnAQIA3AIAIegBIADPAgAhAgUAAN8EACAGAADZBAAgDQUAAOgCACAGAADgAgAgvQEAAO8CADC-AQAADQAQvwEAAO8CADDAAQEAAAABwwFAAJ4CACHEAUAAngIAIdABAQCdAgAh5gEBAJ0CACHnAQIA3AIAIegBIADPAgAhowIAAO4CACADAAAADQAgAQAADgAwAgAADwAgEAUAAOgCACC9AQAA7QIAML4BAAARABC_AQAA7QIAMMABAQCdAgAhwwFAAJ4CACHEAUAAngIAIdABAQCdAgAh3AFAAN0CACHdAQIA3AIAId4BAgDcAgAh3wECANwCACHgAQIA3AIAIeEBAgDcAgAh4gECANwCACHjAQIA3AIAIQIFAADfBAAg3AEAAP4CACARBQAA6AIAIL0BAADtAgAwvgEAABEAEL8BAADtAgAwwAEBAAAAAcMBQACeAgAhxAFAAJ4CACHQAQEAnQIAIdwBQADdAgAh3QECANwCACHeAQIA3AIAId8BAgDcAgAh4AECANwCACHhAQIA3AIAIeIBAgDcAgAh4wECANwCACGiAgAA7AIAIAMAAAARACABAAASADACAAATACASBQAA6AIAIL0BAADqAgAwvgEAABUAEL8BAADqAgAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh0AEBAJ0CACHcAUAAngIAId0BAgDcAgAh3gECANwCACHfAQIA3AIAIeABAgDcAgAh4QECANwCACHiAQIA3AIAIeMBAgDcAgAh5AEIAOsCACHlAQgA6wIAIQMFAADfBAAg5AEAAP4CACDlAQAA_gIAIBMFAADoAgAgvQEAAOoCADC-AQAAFQAQvwEAAOoCADDAAQEAAAABwwFAAJ4CACHEAUAAngIAIdABAQCdAgAh3AFAAJ4CACHdAQIA3AIAId4BAgDcAgAh3wECANwCACHgAQIA3AIAIeEBAgDcAgAh4gECANwCACHjAQIA3AIAIeQBCADrAgAh5QEIAOsCACGhAgAA6QIAIAMAAAAVACABAAAWADACAAAXACANBQAA6AIAIL0BAADmAgAwvgEAABkAEL8BAADmAgAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh0AEBAJ0CACHYAQAA5wKbAiKZAkAAngIAIZsCAQCdAgAhnAJAAN0CACGdAgEA0AIAIQMFAADfBAAgnAIAAP4CACCdAgAA_gIAIA0FAADoAgAgvQEAAOYCADC-AQAAGQAQvwEAAOYCADDAAQEAAAABwwFAAJ4CACHEAUAAngIAIdABAQCdAgAh2AEAAOcCmwIimQJAAJ4CACGbAgEAAAABnAJAAN0CACGdAgEA0AIAIQMAAAAZACABAAAaADACAAAbACADAAAABwAgAQAACAAwAgAACQAgAQAAAA0AIAEAAAARACABAAAAFQAgAQAAABkAIAEAAAAHACADAAAABwAgAQAACAAwAgAACQAgDAMAANgEACAEAADZBAAgBwAA2gQAIAgAANsEACAJAADcBAAgCgAA3QQAIAsAAN4EACD3AQAA_gIAIPgBAAD-AgAg-QEAAP4CACD7AQAA_gIAIPwBAAD-AgAgIAMAAN8CACAEAADgAgAgBwAA4QIAIAgAAOICACAJAADjAgAgCgAA5AIAIAsAAOUCACC9AQAA2QIAML4BAAALABC_AQAA2QIAMMABAQAAAAHDAUAAngIAIcQBQACeAgAh2AEAANsC7wEi6QEBAJ0CACHqAQEAnQIAIesBAQCdAgAh7QEAANoC7QEi7wECANwCACHwAQIA3AIAIfEBAgDcAgAh8gECANwCACHzAQIA3AIAIfQBAQCdAgAh9QEBAJ0CACH2AQEAnQIAIfcBAQDQAgAh-AFAAN0CACH5AUAA3QIAIfoBAgDcAgAh-wEAAN4C7wEj_AFAAN0CACEDAAAACwAgAQAAJAAwAgAAJQAgAwAAAA0AIAEAAA4AMAIAAA8AIAEAAAAHACABAAAABwAgAQAAAAsAIAEAAAANACADAAAACwAgAQAAJAAwAgAAJQAgAQAAAAMAIAEAAAALACABAAAAAQAgCRMAANcCACAUAADYAgAgvQEAANYCADC-AQAAMAAQvwEAANYCADDAAQEAnQIAIcMBQACeAgAhxAFAAJ4CACHrAQEAnQIAIQITAADWBAAgFAAA1wQAIAMAAAAwACABAAAxADACAAABACADAAAAMAAgAQAAMQAwAgAAAQAgAwAAADAAIAEAADEAMAIAAAEAIAYTAADUBAAgFAAA1QQAIMABAQAAAAHDAUAAAAABxAFAAAAAAesBAQAAAAEBGgAANQAgBMABAQAAAAHDAUAAAAABxAFAAAAAAesBAQAAAAEBGgAANwAwARoAADcAMAYTAAC9BAAgFAAAvgQAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIesBAQD8AgAhAgAAAAEAIBoAADoAIATAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHrAQEA_AIAIQIAAAAwACAaAAA8ACACAAAAMAAgGgAAPAAgAwAAAAEAICEAADUAICIAADoAIAEAAAABACABAAAAMAAgAwwAALoEACAnAAC8BAAgKAAAuwQAIAe9AQAA1QIAML4BAABDABC_AQAA1QIAMMABAQCVAgAhwwFAAJYCACHEAUAAlgIAIesBAQCVAgAhAwAAADAAIAEAAEIAMCYAAEMAIAMAAAAwACABAAAxADACAAABACABAAAAGwAgAQAAABsAIAMAAAAZACABAAAaADACAAAbACADAAAAGQAgAQAAGgAwAgAAGwAgAwAAABkAIAEAABoAMAIAABsAIAoFAAC5BAAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB0AEBAAAAAdgBAAAAmwICmQJAAAAAAZsCAQAAAAGcAkAAAAABnQIBAAAAAQEaAABLACAJwAEBAAAAAcMBQAAAAAHEAUAAAAAB0AEBAAAAAdgBAAAAmwICmQJAAAAAAZsCAQAAAAGcAkAAAAABnQIBAAAAAQEaAABNADABGgAATQAwCgUAALgEACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHQAQEA_AIAIdgBAADKA5sCIpkCQAD9AgAhmwIBAPwCACGcAkAAmQMAIZ0CAQCCAwAhAgAAABsAIBoAAFAAIAnAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHQAQEA_AIAIdgBAADKA5sCIpkCQAD9AgAhmwIBAPwCACGcAkAAmQMAIZ0CAQCCAwAhAgAAABkAIBoAAFIAIAIAAAAZACAaAABSACADAAAAGwAgIQAASwAgIgAAUAAgAQAAABsAIAEAAAAZACAFDAAAtQQAICcAALcEACAoAAC2BAAgnAIAAP4CACCdAgAA_gIAIAy9AQAA0QIAML4BAABZABC_AQAA0QIAMMABAQCVAgAhwwFAAJYCACHEAUAAlgIAIdABAQCVAgAh2AEAANICmwIimQJAAJYCACGbAgEAlQIAIZwCQACyAgAhnQIBAKACACEDAAAAGQAgAQAAWAAwJgAAWQAgAwAAABkAIAEAABoAMAIAABsAIAi9AQAAzgIAML4BAABfABC_AQAAzgIAMMABAQAAAAGVAgEAnQIAIZYCQACeAgAhlwIgAM8CACGYAgEA0AIAIQEAAABcACABAAAAXAAgCL0BAADOAgAwvgEAAF8AEL8BAADOAgAwwAEBAJ0CACGVAgEAnQIAIZYCQACeAgAhlwIgAM8CACGYAgEA0AIAIQGYAgAA_gIAIAMAAABfACABAABgADACAABcACADAAAAXwAgAQAAYAAwAgAAXAAgAwAAAF8AIAEAAGAAMAIAAFwAIAXAAQEAAAABlQIBAAAAAZYCQAAAAAGXAiAAAAABmAIBAAAAAQEaAABkACAFwAEBAAAAAZUCAQAAAAGWAkAAAAABlwIgAAAAAZgCAQAAAAEBGgAAZgAwARoAAGYAMAXAAQEA_AIAIZUCAQD8AgAhlgJAAP0CACGXAiAAhAMAIZgCAQCCAwAhAgAAAFwAIBoAAGkAIAXAAQEA_AIAIZUCAQD8AgAhlgJAAP0CACGXAiAAhAMAIZgCAQCCAwAhAgAAAF8AIBoAAGsAIAIAAABfACAaAABrACADAAAAXAAgIQAAZAAgIgAAaQAgAQAAAFwAIAEAAABfACAEDAAAsgQAICcAALQEACAoAACzBAAgmAIAAP4CACAIvQEAAM0CADC-AQAAcgAQvwEAAM0CADDAAQEAlQIAIZUCAQCVAgAhlgJAAJYCACGXAiAAogIAIZgCAQCgAgAhAwAAAF8AIAEAAHEAMCYAAHIAIAMAAABfACABAABgADACAABcACABAAAABQAgAQAAAAUAIAMAAAADACABAAAEADACAAAFACADAAAAAwAgAQAABAAwAgAABQAgAwAAAAMAIAEAAAQAMAIAAAUAIB4DAACtBAAgDwAArgQAIBAAAK8EACARAACwBAAgEgAAsQQAIMABAQAAAAHDAUAAAAABxAFAAAAAAdgBAAAAjAIC6QEBAAAAAf0BAQAAAAH-AQEAAAAB_wECAAAAAYACAQAAAAGBAgIAAAABggIBAAAAAYMCAQAAAAGFAgAAAIUCAoYCAQAAAAGIAgAAAIgCAooCAAAAigICjAICAAAAAY0CAgAAAAGOAgEAAAABjwIBAAAAAZACAQAAAAGRAgEAAAABkgJAAAAAAZMCAQAAAAGUAkAAAAABARoAAHoAIBnAAQEAAAABwwFAAAAAAcQBQAAAAAHYAQAAAIwCAukBAQAAAAH9AQEAAAAB_gEBAAAAAf8BAgAAAAGAAgEAAAABgQICAAAAAYICAQAAAAGDAgEAAAABhQIAAACFAgKGAgEAAAABiAIAAACIAgKKAgAAAIoCAowCAgAAAAGNAgIAAAABjgIBAAAAAY8CAQAAAAGQAgEAAAABkQIBAAAAAZICQAAAAAGTAgEAAAABlAJAAAAAAQEaAAB8ADABGgAAfAAwHgMAAIEEACAPAACCBAAgEAAAgwQAIBEAAIQEACASAACFBAAgwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh2AEAAIAEjAIi6QEBAPwCACH9AQEA_AIAIf4BAQD8AgAh_wECAJADACGAAgEA_AIAIYECAgCQAwAhggIBAPwCACGDAgEA_AIAIYUCAAD9A4UCIoYCAQCCAwAhiAIAAP4DiAIiigIAAP8DigIijAICAJADACGNAgIAkAMAIY4CAQD8AgAhjwIBAIIDACGQAgEAggMAIZECAQCCAwAhkgJAAJkDACGTAgEAggMAIZQCQACZAwAhAgAAAAUAIBoAAH8AIBnAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHYAQAAgASMAiLpAQEA_AIAIf0BAQD8AgAh_gEBAPwCACH_AQIAkAMAIYACAQD8AgAhgQICAJADACGCAgEA_AIAIYMCAQD8AgAhhQIAAP0DhQIihgIBAIIDACGIAgAA_gOIAiKKAgAA_wOKAiKMAgIAkAMAIY0CAgCQAwAhjgIBAPwCACGPAgEAggMAIZACAQCCAwAhkQIBAIIDACGSAkAAmQMAIZMCAQCCAwAhlAJAAJkDACECAAAAAwAgGgAAgQEAIAIAAAADACAaAACBAQAgAwAAAAUAICEAAHoAICIAAH8AIAEAAAAFACABAAAAAwAgDAwAAPgDACAnAAD7AwAgKAAA-gMAIFkAAPkDACBaAAD8AwAghgIAAP4CACCPAgAA_gIAIJACAAD-AgAgkQIAAP4CACCSAgAA_gIAIJMCAAD-AgAglAIAAP4CACAcvQEAAMACADC-AQAAiAEAEL8BAADAAgAwwAEBAJUCACHDAUAAlgIAIcQBQACWAgAh2AEAAMQCjAIi6QEBAJUCACH9AQEAlQIAIf4BAQCVAgAh_wECAKsCACGAAgEAlQIAIYECAgCrAgAhggIBAJUCACGDAgEAlQIAIYUCAADBAoUCIoYCAQCgAgAhiAIAAMICiAIiigIAAMMCigIijAICAKsCACGNAgIAqwIAIY4CAQCVAgAhjwIBAKACACGQAgEAoAIAIZECAQCgAgAhkgJAALICACGTAgEAoAIAIZQCQACyAgAhAwAAAAMAIAEAAIcBADAmAACIAQAgAwAAAAMAIAEAAAQAMAIAAAUAIAEAAAAlACABAAAAJQAgAwAAAAsAIAEAACQAMAIAACUAIAMAAAALACABAAAkADACAAAlACADAAAACwAgAQAAJAAwAgAAJQAgHQMAAPEDACAEAADyAwAgBwAA8wMAIAgAAPQDACAJAAD1AwAgCgAA9gMAIAsAAPcDACDAAQEAAAABwwFAAAAAAcQBQAAAAAHYAQAAAO8BAukBAQAAAAHqAQEAAAAB6wEBAAAAAe0BAAAA7QEC7wECAAAAAfABAgAAAAHxAQIAAAAB8gECAAAAAfMBAgAAAAH0AQEAAAAB9QEBAAAAAfYBAQAAAAH3AQEAAAAB-AFAAAAAAfkBQAAAAAH6AQIAAAAB-wEAAADvAQP8AUAAAAABARoAAJABACAWwAEBAAAAAcMBQAAAAAHEAUAAAAAB2AEAAADvAQLpAQEAAAAB6gEBAAAAAesBAQAAAAHtAQAAAO0BAu8BAgAAAAHwAQIAAAAB8QECAAAAAfIBAgAAAAHzAQIAAAAB9AEBAAAAAfUBAQAAAAH2AQEAAAAB9wEBAAAAAfgBQAAAAAH5AUAAAAAB-gECAAAAAfsBAAAA7wED_AFAAAAAAQEaAACSAQAwARoAAJIBADAdAwAArQMAIAQAAK4DACAHAACvAwAgCAAAsAMAIAkAALEDACAKAACyAwAgCwAAswMAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdgBAACrA-8BIukBAQD8AgAh6gEBAPwCACHrAQEA_AIAIe0BAACqA-0BIu8BAgCQAwAh8AECAJADACHxAQIAkAMAIfIBAgCQAwAh8wECAJADACH0AQEA_AIAIfUBAQD8AgAh9gEBAPwCACH3AQEAggMAIfgBQACZAwAh-QFAAJkDACH6AQIAkAMAIfsBAACsA-8BI_wBQACZAwAhAgAAACUAIBoAAJUBACAWwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh2AEAAKsD7wEi6QEBAPwCACHqAQEA_AIAIesBAQD8AgAh7QEAAKoD7QEi7wECAJADACHwAQIAkAMAIfEBAgCQAwAh8gECAJADACHzAQIAkAMAIfQBAQD8AgAh9QEBAPwCACH2AQEA_AIAIfcBAQCCAwAh-AFAAJkDACH5AUAAmQMAIfoBAgCQAwAh-wEAAKwD7wEj_AFAAJkDACECAAAACwAgGgAAlwEAIAIAAAALACAaAACXAQAgAwAAACUAICEAAJABACAiAACVAQAgAQAAACUAIAEAAAALACAKDAAApQMAICcAAKgDACAoAACnAwAgWQAApgMAIFoAAKkDACD3AQAA_gIAIPgBAAD-AgAg-QEAAP4CACD7AQAA_gIAIPwBAAD-AgAgGb0BAAC2AgAwvgEAAJ4BABC_AQAAtgIAMMABAQCVAgAhwwFAAJYCACHEAUAAlgIAIdgBAAC4Au8BIukBAQCVAgAh6gEBAJUCACHrAQEAlQIAIe0BAAC3Au0BIu8BAgCrAgAh8AECAKsCACHxAQIAqwIAIfIBAgCrAgAh8wECAKsCACH0AQEAlQIAIfUBAQCVAgAh9gEBAJUCACH3AQEAoAIAIfgBQACyAgAh-QFAALICACH6AQIAqwIAIfsBAAC5Au8BI_wBQACyAgAhAwAAAAsAIAEAAJ0BADAmAACeAQAgAwAAAAsAIAEAACQAMAIAACUAIAEAAAAPACABAAAADwAgAwAAAA0AIAEAAA4AMAIAAA8AIAMAAAANACABAAAOADACAAAPACADAAAADQAgAQAADgAwAgAADwAgCQUAAKMDACAGAACkAwAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB0AEBAAAAAeYBAQAAAAHnAQIAAAAB6AEgAAAAAQEaAACmAQAgB8ABAQAAAAHDAUAAAAABxAFAAAAAAdABAQAAAAHmAQEAAAAB5wECAAAAAegBIAAAAAEBGgAAqAEAMAEaAACoAQAwCQUAAKEDACAGAACiAwAgwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh0AEBAPwCACHmAQEA_AIAIecBAgCQAwAh6AEgAIQDACECAAAADwAgGgAAqwEAIAfAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHQAQEA_AIAIeYBAQD8AgAh5wECAJADACHoASAAhAMAIQIAAAANACAaAACtAQAgAgAAAA0AIBoAAK0BACADAAAADwAgIQAApgEAICIAAKsBACABAAAADwAgAQAAAA0AIAUMAACcAwAgJwAAnwMAICgAAJ4DACBZAACdAwAgWgAAoAMAIAq9AQAAtQIAML4BAAC0AQAQvwEAALUCADDAAQEAlQIAIcMBQACWAgAhxAFAAJYCACHQAQEAlQIAIeYBAQCVAgAh5wECAKsCACHoASAAogIAIQMAAAANACABAACzAQAwJgAAtAEAIAMAAAANACABAAAOADACAAAPACABAAAAEwAgAQAAABMAIAMAAAARACABAAASADACAAATACADAAAAEQAgAQAAEgAwAgAAEwAgAwAAABEAIAEAABIAMAIAABMAIA0FAACbAwAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB0AEBAAAAAdwBQAAAAAHdAQIAAAAB3gECAAAAAd8BAgAAAAHgAQIAAAAB4QECAAAAAeIBAgAAAAHjAQIAAAABARoAALwBACAMwAEBAAAAAcMBQAAAAAHEAUAAAAAB0AEBAAAAAdwBQAAAAAHdAQIAAAAB3gECAAAAAd8BAgAAAAHgAQIAAAAB4QECAAAAAeIBAgAAAAHjAQIAAAABARoAAL4BADABGgAAvgEAMA0FAACaAwAgwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh0AEBAPwCACHcAUAAmQMAId0BAgCQAwAh3gECAJADACHfAQIAkAMAIeABAgCQAwAh4QECAJADACHiAQIAkAMAIeMBAgCQAwAhAgAAABMAIBoAAMEBACAMwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh0AEBAPwCACHcAUAAmQMAId0BAgCQAwAh3gECAJADACHfAQIAkAMAIeABAgCQAwAh4QECAJADACHiAQIAkAMAIeMBAgCQAwAhAgAAABEAIBoAAMMBACACAAAAEQAgGgAAwwEAIAMAAAATACAhAAC8AQAgIgAAwQEAIAEAAAATACABAAAAEQAgBgwAAJQDACAnAACXAwAgKAAAlgMAIFkAAJUDACBaAACYAwAg3AEAAP4CACAPvQEAALECADC-AQAAygEAEL8BAACxAgAwwAEBAJUCACHDAUAAlgIAIcQBQACWAgAh0AEBAJUCACHcAUAAsgIAId0BAgCrAgAh3gECAKsCACHfAQIAqwIAIeABAgCrAgAh4QECAKsCACHiAQIAqwIAIeMBAgCrAgAhAwAAABEAIAEAAMkBADAmAADKAQAgAwAAABEAIAEAABIAMAIAABMAIAEAAAAXACABAAAAFwAgAwAAABUAIAEAABYAMAIAABcAIAMAAAAVACABAAAWADACAAAXACADAAAAFQAgAQAAFgAwAgAAFwAgDwUAAJMDACDAAQEAAAABwwFAAAAAAcQBQAAAAAHQAQEAAAAB3AFAAAAAAd0BAgAAAAHeAQIAAAAB3wECAAAAAeABAgAAAAHhAQIAAAAB4gECAAAAAeMBAgAAAAHkAQgAAAAB5QEIAAAAAQEaAADSAQAgDsABAQAAAAHDAUAAAAABxAFAAAAAAdABAQAAAAHcAUAAAAAB3QECAAAAAd4BAgAAAAHfAQIAAAAB4AECAAAAAeEBAgAAAAHiAQIAAAAB4wECAAAAAeQBCAAAAAHlAQgAAAABARoAANQBADABGgAA1AEAMA8FAACSAwAgwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh0AEBAPwCACHcAUAA_QIAId0BAgCQAwAh3gECAJADACHfAQIAkAMAIeABAgCQAwAh4QECAJADACHiAQIAkAMAIeMBAgCQAwAh5AEIAJEDACHlAQgAkQMAIQIAAAAXACAaAADXAQAgDsABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdABAQD8AgAh3AFAAP0CACHdAQIAkAMAId4BAgCQAwAh3wECAJADACHgAQIAkAMAIeEBAgCQAwAh4gECAJADACHjAQIAkAMAIeQBCACRAwAh5QEIAJEDACECAAAAFQAgGgAA2QEAIAIAAAAVACAaAADZAQAgAwAAABcAICEAANIBACAiAADXAQAgAQAAABcAIAEAAAAVACAHDAAAiwMAICcAAI4DACAoAACNAwAgWQAAjAMAIFoAAI8DACDkAQAA_gIAIOUBAAD-AgAgEb0BAACqAgAwvgEAAOABABC_AQAAqgIAMMABAQCVAgAhwwFAAJYCACHEAUAAlgIAIdABAQCVAgAh3AFAAJYCACHdAQIAqwIAId4BAgCrAgAh3wECAKsCACHgAQIAqwIAIeEBAgCrAgAh4gECAKsCACHjAQIAqwIAIeQBCACsAgAh5QEIAKwCACEDAAAAFQAgAQAA3wEAMCYAAOABACADAAAAFQAgAQAAFgAwAgAAFwAgAQAAAAkAIAEAAAAJACADAAAABwAgAQAACAAwAgAACQAgAwAAAAcAIAEAAAgAMAIAAAkAIAMAAAAHACABAAAIADACAAAJACARBQAAiAMAIA0AAIkDACAOAACKAwAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB0AEBAAAAAdEBAQAAAAHSAQEAAAAB0wEBAAAAAdQBAQAAAAHVAQEAAAAB1gEBAAAAAdgBAAAA2AEC2QEBAAAAAdoBIAAAAAHbAQEAAAABARoAAOgBACAOwAEBAAAAAcMBQAAAAAHEAUAAAAAB0AEBAAAAAdEBAQAAAAHSAQEAAAAB0wEBAAAAAdQBAQAAAAHVAQEAAAAB1gEBAAAAAdgBAAAA2AEC2QEBAAAAAdoBIAAAAAHbAQEAAAABARoAAOoBADABGgAA6gEAMAEAAAALACARBQAAhQMAIA0AAIYDACAOAACHAwAgwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh0AEBAIIDACHRAQEA_AIAIdIBAQD8AgAh0wEBAIIDACHUAQEAggMAIdUBAQD8AgAh1gEBAPwCACHYAQAAgwPYASLZAQEAggMAIdoBIACEAwAh2wEBAIIDACECAAAACQAgGgAA7gEAIA7AAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHQAQEAggMAIdEBAQD8AgAh0gEBAPwCACHTAQEAggMAIdQBAQCCAwAh1QEBAPwCACHWAQEA_AIAIdgBAACDA9gBItkBAQCCAwAh2gEgAIQDACHbAQEAggMAIQIAAAAHACAaAADwAQAgAgAAAAcAIBoAAPABACABAAAACwAgAwAAAAkAICEAAOgBACAiAADuAQAgAQAAAAkAIAEAAAAHACAIDAAA_wIAICcAAIEDACAoAACAAwAg0AEAAP4CACDTAQAA_gIAINQBAAD-AgAg2QEAAP4CACDbAQAA_gIAIBG9AQAAnwIAML4BAAD4AQAQvwEAAJ8CADDAAQEAlQIAIcMBQACWAgAhxAFAAJYCACHQAQEAoAIAIdEBAQCVAgAh0gEBAJUCACHTAQEAoAIAIdQBAQCgAgAh1QEBAJUCACHWAQEAlQIAIdgBAAChAtgBItkBAQCgAgAh2gEgAKICACHbAQEAoAIAIQMAAAAHACABAAD3AQAwJgAA-AEAIAMAAAAHACABAAAIADACAAAJACAIvQEAAJwCADC-AQAA_gEAEL8BAACcAgAwwAEBAAAAAcEBAQAAAAHCAQEAnQIAIcMBQACeAgAhxAFAAJ4CACEBAAAA-wEAIAEAAAD7AQAgCL0BAACcAgAwvgEAAP4BABC_AQAAnAIAMMABAQCdAgAhwQEBAJ0CACHCAQEAnQIAIcMBQACeAgAhxAFAAJ4CACEAAwAAAP4BACABAAD_AQAwAgAA-wEAIAMAAAD-AQAgAQAA_wEAMAIAAPsBACADAAAA_gEAIAEAAP8BADACAAD7AQAgBcABAQAAAAHBAQEAAAABwgEBAAAAAcMBQAAAAAHEAUAAAAABARoAAIMCACAFwAEBAAAAAcEBAQAAAAHCAQEAAAABwwFAAAAAAcQBQAAAAAEBGgAAhQIAMAEaAACFAgAwBcABAQD8AgAhwQEBAPwCACHCAQEA_AIAIcMBQAD9AgAhxAFAAP0CACECAAAA-wEAIBoAAIgCACAFwAEBAPwCACHBAQEA_AIAIcIBAQD8AgAhwwFAAP0CACHEAUAA_QIAIQIAAAD-AQAgGgAAigIAIAIAAAD-AQAgGgAAigIAIAMAAAD7AQAgIQAAgwIAICIAAIgCACABAAAA-wEAIAEAAAD-AQAgAwwAAPkCACAnAAD7AgAgKAAA-gIAIAi9AQAAlAIAML4BAACRAgAQvwEAAJQCADDAAQEAlQIAIcEBAQCVAgAhwgEBAJUCACHDAUAAlgIAIcQBQACWAgAhAwAAAP4BACABAACQAgAwJgAAkQIAIAMAAAD-AQAgAQAA_wEAMAIAAPsBACAIvQEAAJQCADC-AQAAkQIAEL8BAACUAgAwwAEBAJUCACHBAQEAlQIAIcIBAQCVAgAhwwFAAJYCACHEAUAAlgIAIQ4MAACYAgAgJwAAmwIAICgAAJsCACDFAQEAAAABxgEBAAAABMcBAQAAAATIAQEAAAAByQEBAAAAAcoBAQAAAAHLAQEAAAABzAEBAJoCACHNAQEAAAABzgEBAAAAAc8BAQAAAAELDAAAmAIAICcAAJkCACAoAACZAgAgxQFAAAAAAcYBQAAAAATHAUAAAAAEyAFAAAAAAckBQAAAAAHKAUAAAAABywFAAAAAAcwBQACXAgAhCwwAAJgCACAnAACZAgAgKAAAmQIAIMUBQAAAAAHGAUAAAAAExwFAAAAABMgBQAAAAAHJAUAAAAABygFAAAAAAcsBQAAAAAHMAUAAlwIAIQjFAQIAAAABxgECAAAABMcBAgAAAATIAQIAAAAByQECAAAAAcoBAgAAAAHLAQIAAAABzAECAJgCACEIxQFAAAAAAcYBQAAAAATHAUAAAAAEyAFAAAAAAckBQAAAAAHKAUAAAAABywFAAAAAAcwBQACZAgAhDgwAAJgCACAnAACbAgAgKAAAmwIAIMUBAQAAAAHGAQEAAAAExwEBAAAABMgBAQAAAAHJAQEAAAABygEBAAAAAcsBAQAAAAHMAQEAmgIAIc0BAQAAAAHOAQEAAAABzwEBAAAAAQvFAQEAAAABxgEBAAAABMcBAQAAAATIAQEAAAAByQEBAAAAAcoBAQAAAAHLAQEAAAABzAEBAJsCACHNAQEAAAABzgEBAAAAAc8BAQAAAAEIvQEAAJwCADC-AQAA_gEAEL8BAACcAgAwwAEBAJ0CACHBAQEAnQIAIcIBAQCdAgAhwwFAAJ4CACHEAUAAngIAIQvFAQEAAAABxgEBAAAABMcBAQAAAATIAQEAAAAByQEBAAAAAcoBAQAAAAHLAQEAAAABzAEBAJsCACHNAQEAAAABzgEBAAAAAc8BAQAAAAEIxQFAAAAAAcYBQAAAAATHAUAAAAAEyAFAAAAAAckBQAAAAAHKAUAAAAABywFAAAAAAcwBQACZAgAhEb0BAACfAgAwvgEAAPgBABC_AQAAnwIAMMABAQCVAgAhwwFAAJYCACHEAUAAlgIAIdABAQCgAgAh0QEBAJUCACHSAQEAlQIAIdMBAQCgAgAh1AEBAKACACHVAQEAlQIAIdYBAQCVAgAh2AEAAKEC2AEi2QEBAKACACHaASAAogIAIdsBAQCgAgAhDgwAAKgCACAnAACpAgAgKAAAqQIAIMUBAQAAAAHGAQEAAAAFxwEBAAAABcgBAQAAAAHJAQEAAAABygEBAAAAAcsBAQAAAAHMAQEApwIAIc0BAQAAAAHOAQEAAAABzwEBAAAAAQcMAACYAgAgJwAApgIAICgAAKYCACDFAQAAANgBAsYBAAAA2AEIxwEAAADYAQjMAQAApQLYASIFDAAAmAIAICcAAKQCACAoAACkAgAgxQEgAAAAAcwBIACjAgAhBQwAAJgCACAnAACkAgAgKAAApAIAIMUBIAAAAAHMASAAowIAIQLFASAAAAABzAEgAKQCACEHDAAAmAIAICcAAKYCACAoAACmAgAgxQEAAADYAQLGAQAAANgBCMcBAAAA2AEIzAEAAKUC2AEiBMUBAAAA2AECxgEAAADYAQjHAQAAANgBCMwBAACmAtgBIg4MAACoAgAgJwAAqQIAICgAAKkCACDFAQEAAAABxgEBAAAABccBAQAAAAXIAQEAAAAByQEBAAAAAcoBAQAAAAHLAQEAAAABzAEBAKcCACHNAQEAAAABzgEBAAAAAc8BAQAAAAEIxQECAAAAAcYBAgAAAAXHAQIAAAAFyAECAAAAAckBAgAAAAHKAQIAAAABywECAAAAAcwBAgCoAgAhC8UBAQAAAAHGAQEAAAAFxwEBAAAABcgBAQAAAAHJAQEAAAABygEBAAAAAcsBAQAAAAHMAQEAqQIAIc0BAQAAAAHOAQEAAAABzwEBAAAAARG9AQAAqgIAML4BAADgAQAQvwEAAKoCADDAAQEAlQIAIcMBQACWAgAhxAFAAJYCACHQAQEAlQIAIdwBQACWAgAh3QECAKsCACHeAQIAqwIAId8BAgCrAgAh4AECAKsCACHhAQIAqwIAIeIBAgCrAgAh4wECAKsCACHkAQgArAIAIeUBCACsAgAhDQwAAJgCACAnAACYAgAgKAAAmAIAIFkAALACACBaAACYAgAgxQECAAAAAcYBAgAAAATHAQIAAAAEyAECAAAAAckBAgAAAAHKAQIAAAABywECAAAAAcwBAgCvAgAhDQwAAKgCACAnAACuAgAgKAAArgIAIFkAAK4CACBaAACuAgAgxQEIAAAAAcYBCAAAAAXHAQgAAAAFyAEIAAAAAckBCAAAAAHKAQgAAAABywEIAAAAAcwBCACtAgAhDQwAAKgCACAnAACuAgAgKAAArgIAIFkAAK4CACBaAACuAgAgxQEIAAAAAcYBCAAAAAXHAQgAAAAFyAEIAAAAAckBCAAAAAHKAQgAAAABywEIAAAAAcwBCACtAgAhCMUBCAAAAAHGAQgAAAAFxwEIAAAABcgBCAAAAAHJAQgAAAABygEIAAAAAcsBCAAAAAHMAQgArgIAIQ0MAACYAgAgJwAAmAIAICgAAJgCACBZAACwAgAgWgAAmAIAIMUBAgAAAAHGAQIAAAAExwECAAAABMgBAgAAAAHJAQIAAAABygECAAAAAcsBAgAAAAHMAQIArwIAIQjFAQgAAAABxgEIAAAABMcBCAAAAATIAQgAAAAByQEIAAAAAcoBCAAAAAHLAQgAAAABzAEIALACACEPvQEAALECADC-AQAAygEAEL8BAACxAgAwwAEBAJUCACHDAUAAlgIAIcQBQACWAgAh0AEBAJUCACHcAUAAsgIAId0BAgCrAgAh3gECAKsCACHfAQIAqwIAIeABAgCrAgAh4QECAKsCACHiAQIAqwIAIeMBAgCrAgAhCwwAAKgCACAnAAC0AgAgKAAAtAIAIMUBQAAAAAHGAUAAAAAFxwFAAAAABcgBQAAAAAHJAUAAAAABygFAAAAAAcsBQAAAAAHMAUAAswIAIQsMAACoAgAgJwAAtAIAICgAALQCACDFAUAAAAABxgFAAAAABccBQAAAAAXIAUAAAAAByQFAAAAAAcoBQAAAAAHLAUAAAAABzAFAALMCACEIxQFAAAAAAcYBQAAAAAXHAUAAAAAFyAFAAAAAAckBQAAAAAHKAUAAAAABywFAAAAAAcwBQAC0AgAhCr0BAAC1AgAwvgEAALQBABC_AQAAtQIAMMABAQCVAgAhwwFAAJYCACHEAUAAlgIAIdABAQCVAgAh5gEBAJUCACHnAQIAqwIAIegBIACiAgAhGb0BAAC2AgAwvgEAAJ4BABC_AQAAtgIAMMABAQCVAgAhwwFAAJYCACHEAUAAlgIAIdgBAAC4Au8BIukBAQCVAgAh6gEBAJUCACHrAQEAlQIAIe0BAAC3Au0BIu8BAgCrAgAh8AECAKsCACHxAQIAqwIAIfIBAgCrAgAh8wECAKsCACH0AQEAlQIAIfUBAQCVAgAh9gEBAJUCACH3AQEAoAIAIfgBQACyAgAh-QFAALICACH6AQIAqwIAIfsBAAC5Au8BI_wBQACyAgAhBwwAAJgCACAnAAC_AgAgKAAAvwIAIMUBAAAA7QECxgEAAADtAQjHAQAAAO0BCMwBAAC-Au0BIgcMAACYAgAgJwAAvQIAICgAAL0CACDFAQAAAO8BAsYBAAAA7wEIxwEAAADvAQjMAQAAvALvASIHDAAAqAIAICcAALsCACAoAAC7AgAgxQEAAADvAQPGAQAAAO8BCccBAAAA7wEJzAEAALoC7wEjBwwAAKgCACAnAAC7AgAgKAAAuwIAIMUBAAAA7wEDxgEAAADvAQnHAQAAAO8BCcwBAAC6Au8BIwTFAQAAAO8BA8YBAAAA7wEJxwEAAADvAQnMAQAAuwLvASMHDAAAmAIAICcAAL0CACAoAAC9AgAgxQEAAADvAQLGAQAAAO8BCMcBAAAA7wEIzAEAALwC7wEiBMUBAAAA7wECxgEAAADvAQjHAQAAAO8BCMwBAAC9Au8BIgcMAACYAgAgJwAAvwIAICgAAL8CACDFAQAAAO0BAsYBAAAA7QEIxwEAAADtAQjMAQAAvgLtASIExQEAAADtAQLGAQAAAO0BCMcBAAAA7QEIzAEAAL8C7QEiHL0BAADAAgAwvgEAAIgBABC_AQAAwAIAMMABAQCVAgAhwwFAAJYCACHEAUAAlgIAIdgBAADEAowCIukBAQCVAgAh_QEBAJUCACH-AQEAlQIAIf8BAgCrAgAhgAIBAJUCACGBAgIAqwIAIYICAQCVAgAhgwIBAJUCACGFAgAAwQKFAiKGAgEAoAIAIYgCAADCAogCIooCAADDAooCIowCAgCrAgAhjQICAKsCACGOAgEAlQIAIY8CAQCgAgAhkAIBAKACACGRAgEAoAIAIZICQACyAgAhkwIBAKACACGUAkAAsgIAIQcMAACYAgAgJwAAzAIAICgAAMwCACDFAQAAAIUCAsYBAAAAhQIIxwEAAACFAgjMAQAAywKFAiIHDAAAmAIAICcAAMoCACAoAADKAgAgxQEAAACIAgLGAQAAAIgCCMcBAAAAiAIIzAEAAMkCiAIiBwwAAJgCACAnAADIAgAgKAAAyAIAIMUBAAAAigICxgEAAACKAgjHAQAAAIoCCMwBAADHAooCIgcMAACYAgAgJwAAxgIAICgAAMYCACDFAQAAAIwCAsYBAAAAjAIIxwEAAACMAgjMAQAAxQKMAiIHDAAAmAIAICcAAMYCACAoAADGAgAgxQEAAACMAgLGAQAAAIwCCMcBAAAAjAIIzAEAAMUCjAIiBMUBAAAAjAICxgEAAACMAgjHAQAAAIwCCMwBAADGAowCIgcMAACYAgAgJwAAyAIAICgAAMgCACDFAQAAAIoCAsYBAAAAigIIxwEAAACKAgjMAQAAxwKKAiIExQEAAACKAgLGAQAAAIoCCMcBAAAAigIIzAEAAMgCigIiBwwAAJgCACAnAADKAgAgKAAAygIAIMUBAAAAiAICxgEAAACIAgjHAQAAAIgCCMwBAADJAogCIgTFAQAAAIgCAsYBAAAAiAIIxwEAAACIAgjMAQAAygKIAiIHDAAAmAIAICcAAMwCACAoAADMAgAgxQEAAACFAgLGAQAAAIUCCMcBAAAAhQIIzAEAAMsChQIiBMUBAAAAhQICxgEAAACFAgjHAQAAAIUCCMwBAADMAoUCIgi9AQAAzQIAML4BAAByABC_AQAAzQIAMMABAQCVAgAhlQIBAJUCACGWAkAAlgIAIZcCIACiAgAhmAIBAKACACEIvQEAAM4CADC-AQAAXwAQvwEAAM4CADDAAQEAnQIAIZUCAQCdAgAhlgJAAJ4CACGXAiAAzwIAIZgCAQDQAgAhAsUBIAAAAAHMASAApAIAIQvFAQEAAAABxgEBAAAABccBAQAAAAXIAQEAAAAByQEBAAAAAcoBAQAAAAHLAQEAAAABzAEBAKkCACHNAQEAAAABzgEBAAAAAc8BAQAAAAEMvQEAANECADC-AQAAWQAQvwEAANECADDAAQEAlQIAIcMBQACWAgAhxAFAAJYCACHQAQEAlQIAIdgBAADSApsCIpkCQACWAgAhmwIBAJUCACGcAkAAsgIAIZ0CAQCgAgAhBwwAAJgCACAnAADUAgAgKAAA1AIAIMUBAAAAmwICxgEAAACbAgjHAQAAAJsCCMwBAADTApsCIgcMAACYAgAgJwAA1AIAICgAANQCACDFAQAAAJsCAsYBAAAAmwIIxwEAAACbAgjMAQAA0wKbAiIExQEAAACbAgLGAQAAAJsCCMcBAAAAmwIIzAEAANQCmwIiB70BAADVAgAwvgEAAEMAEL8BAADVAgAwwAEBAJUCACHDAUAAlgIAIcQBQACWAgAh6wEBAJUCACEJEwAA1wIAIBQAANgCACC9AQAA1gIAML4BAAAwABC_AQAA1gIAMMABAQCdAgAhwwFAAJ4CACHEAUAAngIAIesBAQCdAgAhA54CAAADACCfAgAAAwAgoAIAAAMAIAOeAgAACwAgnwIAAAsAIKACAAALACAgAwAA3wIAIAQAAOACACAHAADhAgAgCAAA4gIAIAkAAOMCACAKAADkAgAgCwAA5QIAIL0BAADZAgAwvgEAAAsAEL8BAADZAgAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh2AEAANsC7wEi6QEBAJ0CACHqAQEAnQIAIesBAQCdAgAh7QEAANoC7QEi7wECANwCACHwAQIA3AIAIfEBAgDcAgAh8gECANwCACHzAQIA3AIAIfQBAQCdAgAh9QEBAJ0CACH2AQEAnQIAIfcBAQDQAgAh-AFAAN0CACH5AUAA3QIAIfoBAgDcAgAh-wEAAN4C7wEj_AFAAN0CACEExQEAAADtAQLGAQAAAO0BCMcBAAAA7QEIzAEAAL8C7QEiBMUBAAAA7wECxgEAAADvAQjHAQAAAO8BCMwBAAC9Au8BIgjFAQIAAAABxgECAAAABMcBAgAAAATIAQIAAAAByQECAAAAAcoBAgAAAAHLAQIAAAABzAECAJgCACEIxQFAAAAAAcYBQAAAAAXHAUAAAAAFyAFAAAAAAckBQAAAAAHKAUAAAAABywFAAAAAAcwBQAC0AgAhBMUBAAAA7wEDxgEAAADvAQnHAQAAAO8BCcwBAAC7Au8BIwsTAADXAgAgFAAA2AIAIL0BAADWAgAwvgEAADAAEL8BAADWAgAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh6wEBAJ0CACGlAgAAMAAgpgIAADAAICMDAADfAgAgDwAA5QIAIBAAAOUCACARAADYAgAgEgAA4QIAIL0BAAD0AgAwvgEAAAMAEL8BAAD0AgAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh2AEAAPgCjAIi6QEBAJ0CACH9AQEAnQIAIf4BAQCdAgAh_wECANwCACGAAgEAnQIAIYECAgDcAgAhggIBAJ0CACGDAgEAnQIAIYUCAAD1AoUCIoYCAQDQAgAhiAIAAPYCiAIiigIAAPcCigIijAICANwCACGNAgIA3AIAIY4CAQCdAgAhjwIBANACACGQAgEA0AIAIZECAQDQAgAhkgJAAN0CACGTAgEA0AIAIZQCQADdAgAhpQIAAAMAIKYCAAADACADngIAAA0AIJ8CAAANACCgAgAADQAgA54CAAARACCfAgAAEQAgoAIAABEAIAOeAgAAFQAgnwIAABUAIKACAAAVACADngIAABkAIJ8CAAAZACCgAgAAGQAgA54CAAAHACCfAgAABwAgoAIAAAcAIA0FAADoAgAgvQEAAOYCADC-AQAAGQAQvwEAAOYCADDAAQEAnQIAIcMBQACeAgAhxAFAAJ4CACHQAQEAnQIAIdgBAADnApsCIpkCQACeAgAhmwIBAJ0CACGcAkAA3QIAIZ0CAQDQAgAhBMUBAAAAmwICxgEAAACbAgjHAQAAAJsCCMwBAADUApsCIiIDAADfAgAgBAAA4AIAIAcAAOECACAIAADiAgAgCQAA4wIAIAoAAOQCACALAADlAgAgvQEAANkCADC-AQAACwAQvwEAANkCADDAAQEAnQIAIcMBQACeAgAhxAFAAJ4CACHYAQAA2wLvASLpAQEAnQIAIeoBAQCdAgAh6wEBAJ0CACHtAQAA2gLtASLvAQIA3AIAIfABAgDcAgAh8QECANwCACHyAQIA3AIAIfMBAgDcAgAh9AEBAJ0CACH1AQEAnQIAIfYBAQCdAgAh9wEBANACACH4AUAA3QIAIfkBQADdAgAh-gECANwCACH7AQAA3gLvASP8AUAA3QIAIaUCAAALACCmAgAACwAgAtABAQAAAAHcAUAAAAABEgUAAOgCACC9AQAA6gIAML4BAAAVABC_AQAA6gIAMMABAQCdAgAhwwFAAJ4CACHEAUAAngIAIdABAQCdAgAh3AFAAJ4CACHdAQIA3AIAId4BAgDcAgAh3wECANwCACHgAQIA3AIAIeEBAgDcAgAh4gECANwCACHjAQIA3AIAIeQBCADrAgAh5QEIAOsCACEIxQEIAAAAAcYBCAAAAAXHAQgAAAAFyAEIAAAAAckBCAAAAAHKAQgAAAABywEIAAAAAcwBCACuAgAhAtABAQAAAAHdAQIAAAABEAUAAOgCACC9AQAA7QIAML4BAAARABC_AQAA7QIAMMABAQCdAgAhwwFAAJ4CACHEAUAAngIAIdABAQCdAgAh3AFAAN0CACHdAQIA3AIAId4BAgDcAgAh3wECANwCACHgAQIA3AIAIeEBAgDcAgAh4gECANwCACHjAQIA3AIAIQLQAQEAAAAB5gEBAAAAAQwFAADoAgAgBgAA4AIAIL0BAADvAgAwvgEAAA0AEL8BAADvAgAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh0AEBAJ0CACHmAQEAnQIAIecBAgDcAgAh6AEgAM8CACEUBQAA8gIAIA0AAOACACAOAADgAgAgvQEAAPACADC-AQAABwAQvwEAAPACADDAAQEAnQIAIcMBQACeAgAhxAFAAJ4CACHQAQEA0AIAIdEBAQCdAgAh0gEBAJ0CACHTAQEA0AIAIdQBAQDQAgAh1QEBAJ0CACHWAQEAnQIAIdgBAADxAtgBItkBAQDQAgAh2gEgAM8CACHbAQEA0AIAIQTFAQAAANgBAsYBAAAA2AEIxwEAAADYAQjMAQAApgLYASIiAwAA3wIAIAQAAOACACAHAADhAgAgCAAA4gIAIAkAAOMCACAKAADkAgAgCwAA5QIAIL0BAADZAgAwvgEAAAsAEL8BAADZAgAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh2AEAANsC7wEi6QEBAJ0CACHqAQEAnQIAIesBAQCdAgAh7QEAANoC7QEi7wECANwCACHwAQIA3AIAIfEBAgDcAgAh8gECANwCACHzAQIA3AIAIfQBAQCdAgAh9QEBAJ0CACH2AQEAnQIAIfcBAQDQAgAh-AFAAN0CACH5AUAA3QIAIfoBAgDcAgAh-wEAAN4C7wEj_AFAAN0CACGlAgAACwAgpgIAAAsAIALpAQEAAAAB_QEBAAAAASEDAADfAgAgDwAA5QIAIBAAAOUCACARAADYAgAgEgAA4QIAIL0BAAD0AgAwvgEAAAMAEL8BAAD0AgAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh2AEAAPgCjAIi6QEBAJ0CACH9AQEAnQIAIf4BAQCdAgAh_wECANwCACGAAgEAnQIAIYECAgDcAgAhggIBAJ0CACGDAgEAnQIAIYUCAAD1AoUCIoYCAQDQAgAhiAIAAPYCiAIiigIAAPcCigIijAICANwCACGNAgIA3AIAIY4CAQCdAgAhjwIBANACACGQAgEA0AIAIZECAQDQAgAhkgJAAN0CACGTAgEA0AIAIZQCQADdAgAhBMUBAAAAhQICxgEAAACFAgjHAQAAAIUCCMwBAADMAoUCIgTFAQAAAIgCAsYBAAAAiAIIxwEAAACIAgjMAQAAygKIAiIExQEAAACKAgLGAQAAAIoCCMcBAAAAigIIzAEAAMgCigIiBMUBAAAAjAICxgEAAACMAgjHAQAAAIwCCMwBAADGAowCIgAAAAGqAgEAAAABAaoCQAAAAAEAAAAAAaoCAQAAAAEBqgIAAADYAQIBqgIgAAAAAQchAACXBQAgIgAAoAUAIKcCAACYBQAgqAIAAJ8FACCrAgAACwAgrAIAAAsAIK0CAAAlACAFIQAAlQUAICIAAJ0FACCnAgAAlgUAIKgCAACcBQAgrQIAAAUAIAUhAACTBQAgIgAAmgUAIKcCAACUBQAgqAIAAJkFACCtAgAABQAgAyEAAJcFACCnAgAAmAUAIK0CAAAlACADIQAAlQUAIKcCAACWBQAgrQIAAAUAIAMhAACTBQAgpwIAAJQFACCtAgAABQAgAAAAAAAFqgICAAAAAbACAgAAAAGxAgIAAAABsgICAAAAAbMCAgAAAAEFqgIIAAAAAbACCAAAAAGxAggAAAABsgIIAAAAAbMCCAAAAAEFIQAAjgUAICIAAJEFACCnAgAAjwUAIKgCAACQBQAgrQIAACUAIAMhAACOBQAgpwIAAI8FACCtAgAAJQAgAAAAAAABqgJAAAAAAQUhAACJBQAgIgAAjAUAIKcCAACKBQAgqAIAAIsFACCtAgAAJQAgAyEAAIkFACCnAgAAigUAIK0CAAAlACAAAAAAAAUhAACBBQAgIgAAhwUAIKcCAACCBQAgqAIAAIYFACCtAgAAJQAgBSEAAP8EACAiAACEBQAgpwIAAIAFACCoAgAAgwUAIK0CAAAFACADIQAAgQUAIKcCAACCBQAgrQIAACUAIAMhAAD_BAAgpwIAAIAFACCtAgAABQAgAAAAAAABqgIAAADtAQIBqgIAAADvAQIBqgIAAADvAQMFIQAA8gQAICIAAP0EACCnAgAA8wQAIKgCAAD8BAAgrQIAAAEAIAUhAADwBAAgIgAA-gQAIKcCAADxBAAgqAIAAPkEACCtAgAABQAgCyEAAOUDADAiAADqAwAwpwIAAOYDADCoAgAA5wMAMKkCAADoAwAgqgIAAOkDADCrAgAA6QMAMKwCAADpAwAwrQIAAOkDADCuAgAA6wMAMK8CAADsAwAwCyEAANkDADAiAADeAwAwpwIAANoDADCoAgAA2wMAMKkCAADcAwAgqgIAAN0DADCrAgAA3QMAMKwCAADdAwAwrQIAAN0DADCuAgAA3wMAMK8CAADgAwAwCyEAAM0DADAiAADSAwAwpwIAAM4DADCoAgAAzwMAMKkCAADQAwAgqgIAANEDADCrAgAA0QMAMKwCAADRAwAwrQIAANEDADCuAgAA0wMAMK8CAADUAwAwCyEAAMADADAiAADFAwAwpwIAAMEDADCoAgAAwgMAMKkCAADDAwAgqgIAAMQDADCrAgAAxAMAMKwCAADEAwAwrQIAAMQDADCuAgAAxgMAMK8CAADHAwAwCyEAALQDADAiAAC5AwAwpwIAALUDADCoAgAAtgMAMKkCAAC3AwAgqgIAALgDADCrAgAAuAMAMKwCAAC4AwAwrQIAALgDADCuAgAAugMAMK8CAAC7AwAwDw0AAIkDACAOAACKAwAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB0QEBAAAAAdIBAQAAAAHTAQEAAAAB1AEBAAAAAdUBAQAAAAHWAQEAAAAB2AEAAADYAQLZAQEAAAAB2gEgAAAAAdsBAQAAAAECAAAACQAgIQAAvwMAIAMAAAAJACAhAAC_AwAgIgAAvgMAIAEaAAD4BAAwFAUAAPICACANAADgAgAgDgAA4AIAIL0BAADwAgAwvgEAAAcAEL8BAADwAgAwwAEBAAAAAcMBQACeAgAhxAFAAJ4CACHQAQEA0AIAIdEBAQCdAgAh0gEBAJ0CACHTAQEA0AIAIdQBAQDQAgAh1QEBAJ0CACHWAQEAnQIAIdgBAADxAtgBItkBAQDQAgAh2gEgAM8CACHbAQEA0AIAIQIAAAAJACAaAAC-AwAgAgAAALwDACAaAAC9AwAgEb0BAAC7AwAwvgEAALwDABC_AQAAuwMAMMABAQCdAgAhwwFAAJ4CACHEAUAAngIAIdABAQDQAgAh0QEBAJ0CACHSAQEAnQIAIdMBAQDQAgAh1AEBANACACHVAQEAnQIAIdYBAQCdAgAh2AEAAPEC2AEi2QEBANACACHaASAAzwIAIdsBAQDQAgAhEb0BAAC7AwAwvgEAALwDABC_AQAAuwMAMMABAQCdAgAhwwFAAJ4CACHEAUAAngIAIdABAQDQAgAh0QEBAJ0CACHSAQEAnQIAIdMBAQDQAgAh1AEBANACACHVAQEAnQIAIdYBAQCdAgAh2AEAAPEC2AEi2QEBANACACHaASAAzwIAIdsBAQDQAgAhDcABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdEBAQD8AgAh0gEBAPwCACHTAQEAggMAIdQBAQCCAwAh1QEBAPwCACHWAQEA_AIAIdgBAACDA9gBItkBAQCCAwAh2gEgAIQDACHbAQEAggMAIQ8NAACGAwAgDgAAhwMAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdEBAQD8AgAh0gEBAPwCACHTAQEAggMAIdQBAQCCAwAh1QEBAPwCACHWAQEA_AIAIdgBAACDA9gBItkBAQCCAwAh2gEgAIQDACHbAQEAggMAIQ8NAACJAwAgDgAAigMAIMABAQAAAAHDAUAAAAABxAFAAAAAAdEBAQAAAAHSAQEAAAAB0wEBAAAAAdQBAQAAAAHVAQEAAAAB1gEBAAAAAdgBAAAA2AEC2QEBAAAAAdoBIAAAAAHbAQEAAAABCMABAQAAAAHDAUAAAAABxAFAAAAAAdgBAAAAmwICmQJAAAAAAZsCAQAAAAGcAkAAAAABnQIBAAAAAQIAAAAbACAhAADMAwAgAwAAABsAICEAAMwDACAiAADLAwAgARoAAPcEADANBQAA6AIAIL0BAADmAgAwvgEAABkAEL8BAADmAgAwwAEBAAAAAcMBQACeAgAhxAFAAJ4CACHQAQEAnQIAIdgBAADnApsCIpkCQACeAgAhmwIBAAAAAZwCQADdAgAhnQIBANACACECAAAAGwAgGgAAywMAIAIAAADIAwAgGgAAyQMAIAy9AQAAxwMAML4BAADIAwAQvwEAAMcDADDAAQEAnQIAIcMBQACeAgAhxAFAAJ4CACHQAQEAnQIAIdgBAADnApsCIpkCQACeAgAhmwIBAJ0CACGcAkAA3QIAIZ0CAQDQAgAhDL0BAADHAwAwvgEAAMgDABC_AQAAxwMAMMABAQCdAgAhwwFAAJ4CACHEAUAAngIAIdABAQCdAgAh2AEAAOcCmwIimQJAAJ4CACGbAgEAnQIAIZwCQADdAgAhnQIBANACACEIwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh2AEAAMoDmwIimQJAAP0CACGbAgEA_AIAIZwCQACZAwAhnQIBAIIDACEBqgIAAACbAgIIwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh2AEAAMoDmwIimQJAAP0CACGbAgEA_AIAIZwCQACZAwAhnQIBAIIDACEIwAEBAAAAAcMBQAAAAAHEAUAAAAAB2AEAAACbAgKZAkAAAAABmwIBAAAAAZwCQAAAAAGdAgEAAAABDcABAQAAAAHDAUAAAAABxAFAAAAAAdwBQAAAAAHdAQIAAAAB3gECAAAAAd8BAgAAAAHgAQIAAAAB4QECAAAAAeIBAgAAAAHjAQIAAAAB5AEIAAAAAeUBCAAAAAECAAAAFwAgIQAA2AMAIAMAAAAXACAhAADYAwAgIgAA1wMAIAEaAAD2BAAwEwUAAOgCACC9AQAA6gIAML4BAAAVABC_AQAA6gIAMMABAQAAAAHDAUAAngIAIcQBQACeAgAh0AEBAJ0CACHcAUAAngIAId0BAgDcAgAh3gECANwCACHfAQIA3AIAIeABAgDcAgAh4QECANwCACHiAQIA3AIAIeMBAgDcAgAh5AEIAOsCACHlAQgA6wIAIaECAADpAgAgAgAAABcAIBoAANcDACACAAAA1QMAIBoAANYDACARvQEAANQDADC-AQAA1QMAEL8BAADUAwAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh0AEBAJ0CACHcAUAAngIAId0BAgDcAgAh3gECANwCACHfAQIA3AIAIeABAgDcAgAh4QECANwCACHiAQIA3AIAIeMBAgDcAgAh5AEIAOsCACHlAQgA6wIAIRG9AQAA1AMAML4BAADVAwAQvwEAANQDADDAAQEAnQIAIcMBQACeAgAhxAFAAJ4CACHQAQEAnQIAIdwBQACeAgAh3QECANwCACHeAQIA3AIAId8BAgDcAgAh4AECANwCACHhAQIA3AIAIeIBAgDcAgAh4wECANwCACHkAQgA6wIAIeUBCADrAgAhDcABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdwBQAD9AgAh3QECAJADACHeAQIAkAMAId8BAgCQAwAh4AECAJADACHhAQIAkAMAIeIBAgCQAwAh4wECAJADACHkAQgAkQMAIeUBCACRAwAhDcABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdwBQAD9AgAh3QECAJADACHeAQIAkAMAId8BAgCQAwAh4AECAJADACHhAQIAkAMAIeIBAgCQAwAh4wECAJADACHkAQgAkQMAIeUBCACRAwAhDcABAQAAAAHDAUAAAAABxAFAAAAAAdwBQAAAAAHdAQIAAAAB3gECAAAAAd8BAgAAAAHgAQIAAAAB4QECAAAAAeIBAgAAAAHjAQIAAAAB5AEIAAAAAeUBCAAAAAELwAEBAAAAAcMBQAAAAAHEAUAAAAAB3AFAAAAAAd0BAgAAAAHeAQIAAAAB3wECAAAAAeABAgAAAAHhAQIAAAAB4gECAAAAAeMBAgAAAAECAAAAEwAgIQAA5AMAIAMAAAATACAhAADkAwAgIgAA4wMAIAEaAAD1BAAwEQUAAOgCACC9AQAA7QIAML4BAAARABC_AQAA7QIAMMABAQAAAAHDAUAAngIAIcQBQACeAgAh0AEBAJ0CACHcAUAA3QIAId0BAgDcAgAh3gECANwCACHfAQIA3AIAIeABAgDcAgAh4QECANwCACHiAQIA3AIAIeMBAgDcAgAhogIAAOwCACACAAAAEwAgGgAA4wMAIAIAAADhAwAgGgAA4gMAIA-9AQAA4AMAML4BAADhAwAQvwEAAOADADDAAQEAnQIAIcMBQACeAgAhxAFAAJ4CACHQAQEAnQIAIdwBQADdAgAh3QECANwCACHeAQIA3AIAId8BAgDcAgAh4AECANwCACHhAQIA3AIAIeIBAgDcAgAh4wECANwCACEPvQEAAOADADC-AQAA4QMAEL8BAADgAwAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh0AEBAJ0CACHcAUAA3QIAId0BAgDcAgAh3gECANwCACHfAQIA3AIAIeABAgDcAgAh4QECANwCACHiAQIA3AIAIeMBAgDcAgAhC8ABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdwBQACZAwAh3QECAJADACHeAQIAkAMAId8BAgCQAwAh4AECAJADACHhAQIAkAMAIeIBAgCQAwAh4wECAJADACELwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh3AFAAJkDACHdAQIAkAMAId4BAgCQAwAh3wECAJADACHgAQIAkAMAIeEBAgCQAwAh4gECAJADACHjAQIAkAMAIQvAAQEAAAABwwFAAAAAAcQBQAAAAAHcAUAAAAAB3QECAAAAAd4BAgAAAAHfAQIAAAAB4AECAAAAAeEBAgAAAAHiAQIAAAAB4wECAAAAAQcGAACkAwAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB5gEBAAAAAecBAgAAAAHoASAAAAABAgAAAA8AICEAAPADACADAAAADwAgIQAA8AMAICIAAO8DACABGgAA9AQAMA0FAADoAgAgBgAA4AIAIL0BAADvAgAwvgEAAA0AEL8BAADvAgAwwAEBAAAAAcMBQACeAgAhxAFAAJ4CACHQAQEAnQIAIeYBAQCdAgAh5wECANwCACHoASAAzwIAIaMCAADuAgAgAgAAAA8AIBoAAO8DACACAAAA7QMAIBoAAO4DACAKvQEAAOwDADC-AQAA7QMAEL8BAADsAwAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh0AEBAJ0CACHmAQEAnQIAIecBAgDcAgAh6AEgAM8CACEKvQEAAOwDADC-AQAA7QMAEL8BAADsAwAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh0AEBAJ0CACHmAQEAnQIAIecBAgDcAgAh6AEgAM8CACEGwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh5gEBAPwCACHnAQIAkAMAIegBIACEAwAhBwYAAKIDACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHmAQEA_AIAIecBAgCQAwAh6AEgAIQDACEHBgAApAMAIMABAQAAAAHDAUAAAAABxAFAAAAAAeYBAQAAAAHnAQIAAAAB6AEgAAAAAQMhAADyBAAgpwIAAPMEACCtAgAAAQAgAyEAAPAEACCnAgAA8QQAIK0CAAAFACAEIQAA5QMAMKcCAADmAwAwqQIAAOgDACCtAgAA6QMAMAQhAADZAwAwpwIAANoDADCpAgAA3AMAIK0CAADdAwAwBCEAAM0DADCnAgAAzgMAMKkCAADQAwAgrQIAANEDADAEIQAAwAMAMKcCAADBAwAwqQIAAMMDACCtAgAAxAMAMAQhAAC0AwAwpwIAALUDADCpAgAAtwMAIK0CAAC4AwAwAAAAAAABqgIAAACFAgIBqgIAAACIAgIBqgIAAACKAgIBqgIAAACMAgIFIQAA5wQAICIAAO4EACCnAgAA6AQAIKgCAADtBAAgrQIAAAEAIAshAACkBAAwIgAAqAQAMKcCAAClBAAwqAIAAKYEADCpAgAApwQAIKoCAAC4AwAwqwIAALgDADCsAgAAuAMAMK0CAAC4AwAwrgIAAKkEADCvAgAAuwMAMAshAACbBAAwIgAAnwQAMKcCAACcBAAwqAIAAJ0EADCpAgAAngQAIKoCAAC4AwAwqwIAALgDADCsAgAAuAMAMK0CAAC4AwAwrgIAAKAEADCvAgAAuwMAMAshAACPBAAwIgAAlAQAMKcCAACQBAAwqAIAAJEEADCpAgAAkgQAIKoCAACTBAAwqwIAAJMEADCsAgAAkwQAMK0CAACTBAAwrgIAAJUEADCvAgAAlgQAMAshAACGBAAwIgAAigQAMKcCAACHBAAwqAIAAIgEADCpAgAAiQQAIKoCAADpAwAwqwIAAOkDADCsAgAA6QMAMK0CAADpAwAwrgIAAIsEADCvAgAA7AMAMAcFAACjAwAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB0AEBAAAAAecBAgAAAAHoASAAAAABAgAAAA8AICEAAI4EACADAAAADwAgIQAAjgQAICIAAI0EACABGgAA7AQAMAIAAAAPACAaAACNBAAgAgAAAO0DACAaAACMBAAgBsABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdABAQD8AgAh5wECAJADACHoASAAhAMAIQcFAAChAwAgwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh0AEBAPwCACHnAQIAkAMAIegBIACEAwAhBwUAAKMDACDAAQEAAAABwwFAAAAAAcQBQAAAAAHQAQEAAAAB5wECAAAAAegBIAAAAAEbAwAA8QMAIAcAAPMDACAIAAD0AwAgCQAA9QMAIAoAAPYDACALAAD3AwAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB2AEAAADvAQLpAQEAAAAB6wEBAAAAAe0BAAAA7QEC7wECAAAAAfABAgAAAAHxAQIAAAAB8gECAAAAAfMBAgAAAAH0AQEAAAAB9QEBAAAAAfYBAQAAAAH3AQEAAAAB-AFAAAAAAfkBQAAAAAH6AQIAAAAB-wEAAADvAQP8AUAAAAABAgAAACUAICEAAJoEACADAAAAJQAgIQAAmgQAICIAAJkEACABGgAA6wQAMCADAADfAgAgBAAA4AIAIAcAAOECACAIAADiAgAgCQAA4wIAIAoAAOQCACALAADlAgAgvQEAANkCADC-AQAACwAQvwEAANkCADDAAQEAAAABwwFAAJ4CACHEAUAAngIAIdgBAADbAu8BIukBAQCdAgAh6gEBAJ0CACHrAQEAnQIAIe0BAADaAu0BIu8BAgDcAgAh8AECANwCACHxAQIA3AIAIfIBAgDcAgAh8wECANwCACH0AQEAnQIAIfUBAQCdAgAh9gEBAJ0CACH3AQEA0AIAIfgBQADdAgAh-QFAAN0CACH6AQIA3AIAIfsBAADeAu8BI_wBQADdAgAhAgAAACUAIBoAAJkEACACAAAAlwQAIBoAAJgEACAZvQEAAJYEADC-AQAAlwQAEL8BAACWBAAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh2AEAANsC7wEi6QEBAJ0CACHqAQEAnQIAIesBAQCdAgAh7QEAANoC7QEi7wECANwCACHwAQIA3AIAIfEBAgDcAgAh8gECANwCACHzAQIA3AIAIfQBAQCdAgAh9QEBAJ0CACH2AQEAnQIAIfcBAQDQAgAh-AFAAN0CACH5AUAA3QIAIfoBAgDcAgAh-wEAAN4C7wEj_AFAAN0CACEZvQEAAJYEADC-AQAAlwQAEL8BAACWBAAwwAEBAJ0CACHDAUAAngIAIcQBQACeAgAh2AEAANsC7wEi6QEBAJ0CACHqAQEAnQIAIesBAQCdAgAh7QEAANoC7QEi7wECANwCACHwAQIA3AIAIfEBAgDcAgAh8gECANwCACHzAQIA3AIAIfQBAQCdAgAh9QEBAJ0CACH2AQEAnQIAIfcBAQDQAgAh-AFAAN0CACH5AUAA3QIAIfoBAgDcAgAh-wEAAN4C7wEj_AFAAN0CACEVwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh2AEAAKsD7wEi6QEBAPwCACHrAQEA_AIAIe0BAACqA-0BIu8BAgCQAwAh8AECAJADACHxAQIAkAMAIfIBAgCQAwAh8wECAJADACH0AQEA_AIAIfUBAQD8AgAh9gEBAPwCACH3AQEAggMAIfgBQACZAwAh-QFAAJkDACH6AQIAkAMAIfsBAACsA-8BI_wBQACZAwAhGwMAAK0DACAHAACvAwAgCAAAsAMAIAkAALEDACAKAACyAwAgCwAAswMAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdgBAACrA-8BIukBAQD8AgAh6wEBAPwCACHtAQAAqgPtASLvAQIAkAMAIfABAgCQAwAh8QECAJADACHyAQIAkAMAIfMBAgCQAwAh9AEBAPwCACH1AQEA_AIAIfYBAQD8AgAh9wEBAIIDACH4AUAAmQMAIfkBQACZAwAh-gECAJADACH7AQAArAPvASP8AUAAmQMAIRsDAADxAwAgBwAA8wMAIAgAAPQDACAJAAD1AwAgCgAA9gMAIAsAAPcDACDAAQEAAAABwwFAAAAAAcQBQAAAAAHYAQAAAO8BAukBAQAAAAHrAQEAAAAB7QEAAADtAQLvAQIAAAAB8AECAAAAAfEBAgAAAAHyAQIAAAAB8wECAAAAAfQBAQAAAAH1AQEAAAAB9gEBAAAAAfcBAQAAAAH4AUAAAAAB-QFAAAAAAfoBAgAAAAH7AQAAAO8BA_wBQAAAAAEPBQAAiAMAIA0AAIkDACDAAQEAAAABwwFAAAAAAcQBQAAAAAHQAQEAAAAB0QEBAAAAAdMBAQAAAAHUAQEAAAAB1QEBAAAAAdYBAQAAAAHYAQAAANgBAtkBAQAAAAHaASAAAAAB2wEBAAAAAQIAAAAJACAhAACjBAAgAwAAAAkAICEAAKMEACAiAACiBAAgARoAAOoEADACAAAACQAgGgAAogQAIAIAAAC8AwAgGgAAoQQAIA3AAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHQAQEAggMAIdEBAQD8AgAh0wEBAIIDACHUAQEAggMAIdUBAQD8AgAh1gEBAPwCACHYAQAAgwPYASLZAQEAggMAIdoBIACEAwAh2wEBAIIDACEPBQAAhQMAIA0AAIYDACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHQAQEAggMAIdEBAQD8AgAh0wEBAIIDACHUAQEAggMAIdUBAQD8AgAh1gEBAPwCACHYAQAAgwPYASLZAQEAggMAIdoBIACEAwAh2wEBAIIDACEPBQAAiAMAIA0AAIkDACDAAQEAAAABwwFAAAAAAcQBQAAAAAHQAQEAAAAB0QEBAAAAAdMBAQAAAAHUAQEAAAAB1QEBAAAAAdYBAQAAAAHYAQAAANgBAtkBAQAAAAHaASAAAAAB2wEBAAAAAQ8FAACIAwAgDgAAigMAIMABAQAAAAHDAUAAAAABxAFAAAAAAdABAQAAAAHSAQEAAAAB0wEBAAAAAdQBAQAAAAHVAQEAAAAB1gEBAAAAAdgBAAAA2AEC2QEBAAAAAdoBIAAAAAHbAQEAAAABAgAAAAkAICEAAKwEACADAAAACQAgIQAArAQAICIAAKsEACABGgAA6QQAMAIAAAAJACAaAACrBAAgAgAAALwDACAaAACqBAAgDcABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdABAQCCAwAh0gEBAPwCACHTAQEAggMAIdQBAQCCAwAh1QEBAPwCACHWAQEA_AIAIdgBAACDA9gBItkBAQCCAwAh2gEgAIQDACHbAQEAggMAIQ8FAACFAwAgDgAAhwMAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdABAQCCAwAh0gEBAPwCACHTAQEAggMAIdQBAQCCAwAh1QEBAPwCACHWAQEA_AIAIdgBAACDA9gBItkBAQCCAwAh2gEgAIQDACHbAQEAggMAIQ8FAACIAwAgDgAAigMAIMABAQAAAAHDAUAAAAABxAFAAAAAAdABAQAAAAHSAQEAAAAB0wEBAAAAAdQBAQAAAAHVAQEAAAAB1gEBAAAAAdgBAAAA2AEC2QEBAAAAAdoBIAAAAAHbAQEAAAABAyEAAOcEACCnAgAA6AQAIK0CAAABACAEIQAApAQAMKcCAAClBAAwqQIAAKcEACCtAgAAuAMAMAQhAACbBAAwpwIAAJwEADCpAgAAngQAIK0CAAC4AwAwBCEAAI8EADCnAgAAkAQAMKkCAACSBAAgrQIAAJMEADAEIQAAhgQAMKcCAACHBAAwqQIAAIkEACCtAgAA6QMAMAAAAAAAAAUhAADiBAAgIgAA5QQAIKcCAADjBAAgqAIAAOQEACCtAgAAJQAgAyEAAOIEACCnAgAA4wQAIK0CAAAlACAAAAALIQAAyAQAMCIAAM0EADCnAgAAyQQAMKgCAADKBAAwqQIAAMsEACCqAgAAzAQAMKsCAADMBAAwrAIAAMwEADCtAgAAzAQAMK4CAADOBAAwrwIAAM8EADALIQAAvwQAMCIAAMMEADCnAgAAwAQAMKgCAADBBAAwqQIAAMIEACCqAgAAkwQAMKsCAACTBAAwrAIAAJMEADCtAgAAkwQAMK4CAADEBAAwrwIAAJYEADAbBAAA8gMAIAcAAPMDACAIAAD0AwAgCQAA9QMAIAoAAPYDACALAAD3AwAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB2AEAAADvAQLqAQEAAAAB6wEBAAAAAe0BAAAA7QEC7wECAAAAAfABAgAAAAHxAQIAAAAB8gECAAAAAfMBAgAAAAH0AQEAAAAB9QEBAAAAAfYBAQAAAAH3AQEAAAAB-AFAAAAAAfkBQAAAAAH6AQIAAAAB-wEAAADvAQP8AUAAAAABAgAAACUAICEAAMcEACADAAAAJQAgIQAAxwQAICIAAMYEACABGgAA4QQAMAIAAAAlACAaAADGBAAgAgAAAJcEACAaAADFBAAgFcABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdgBAACrA-8BIuoBAQD8AgAh6wEBAPwCACHtAQAAqgPtASLvAQIAkAMAIfABAgCQAwAh8QECAJADACHyAQIAkAMAIfMBAgCQAwAh9AEBAPwCACH1AQEA_AIAIfYBAQD8AgAh9wEBAIIDACH4AUAAmQMAIfkBQACZAwAh-gECAJADACH7AQAArAPvASP8AUAAmQMAIRsEAACuAwAgBwAArwMAIAgAALADACAJAACxAwAgCgAAsgMAIAsAALMDACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHYAQAAqwPvASLqAQEA_AIAIesBAQD8AgAh7QEAAKoD7QEi7wECAJADACHwAQIAkAMAIfEBAgCQAwAh8gECAJADACHzAQIAkAMAIfQBAQD8AgAh9QEBAPwCACH2AQEA_AIAIfcBAQCCAwAh-AFAAJkDACH5AUAAmQMAIfoBAgCQAwAh-wEAAKwD7wEj_AFAAJkDACEbBAAA8gMAIAcAAPMDACAIAAD0AwAgCQAA9QMAIAoAAPYDACALAAD3AwAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB2AEAAADvAQLqAQEAAAAB6wEBAAAAAe0BAAAA7QEC7wECAAAAAfABAgAAAAHxAQIAAAAB8gECAAAAAfMBAgAAAAH0AQEAAAAB9QEBAAAAAfYBAQAAAAH3AQEAAAAB-AFAAAAAAfkBQAAAAAH6AQIAAAAB-wEAAADvAQP8AUAAAAABHA8AAK4EACAQAACvBAAgEQAAsAQAIBIAALEEACDAAQEAAAABwwFAAAAAAcQBQAAAAAHYAQAAAIwCAv0BAQAAAAH-AQEAAAAB_wECAAAAAYACAQAAAAGBAgIAAAABggIBAAAAAYMCAQAAAAGFAgAAAIUCAoYCAQAAAAGIAgAAAIgCAooCAAAAigICjAICAAAAAY0CAgAAAAGOAgEAAAABjwIBAAAAAZACAQAAAAGRAgEAAAABkgJAAAAAAZMCAQAAAAGUAkAAAAABAgAAAAUAICEAANMEACADAAAABQAgIQAA0wQAICIAANIEACABGgAA4AQAMCIDAADfAgAgDwAA5QIAIBAAAOUCACARAADYAgAgEgAA4QIAIL0BAAD0AgAwvgEAAAMAEL8BAAD0AgAwwAEBAAAAAcMBQACeAgAhxAFAAJ4CACHYAQAA-AKMAiLpAQEAnQIAIf0BAQCdAgAh_gEBAJ0CACH_AQIA3AIAIYACAQCdAgAhgQICANwCACGCAgEAnQIAIYMCAQCdAgAhhQIAAPUChQIihgIBANACACGIAgAA9gKIAiKKAgAA9wKKAiKMAgIA3AIAIY0CAgDcAgAhjgIBAJ0CACGPAgEA0AIAIZACAQDQAgAhkQIBANACACGSAkAA3QIAIZMCAQDQAgAhlAJAAN0CACGkAgAA8wIAIAIAAAAFACAaAADSBAAgAgAAANAEACAaAADRBAAgHL0BAADPBAAwvgEAANAEABC_AQAAzwQAMMABAQCdAgAhwwFAAJ4CACHEAUAAngIAIdgBAAD4AowCIukBAQCdAgAh_QEBAJ0CACH-AQEAnQIAIf8BAgDcAgAhgAIBAJ0CACGBAgIA3AIAIYICAQCdAgAhgwIBAJ0CACGFAgAA9QKFAiKGAgEA0AIAIYgCAAD2AogCIooCAAD3AooCIowCAgDcAgAhjQICANwCACGOAgEAnQIAIY8CAQDQAgAhkAIBANACACGRAgEA0AIAIZICQADdAgAhkwIBANACACGUAkAA3QIAIRy9AQAAzwQAML4BAADQBAAQvwEAAM8EADDAAQEAnQIAIcMBQACeAgAhxAFAAJ4CACHYAQAA-AKMAiLpAQEAnQIAIf0BAQCdAgAh_gEBAJ0CACH_AQIA3AIAIYACAQCdAgAhgQICANwCACGCAgEAnQIAIYMCAQCdAgAhhQIAAPUChQIihgIBANACACGIAgAA9gKIAiKKAgAA9wKKAiKMAgIA3AIAIY0CAgDcAgAhjgIBAJ0CACGPAgEA0AIAIZACAQDQAgAhkQIBANACACGSAkAA3QIAIZMCAQDQAgAhlAJAAN0CACEYwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh2AEAAIAEjAIi_QEBAPwCACH-AQEA_AIAIf8BAgCQAwAhgAIBAPwCACGBAgIAkAMAIYICAQD8AgAhgwIBAPwCACGFAgAA_QOFAiKGAgEAggMAIYgCAAD-A4gCIooCAAD_A4oCIowCAgCQAwAhjQICAJADACGOAgEA_AIAIY8CAQCCAwAhkAIBAIIDACGRAgEAggMAIZICQACZAwAhkwIBAIIDACGUAkAAmQMAIRwPAACCBAAgEAAAgwQAIBEAAIQEACASAACFBAAgwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh2AEAAIAEjAIi_QEBAPwCACH-AQEA_AIAIf8BAgCQAwAhgAIBAPwCACGBAgIAkAMAIYICAQD8AgAhgwIBAPwCACGFAgAA_QOFAiKGAgEAggMAIYgCAAD-A4gCIooCAAD_A4oCIowCAgCQAwAhjQICAJADACGOAgEA_AIAIY8CAQCCAwAhkAIBAIIDACGRAgEAggMAIZICQACZAwAhkwIBAIIDACGUAkAAmQMAIRwPAACuBAAgEAAArwQAIBEAALAEACASAACxBAAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB2AEAAACMAgL9AQEAAAAB_gEBAAAAAf8BAgAAAAGAAgEAAAABgQICAAAAAYICAQAAAAGDAgEAAAABhQIAAACFAgKGAgEAAAABiAIAAACIAgKKAgAAAIoCAowCAgAAAAGNAgIAAAABjgIBAAAAAY8CAQAAAAGQAgEAAAABkQIBAAAAAZICQAAAAAGTAgEAAAABlAJAAAAAAQQhAADIBAAwpwIAAMkEADCpAgAAywQAIK0CAADMBAAwBCEAAL8EADCnAgAAwAQAMKkCAADCBAAgrQIAAJMEADAAAAITAADWBAAgFAAA1wQAIAwDAADYBAAgDwAA3gQAIBAAAN4EACARAADXBAAgEgAA2gQAIIYCAAD-AgAgjwIAAP4CACCQAgAA_gIAIJECAAD-AgAgkgIAAP4CACCTAgAA_gIAIJQCAAD-AgAgAAAAAAAMAwAA2AQAIAQAANkEACAHAADaBAAgCAAA2wQAIAkAANwEACAKAADdBAAgCwAA3gQAIPcBAAD-AgAg-AEAAP4CACD5AQAA_gIAIPsBAAD-AgAg_AEAAP4CACAYwAEBAAAAAcMBQAAAAAHEAUAAAAAB2AEAAACMAgL9AQEAAAAB_gEBAAAAAf8BAgAAAAGAAgEAAAABgQICAAAAAYICAQAAAAGDAgEAAAABhQIAAACFAgKGAgEAAAABiAIAAACIAgKKAgAAAIoCAowCAgAAAAGNAgIAAAABjgIBAAAAAY8CAQAAAAGQAgEAAAABkQIBAAAAAZICQAAAAAGTAgEAAAABlAJAAAAAARXAAQEAAAABwwFAAAAAAcQBQAAAAAHYAQAAAO8BAuoBAQAAAAHrAQEAAAAB7QEAAADtAQLvAQIAAAAB8AECAAAAAfEBAgAAAAHyAQIAAAAB8wECAAAAAfQBAQAAAAH1AQEAAAAB9gEBAAAAAfcBAQAAAAH4AUAAAAAB-QFAAAAAAfoBAgAAAAH7AQAAAO8BA_wBQAAAAAEcAwAA8QMAIAQAAPIDACAHAADzAwAgCAAA9AMAIAkAAPUDACALAAD3AwAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB2AEAAADvAQLpAQEAAAAB6gEBAAAAAesBAQAAAAHtAQAAAO0BAu8BAgAAAAHwAQIAAAAB8QECAAAAAfIBAgAAAAHzAQIAAAAB9AEBAAAAAfUBAQAAAAH2AQEAAAAB9wEBAAAAAfgBQAAAAAH5AUAAAAAB-gECAAAAAfsBAAAA7wED_AFAAAAAAQIAAAAlACAhAADiBAAgAwAAAAsAICEAAOIEACAiAADmBAAgHgAAAAsAIAMAAK0DACAEAACuAwAgBwAArwMAIAgAALADACAJAACxAwAgCwAAswMAIBoAAOYEACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHYAQAAqwPvASLpAQEA_AIAIeoBAQD8AgAh6wEBAPwCACHtAQAAqgPtASLvAQIAkAMAIfABAgCQAwAh8QECAJADACHyAQIAkAMAIfMBAgCQAwAh9AEBAPwCACH1AQEA_AIAIfYBAQD8AgAh9wEBAIIDACH4AUAAmQMAIfkBQACZAwAh-gECAJADACH7AQAArAPvASP8AUAAmQMAIRwDAACtAwAgBAAArgMAIAcAAK8DACAIAACwAwAgCQAAsQMAIAsAALMDACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHYAQAAqwPvASLpAQEA_AIAIeoBAQD8AgAh6wEBAPwCACHtAQAAqgPtASLvAQIAkAMAIfABAgCQAwAh8QECAJADACHyAQIAkAMAIfMBAgCQAwAh9AEBAPwCACH1AQEA_AIAIfYBAQD8AgAh9wEBAIIDACH4AUAAmQMAIfkBQACZAwAh-gECAJADACH7AQAArAPvASP8AUAAmQMAIQUUAADVBAAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB6wEBAAAAAQIAAAABACAhAADnBAAgDcABAQAAAAHDAUAAAAABxAFAAAAAAdABAQAAAAHSAQEAAAAB0wEBAAAAAdQBAQAAAAHVAQEAAAAB1gEBAAAAAdgBAAAA2AEC2QEBAAAAAdoBIAAAAAHbAQEAAAABDcABAQAAAAHDAUAAAAABxAFAAAAAAdABAQAAAAHRAQEAAAAB0wEBAAAAAdQBAQAAAAHVAQEAAAAB1gEBAAAAAdgBAAAA2AEC2QEBAAAAAdoBIAAAAAHbAQEAAAABFcABAQAAAAHDAUAAAAABxAFAAAAAAdgBAAAA7wEC6QEBAAAAAesBAQAAAAHtAQAAAO0BAu8BAgAAAAHwAQIAAAAB8QECAAAAAfIBAgAAAAHzAQIAAAAB9AEBAAAAAfUBAQAAAAH2AQEAAAAB9wEBAAAAAfgBQAAAAAH5AUAAAAAB-gECAAAAAfsBAAAA7wED_AFAAAAAAQbAAQEAAAABwwFAAAAAAcQBQAAAAAHQAQEAAAAB5wECAAAAAegBIAAAAAEDAAAAMAAgIQAA5wQAICIAAO8EACAHAAAAMAAgFAAAvgQAIBoAAO8EACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHrAQEA_AIAIQUUAAC-BAAgwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh6wEBAPwCACEdAwAArQQAIA8AAK4EACAQAACvBAAgEgAAsQQAIMABAQAAAAHDAUAAAAABxAFAAAAAAdgBAAAAjAIC6QEBAAAAAf0BAQAAAAH-AQEAAAAB_wECAAAAAYACAQAAAAGBAgIAAAABggIBAAAAAYMCAQAAAAGFAgAAAIUCAoYCAQAAAAGIAgAAAIgCAooCAAAAigICjAICAAAAAY0CAgAAAAGOAgEAAAABjwIBAAAAAZACAQAAAAGRAgEAAAABkgJAAAAAAZMCAQAAAAGUAkAAAAABAgAAAAUAICEAAPAEACAFEwAA1AQAIMABAQAAAAHDAUAAAAABxAFAAAAAAesBAQAAAAECAAAAAQAgIQAA8gQAIAbAAQEAAAABwwFAAAAAAcQBQAAAAAHmAQEAAAAB5wECAAAAAegBIAAAAAELwAEBAAAAAcMBQAAAAAHEAUAAAAAB3AFAAAAAAd0BAgAAAAHeAQIAAAAB3wECAAAAAeABAgAAAAHhAQIAAAAB4gECAAAAAeMBAgAAAAENwAEBAAAAAcMBQAAAAAHEAUAAAAAB3AFAAAAAAd0BAgAAAAHeAQIAAAAB3wECAAAAAeABAgAAAAHhAQIAAAAB4gECAAAAAeMBAgAAAAHkAQgAAAAB5QEIAAAAAQjAAQEAAAABwwFAAAAAAcQBQAAAAAHYAQAAAJsCApkCQAAAAAGbAgEAAAABnAJAAAAAAZ0CAQAAAAENwAEBAAAAAcMBQAAAAAHEAUAAAAAB0QEBAAAAAdIBAQAAAAHTAQEAAAAB1AEBAAAAAdUBAQAAAAHWAQEAAAAB2AEAAADYAQLZAQEAAAAB2gEgAAAAAdsBAQAAAAEDAAAAAwAgIQAA8AQAICIAAPsEACAfAAAAAwAgAwAAgQQAIA8AAIIEACAQAACDBAAgEgAAhQQAIBoAAPsEACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHYAQAAgASMAiLpAQEA_AIAIf0BAQD8AgAh_gEBAPwCACH_AQIAkAMAIYACAQD8AgAhgQICAJADACGCAgEA_AIAIYMCAQD8AgAhhQIAAP0DhQIihgIBAIIDACGIAgAA_gOIAiKKAgAA_wOKAiKMAgIAkAMAIY0CAgCQAwAhjgIBAPwCACGPAgEAggMAIZACAQCCAwAhkQIBAIIDACGSAkAAmQMAIZMCAQCCAwAhlAJAAJkDACEdAwAAgQQAIA8AAIIEACAQAACDBAAgEgAAhQQAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdgBAACABIwCIukBAQD8AgAh_QEBAPwCACH-AQEA_AIAIf8BAgCQAwAhgAIBAPwCACGBAgIAkAMAIYICAQD8AgAhgwIBAPwCACGFAgAA_QOFAiKGAgEAggMAIYgCAAD-A4gCIooCAAD_A4oCIowCAgCQAwAhjQICAJADACGOAgEA_AIAIY8CAQCCAwAhkAIBAIIDACGRAgEAggMAIZICQACZAwAhkwIBAIIDACGUAkAAmQMAIQMAAAAwACAhAADyBAAgIgAA_gQAIAcAAAAwACATAAC9BAAgGgAA_gQAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIesBAQD8AgAhBRMAAL0EACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHrAQEA_AIAIR0DAACtBAAgDwAArgQAIBAAAK8EACARAACwBAAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB2AEAAACMAgLpAQEAAAAB_QEBAAAAAf4BAQAAAAH_AQIAAAABgAIBAAAAAYECAgAAAAGCAgEAAAABgwIBAAAAAYUCAAAAhQIChgIBAAAAAYgCAAAAiAICigIAAACKAgKMAgIAAAABjQICAAAAAY4CAQAAAAGPAgEAAAABkAIBAAAAAZECAQAAAAGSAkAAAAABkwIBAAAAAZQCQAAAAAECAAAABQAgIQAA_wQAIBwDAADxAwAgBAAA8gMAIAgAAPQDACAJAAD1AwAgCgAA9gMAIAsAAPcDACDAAQEAAAABwwFAAAAAAcQBQAAAAAHYAQAAAO8BAukBAQAAAAHqAQEAAAAB6wEBAAAAAe0BAAAA7QEC7wECAAAAAfABAgAAAAHxAQIAAAAB8gECAAAAAfMBAgAAAAH0AQEAAAAB9QEBAAAAAfYBAQAAAAH3AQEAAAAB-AFAAAAAAfkBQAAAAAH6AQIAAAAB-wEAAADvAQP8AUAAAAABAgAAACUAICEAAIEFACADAAAAAwAgIQAA_wQAICIAAIUFACAfAAAAAwAgAwAAgQQAIA8AAIIEACAQAACDBAAgEQAAhAQAIBoAAIUFACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHYAQAAgASMAiLpAQEA_AIAIf0BAQD8AgAh_gEBAPwCACH_AQIAkAMAIYACAQD8AgAhgQICAJADACGCAgEA_AIAIYMCAQD8AgAhhQIAAP0DhQIihgIBAIIDACGIAgAA_gOIAiKKAgAA_wOKAiKMAgIAkAMAIY0CAgCQAwAhjgIBAPwCACGPAgEAggMAIZACAQCCAwAhkQIBAIIDACGSAkAAmQMAIZMCAQCCAwAhlAJAAJkDACEdAwAAgQQAIA8AAIIEACAQAACDBAAgEQAAhAQAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdgBAACABIwCIukBAQD8AgAh_QEBAPwCACH-AQEA_AIAIf8BAgCQAwAhgAIBAPwCACGBAgIAkAMAIYICAQD8AgAhgwIBAPwCACGFAgAA_QOFAiKGAgEAggMAIYgCAAD-A4gCIooCAAD_A4oCIowCAgCQAwAhjQICAJADACGOAgEA_AIAIY8CAQCCAwAhkAIBAIIDACGRAgEAggMAIZICQACZAwAhkwIBAIIDACGUAkAAmQMAIQMAAAALACAhAACBBQAgIgAAiAUAIB4AAAALACADAACtAwAgBAAArgMAIAgAALADACAJAACxAwAgCgAAsgMAIAsAALMDACAaAACIBQAgwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh2AEAAKsD7wEi6QEBAPwCACHqAQEA_AIAIesBAQD8AgAh7QEAAKoD7QEi7wECAJADACHwAQIAkAMAIfEBAgCQAwAh8gECAJADACHzAQIAkAMAIfQBAQD8AgAh9QEBAPwCACH2AQEA_AIAIfcBAQCCAwAh-AFAAJkDACH5AUAAmQMAIfoBAgCQAwAh-wEAAKwD7wEj_AFAAJkDACEcAwAArQMAIAQAAK4DACAIAACwAwAgCQAAsQMAIAoAALIDACALAACzAwAgwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh2AEAAKsD7wEi6QEBAPwCACHqAQEA_AIAIesBAQD8AgAh7QEAAKoD7QEi7wECAJADACHwAQIAkAMAIfEBAgCQAwAh8gECAJADACHzAQIAkAMAIfQBAQD8AgAh9QEBAPwCACH2AQEA_AIAIfcBAQCCAwAh-AFAAJkDACH5AUAAmQMAIfoBAgCQAwAh-wEAAKwD7wEj_AFAAJkDACEcAwAA8QMAIAQAAPIDACAHAADzAwAgCQAA9QMAIAoAAPYDACALAAD3AwAgwAEBAAAAAcMBQAAAAAHEAUAAAAAB2AEAAADvAQLpAQEAAAAB6gEBAAAAAesBAQAAAAHtAQAAAO0BAu8BAgAAAAHwAQIAAAAB8QECAAAAAfIBAgAAAAHzAQIAAAAB9AEBAAAAAfUBAQAAAAH2AQEAAAAB9wEBAAAAAfgBQAAAAAH5AUAAAAAB-gECAAAAAfsBAAAA7wED_AFAAAAAAQIAAAAlACAhAACJBQAgAwAAAAsAICEAAIkFACAiAACNBQAgHgAAAAsAIAMAAK0DACAEAACuAwAgBwAArwMAIAkAALEDACAKAACyAwAgCwAAswMAIBoAAI0FACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHYAQAAqwPvASLpAQEA_AIAIeoBAQD8AgAh6wEBAPwCACHtAQAAqgPtASLvAQIAkAMAIfABAgCQAwAh8QECAJADACHyAQIAkAMAIfMBAgCQAwAh9AEBAPwCACH1AQEA_AIAIfYBAQD8AgAh9wEBAIIDACH4AUAAmQMAIfkBQACZAwAh-gECAJADACH7AQAArAPvASP8AUAAmQMAIRwDAACtAwAgBAAArgMAIAcAAK8DACAJAACxAwAgCgAAsgMAIAsAALMDACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHYAQAAqwPvASLpAQEA_AIAIeoBAQD8AgAh6wEBAPwCACHtAQAAqgPtASLvAQIAkAMAIfABAgCQAwAh8QECAJADACHyAQIAkAMAIfMBAgCQAwAh9AEBAPwCACH1AQEA_AIAIfYBAQD8AgAh9wEBAIIDACH4AUAAmQMAIfkBQACZAwAh-gECAJADACH7AQAArAPvASP8AUAAmQMAIRwDAADxAwAgBAAA8gMAIAcAAPMDACAIAAD0AwAgCgAA9gMAIAsAAPcDACDAAQEAAAABwwFAAAAAAcQBQAAAAAHYAQAAAO8BAukBAQAAAAHqAQEAAAAB6wEBAAAAAe0BAAAA7QEC7wECAAAAAfABAgAAAAHxAQIAAAAB8gECAAAAAfMBAgAAAAH0AQEAAAAB9QEBAAAAAfYBAQAAAAH3AQEAAAAB-AFAAAAAAfkBQAAAAAH6AQIAAAAB-wEAAADvAQP8AUAAAAABAgAAACUAICEAAI4FACADAAAACwAgIQAAjgUAICIAAJIFACAeAAAACwAgAwAArQMAIAQAAK4DACAHAACvAwAgCAAAsAMAIAoAALIDACALAACzAwAgGgAAkgUAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdgBAACrA-8BIukBAQD8AgAh6gEBAPwCACHrAQEA_AIAIe0BAACqA-0BIu8BAgCQAwAh8AECAJADACHxAQIAkAMAIfIBAgCQAwAh8wECAJADACH0AQEA_AIAIfUBAQD8AgAh9gEBAPwCACH3AQEAggMAIfgBQACZAwAh-QFAAJkDACH6AQIAkAMAIfsBAACsA-8BI_wBQACZAwAhHAMAAK0DACAEAACuAwAgBwAArwMAIAgAALADACAKAACyAwAgCwAAswMAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdgBAACrA-8BIukBAQD8AgAh6gEBAPwCACHrAQEA_AIAIe0BAACqA-0BIu8BAgCQAwAh8AECAJADACHxAQIAkAMAIfIBAgCQAwAh8wECAJADACH0AQEA_AIAIfUBAQD8AgAh9gEBAPwCACH3AQEAggMAIfgBQACZAwAh-QFAAJkDACH6AQIAkAMAIfsBAACsA-8BI_wBQACZAwAhHQMAAK0EACAPAACuBAAgEQAAsAQAIBIAALEEACDAAQEAAAABwwFAAAAAAcQBQAAAAAHYAQAAAIwCAukBAQAAAAH9AQEAAAAB_gEBAAAAAf8BAgAAAAGAAgEAAAABgQICAAAAAYICAQAAAAGDAgEAAAABhQIAAACFAgKGAgEAAAABiAIAAACIAgKKAgAAAIoCAowCAgAAAAGNAgIAAAABjgIBAAAAAY8CAQAAAAGQAgEAAAABkQIBAAAAAZICQAAAAAGTAgEAAAABlAJAAAAAAQIAAAAFACAhAACTBQAgHQMAAK0EACAQAACvBAAgEQAAsAQAIBIAALEEACDAAQEAAAABwwFAAAAAAcQBQAAAAAHYAQAAAIwCAukBAQAAAAH9AQEAAAAB_gEBAAAAAf8BAgAAAAGAAgEAAAABgQICAAAAAYICAQAAAAGDAgEAAAABhQIAAACFAgKGAgEAAAABiAIAAACIAgKKAgAAAIoCAowCAgAAAAGNAgIAAAABjgIBAAAAAY8CAQAAAAGQAgEAAAABkQIBAAAAAZICQAAAAAGTAgEAAAABlAJAAAAAAQIAAAAFACAhAACVBQAgHAMAAPEDACAEAADyAwAgBwAA8wMAIAgAAPQDACAJAAD1AwAgCgAA9gMAIMABAQAAAAHDAUAAAAABxAFAAAAAAdgBAAAA7wEC6QEBAAAAAeoBAQAAAAHrAQEAAAAB7QEAAADtAQLvAQIAAAAB8AECAAAAAfEBAgAAAAHyAQIAAAAB8wECAAAAAfQBAQAAAAH1AQEAAAAB9gEBAAAAAfcBAQAAAAH4AUAAAAAB-QFAAAAAAfoBAgAAAAH7AQAAAO8BA_wBQAAAAAECAAAAJQAgIQAAlwUAIAMAAAADACAhAACTBQAgIgAAmwUAIB8AAAADACADAACBBAAgDwAAggQAIBEAAIQEACASAACFBAAgGgAAmwUAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdgBAACABIwCIukBAQD8AgAh_QEBAPwCACH-AQEA_AIAIf8BAgCQAwAhgAIBAPwCACGBAgIAkAMAIYICAQD8AgAhgwIBAPwCACGFAgAA_QOFAiKGAgEAggMAIYgCAAD-A4gCIooCAAD_A4oCIowCAgCQAwAhjQICAJADACGOAgEA_AIAIY8CAQCCAwAhkAIBAIIDACGRAgEAggMAIZICQACZAwAhkwIBAIIDACGUAkAAmQMAIR0DAACBBAAgDwAAggQAIBEAAIQEACASAACFBAAgwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh2AEAAIAEjAIi6QEBAPwCACH9AQEA_AIAIf4BAQD8AgAh_wECAJADACGAAgEA_AIAIYECAgCQAwAhggIBAPwCACGDAgEA_AIAIYUCAAD9A4UCIoYCAQCCAwAhiAIAAP4DiAIiigIAAP8DigIijAICAJADACGNAgIAkAMAIY4CAQD8AgAhjwIBAIIDACGQAgEAggMAIZECAQCCAwAhkgJAAJkDACGTAgEAggMAIZQCQACZAwAhAwAAAAMAICEAAJUFACAiAACeBQAgHwAAAAMAIAMAAIEEACAQAACDBAAgEQAAhAQAIBIAAIUEACAaAACeBQAgwAEBAPwCACHDAUAA_QIAIcQBQAD9AgAh2AEAAIAEjAIi6QEBAPwCACH9AQEA_AIAIf4BAQD8AgAh_wECAJADACGAAgEA_AIAIYECAgCQAwAhggIBAPwCACGDAgEA_AIAIYUCAAD9A4UCIoYCAQCCAwAhiAIAAP4DiAIiigIAAP8DigIijAICAJADACGNAgIAkAMAIY4CAQD8AgAhjwIBAIIDACGQAgEAggMAIZECAQCCAwAhkgJAAJkDACGTAgEAggMAIZQCQACZAwAhHQMAAIEEACAQAACDBAAgEQAAhAQAIBIAAIUEACDAAQEA_AIAIcMBQAD9AgAhxAFAAP0CACHYAQAAgASMAiLpAQEA_AIAIf0BAQD8AgAh_gEBAPwCACH_AQIAkAMAIYACAQD8AgAhgQICAJADACGCAgEA_AIAIYMCAQD8AgAhhQIAAP0DhQIihgIBAIIDACGIAgAA_gOIAiKKAgAA_wOKAiKMAgIAkAMAIY0CAgCQAwAhjgIBAPwCACGPAgEAggMAIZACAQCCAwAhkQIBAIIDACGSAkAAmQMAIZMCAQCCAwAhlAJAAJkDACEDAAAACwAgIQAAlwUAICIAAKEFACAeAAAACwAgAwAArQMAIAQAAK4DACAHAACvAwAgCAAAsAMAIAkAALEDACAKAACyAwAgGgAAoQUAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdgBAACrA-8BIukBAQD8AgAh6gEBAPwCACHrAQEA_AIAIe0BAACqA-0BIu8BAgCQAwAh8AECAJADACHxAQIAkAMAIfIBAgCQAwAh8wECAJADACH0AQEA_AIAIfUBAQD8AgAh9gEBAPwCACH3AQEAggMAIfgBQACZAwAh-QFAAJkDACH6AQIAkAMAIfsBAACsA-8BI_wBQACZAwAhHAMAAK0DACAEAACuAwAgBwAArwMAIAgAALADACAJAACxAwAgCgAAsgMAIMABAQD8AgAhwwFAAP0CACHEAUAA_QIAIdgBAACrA-8BIukBAQD8AgAh6gEBAPwCACHrAQEA_AIAIe0BAACqA-0BIu8BAgCQAwAh8AECAJADACHxAQIAkAMAIfIBAgCQAwAh8wECAJADACH0AQEA_AIAIfUBAQD8AgAh9gEBAPwCACH3AQEAggMAIfgBQACZAwAh-QFAAJkDACH6AQIAkAMAIfsBAACsA-8BI_wBQACZAwAhAwwACxMGAhQsBAYDAAEMAAoPCgMQIwMRJgQSJwUDBQwEDQACDgACCAMAAQQAAgcQBQgUBgkYBwocCAsdAwwACQIFAAQGAAIBBQAEAQUABAEFAAQFBx4ACB8ACSAACiEACyIABA8oABApABEqABIrAAITLQAULgAAAAADDAAQJwARKAASAAAAAwwAECcAESgAEgEFAAQBBQAEAwwAFycAGCgAGQAAAAMMABcnABgoABkAAAADDAAfJwAgKAAhAAAAAwwAHycAICgAIQEDAAEBAwABBQwAJicAKSgAKlkAJ1oAKAAAAAAABQwAJicAKSgAKlkAJ1oAKAIDAAEEAAICAwABBAACBQwALycAMigAM1kAMFoAMQAAAAAABQwALycAMigAM1kAMFoAMQIFAAQGAAICBQAEBgACBQwAOCcAOygAPFkAOVoAOgAAAAAABQwAOCcAOygAPFkAOVoAOgEFAAQBBQAEBQwAQScARCgARVkAQloAQwAAAAAABQwAQScARCgARVkAQloAQwEFAAQBBQAEBQwASicATSgATlkAS1oATAAAAAAABQwASicATSgATlkAS1oATAMF7QEEDQACDgACAwXzAQQNAAIOAAIDDABTJwBUKABVAAAAAwwAUycAVCgAVQAAAAMMAFsnAFwoAF0AAAADDABbJwBcKABdFQIBFi8BFzIBGDMBGTQBGzYBHDgMHTkNHjsBHz0MID4OIz8BJEABJUEMKUQPKkUTK0YILEcILUgILkkIL0oIMEwIMU4MMk8UM1EINFMMNVQVNlUIN1YIOFcMOVoWOlsaO10bPF4bPWEbPmIbP2MbQGUbQWcMQmgcQ2obRGwMRW0dRm4bR28bSHAMSXMeSnQiS3UCTHYCTXcCTngCT3kCUHsCUX0MUn4jU4ABAlSCAQxVgwEkVoQBAleFAQJYhgEMW4kBJVyKAStdiwEEXowBBF-NAQRgjgEEYY8BBGKRAQRjkwEMZJQBLGWWAQRmmAEMZ5kBLWiaAQRpmwEEapwBDGufAS5soAE0baEBBW6iAQVvowEFcKQBBXGlAQVypwEFc6kBDHSqATV1rAEFdq4BDHevATZ4sAEFebEBBXqyAQx7tQE3fLYBPX23AQZ-uAEGf7kBBoABugEGgQG7AQaCAb0BBoMBvwEMhAHAAT6FAcIBBoYBxAEMhwHFAT-IAcYBBokBxwEGigHIAQyLAcsBQIwBzAFGjQHNAQeOAc4BB48BzwEHkAHQAQeRAdEBB5IB0wEHkwHVAQyUAdYBR5UB2AEHlgHaAQyXAdsBSJgB3AEHmQHdAQeaAd4BDJsB4QFJnAHiAU-dAeMBA54B5AEDnwHlAQOgAeYBA6EB5wEDogHpAQOjAesBDKQB7AFQpQHvAQOmAfEBDKcB8gFRqAH0AQOpAfUBA6oB9gEMqwH5AVKsAfoBVq0B_AFXrgH9AVevAYACV7ABgQJXsQGCAleyAYQCV7MBhgIMtAGHAli1AYkCV7YBiwIMtwGMAlm4AY0CV7kBjgJXugGPAgy7AZICWrwBkwJe" +} + +async function decodeBase64AsWasm(wasmBase64: string): Promise { + const { Buffer } = await import('node:buffer') + const wasmArray = Buffer.from(wasmBase64, 'base64') + return new WebAssembly.Module(wasmArray) +} + +config.compilerWasm = { + getRuntime: async () => await import("@prisma/client/runtime/query_compiler_fast_bg.sqlite.js"), + + getQueryCompilerWasmModule: async () => { + const { wasm } = await import("@prisma/client/runtime/query_compiler_fast_bg.sqlite.wasm-base64.js") + return await decodeBase64AsWasm(wasm) + }, + + importName: "./query_compiler_fast_bg.js" +} + + + +export type LogOptions = + 'log' extends keyof ClientOptions ? ClientOptions['log'] extends Array ? Prisma.GetEvents : never : never + +export interface PrismaClientConstructor { + /** + * ## Prisma Client + * + * Type-safe database client for TypeScript + * @example + * ``` + * const prisma = new PrismaClient({ + * adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }) + * }) + * // Fetch zero or more Workspaces + * const workspaces = await prisma.workspace.findMany() + * ``` + * + * Read more in our [docs](https://pris.ly/d/client). + */ + + new < + Options extends Prisma.PrismaClientOptions = Prisma.PrismaClientOptions, + LogOpts extends LogOptions = LogOptions, + OmitOpts extends Prisma.PrismaClientOptions['omit'] = Options extends { omit: infer U } ? U : Prisma.PrismaClientOptions['omit'], + ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs + >(options: Prisma.Subset ): PrismaClient +} + +/** + * ## Prisma Client + * + * Type-safe database client for TypeScript + * @example + * ``` + * const prisma = new PrismaClient({ + * adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }) + * }) + * // Fetch zero or more Workspaces + * const workspaces = await prisma.workspace.findMany() + * ``` + * + * Read more in our [docs](https://pris.ly/d/client). + */ + +export interface PrismaClient< + in LogOpts extends Prisma.LogLevel = never, + in out OmitOpts extends Prisma.PrismaClientOptions['omit'] = undefined, + in out ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs +> { + [K: symbol]: { types: Prisma.TypeMap['other'] } + + $on(eventType: V, callback: (event: V extends 'query' ? Prisma.QueryEvent : Prisma.LogEvent) => void): PrismaClient; + + /** + * Connect with the database + */ + $connect(): runtime.Types.Utils.JsPromise; + + /** + * Disconnect from the database + */ + $disconnect(): runtime.Types.Utils.JsPromise; + +/** + * Executes a prepared raw query and returns the number of affected rows. + * @example + * ``` + * const result = await prisma.$executeRaw`UPDATE User SET cool = ${true} WHERE email = ${'user@email.com'};` + * ``` + * + * Read more in our [docs](https://pris.ly/d/raw-queries). + */ + $executeRaw(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise; + + /** + * Executes a raw query and returns the number of affected rows. + * Susceptible to SQL injections, see documentation. + * @example + * ``` + * const result = await prisma.$executeRawUnsafe('UPDATE User SET cool = $1 WHERE email = $2 ;', true, 'user@email.com') + * ``` + * + * Read more in our [docs](https://pris.ly/d/raw-queries). + */ + $executeRawUnsafe(query: string, ...values: any[]): Prisma.PrismaPromise; + + /** + * Performs a prepared raw query and returns the `SELECT` data. + * @example + * ``` + * const result = await prisma.$queryRaw`SELECT * FROM User WHERE id = ${1} OR email = ${'user@email.com'};` + * ``` + * + * Read more in our [docs](https://pris.ly/d/raw-queries). + */ + $queryRaw(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise; + + /** + * Performs a raw query and returns the `SELECT` data. + * Susceptible to SQL injections, see documentation. + * @example + * ``` + * const result = await prisma.$queryRawUnsafe('SELECT * FROM User WHERE id = $1 OR email = $2;', 1, 'user@email.com') + * ``` + * + * Read more in our [docs](https://pris.ly/d/raw-queries). + */ + $queryRawUnsafe(query: string, ...values: any[]): Prisma.PrismaPromise; + + + /** + * Allows the running of a sequence of read/write operations that are guaranteed to either succeed or fail as a whole. + * @example + * ``` + * const [george, bob, alice] = await prisma.$transaction([ + * prisma.user.create({ data: { name: 'George' } }), + * prisma.user.create({ data: { name: 'Bob' } }), + * prisma.user.create({ data: { name: 'Alice' } }), + * ]) + * ``` + * + * Read more in our [docs](https://www.prisma.io/docs/orm/prisma-client/queries/transactions). + */ + $transaction

[]>(arg: [...P], options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise> + + $transaction(fn: (prisma: Omit) => runtime.Types.Utils.JsPromise, options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise + + $extends: runtime.Types.Extensions.ExtendsHook<"extends", Prisma.TypeMapCb, ExtArgs, runtime.Types.Utils.Call, { + extArgs: ExtArgs + }>> + + /** + * `prisma.workspace`: Exposes CRUD operations for the **Workspace** model. + * Example usage: + * ```ts + * // Fetch zero or more Workspaces + * const workspaces = await prisma.workspace.findMany() + * ``` + */ + get workspace(): Prisma.WorkspaceDelegate; + + /** + * `prisma.sendJob`: Exposes CRUD operations for the **SendJob** model. + * Example usage: + * ```ts + * // Fetch zero or more SendJobs + * const sendJobs = await prisma.sendJob.findMany() + * ``` + */ + get sendJob(): Prisma.SendJobDelegate; + + /** + * `prisma.jobRunLog`: Exposes CRUD operations for the **JobRunLog** model. + * Example usage: + * ```ts + * // Fetch zero or more JobRunLogs + * const jobRunLogs = await prisma.jobRunLog.findMany() + * ``` + */ + get jobRunLog(): Prisma.JobRunLogDelegate; + + /** + * `prisma.inbox`: Exposes CRUD operations for the **Inbox** model. + * Example usage: + * ```ts + * // Fetch zero or more Inboxes + * const inboxes = await prisma.inbox.findMany() + * ``` + */ + get inbox(): Prisma.InboxDelegate; + + /** + * `prisma.warmupCampaign`: Exposes CRUD operations for the **WarmupCampaign** model. + * Example usage: + * ```ts + * // Fetch zero or more WarmupCampaigns + * const warmupCampaigns = await prisma.warmupCampaign.findMany() + * ``` + */ + get warmupCampaign(): Prisma.WarmupCampaignDelegate; + + /** + * `prisma.campaignSatellite`: Exposes CRUD operations for the **CampaignSatellite** model. + * Example usage: + * ```ts + * // Fetch zero or more CampaignSatellites + * const campaignSatellites = await prisma.campaignSatellite.findMany() + * ``` + */ + get campaignSatellite(): Prisma.CampaignSatelliteDelegate; + + /** + * `prisma.dailySchedule`: Exposes CRUD operations for the **DailySchedule** model. + * Example usage: + * ```ts + * // Fetch zero or more DailySchedules + * const dailySchedules = await prisma.dailySchedule.findMany() + * ``` + */ + get dailySchedule(): Prisma.DailyScheduleDelegate; + + /** + * `prisma.dailyStat`: Exposes CRUD operations for the **DailyStat** model. + * Example usage: + * ```ts + * // Fetch zero or more DailyStats + * const dailyStats = await prisma.dailyStat.findMany() + * ``` + */ + get dailyStat(): Prisma.DailyStatDelegate; + + /** + * `prisma.warmupLog`: Exposes CRUD operations for the **WarmupLog** model. + * Example usage: + * ```ts + * // Fetch zero or more WarmupLogs + * const warmupLogs = await prisma.warmupLog.findMany() + * ``` + */ + get warmupLog(): Prisma.WarmupLogDelegate; + + /** + * `prisma.setting`: Exposes CRUD operations for the **Setting** model. + * Example usage: + * ```ts + * // Fetch zero or more Settings + * const settings = await prisma.setting.findMany() + * ``` + */ + get setting(): Prisma.SettingDelegate; +} + +export function getPrismaClientClass(): PrismaClientConstructor { + return runtime.getPrismaClient(config) as unknown as PrismaClientConstructor +} diff --git a/apps/api/src/generated/prisma/internal/prismaNamespace.ts b/apps/api/src/generated/prisma/internal/prismaNamespace.ts new file mode 100644 index 0000000..f57906a --- /dev/null +++ b/apps/api/src/generated/prisma/internal/prismaNamespace.ts @@ -0,0 +1,1663 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * WARNING: This is an internal file that is subject to change! + * + * 🛑 Under no circumstances should you import this file directly! 🛑 + * + * All exports from this file are wrapped under a `Prisma` namespace object in the client.ts file. + * While this enables partial backward compatibility, it is not part of the stable public API. + * + * If you are looking for your Models, Enums, and Input Types, please import them from the respective + * model files in the `model` directory! + */ + +import * as runtime from "@prisma/client/runtime/client" +import type * as Prisma from "../models" +import { type PrismaClient } from "./class" + +export type * from '../models' + +export type DMMF = typeof runtime.DMMF + +export type PrismaPromise = runtime.Types.Public.PrismaPromise + +/** + * Prisma Errors + */ + +export const PrismaClientKnownRequestError = runtime.PrismaClientKnownRequestError +export type PrismaClientKnownRequestError = runtime.PrismaClientKnownRequestError + +export const PrismaClientUnknownRequestError = runtime.PrismaClientUnknownRequestError +export type PrismaClientUnknownRequestError = runtime.PrismaClientUnknownRequestError + +export const PrismaClientRustPanicError = runtime.PrismaClientRustPanicError +export type PrismaClientRustPanicError = runtime.PrismaClientRustPanicError + +export const PrismaClientInitializationError = runtime.PrismaClientInitializationError +export type PrismaClientInitializationError = runtime.PrismaClientInitializationError + +export const PrismaClientValidationError = runtime.PrismaClientValidationError +export type PrismaClientValidationError = runtime.PrismaClientValidationError + +/** + * Re-export of sql-template-tag + */ +export const sql = runtime.sqltag +export const empty = runtime.empty +export const join = runtime.join +export const raw = runtime.raw +export const Sql = runtime.Sql +export type Sql = runtime.Sql + + + +/** + * Decimal.js + */ +export const Decimal = runtime.Decimal +export type Decimal = runtime.Decimal + +export type DecimalJsLike = runtime.DecimalJsLike + +/** +* Extensions +*/ +export type Extension = runtime.Types.Extensions.UserArgs +export const getExtensionContext = runtime.Extensions.getExtensionContext +export type Args = runtime.Types.Public.Args +export type Payload = runtime.Types.Public.Payload +export type Result = runtime.Types.Public.Result +export type Exact = runtime.Types.Public.Exact + +export type PrismaVersion = { + client: string + engine: string +} + +/** + * Prisma Client JS version: 7.8.0 + * Query Engine version: 3c6e192761c0362d496ed980de936e2f3cebcd3a + */ +export const prismaVersion: PrismaVersion = { + client: "7.8.0", + engine: "3c6e192761c0362d496ed980de936e2f3cebcd3a" +} + +/** + * Utility Types + */ + +export type Bytes = runtime.Bytes +export type JsonObject = runtime.JsonObject +export type JsonArray = runtime.JsonArray +export type JsonValue = runtime.JsonValue +export type InputJsonObject = runtime.InputJsonObject +export type InputJsonArray = runtime.InputJsonArray +export type InputJsonValue = runtime.InputJsonValue + + +export const NullTypes = { + DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull), + JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull), + AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull), +} +/** + * Helper for filtering JSON entries that have `null` on the database (empty on the db) + * + * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field + */ +export const DbNull = runtime.DbNull + +/** + * Helper for filtering JSON entries that have JSON `null` values (not empty on the db) + * + * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field + */ +export const JsonNull = runtime.JsonNull + +/** + * Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull` + * + * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field + */ +export const AnyNull = runtime.AnyNull + + +type SelectAndInclude = { + select: any + include: any +} + +type SelectAndOmit = { + select: any + omit: any +} + +/** + * From T, pick a set of properties whose keys are in the union K + */ +type Prisma__Pick = { + [P in K]: T[P]; +}; + +export type Enumerable = T | Array; + +/** + * Subset + * @desc From `T` pick properties that exist in `U`. Simple version of Intersection + */ +export type Subset = { + [key in keyof T]: key extends keyof U ? T[key] : never; +}; + +/** + * SelectSubset + * @desc From `T` pick properties that exist in `U`. Simple version of Intersection. + * Additionally, it validates, if both select and include are present. If the case, it errors. + */ +export type SelectSubset = { + [key in keyof T]: key extends keyof U ? T[key] : never +} & + (T extends SelectAndInclude + ? 'Please either choose `select` or `include`.' + : T extends SelectAndOmit + ? 'Please either choose `select` or `omit`.' + : {}) + +/** + * Subset + Intersection + * @desc From `T` pick properties that exist in `U` and intersect `K` + */ +export type SubsetIntersection = { + [key in keyof T]: key extends keyof U ? T[key] : never +} & + K + +type Without = { [P in Exclude]?: never }; + +/** + * XOR is needed to have a real mutually exclusive union type + * https://stackoverflow.com/questions/42123407/does-typescript-support-mutually-exclusive-types + */ +export type XOR = + T extends object ? + U extends object ? + (Without & U) | (Without & T) + : U : T + + +/** + * Is T a Record? + */ +type IsObject = T extends Array +? False +: T extends Date +? False +: T extends Uint8Array +? False +: T extends BigInt +? False +: T extends object +? True +: False + + +/** + * If it's T[], return T + */ +export type UnEnumerate = T extends Array ? U : T + +/** + * From ts-toolbelt + */ + +type __Either = Omit & + { + // Merge all but K + [P in K]: Prisma__Pick // With K possibilities + }[K] + +type EitherStrict = Strict<__Either> + +type EitherLoose = ComputeRaw<__Either> + +type _Either< + O extends object, + K extends Key, + strict extends Boolean +> = { + 1: EitherStrict + 0: EitherLoose +}[strict] + +export type Either< + O extends object, + K extends Key, + strict extends Boolean = 1 +> = O extends unknown ? _Either : never + +export type Union = any + +export type PatchUndefined = { + [K in keyof O]: O[K] extends undefined ? At : O[K] +} & {} + +/** Helper Types for "Merge" **/ +export type IntersectOf = ( + U extends unknown ? (k: U) => void : never +) extends (k: infer I) => void + ? I + : never + +export type Overwrite = { + [K in keyof O]: K extends keyof O1 ? O1[K] : O[K]; +} & {}; + +type _Merge = IntersectOf; +}>>; + +type Key = string | number | symbol; +type AtStrict = O[K & keyof O]; +type AtLoose = O extends unknown ? AtStrict : never; +export type At = { + 1: AtStrict; + 0: AtLoose; +}[strict]; + +export type ComputeRaw = A extends Function ? A : { + [K in keyof A]: A[K]; +} & {}; + +export type OptionalFlat = { + [K in keyof O]?: O[K]; +} & {}; + +type _Record = { + [P in K]: T; +}; + +// cause typescript not to expand types and preserve names +type NoExpand = T extends unknown ? T : never; + +// this type assumes the passed object is entirely optional +export type AtLeast = NoExpand< + O extends unknown + ? | (K extends keyof O ? { [P in K]: O[P] } & O : O) + | {[P in keyof O as P extends K ? P : never]-?: O[P]} & O + : never>; + +type _Strict = U extends unknown ? U & OptionalFlat<_Record, keyof U>, never>> : never; + +export type Strict = ComputeRaw<_Strict>; +/** End Helper Types for "Merge" **/ + +export type Merge = ComputeRaw<_Merge>>; + +export type Boolean = True | False + +export type True = 1 + +export type False = 0 + +export type Not = { + 0: 1 + 1: 0 +}[B] + +export type Extends = [A1] extends [never] + ? 0 // anything `never` is false + : A1 extends A2 + ? 1 + : 0 + +export type Has = Not< + Extends, U1> +> + +export type Or = { + 0: { + 0: 0 + 1: 1 + } + 1: { + 0: 1 + 1: 1 + } +}[B1][B2] + +export type Keys = U extends unknown ? keyof U : never + +export type GetScalarType = O extends object ? { + [P in keyof T]: P extends keyof O + ? O[P] + : never +} : never + +type FieldPaths< + T, + U = Omit +> = IsObject extends True ? U : T + +export type GetHavingFields = { + [K in keyof T]: Or< + Or, Extends<'AND', K>>, + Extends<'NOT', K> + > extends True + ? // infer is only needed to not hit TS limit + // based on the brilliant idea of Pierre-Antoine Mills + // https://github.com/microsoft/TypeScript/issues/30188#issuecomment-478938437 + T[K] extends infer TK + ? GetHavingFields extends object ? Merge> : never> + : never + : {} extends FieldPaths + ? never + : K +}[keyof T] + +/** + * Convert tuple to union + */ +type _TupleToUnion = T extends (infer E)[] ? E : never +type TupleToUnion = _TupleToUnion +export type MaybeTupleToUnion = T extends any[] ? TupleToUnion : T + +/** + * Like `Pick`, but additionally can also accept an array of keys + */ +export type PickEnumerable | keyof T> = Prisma__Pick> + +/** + * Exclude all keys with underscores + */ +export type ExcludeUnderscoreKeys = T extends `_${string}` ? never : T + + +export type FieldRef = runtime.FieldRef + +type FieldRefInputType = Model extends never ? never : FieldRef + + +export const ModelName = { + Workspace: 'Workspace', + SendJob: 'SendJob', + JobRunLog: 'JobRunLog', + Inbox: 'Inbox', + WarmupCampaign: 'WarmupCampaign', + CampaignSatellite: 'CampaignSatellite', + DailySchedule: 'DailySchedule', + DailyStat: 'DailyStat', + WarmupLog: 'WarmupLog', + Setting: 'Setting' +} as const + +export type ModelName = (typeof ModelName)[keyof typeof ModelName] + + + +export interface TypeMapCb extends runtime.Types.Utils.Fn<{extArgs: runtime.Types.Extensions.InternalArgs }, runtime.Types.Utils.Record> { + returns: TypeMap +} + +export type TypeMap = { + globalOmitOptions: { + omit: GlobalOmitOptions + } + meta: { + modelProps: "workspace" | "sendJob" | "jobRunLog" | "inbox" | "warmupCampaign" | "campaignSatellite" | "dailySchedule" | "dailyStat" | "warmupLog" | "setting" + txIsolationLevel: TransactionIsolationLevel + } + model: { + Workspace: { + payload: Prisma.$WorkspacePayload + fields: Prisma.WorkspaceFieldRefs + operations: { + findUnique: { + args: Prisma.WorkspaceFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.WorkspaceFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.WorkspaceFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.WorkspaceFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.WorkspaceFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.WorkspaceCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.WorkspaceCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.WorkspaceCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.WorkspaceDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.WorkspaceUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.WorkspaceDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.WorkspaceUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.WorkspaceUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.WorkspaceUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.WorkspaceAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.WorkspaceGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.WorkspaceCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + SendJob: { + payload: Prisma.$SendJobPayload + fields: Prisma.SendJobFieldRefs + operations: { + findUnique: { + args: Prisma.SendJobFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.SendJobFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.SendJobFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.SendJobFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.SendJobFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.SendJobCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.SendJobCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.SendJobCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.SendJobDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.SendJobUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.SendJobDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.SendJobUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.SendJobUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.SendJobUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.SendJobAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.SendJobGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.SendJobCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + JobRunLog: { + payload: Prisma.$JobRunLogPayload + fields: Prisma.JobRunLogFieldRefs + operations: { + findUnique: { + args: Prisma.JobRunLogFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.JobRunLogFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.JobRunLogFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.JobRunLogFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.JobRunLogFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.JobRunLogCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.JobRunLogCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.JobRunLogCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.JobRunLogDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.JobRunLogUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.JobRunLogDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.JobRunLogUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.JobRunLogUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.JobRunLogUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.JobRunLogAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.JobRunLogGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.JobRunLogCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + Inbox: { + payload: Prisma.$InboxPayload + fields: Prisma.InboxFieldRefs + operations: { + findUnique: { + args: Prisma.InboxFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.InboxFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.InboxFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.InboxFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.InboxFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.InboxCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.InboxCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.InboxCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.InboxDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.InboxUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.InboxDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.InboxUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.InboxUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.InboxUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.InboxAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.InboxGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.InboxCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + WarmupCampaign: { + payload: Prisma.$WarmupCampaignPayload + fields: Prisma.WarmupCampaignFieldRefs + operations: { + findUnique: { + args: Prisma.WarmupCampaignFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.WarmupCampaignFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.WarmupCampaignFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.WarmupCampaignFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.WarmupCampaignFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.WarmupCampaignCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.WarmupCampaignCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.WarmupCampaignCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.WarmupCampaignDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.WarmupCampaignUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.WarmupCampaignDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.WarmupCampaignUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.WarmupCampaignUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.WarmupCampaignUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.WarmupCampaignAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.WarmupCampaignGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.WarmupCampaignCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + CampaignSatellite: { + payload: Prisma.$CampaignSatellitePayload + fields: Prisma.CampaignSatelliteFieldRefs + operations: { + findUnique: { + args: Prisma.CampaignSatelliteFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.CampaignSatelliteFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.CampaignSatelliteFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.CampaignSatelliteFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.CampaignSatelliteFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.CampaignSatelliteCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.CampaignSatelliteCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.CampaignSatelliteCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.CampaignSatelliteDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.CampaignSatelliteUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.CampaignSatelliteDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.CampaignSatelliteUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.CampaignSatelliteUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.CampaignSatelliteUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.CampaignSatelliteAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.CampaignSatelliteGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.CampaignSatelliteCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + DailySchedule: { + payload: Prisma.$DailySchedulePayload + fields: Prisma.DailyScheduleFieldRefs + operations: { + findUnique: { + args: Prisma.DailyScheduleFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.DailyScheduleFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.DailyScheduleFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.DailyScheduleFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.DailyScheduleFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.DailyScheduleCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.DailyScheduleCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.DailyScheduleCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.DailyScheduleDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.DailyScheduleUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.DailyScheduleDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.DailyScheduleUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.DailyScheduleUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.DailyScheduleUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.DailyScheduleAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.DailyScheduleGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.DailyScheduleCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + DailyStat: { + payload: Prisma.$DailyStatPayload + fields: Prisma.DailyStatFieldRefs + operations: { + findUnique: { + args: Prisma.DailyStatFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.DailyStatFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.DailyStatFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.DailyStatFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.DailyStatFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.DailyStatCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.DailyStatCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.DailyStatCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.DailyStatDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.DailyStatUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.DailyStatDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.DailyStatUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.DailyStatUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.DailyStatUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.DailyStatAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.DailyStatGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.DailyStatCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + WarmupLog: { + payload: Prisma.$WarmupLogPayload + fields: Prisma.WarmupLogFieldRefs + operations: { + findUnique: { + args: Prisma.WarmupLogFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.WarmupLogFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.WarmupLogFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.WarmupLogFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.WarmupLogFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.WarmupLogCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.WarmupLogCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.WarmupLogCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.WarmupLogDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.WarmupLogUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.WarmupLogDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.WarmupLogUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.WarmupLogUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.WarmupLogUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.WarmupLogAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.WarmupLogGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.WarmupLogCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + Setting: { + payload: Prisma.$SettingPayload + fields: Prisma.SettingFieldRefs + operations: { + findUnique: { + args: Prisma.SettingFindUniqueArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findUniqueOrThrow: { + args: Prisma.SettingFindUniqueOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findFirst: { + args: Prisma.SettingFindFirstArgs + result: runtime.Types.Utils.PayloadToResult | null + } + findFirstOrThrow: { + args: Prisma.SettingFindFirstOrThrowArgs + result: runtime.Types.Utils.PayloadToResult + } + findMany: { + args: Prisma.SettingFindManyArgs + result: runtime.Types.Utils.PayloadToResult[] + } + create: { + args: Prisma.SettingCreateArgs + result: runtime.Types.Utils.PayloadToResult + } + createMany: { + args: Prisma.SettingCreateManyArgs + result: BatchPayload + } + createManyAndReturn: { + args: Prisma.SettingCreateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + delete: { + args: Prisma.SettingDeleteArgs + result: runtime.Types.Utils.PayloadToResult + } + update: { + args: Prisma.SettingUpdateArgs + result: runtime.Types.Utils.PayloadToResult + } + deleteMany: { + args: Prisma.SettingDeleteManyArgs + result: BatchPayload + } + updateMany: { + args: Prisma.SettingUpdateManyArgs + result: BatchPayload + } + updateManyAndReturn: { + args: Prisma.SettingUpdateManyAndReturnArgs + result: runtime.Types.Utils.PayloadToResult[] + } + upsert: { + args: Prisma.SettingUpsertArgs + result: runtime.Types.Utils.PayloadToResult + } + aggregate: { + args: Prisma.SettingAggregateArgs + result: runtime.Types.Utils.Optional + } + groupBy: { + args: Prisma.SettingGroupByArgs + result: runtime.Types.Utils.Optional[] + } + count: { + args: Prisma.SettingCountArgs + result: runtime.Types.Utils.Optional | number + } + } + } + } +} & { + other: { + payload: any + operations: { + $executeRaw: { + args: [query: TemplateStringsArray | Sql, ...values: any[]], + result: any + } + $executeRawUnsafe: { + args: [query: string, ...values: any[]], + result: any + } + $queryRaw: { + args: [query: TemplateStringsArray | Sql, ...values: any[]], + result: any + } + $queryRawUnsafe: { + args: [query: string, ...values: any[]], + result: any + } + } + } +} + +/** + * Enums + */ + +export const TransactionIsolationLevel = runtime.makeStrictEnum({ + Serializable: 'Serializable' +} as const) + +export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel] + + +export const WorkspaceScalarFieldEnum = { + id: 'id', + name: 'name', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type WorkspaceScalarFieldEnum = (typeof WorkspaceScalarFieldEnum)[keyof typeof WorkspaceScalarFieldEnum] + + +export const SendJobScalarFieldEnum = { + id: 'id', + campaignId: 'campaignId', + scheduledAt: 'scheduledAt', + status: 'status', + idempotencyKey: 'idempotencyKey', + processedAt: 'processedAt', + errorMessage: 'errorMessage', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type SendJobScalarFieldEnum = (typeof SendJobScalarFieldEnum)[keyof typeof SendJobScalarFieldEnum] + + +export const JobRunLogScalarFieldEnum = { + id: 'id', + jobName: 'jobName', + ranAt: 'ranAt', + success: 'success', + message: 'message' +} as const + +export type JobRunLogScalarFieldEnum = (typeof JobRunLogScalarFieldEnum)[keyof typeof JobRunLogScalarFieldEnum] + + +export const InboxScalarFieldEnum = { + id: 'id', + workspaceId: 'workspaceId', + email: 'email', + smtpHost: 'smtpHost', + smtpPort: 'smtpPort', + imapHost: 'imapHost', + imapPort: 'imapPort', + username: 'username', + password: 'password', + authType: 'authType', + oauthRefreshToken: 'oauthRefreshToken', + role: 'role', + provider: 'provider', + status: 'status', + dailyLimit: 'dailyLimit', + currentRamp: 'currentRamp', + industry: 'industry', + senderName: 'senderName', + companyName: 'companyName', + lastError: 'lastError', + errorAt: 'errorAt', + spamFolderPath: 'spamFolderPath', + lastSpamRescuedAt: 'lastSpamRescuedAt', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type InboxScalarFieldEnum = (typeof InboxScalarFieldEnum)[keyof typeof InboxScalarFieldEnum] + + +export const WarmupCampaignScalarFieldEnum = { + id: 'id', + workspaceId: 'workspaceId', + primaryMailboxId: 'primaryMailboxId', + name: 'name', + recipe: 'recipe', + status: 'status', + durationDays: 'durationDays', + minEmailsPerDay: 'minEmailsPerDay', + maxEmailsPerDay: 'maxEmailsPerDay', + replyRatePercent: 'replyRatePercent', + maintenanceVolumePercent: 'maintenanceVolumePercent', + timezone: 'timezone', + sendWindowStart: 'sendWindowStart', + sendWindowEnd: 'sendWindowEnd', + customSchedule: 'customSchedule', + startedAt: 'startedAt', + endsAt: 'endsAt', + currentDay: 'currentDay', + pausedFromStatus: 'pausedFromStatus', + lastSendAt: 'lastSendAt', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type WarmupCampaignScalarFieldEnum = (typeof WarmupCampaignScalarFieldEnum)[keyof typeof WarmupCampaignScalarFieldEnum] + + +export const CampaignSatelliteScalarFieldEnum = { + id: 'id', + campaignId: 'campaignId', + satelliteMailboxId: 'satelliteMailboxId', + weight: 'weight', + active: 'active', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type CampaignSatelliteScalarFieldEnum = (typeof CampaignSatelliteScalarFieldEnum)[keyof typeof CampaignSatelliteScalarFieldEnum] + + +export const DailyScheduleScalarFieldEnum = { + id: 'id', + campaignId: 'campaignId', + dayIndex: 'dayIndex', + date: 'date', + plannedSends: 'plannedSends', + plannedReplies: 'plannedReplies', + actualSends: 'actualSends', + actualReplies: 'actualReplies', + spamDetected: 'spamDetected', + spamRescued: 'spamRescued', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type DailyScheduleScalarFieldEnum = (typeof DailyScheduleScalarFieldEnum)[keyof typeof DailyScheduleScalarFieldEnum] + + +export const DailyStatScalarFieldEnum = { + id: 'id', + campaignId: 'campaignId', + date: 'date', + dayIndex: 'dayIndex', + plannedSends: 'plannedSends', + plannedReplies: 'plannedReplies', + actualSends: 'actualSends', + actualReplies: 'actualReplies', + spamDetected: 'spamDetected', + spamRescued: 'spamRescued', + replyRateActual: 'replyRateActual', + inboxPlacementRate: 'inboxPlacementRate', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type DailyStatScalarFieldEnum = (typeof DailyStatScalarFieldEnum)[keyof typeof DailyStatScalarFieldEnum] + + +export const WarmupLogScalarFieldEnum = { + id: 'id', + campaignId: 'campaignId', + senderId: 'senderId', + receiverId: 'receiverId', + messageId: 'messageId', + inReplyTo: 'inReplyTo', + subject: 'subject', + body: 'body', + status: 'status', + placement: 'placement', + rescuedFromSpam: 'rescuedFromSpam', + validationResult: 'validationResult', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type WarmupLogScalarFieldEnum = (typeof WarmupLogScalarFieldEnum)[keyof typeof WarmupLogScalarFieldEnum] + + +export const SettingScalarFieldEnum = { + id: 'id', + key: 'key', + value: 'value', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type SettingScalarFieldEnum = (typeof SettingScalarFieldEnum)[keyof typeof SettingScalarFieldEnum] + + +export const SortOrder = { + asc: 'asc', + desc: 'desc' +} as const + +export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder] + + +export const NullsOrder = { + first: 'first', + last: 'last' +} as const + +export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder] + + + +/** + * Field references + */ + + +/** + * Reference to a field of type 'String' + */ +export type StringFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'String'> + + + +/** + * Reference to a field of type 'DateTime' + */ +export type DateTimeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'DateTime'> + + + +/** + * Reference to a field of type 'SendJobStatus' + */ +export type EnumSendJobStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'SendJobStatus'> + + + +/** + * Reference to a field of type 'Boolean' + */ +export type BooleanFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Boolean'> + + + +/** + * Reference to a field of type 'Int' + */ +export type IntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Int'> + + + +/** + * Reference to a field of type 'MailboxAuthType' + */ +export type EnumMailboxAuthTypeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'MailboxAuthType'> + + + +/** + * Reference to a field of type 'MailboxRole' + */ +export type EnumMailboxRoleFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'MailboxRole'> + + + +/** + * Reference to a field of type 'MailboxProvider' + */ +export type EnumMailboxProviderFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'MailboxProvider'> + + + +/** + * Reference to a field of type 'InboxStatus' + */ +export type EnumInboxStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'InboxStatus'> + + + +/** + * Reference to a field of type 'CampaignRecipe' + */ +export type EnumCampaignRecipeFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'CampaignRecipe'> + + + +/** + * Reference to a field of type 'CampaignStatus' + */ +export type EnumCampaignStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'CampaignStatus'> + + + +/** + * Reference to a field of type 'Float' + */ +export type FloatFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Float'> + + + +/** + * Reference to a field of type 'WarmupStatus' + */ +export type EnumWarmupStatusFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'WarmupStatus'> + + +/** + * Batch Payload for updateMany & deleteMany & createMany + */ +export type BatchPayload = { + count: number +} + +export const defineExtension = runtime.Extensions.defineExtension as unknown as runtime.Types.Extensions.ExtendsHook<"define", TypeMapCb, runtime.Types.Extensions.DefaultArgs> +export type DefaultPrismaClient = PrismaClient +export type ErrorFormat = 'pretty' | 'colorless' | 'minimal' +export type PrismaClientOptions = ({ + /** + * Instance of a Driver Adapter, e.g., like one provided by `@prisma/adapter-pg`. + */ + adapter: runtime.SqlDriverAdapterFactory + accelerateUrl?: never +} | { + /** + * Prisma Accelerate URL allowing the client to connect through Accelerate instead of a direct database. + */ + accelerateUrl: string + adapter?: never +}) & { + /** + * @default "colorless" + */ + errorFormat?: ErrorFormat + /** + * @example + * ``` + * // Shorthand for `emit: 'stdout'` + * log: ['query', 'info', 'warn', 'error'] + * + * // Emit as events only + * log: [ + * { emit: 'event', level: 'query' }, + * { emit: 'event', level: 'info' }, + * { emit: 'event', level: 'warn' } + * { emit: 'event', level: 'error' } + * ] + * + * / Emit as events and log to stdout + * og: [ + * { emit: 'stdout', level: 'query' }, + * { emit: 'stdout', level: 'info' }, + * { emit: 'stdout', level: 'warn' } + * { emit: 'stdout', level: 'error' } + * + * ``` + * Read more in our [docs](https://pris.ly/d/logging). + */ + log?: (LogLevel | LogDefinition)[] + /** + * The default values for transactionOptions + * maxWait ?= 2000 + * timeout ?= 5000 + */ + transactionOptions?: { + maxWait?: number + timeout?: number + isolationLevel?: TransactionIsolationLevel + } + /** + * Global configuration for omitting model fields by default. + * + * @example + * ``` + * const prisma = new PrismaClient({ + * omit: { + * user: { + * password: true + * } + * } + * }) + * ``` + */ + omit?: GlobalOmitConfig + /** + * SQL commenter plugins that add metadata to SQL queries as comments. + * Comments follow the sqlcommenter format: https://google.github.io/sqlcommenter/ + * + * @example + * ``` + * const prisma = new PrismaClient({ + * adapter, + * comments: [ + * traceContext(), + * queryInsights(), + * ], + * }) + * ``` + */ + comments?: runtime.SqlCommenterPlugin[] + /** + * Optional maximum size for the query plan cache. If not provided, a default size will be used. + * A value of `0` can be used to disable the cache entirely. A higher cache size can improve + * performance for applications that execute a large number of unique queries, while a smaller + * cache size can reduce memory usage. + * + * @example + * ``` + * const prisma = new PrismaClient({ + * adapter, + * queryPlanCacheMaxSize: 100, + * }) + * ``` + */ + queryPlanCacheMaxSize?: number +} +export type GlobalOmitConfig = { + workspace?: Prisma.WorkspaceOmit + sendJob?: Prisma.SendJobOmit + jobRunLog?: Prisma.JobRunLogOmit + inbox?: Prisma.InboxOmit + warmupCampaign?: Prisma.WarmupCampaignOmit + campaignSatellite?: Prisma.CampaignSatelliteOmit + dailySchedule?: Prisma.DailyScheduleOmit + dailyStat?: Prisma.DailyStatOmit + warmupLog?: Prisma.WarmupLogOmit + setting?: Prisma.SettingOmit +} + +/* Types for Logging */ +export type LogLevel = 'info' | 'query' | 'warn' | 'error' +export type LogDefinition = { + level: LogLevel + emit: 'stdout' | 'event' +} + +export type CheckIsLogLevel = T extends LogLevel ? T : never; + +export type GetLogType = CheckIsLogLevel< + T extends LogDefinition ? T['level'] : T +>; + +export type GetEvents = T extends Array + ? GetLogType + : never; + +export type QueryEvent = { + timestamp: Date + query: string + params: string + duration: number + target: string +} + +export type LogEvent = { + timestamp: Date + message: string + target: string +} +/* End Types for Logging */ + + +export type PrismaAction = + | 'findUnique' + | 'findUniqueOrThrow' + | 'findMany' + | 'findFirst' + | 'findFirstOrThrow' + | 'create' + | 'createMany' + | 'createManyAndReturn' + | 'update' + | 'updateMany' + | 'updateManyAndReturn' + | 'upsert' + | 'delete' + | 'deleteMany' + | 'executeRaw' + | 'queryRaw' + | 'aggregate' + | 'count' + | 'runCommandRaw' + | 'findRaw' + | 'groupBy' + +/** + * `PrismaClient` proxy available in interactive transactions. + */ +export type TransactionClient = Omit + diff --git a/apps/api/src/generated/prisma/internal/prismaNamespaceBrowser.ts b/apps/api/src/generated/prisma/internal/prismaNamespaceBrowser.ts new file mode 100644 index 0000000..5bac16d --- /dev/null +++ b/apps/api/src/generated/prisma/internal/prismaNamespaceBrowser.ts @@ -0,0 +1,270 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * WARNING: This is an internal file that is subject to change! + * + * 🛑 Under no circumstances should you import this file directly! 🛑 + * + * All exports from this file are wrapped under a `Prisma` namespace object in the browser.ts file. + * While this enables partial backward compatibility, it is not part of the stable public API. + * + * If you are looking for your Models, Enums, and Input Types, please import them from the respective + * model files in the `model` directory! + */ + +import * as runtime from "@prisma/client/runtime/index-browser" + +export type * from '../models' +export type * from './prismaNamespace' + +export const Decimal = runtime.Decimal + + +export const NullTypes = { + DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull), + JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull), + AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull), +} +/** + * Helper for filtering JSON entries that have `null` on the database (empty on the db) + * + * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field + */ +export const DbNull = runtime.DbNull + +/** + * Helper for filtering JSON entries that have JSON `null` values (not empty on the db) + * + * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field + */ +export const JsonNull = runtime.JsonNull + +/** + * Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull` + * + * @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field + */ +export const AnyNull = runtime.AnyNull + + +export const ModelName = { + Workspace: 'Workspace', + SendJob: 'SendJob', + JobRunLog: 'JobRunLog', + Inbox: 'Inbox', + WarmupCampaign: 'WarmupCampaign', + CampaignSatellite: 'CampaignSatellite', + DailySchedule: 'DailySchedule', + DailyStat: 'DailyStat', + WarmupLog: 'WarmupLog', + Setting: 'Setting' +} as const + +export type ModelName = (typeof ModelName)[keyof typeof ModelName] + +/* + * Enums + */ + +export const TransactionIsolationLevel = runtime.makeStrictEnum({ + Serializable: 'Serializable' +} as const) + +export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel] + + +export const WorkspaceScalarFieldEnum = { + id: 'id', + name: 'name', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type WorkspaceScalarFieldEnum = (typeof WorkspaceScalarFieldEnum)[keyof typeof WorkspaceScalarFieldEnum] + + +export const SendJobScalarFieldEnum = { + id: 'id', + campaignId: 'campaignId', + scheduledAt: 'scheduledAt', + status: 'status', + idempotencyKey: 'idempotencyKey', + processedAt: 'processedAt', + errorMessage: 'errorMessage', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type SendJobScalarFieldEnum = (typeof SendJobScalarFieldEnum)[keyof typeof SendJobScalarFieldEnum] + + +export const JobRunLogScalarFieldEnum = { + id: 'id', + jobName: 'jobName', + ranAt: 'ranAt', + success: 'success', + message: 'message' +} as const + +export type JobRunLogScalarFieldEnum = (typeof JobRunLogScalarFieldEnum)[keyof typeof JobRunLogScalarFieldEnum] + + +export const InboxScalarFieldEnum = { + id: 'id', + workspaceId: 'workspaceId', + email: 'email', + smtpHost: 'smtpHost', + smtpPort: 'smtpPort', + imapHost: 'imapHost', + imapPort: 'imapPort', + username: 'username', + password: 'password', + authType: 'authType', + oauthRefreshToken: 'oauthRefreshToken', + role: 'role', + provider: 'provider', + status: 'status', + dailyLimit: 'dailyLimit', + currentRamp: 'currentRamp', + industry: 'industry', + senderName: 'senderName', + companyName: 'companyName', + lastError: 'lastError', + errorAt: 'errorAt', + spamFolderPath: 'spamFolderPath', + lastSpamRescuedAt: 'lastSpamRescuedAt', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type InboxScalarFieldEnum = (typeof InboxScalarFieldEnum)[keyof typeof InboxScalarFieldEnum] + + +export const WarmupCampaignScalarFieldEnum = { + id: 'id', + workspaceId: 'workspaceId', + primaryMailboxId: 'primaryMailboxId', + name: 'name', + recipe: 'recipe', + status: 'status', + durationDays: 'durationDays', + minEmailsPerDay: 'minEmailsPerDay', + maxEmailsPerDay: 'maxEmailsPerDay', + replyRatePercent: 'replyRatePercent', + maintenanceVolumePercent: 'maintenanceVolumePercent', + timezone: 'timezone', + sendWindowStart: 'sendWindowStart', + sendWindowEnd: 'sendWindowEnd', + customSchedule: 'customSchedule', + startedAt: 'startedAt', + endsAt: 'endsAt', + currentDay: 'currentDay', + pausedFromStatus: 'pausedFromStatus', + lastSendAt: 'lastSendAt', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type WarmupCampaignScalarFieldEnum = (typeof WarmupCampaignScalarFieldEnum)[keyof typeof WarmupCampaignScalarFieldEnum] + + +export const CampaignSatelliteScalarFieldEnum = { + id: 'id', + campaignId: 'campaignId', + satelliteMailboxId: 'satelliteMailboxId', + weight: 'weight', + active: 'active', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type CampaignSatelliteScalarFieldEnum = (typeof CampaignSatelliteScalarFieldEnum)[keyof typeof CampaignSatelliteScalarFieldEnum] + + +export const DailyScheduleScalarFieldEnum = { + id: 'id', + campaignId: 'campaignId', + dayIndex: 'dayIndex', + date: 'date', + plannedSends: 'plannedSends', + plannedReplies: 'plannedReplies', + actualSends: 'actualSends', + actualReplies: 'actualReplies', + spamDetected: 'spamDetected', + spamRescued: 'spamRescued', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type DailyScheduleScalarFieldEnum = (typeof DailyScheduleScalarFieldEnum)[keyof typeof DailyScheduleScalarFieldEnum] + + +export const DailyStatScalarFieldEnum = { + id: 'id', + campaignId: 'campaignId', + date: 'date', + dayIndex: 'dayIndex', + plannedSends: 'plannedSends', + plannedReplies: 'plannedReplies', + actualSends: 'actualSends', + actualReplies: 'actualReplies', + spamDetected: 'spamDetected', + spamRescued: 'spamRescued', + replyRateActual: 'replyRateActual', + inboxPlacementRate: 'inboxPlacementRate', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type DailyStatScalarFieldEnum = (typeof DailyStatScalarFieldEnum)[keyof typeof DailyStatScalarFieldEnum] + + +export const WarmupLogScalarFieldEnum = { + id: 'id', + campaignId: 'campaignId', + senderId: 'senderId', + receiverId: 'receiverId', + messageId: 'messageId', + inReplyTo: 'inReplyTo', + subject: 'subject', + body: 'body', + status: 'status', + placement: 'placement', + rescuedFromSpam: 'rescuedFromSpam', + validationResult: 'validationResult', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type WarmupLogScalarFieldEnum = (typeof WarmupLogScalarFieldEnum)[keyof typeof WarmupLogScalarFieldEnum] + + +export const SettingScalarFieldEnum = { + id: 'id', + key: 'key', + value: 'value', + createdAt: 'createdAt', + updatedAt: 'updatedAt' +} as const + +export type SettingScalarFieldEnum = (typeof SettingScalarFieldEnum)[keyof typeof SettingScalarFieldEnum] + + +export const SortOrder = { + asc: 'asc', + desc: 'desc' +} as const + +export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder] + + +export const NullsOrder = { + first: 'first', + last: 'last' +} as const + +export type NullsOrder = (typeof NullsOrder)[keyof typeof NullsOrder] + diff --git a/apps/api/src/generated/prisma/models.ts b/apps/api/src/generated/prisma/models.ts new file mode 100644 index 0000000..b6682e9 --- /dev/null +++ b/apps/api/src/generated/prisma/models.ts @@ -0,0 +1,21 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This is a barrel export file for all models and their related types. + * + * 🟢 You can import this file directly. + */ +export type * from './models/Workspace' +export type * from './models/SendJob' +export type * from './models/JobRunLog' +export type * from './models/Inbox' +export type * from './models/WarmupCampaign' +export type * from './models/CampaignSatellite' +export type * from './models/DailySchedule' +export type * from './models/DailyStat' +export type * from './models/WarmupLog' +export type * from './models/Setting' +export type * from './commonInputTypes' \ No newline at end of file diff --git a/apps/api/src/generated/prisma/models/CampaignSatellite.ts b/apps/api/src/generated/prisma/models/CampaignSatellite.ts new file mode 100644 index 0000000..5b3aaae --- /dev/null +++ b/apps/api/src/generated/prisma/models/CampaignSatellite.ts @@ -0,0 +1,1593 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `CampaignSatellite` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums" +import type * as Prisma from "../internal/prismaNamespace" + +/** + * Model CampaignSatellite + * + */ +export type CampaignSatelliteModel = runtime.Types.Result.DefaultSelection + +export type AggregateCampaignSatellite = { + _count: CampaignSatelliteCountAggregateOutputType | null + _avg: CampaignSatelliteAvgAggregateOutputType | null + _sum: CampaignSatelliteSumAggregateOutputType | null + _min: CampaignSatelliteMinAggregateOutputType | null + _max: CampaignSatelliteMaxAggregateOutputType | null +} + +export type CampaignSatelliteAvgAggregateOutputType = { + weight: number | null +} + +export type CampaignSatelliteSumAggregateOutputType = { + weight: number | null +} + +export type CampaignSatelliteMinAggregateOutputType = { + id: string | null + campaignId: string | null + satelliteMailboxId: string | null + weight: number | null + active: boolean | null + createdAt: Date | null + updatedAt: Date | null +} + +export type CampaignSatelliteMaxAggregateOutputType = { + id: string | null + campaignId: string | null + satelliteMailboxId: string | null + weight: number | null + active: boolean | null + createdAt: Date | null + updatedAt: Date | null +} + +export type CampaignSatelliteCountAggregateOutputType = { + id: number + campaignId: number + satelliteMailboxId: number + weight: number + active: number + createdAt: number + updatedAt: number + _all: number +} + + +export type CampaignSatelliteAvgAggregateInputType = { + weight?: true +} + +export type CampaignSatelliteSumAggregateInputType = { + weight?: true +} + +export type CampaignSatelliteMinAggregateInputType = { + id?: true + campaignId?: true + satelliteMailboxId?: true + weight?: true + active?: true + createdAt?: true + updatedAt?: true +} + +export type CampaignSatelliteMaxAggregateInputType = { + id?: true + campaignId?: true + satelliteMailboxId?: true + weight?: true + active?: true + createdAt?: true + updatedAt?: true +} + +export type CampaignSatelliteCountAggregateInputType = { + id?: true + campaignId?: true + satelliteMailboxId?: true + weight?: true + active?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type CampaignSatelliteAggregateArgs = { + /** + * Filter which CampaignSatellite to aggregate. + */ + where?: Prisma.CampaignSatelliteWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CampaignSatellites to fetch. + */ + orderBy?: Prisma.CampaignSatelliteOrderByWithRelationInput | Prisma.CampaignSatelliteOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.CampaignSatelliteWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CampaignSatellites from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CampaignSatellites. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned CampaignSatellites + **/ + _count?: true | CampaignSatelliteCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: CampaignSatelliteAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: CampaignSatelliteSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: CampaignSatelliteMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: CampaignSatelliteMaxAggregateInputType +} + +export type GetCampaignSatelliteAggregateType = { + [P in keyof T & keyof AggregateCampaignSatellite]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type CampaignSatelliteGroupByArgs = { + where?: Prisma.CampaignSatelliteWhereInput + orderBy?: Prisma.CampaignSatelliteOrderByWithAggregationInput | Prisma.CampaignSatelliteOrderByWithAggregationInput[] + by: Prisma.CampaignSatelliteScalarFieldEnum[] | Prisma.CampaignSatelliteScalarFieldEnum + having?: Prisma.CampaignSatelliteScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: CampaignSatelliteCountAggregateInputType | true + _avg?: CampaignSatelliteAvgAggregateInputType + _sum?: CampaignSatelliteSumAggregateInputType + _min?: CampaignSatelliteMinAggregateInputType + _max?: CampaignSatelliteMaxAggregateInputType +} + +export type CampaignSatelliteGroupByOutputType = { + id: string + campaignId: string + satelliteMailboxId: string + weight: number + active: boolean + createdAt: Date + updatedAt: Date + _count: CampaignSatelliteCountAggregateOutputType | null + _avg: CampaignSatelliteAvgAggregateOutputType | null + _sum: CampaignSatelliteSumAggregateOutputType | null + _min: CampaignSatelliteMinAggregateOutputType | null + _max: CampaignSatelliteMaxAggregateOutputType | null +} + +export type GetCampaignSatelliteGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof CampaignSatelliteGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type CampaignSatelliteWhereInput = { + AND?: Prisma.CampaignSatelliteWhereInput | Prisma.CampaignSatelliteWhereInput[] + OR?: Prisma.CampaignSatelliteWhereInput[] + NOT?: Prisma.CampaignSatelliteWhereInput | Prisma.CampaignSatelliteWhereInput[] + id?: Prisma.StringFilter<"CampaignSatellite"> | string + campaignId?: Prisma.StringFilter<"CampaignSatellite"> | string + satelliteMailboxId?: Prisma.StringFilter<"CampaignSatellite"> | string + weight?: Prisma.IntFilter<"CampaignSatellite"> | number + active?: Prisma.BoolFilter<"CampaignSatellite"> | boolean + createdAt?: Prisma.DateTimeFilter<"CampaignSatellite"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"CampaignSatellite"> | Date | string + campaign?: Prisma.XOR + satelliteMailbox?: Prisma.XOR +} + +export type CampaignSatelliteOrderByWithRelationInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + satelliteMailboxId?: Prisma.SortOrder + weight?: Prisma.SortOrder + active?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + campaign?: Prisma.WarmupCampaignOrderByWithRelationInput + satelliteMailbox?: Prisma.InboxOrderByWithRelationInput +} + +export type CampaignSatelliteWhereUniqueInput = Prisma.AtLeast<{ + id?: string + campaignId_satelliteMailboxId?: Prisma.CampaignSatelliteCampaignIdSatelliteMailboxIdCompoundUniqueInput + AND?: Prisma.CampaignSatelliteWhereInput | Prisma.CampaignSatelliteWhereInput[] + OR?: Prisma.CampaignSatelliteWhereInput[] + NOT?: Prisma.CampaignSatelliteWhereInput | Prisma.CampaignSatelliteWhereInput[] + campaignId?: Prisma.StringFilter<"CampaignSatellite"> | string + satelliteMailboxId?: Prisma.StringFilter<"CampaignSatellite"> | string + weight?: Prisma.IntFilter<"CampaignSatellite"> | number + active?: Prisma.BoolFilter<"CampaignSatellite"> | boolean + createdAt?: Prisma.DateTimeFilter<"CampaignSatellite"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"CampaignSatellite"> | Date | string + campaign?: Prisma.XOR + satelliteMailbox?: Prisma.XOR +}, "id" | "campaignId_satelliteMailboxId"> + +export type CampaignSatelliteOrderByWithAggregationInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + satelliteMailboxId?: Prisma.SortOrder + weight?: Prisma.SortOrder + active?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.CampaignSatelliteCountOrderByAggregateInput + _avg?: Prisma.CampaignSatelliteAvgOrderByAggregateInput + _max?: Prisma.CampaignSatelliteMaxOrderByAggregateInput + _min?: Prisma.CampaignSatelliteMinOrderByAggregateInput + _sum?: Prisma.CampaignSatelliteSumOrderByAggregateInput +} + +export type CampaignSatelliteScalarWhereWithAggregatesInput = { + AND?: Prisma.CampaignSatelliteScalarWhereWithAggregatesInput | Prisma.CampaignSatelliteScalarWhereWithAggregatesInput[] + OR?: Prisma.CampaignSatelliteScalarWhereWithAggregatesInput[] + NOT?: Prisma.CampaignSatelliteScalarWhereWithAggregatesInput | Prisma.CampaignSatelliteScalarWhereWithAggregatesInput[] + id?: Prisma.StringWithAggregatesFilter<"CampaignSatellite"> | string + campaignId?: Prisma.StringWithAggregatesFilter<"CampaignSatellite"> | string + satelliteMailboxId?: Prisma.StringWithAggregatesFilter<"CampaignSatellite"> | string + weight?: Prisma.IntWithAggregatesFilter<"CampaignSatellite"> | number + active?: Prisma.BoolWithAggregatesFilter<"CampaignSatellite"> | boolean + createdAt?: Prisma.DateTimeWithAggregatesFilter<"CampaignSatellite"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"CampaignSatellite"> | Date | string +} + +export type CampaignSatelliteCreateInput = { + id?: string + weight?: number + active?: boolean + createdAt?: Date | string + updatedAt?: Date | string + campaign: Prisma.WarmupCampaignCreateNestedOneWithoutSatellitesInput + satelliteMailbox: Prisma.InboxCreateNestedOneWithoutSatelliteCampaignsInput +} + +export type CampaignSatelliteUncheckedCreateInput = { + id?: string + campaignId: string + satelliteMailboxId: string + weight?: number + active?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CampaignSatelliteUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + weight?: Prisma.IntFieldUpdateOperationsInput | number + active?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + campaign?: Prisma.WarmupCampaignUpdateOneRequiredWithoutSatellitesNestedInput + satelliteMailbox?: Prisma.InboxUpdateOneRequiredWithoutSatelliteCampaignsNestedInput +} + +export type CampaignSatelliteUncheckedUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.StringFieldUpdateOperationsInput | string + satelliteMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + weight?: Prisma.IntFieldUpdateOperationsInput | number + active?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CampaignSatelliteCreateManyInput = { + id?: string + campaignId: string + satelliteMailboxId: string + weight?: number + active?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CampaignSatelliteUpdateManyMutationInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + weight?: Prisma.IntFieldUpdateOperationsInput | number + active?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CampaignSatelliteUncheckedUpdateManyInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.StringFieldUpdateOperationsInput | string + satelliteMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + weight?: Prisma.IntFieldUpdateOperationsInput | number + active?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CampaignSatelliteListRelationFilter = { + every?: Prisma.CampaignSatelliteWhereInput + some?: Prisma.CampaignSatelliteWhereInput + none?: Prisma.CampaignSatelliteWhereInput +} + +export type CampaignSatelliteOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type CampaignSatelliteCampaignIdSatelliteMailboxIdCompoundUniqueInput = { + campaignId: string + satelliteMailboxId: string +} + +export type CampaignSatelliteCountOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + satelliteMailboxId?: Prisma.SortOrder + weight?: Prisma.SortOrder + active?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type CampaignSatelliteAvgOrderByAggregateInput = { + weight?: Prisma.SortOrder +} + +export type CampaignSatelliteMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + satelliteMailboxId?: Prisma.SortOrder + weight?: Prisma.SortOrder + active?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type CampaignSatelliteMinOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + satelliteMailboxId?: Prisma.SortOrder + weight?: Prisma.SortOrder + active?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type CampaignSatelliteSumOrderByAggregateInput = { + weight?: Prisma.SortOrder +} + +export type CampaignSatelliteCreateNestedManyWithoutSatelliteMailboxInput = { + create?: Prisma.XOR | Prisma.CampaignSatelliteCreateWithoutSatelliteMailboxInput[] | Prisma.CampaignSatelliteUncheckedCreateWithoutSatelliteMailboxInput[] + connectOrCreate?: Prisma.CampaignSatelliteCreateOrConnectWithoutSatelliteMailboxInput | Prisma.CampaignSatelliteCreateOrConnectWithoutSatelliteMailboxInput[] + createMany?: Prisma.CampaignSatelliteCreateManySatelliteMailboxInputEnvelope + connect?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] +} + +export type CampaignSatelliteUncheckedCreateNestedManyWithoutSatelliteMailboxInput = { + create?: Prisma.XOR | Prisma.CampaignSatelliteCreateWithoutSatelliteMailboxInput[] | Prisma.CampaignSatelliteUncheckedCreateWithoutSatelliteMailboxInput[] + connectOrCreate?: Prisma.CampaignSatelliteCreateOrConnectWithoutSatelliteMailboxInput | Prisma.CampaignSatelliteCreateOrConnectWithoutSatelliteMailboxInput[] + createMany?: Prisma.CampaignSatelliteCreateManySatelliteMailboxInputEnvelope + connect?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] +} + +export type CampaignSatelliteUpdateManyWithoutSatelliteMailboxNestedInput = { + create?: Prisma.XOR | Prisma.CampaignSatelliteCreateWithoutSatelliteMailboxInput[] | Prisma.CampaignSatelliteUncheckedCreateWithoutSatelliteMailboxInput[] + connectOrCreate?: Prisma.CampaignSatelliteCreateOrConnectWithoutSatelliteMailboxInput | Prisma.CampaignSatelliteCreateOrConnectWithoutSatelliteMailboxInput[] + upsert?: Prisma.CampaignSatelliteUpsertWithWhereUniqueWithoutSatelliteMailboxInput | Prisma.CampaignSatelliteUpsertWithWhereUniqueWithoutSatelliteMailboxInput[] + createMany?: Prisma.CampaignSatelliteCreateManySatelliteMailboxInputEnvelope + set?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + disconnect?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + delete?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + connect?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + update?: Prisma.CampaignSatelliteUpdateWithWhereUniqueWithoutSatelliteMailboxInput | Prisma.CampaignSatelliteUpdateWithWhereUniqueWithoutSatelliteMailboxInput[] + updateMany?: Prisma.CampaignSatelliteUpdateManyWithWhereWithoutSatelliteMailboxInput | Prisma.CampaignSatelliteUpdateManyWithWhereWithoutSatelliteMailboxInput[] + deleteMany?: Prisma.CampaignSatelliteScalarWhereInput | Prisma.CampaignSatelliteScalarWhereInput[] +} + +export type CampaignSatelliteUncheckedUpdateManyWithoutSatelliteMailboxNestedInput = { + create?: Prisma.XOR | Prisma.CampaignSatelliteCreateWithoutSatelliteMailboxInput[] | Prisma.CampaignSatelliteUncheckedCreateWithoutSatelliteMailboxInput[] + connectOrCreate?: Prisma.CampaignSatelliteCreateOrConnectWithoutSatelliteMailboxInput | Prisma.CampaignSatelliteCreateOrConnectWithoutSatelliteMailboxInput[] + upsert?: Prisma.CampaignSatelliteUpsertWithWhereUniqueWithoutSatelliteMailboxInput | Prisma.CampaignSatelliteUpsertWithWhereUniqueWithoutSatelliteMailboxInput[] + createMany?: Prisma.CampaignSatelliteCreateManySatelliteMailboxInputEnvelope + set?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + disconnect?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + delete?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + connect?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + update?: Prisma.CampaignSatelliteUpdateWithWhereUniqueWithoutSatelliteMailboxInput | Prisma.CampaignSatelliteUpdateWithWhereUniqueWithoutSatelliteMailboxInput[] + updateMany?: Prisma.CampaignSatelliteUpdateManyWithWhereWithoutSatelliteMailboxInput | Prisma.CampaignSatelliteUpdateManyWithWhereWithoutSatelliteMailboxInput[] + deleteMany?: Prisma.CampaignSatelliteScalarWhereInput | Prisma.CampaignSatelliteScalarWhereInput[] +} + +export type CampaignSatelliteCreateNestedManyWithoutCampaignInput = { + create?: Prisma.XOR | Prisma.CampaignSatelliteCreateWithoutCampaignInput[] | Prisma.CampaignSatelliteUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.CampaignSatelliteCreateOrConnectWithoutCampaignInput | Prisma.CampaignSatelliteCreateOrConnectWithoutCampaignInput[] + createMany?: Prisma.CampaignSatelliteCreateManyCampaignInputEnvelope + connect?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] +} + +export type CampaignSatelliteUncheckedCreateNestedManyWithoutCampaignInput = { + create?: Prisma.XOR | Prisma.CampaignSatelliteCreateWithoutCampaignInput[] | Prisma.CampaignSatelliteUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.CampaignSatelliteCreateOrConnectWithoutCampaignInput | Prisma.CampaignSatelliteCreateOrConnectWithoutCampaignInput[] + createMany?: Prisma.CampaignSatelliteCreateManyCampaignInputEnvelope + connect?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] +} + +export type CampaignSatelliteUpdateManyWithoutCampaignNestedInput = { + create?: Prisma.XOR | Prisma.CampaignSatelliteCreateWithoutCampaignInput[] | Prisma.CampaignSatelliteUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.CampaignSatelliteCreateOrConnectWithoutCampaignInput | Prisma.CampaignSatelliteCreateOrConnectWithoutCampaignInput[] + upsert?: Prisma.CampaignSatelliteUpsertWithWhereUniqueWithoutCampaignInput | Prisma.CampaignSatelliteUpsertWithWhereUniqueWithoutCampaignInput[] + createMany?: Prisma.CampaignSatelliteCreateManyCampaignInputEnvelope + set?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + disconnect?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + delete?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + connect?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + update?: Prisma.CampaignSatelliteUpdateWithWhereUniqueWithoutCampaignInput | Prisma.CampaignSatelliteUpdateWithWhereUniqueWithoutCampaignInput[] + updateMany?: Prisma.CampaignSatelliteUpdateManyWithWhereWithoutCampaignInput | Prisma.CampaignSatelliteUpdateManyWithWhereWithoutCampaignInput[] + deleteMany?: Prisma.CampaignSatelliteScalarWhereInput | Prisma.CampaignSatelliteScalarWhereInput[] +} + +export type CampaignSatelliteUncheckedUpdateManyWithoutCampaignNestedInput = { + create?: Prisma.XOR | Prisma.CampaignSatelliteCreateWithoutCampaignInput[] | Prisma.CampaignSatelliteUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.CampaignSatelliteCreateOrConnectWithoutCampaignInput | Prisma.CampaignSatelliteCreateOrConnectWithoutCampaignInput[] + upsert?: Prisma.CampaignSatelliteUpsertWithWhereUniqueWithoutCampaignInput | Prisma.CampaignSatelliteUpsertWithWhereUniqueWithoutCampaignInput[] + createMany?: Prisma.CampaignSatelliteCreateManyCampaignInputEnvelope + set?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + disconnect?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + delete?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + connect?: Prisma.CampaignSatelliteWhereUniqueInput | Prisma.CampaignSatelliteWhereUniqueInput[] + update?: Prisma.CampaignSatelliteUpdateWithWhereUniqueWithoutCampaignInput | Prisma.CampaignSatelliteUpdateWithWhereUniqueWithoutCampaignInput[] + updateMany?: Prisma.CampaignSatelliteUpdateManyWithWhereWithoutCampaignInput | Prisma.CampaignSatelliteUpdateManyWithWhereWithoutCampaignInput[] + deleteMany?: Prisma.CampaignSatelliteScalarWhereInput | Prisma.CampaignSatelliteScalarWhereInput[] +} + +export type CampaignSatelliteCreateWithoutSatelliteMailboxInput = { + id?: string + weight?: number + active?: boolean + createdAt?: Date | string + updatedAt?: Date | string + campaign: Prisma.WarmupCampaignCreateNestedOneWithoutSatellitesInput +} + +export type CampaignSatelliteUncheckedCreateWithoutSatelliteMailboxInput = { + id?: string + campaignId: string + weight?: number + active?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CampaignSatelliteCreateOrConnectWithoutSatelliteMailboxInput = { + where: Prisma.CampaignSatelliteWhereUniqueInput + create: Prisma.XOR +} + +export type CampaignSatelliteCreateManySatelliteMailboxInputEnvelope = { + data: Prisma.CampaignSatelliteCreateManySatelliteMailboxInput | Prisma.CampaignSatelliteCreateManySatelliteMailboxInput[] +} + +export type CampaignSatelliteUpsertWithWhereUniqueWithoutSatelliteMailboxInput = { + where: Prisma.CampaignSatelliteWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type CampaignSatelliteUpdateWithWhereUniqueWithoutSatelliteMailboxInput = { + where: Prisma.CampaignSatelliteWhereUniqueInput + data: Prisma.XOR +} + +export type CampaignSatelliteUpdateManyWithWhereWithoutSatelliteMailboxInput = { + where: Prisma.CampaignSatelliteScalarWhereInput + data: Prisma.XOR +} + +export type CampaignSatelliteScalarWhereInput = { + AND?: Prisma.CampaignSatelliteScalarWhereInput | Prisma.CampaignSatelliteScalarWhereInput[] + OR?: Prisma.CampaignSatelliteScalarWhereInput[] + NOT?: Prisma.CampaignSatelliteScalarWhereInput | Prisma.CampaignSatelliteScalarWhereInput[] + id?: Prisma.StringFilter<"CampaignSatellite"> | string + campaignId?: Prisma.StringFilter<"CampaignSatellite"> | string + satelliteMailboxId?: Prisma.StringFilter<"CampaignSatellite"> | string + weight?: Prisma.IntFilter<"CampaignSatellite"> | number + active?: Prisma.BoolFilter<"CampaignSatellite"> | boolean + createdAt?: Prisma.DateTimeFilter<"CampaignSatellite"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"CampaignSatellite"> | Date | string +} + +export type CampaignSatelliteCreateWithoutCampaignInput = { + id?: string + weight?: number + active?: boolean + createdAt?: Date | string + updatedAt?: Date | string + satelliteMailbox: Prisma.InboxCreateNestedOneWithoutSatelliteCampaignsInput +} + +export type CampaignSatelliteUncheckedCreateWithoutCampaignInput = { + id?: string + satelliteMailboxId: string + weight?: number + active?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CampaignSatelliteCreateOrConnectWithoutCampaignInput = { + where: Prisma.CampaignSatelliteWhereUniqueInput + create: Prisma.XOR +} + +export type CampaignSatelliteCreateManyCampaignInputEnvelope = { + data: Prisma.CampaignSatelliteCreateManyCampaignInput | Prisma.CampaignSatelliteCreateManyCampaignInput[] +} + +export type CampaignSatelliteUpsertWithWhereUniqueWithoutCampaignInput = { + where: Prisma.CampaignSatelliteWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type CampaignSatelliteUpdateWithWhereUniqueWithoutCampaignInput = { + where: Prisma.CampaignSatelliteWhereUniqueInput + data: Prisma.XOR +} + +export type CampaignSatelliteUpdateManyWithWhereWithoutCampaignInput = { + where: Prisma.CampaignSatelliteScalarWhereInput + data: Prisma.XOR +} + +export type CampaignSatelliteCreateManySatelliteMailboxInput = { + id?: string + campaignId: string + weight?: number + active?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CampaignSatelliteUpdateWithoutSatelliteMailboxInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + weight?: Prisma.IntFieldUpdateOperationsInput | number + active?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + campaign?: Prisma.WarmupCampaignUpdateOneRequiredWithoutSatellitesNestedInput +} + +export type CampaignSatelliteUncheckedUpdateWithoutSatelliteMailboxInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.StringFieldUpdateOperationsInput | string + weight?: Prisma.IntFieldUpdateOperationsInput | number + active?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CampaignSatelliteUncheckedUpdateManyWithoutSatelliteMailboxInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.StringFieldUpdateOperationsInput | string + weight?: Prisma.IntFieldUpdateOperationsInput | number + active?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CampaignSatelliteCreateManyCampaignInput = { + id?: string + satelliteMailboxId: string + weight?: number + active?: boolean + createdAt?: Date | string + updatedAt?: Date | string +} + +export type CampaignSatelliteUpdateWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + weight?: Prisma.IntFieldUpdateOperationsInput | number + active?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + satelliteMailbox?: Prisma.InboxUpdateOneRequiredWithoutSatelliteCampaignsNestedInput +} + +export type CampaignSatelliteUncheckedUpdateWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + satelliteMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + weight?: Prisma.IntFieldUpdateOperationsInput | number + active?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type CampaignSatelliteUncheckedUpdateManyWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + satelliteMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + weight?: Prisma.IntFieldUpdateOperationsInput | number + active?: Prisma.BoolFieldUpdateOperationsInput | boolean + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + + +export type CampaignSatelliteSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + satelliteMailboxId?: boolean + weight?: boolean + active?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs + satelliteMailbox?: boolean | Prisma.InboxDefaultArgs +}, ExtArgs["result"]["campaignSatellite"]> + +export type CampaignSatelliteSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + satelliteMailboxId?: boolean + weight?: boolean + active?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs + satelliteMailbox?: boolean | Prisma.InboxDefaultArgs +}, ExtArgs["result"]["campaignSatellite"]> + +export type CampaignSatelliteSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + satelliteMailboxId?: boolean + weight?: boolean + active?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs + satelliteMailbox?: boolean | Prisma.InboxDefaultArgs +}, ExtArgs["result"]["campaignSatellite"]> + +export type CampaignSatelliteSelectScalar = { + id?: boolean + campaignId?: boolean + satelliteMailboxId?: boolean + weight?: boolean + active?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type CampaignSatelliteOmit = runtime.Types.Extensions.GetOmit<"id" | "campaignId" | "satelliteMailboxId" | "weight" | "active" | "createdAt" | "updatedAt", ExtArgs["result"]["campaignSatellite"]> +export type CampaignSatelliteInclude = { + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs + satelliteMailbox?: boolean | Prisma.InboxDefaultArgs +} +export type CampaignSatelliteIncludeCreateManyAndReturn = { + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs + satelliteMailbox?: boolean | Prisma.InboxDefaultArgs +} +export type CampaignSatelliteIncludeUpdateManyAndReturn = { + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs + satelliteMailbox?: boolean | Prisma.InboxDefaultArgs +} + +export type $CampaignSatellitePayload = { + name: "CampaignSatellite" + objects: { + campaign: Prisma.$WarmupCampaignPayload + satelliteMailbox: Prisma.$InboxPayload + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: string + campaignId: string + satelliteMailboxId: string + weight: number + active: boolean + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["campaignSatellite"]> + composites: {} +} + +export type CampaignSatelliteGetPayload = runtime.Types.Result.GetResult + +export type CampaignSatelliteCountArgs = + Omit & { + select?: CampaignSatelliteCountAggregateInputType | true + } + +export interface CampaignSatelliteDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['CampaignSatellite'], meta: { name: 'CampaignSatellite' } } + /** + * Find zero or one CampaignSatellite that matches the filter. + * @param {CampaignSatelliteFindUniqueArgs} args - Arguments to find a CampaignSatellite + * @example + * // Get one CampaignSatellite + * const campaignSatellite = await prisma.campaignSatellite.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__CampaignSatelliteClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one CampaignSatellite that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {CampaignSatelliteFindUniqueOrThrowArgs} args - Arguments to find a CampaignSatellite + * @example + * // Get one CampaignSatellite + * const campaignSatellite = await prisma.campaignSatellite.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__CampaignSatelliteClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first CampaignSatellite that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CampaignSatelliteFindFirstArgs} args - Arguments to find a CampaignSatellite + * @example + * // Get one CampaignSatellite + * const campaignSatellite = await prisma.campaignSatellite.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__CampaignSatelliteClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first CampaignSatellite that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CampaignSatelliteFindFirstOrThrowArgs} args - Arguments to find a CampaignSatellite + * @example + * // Get one CampaignSatellite + * const campaignSatellite = await prisma.campaignSatellite.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__CampaignSatelliteClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more CampaignSatellites that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CampaignSatelliteFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all CampaignSatellites + * const campaignSatellites = await prisma.campaignSatellite.findMany() + * + * // Get first 10 CampaignSatellites + * const campaignSatellites = await prisma.campaignSatellite.findMany({ take: 10 }) + * + * // Only select the `id` + * const campaignSatelliteWithIdOnly = await prisma.campaignSatellite.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a CampaignSatellite. + * @param {CampaignSatelliteCreateArgs} args - Arguments to create a CampaignSatellite. + * @example + * // Create one CampaignSatellite + * const CampaignSatellite = await prisma.campaignSatellite.create({ + * data: { + * // ... data to create a CampaignSatellite + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__CampaignSatelliteClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many CampaignSatellites. + * @param {CampaignSatelliteCreateManyArgs} args - Arguments to create many CampaignSatellites. + * @example + * // Create many CampaignSatellites + * const campaignSatellite = await prisma.campaignSatellite.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many CampaignSatellites and returns the data saved in the database. + * @param {CampaignSatelliteCreateManyAndReturnArgs} args - Arguments to create many CampaignSatellites. + * @example + * // Create many CampaignSatellites + * const campaignSatellite = await prisma.campaignSatellite.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many CampaignSatellites and only return the `id` + * const campaignSatelliteWithIdOnly = await prisma.campaignSatellite.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a CampaignSatellite. + * @param {CampaignSatelliteDeleteArgs} args - Arguments to delete one CampaignSatellite. + * @example + * // Delete one CampaignSatellite + * const CampaignSatellite = await prisma.campaignSatellite.delete({ + * where: { + * // ... filter to delete one CampaignSatellite + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__CampaignSatelliteClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one CampaignSatellite. + * @param {CampaignSatelliteUpdateArgs} args - Arguments to update one CampaignSatellite. + * @example + * // Update one CampaignSatellite + * const campaignSatellite = await prisma.campaignSatellite.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__CampaignSatelliteClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more CampaignSatellites. + * @param {CampaignSatelliteDeleteManyArgs} args - Arguments to filter CampaignSatellites to delete. + * @example + * // Delete a few CampaignSatellites + * const { count } = await prisma.campaignSatellite.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more CampaignSatellites. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CampaignSatelliteUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many CampaignSatellites + * const campaignSatellite = await prisma.campaignSatellite.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more CampaignSatellites and returns the data updated in the database. + * @param {CampaignSatelliteUpdateManyAndReturnArgs} args - Arguments to update many CampaignSatellites. + * @example + * // Update many CampaignSatellites + * const campaignSatellite = await prisma.campaignSatellite.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more CampaignSatellites and only return the `id` + * const campaignSatelliteWithIdOnly = await prisma.campaignSatellite.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one CampaignSatellite. + * @param {CampaignSatelliteUpsertArgs} args - Arguments to update or create a CampaignSatellite. + * @example + * // Update or create a CampaignSatellite + * const campaignSatellite = await prisma.campaignSatellite.upsert({ + * create: { + * // ... data to create a CampaignSatellite + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the CampaignSatellite we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__CampaignSatelliteClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of CampaignSatellites. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CampaignSatelliteCountArgs} args - Arguments to filter CampaignSatellites to count. + * @example + * // Count the number of CampaignSatellites + * const count = await prisma.campaignSatellite.count({ + * where: { + * // ... the filter for the CampaignSatellites we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a CampaignSatellite. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CampaignSatelliteAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by CampaignSatellite. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {CampaignSatelliteGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends CampaignSatelliteGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: CampaignSatelliteGroupByArgs['orderBy'] } + : { orderBy?: CampaignSatelliteGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetCampaignSatelliteGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the CampaignSatellite model + */ +readonly fields: CampaignSatelliteFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for CampaignSatellite. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__CampaignSatelliteClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + campaign = {}>(args?: Prisma.Subset>): Prisma.Prisma__WarmupCampaignClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + satelliteMailbox = {}>(args?: Prisma.Subset>): Prisma.Prisma__InboxClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the CampaignSatellite model + */ +export interface CampaignSatelliteFieldRefs { + readonly id: Prisma.FieldRef<"CampaignSatellite", 'String'> + readonly campaignId: Prisma.FieldRef<"CampaignSatellite", 'String'> + readonly satelliteMailboxId: Prisma.FieldRef<"CampaignSatellite", 'String'> + readonly weight: Prisma.FieldRef<"CampaignSatellite", 'Int'> + readonly active: Prisma.FieldRef<"CampaignSatellite", 'Boolean'> + readonly createdAt: Prisma.FieldRef<"CampaignSatellite", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"CampaignSatellite", 'DateTime'> +} + + +// Custom InputTypes +/** + * CampaignSatellite findUnique + */ +export type CampaignSatelliteFindUniqueArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelect | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteInclude | null + /** + * Filter, which CampaignSatellite to fetch. + */ + where: Prisma.CampaignSatelliteWhereUniqueInput +} + +/** + * CampaignSatellite findUniqueOrThrow + */ +export type CampaignSatelliteFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelect | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteInclude | null + /** + * Filter, which CampaignSatellite to fetch. + */ + where: Prisma.CampaignSatelliteWhereUniqueInput +} + +/** + * CampaignSatellite findFirst + */ +export type CampaignSatelliteFindFirstArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelect | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteInclude | null + /** + * Filter, which CampaignSatellite to fetch. + */ + where?: Prisma.CampaignSatelliteWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CampaignSatellites to fetch. + */ + orderBy?: Prisma.CampaignSatelliteOrderByWithRelationInput | Prisma.CampaignSatelliteOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for CampaignSatellites. + */ + cursor?: Prisma.CampaignSatelliteWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CampaignSatellites from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CampaignSatellites. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CampaignSatellites. + */ + distinct?: Prisma.CampaignSatelliteScalarFieldEnum | Prisma.CampaignSatelliteScalarFieldEnum[] +} + +/** + * CampaignSatellite findFirstOrThrow + */ +export type CampaignSatelliteFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelect | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteInclude | null + /** + * Filter, which CampaignSatellite to fetch. + */ + where?: Prisma.CampaignSatelliteWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CampaignSatellites to fetch. + */ + orderBy?: Prisma.CampaignSatelliteOrderByWithRelationInput | Prisma.CampaignSatelliteOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for CampaignSatellites. + */ + cursor?: Prisma.CampaignSatelliteWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CampaignSatellites from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CampaignSatellites. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CampaignSatellites. + */ + distinct?: Prisma.CampaignSatelliteScalarFieldEnum | Prisma.CampaignSatelliteScalarFieldEnum[] +} + +/** + * CampaignSatellite findMany + */ +export type CampaignSatelliteFindManyArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelect | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteInclude | null + /** + * Filter, which CampaignSatellites to fetch. + */ + where?: Prisma.CampaignSatelliteWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of CampaignSatellites to fetch. + */ + orderBy?: Prisma.CampaignSatelliteOrderByWithRelationInput | Prisma.CampaignSatelliteOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing CampaignSatellites. + */ + cursor?: Prisma.CampaignSatelliteWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` CampaignSatellites from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` CampaignSatellites. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of CampaignSatellites. + */ + distinct?: Prisma.CampaignSatelliteScalarFieldEnum | Prisma.CampaignSatelliteScalarFieldEnum[] +} + +/** + * CampaignSatellite create + */ +export type CampaignSatelliteCreateArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelect | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteInclude | null + /** + * The data needed to create a CampaignSatellite. + */ + data: Prisma.XOR +} + +/** + * CampaignSatellite createMany + */ +export type CampaignSatelliteCreateManyArgs = { + /** + * The data used to create many CampaignSatellites. + */ + data: Prisma.CampaignSatelliteCreateManyInput | Prisma.CampaignSatelliteCreateManyInput[] +} + +/** + * CampaignSatellite createManyAndReturn + */ +export type CampaignSatelliteCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelectCreateManyAndReturn | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * The data used to create many CampaignSatellites. + */ + data: Prisma.CampaignSatelliteCreateManyInput | Prisma.CampaignSatelliteCreateManyInput[] + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteIncludeCreateManyAndReturn | null +} + +/** + * CampaignSatellite update + */ +export type CampaignSatelliteUpdateArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelect | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteInclude | null + /** + * The data needed to update a CampaignSatellite. + */ + data: Prisma.XOR + /** + * Choose, which CampaignSatellite to update. + */ + where: Prisma.CampaignSatelliteWhereUniqueInput +} + +/** + * CampaignSatellite updateMany + */ +export type CampaignSatelliteUpdateManyArgs = { + /** + * The data used to update CampaignSatellites. + */ + data: Prisma.XOR + /** + * Filter which CampaignSatellites to update + */ + where?: Prisma.CampaignSatelliteWhereInput + /** + * Limit how many CampaignSatellites to update. + */ + limit?: number +} + +/** + * CampaignSatellite updateManyAndReturn + */ +export type CampaignSatelliteUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * The data used to update CampaignSatellites. + */ + data: Prisma.XOR + /** + * Filter which CampaignSatellites to update + */ + where?: Prisma.CampaignSatelliteWhereInput + /** + * Limit how many CampaignSatellites to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteIncludeUpdateManyAndReturn | null +} + +/** + * CampaignSatellite upsert + */ +export type CampaignSatelliteUpsertArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelect | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteInclude | null + /** + * The filter to search for the CampaignSatellite to update in case it exists. + */ + where: Prisma.CampaignSatelliteWhereUniqueInput + /** + * In case the CampaignSatellite found by the `where` argument doesn't exist, create a new CampaignSatellite with this data. + */ + create: Prisma.XOR + /** + * In case the CampaignSatellite was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * CampaignSatellite delete + */ +export type CampaignSatelliteDeleteArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelect | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteInclude | null + /** + * Filter which CampaignSatellite to delete. + */ + where: Prisma.CampaignSatelliteWhereUniqueInput +} + +/** + * CampaignSatellite deleteMany + */ +export type CampaignSatelliteDeleteManyArgs = { + /** + * Filter which CampaignSatellites to delete + */ + where?: Prisma.CampaignSatelliteWhereInput + /** + * Limit how many CampaignSatellites to delete. + */ + limit?: number +} + +/** + * CampaignSatellite without action + */ +export type CampaignSatelliteDefaultArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelect | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteInclude | null +} diff --git a/apps/api/src/generated/prisma/models/DailySchedule.ts b/apps/api/src/generated/prisma/models/DailySchedule.ts new file mode 100644 index 0000000..1e14896 --- /dev/null +++ b/apps/api/src/generated/prisma/models/DailySchedule.ts @@ -0,0 +1,1673 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `DailySchedule` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums" +import type * as Prisma from "../internal/prismaNamespace" + +/** + * Model DailySchedule + * + */ +export type DailyScheduleModel = runtime.Types.Result.DefaultSelection + +export type AggregateDailySchedule = { + _count: DailyScheduleCountAggregateOutputType | null + _avg: DailyScheduleAvgAggregateOutputType | null + _sum: DailyScheduleSumAggregateOutputType | null + _min: DailyScheduleMinAggregateOutputType | null + _max: DailyScheduleMaxAggregateOutputType | null +} + +export type DailyScheduleAvgAggregateOutputType = { + dayIndex: number | null + plannedSends: number | null + plannedReplies: number | null + actualSends: number | null + actualReplies: number | null + spamDetected: number | null + spamRescued: number | null +} + +export type DailyScheduleSumAggregateOutputType = { + dayIndex: number | null + plannedSends: number | null + plannedReplies: number | null + actualSends: number | null + actualReplies: number | null + spamDetected: number | null + spamRescued: number | null +} + +export type DailyScheduleMinAggregateOutputType = { + id: string | null + campaignId: string | null + dayIndex: number | null + date: Date | null + plannedSends: number | null + plannedReplies: number | null + actualSends: number | null + actualReplies: number | null + spamDetected: number | null + spamRescued: number | null + createdAt: Date | null + updatedAt: Date | null +} + +export type DailyScheduleMaxAggregateOutputType = { + id: string | null + campaignId: string | null + dayIndex: number | null + date: Date | null + plannedSends: number | null + plannedReplies: number | null + actualSends: number | null + actualReplies: number | null + spamDetected: number | null + spamRescued: number | null + createdAt: Date | null + updatedAt: Date | null +} + +export type DailyScheduleCountAggregateOutputType = { + id: number + campaignId: number + dayIndex: number + date: number + plannedSends: number + plannedReplies: number + actualSends: number + actualReplies: number + spamDetected: number + spamRescued: number + createdAt: number + updatedAt: number + _all: number +} + + +export type DailyScheduleAvgAggregateInputType = { + dayIndex?: true + plannedSends?: true + plannedReplies?: true + actualSends?: true + actualReplies?: true + spamDetected?: true + spamRescued?: true +} + +export type DailyScheduleSumAggregateInputType = { + dayIndex?: true + plannedSends?: true + plannedReplies?: true + actualSends?: true + actualReplies?: true + spamDetected?: true + spamRescued?: true +} + +export type DailyScheduleMinAggregateInputType = { + id?: true + campaignId?: true + dayIndex?: true + date?: true + plannedSends?: true + plannedReplies?: true + actualSends?: true + actualReplies?: true + spamDetected?: true + spamRescued?: true + createdAt?: true + updatedAt?: true +} + +export type DailyScheduleMaxAggregateInputType = { + id?: true + campaignId?: true + dayIndex?: true + date?: true + plannedSends?: true + plannedReplies?: true + actualSends?: true + actualReplies?: true + spamDetected?: true + spamRescued?: true + createdAt?: true + updatedAt?: true +} + +export type DailyScheduleCountAggregateInputType = { + id?: true + campaignId?: true + dayIndex?: true + date?: true + plannedSends?: true + plannedReplies?: true + actualSends?: true + actualReplies?: true + spamDetected?: true + spamRescued?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type DailyScheduleAggregateArgs = { + /** + * Filter which DailySchedule to aggregate. + */ + where?: Prisma.DailyScheduleWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of DailySchedules to fetch. + */ + orderBy?: Prisma.DailyScheduleOrderByWithRelationInput | Prisma.DailyScheduleOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.DailyScheduleWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` DailySchedules from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` DailySchedules. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned DailySchedules + **/ + _count?: true | DailyScheduleCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: DailyScheduleAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: DailyScheduleSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: DailyScheduleMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: DailyScheduleMaxAggregateInputType +} + +export type GetDailyScheduleAggregateType = { + [P in keyof T & keyof AggregateDailySchedule]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type DailyScheduleGroupByArgs = { + where?: Prisma.DailyScheduleWhereInput + orderBy?: Prisma.DailyScheduleOrderByWithAggregationInput | Prisma.DailyScheduleOrderByWithAggregationInput[] + by: Prisma.DailyScheduleScalarFieldEnum[] | Prisma.DailyScheduleScalarFieldEnum + having?: Prisma.DailyScheduleScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: DailyScheduleCountAggregateInputType | true + _avg?: DailyScheduleAvgAggregateInputType + _sum?: DailyScheduleSumAggregateInputType + _min?: DailyScheduleMinAggregateInputType + _max?: DailyScheduleMaxAggregateInputType +} + +export type DailyScheduleGroupByOutputType = { + id: string + campaignId: string + dayIndex: number + date: Date | null + plannedSends: number + plannedReplies: number + actualSends: number + actualReplies: number + spamDetected: number + spamRescued: number + createdAt: Date + updatedAt: Date + _count: DailyScheduleCountAggregateOutputType | null + _avg: DailyScheduleAvgAggregateOutputType | null + _sum: DailyScheduleSumAggregateOutputType | null + _min: DailyScheduleMinAggregateOutputType | null + _max: DailyScheduleMaxAggregateOutputType | null +} + +export type GetDailyScheduleGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof DailyScheduleGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type DailyScheduleWhereInput = { + AND?: Prisma.DailyScheduleWhereInput | Prisma.DailyScheduleWhereInput[] + OR?: Prisma.DailyScheduleWhereInput[] + NOT?: Prisma.DailyScheduleWhereInput | Prisma.DailyScheduleWhereInput[] + id?: Prisma.StringFilter<"DailySchedule"> | string + campaignId?: Prisma.StringFilter<"DailySchedule"> | string + dayIndex?: Prisma.IntFilter<"DailySchedule"> | number + date?: Prisma.DateTimeNullableFilter<"DailySchedule"> | Date | string | null + plannedSends?: Prisma.IntFilter<"DailySchedule"> | number + plannedReplies?: Prisma.IntFilter<"DailySchedule"> | number + actualSends?: Prisma.IntFilter<"DailySchedule"> | number + actualReplies?: Prisma.IntFilter<"DailySchedule"> | number + spamDetected?: Prisma.IntFilter<"DailySchedule"> | number + spamRescued?: Prisma.IntFilter<"DailySchedule"> | number + createdAt?: Prisma.DateTimeFilter<"DailySchedule"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"DailySchedule"> | Date | string + campaign?: Prisma.XOR +} + +export type DailyScheduleOrderByWithRelationInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + dayIndex?: Prisma.SortOrder + date?: Prisma.SortOrderInput | Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + campaign?: Prisma.WarmupCampaignOrderByWithRelationInput +} + +export type DailyScheduleWhereUniqueInput = Prisma.AtLeast<{ + id?: string + campaignId_dayIndex?: Prisma.DailyScheduleCampaignIdDayIndexCompoundUniqueInput + AND?: Prisma.DailyScheduleWhereInput | Prisma.DailyScheduleWhereInput[] + OR?: Prisma.DailyScheduleWhereInput[] + NOT?: Prisma.DailyScheduleWhereInput | Prisma.DailyScheduleWhereInput[] + campaignId?: Prisma.StringFilter<"DailySchedule"> | string + dayIndex?: Prisma.IntFilter<"DailySchedule"> | number + date?: Prisma.DateTimeNullableFilter<"DailySchedule"> | Date | string | null + plannedSends?: Prisma.IntFilter<"DailySchedule"> | number + plannedReplies?: Prisma.IntFilter<"DailySchedule"> | number + actualSends?: Prisma.IntFilter<"DailySchedule"> | number + actualReplies?: Prisma.IntFilter<"DailySchedule"> | number + spamDetected?: Prisma.IntFilter<"DailySchedule"> | number + spamRescued?: Prisma.IntFilter<"DailySchedule"> | number + createdAt?: Prisma.DateTimeFilter<"DailySchedule"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"DailySchedule"> | Date | string + campaign?: Prisma.XOR +}, "id" | "campaignId_dayIndex"> + +export type DailyScheduleOrderByWithAggregationInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + dayIndex?: Prisma.SortOrder + date?: Prisma.SortOrderInput | Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.DailyScheduleCountOrderByAggregateInput + _avg?: Prisma.DailyScheduleAvgOrderByAggregateInput + _max?: Prisma.DailyScheduleMaxOrderByAggregateInput + _min?: Prisma.DailyScheduleMinOrderByAggregateInput + _sum?: Prisma.DailyScheduleSumOrderByAggregateInput +} + +export type DailyScheduleScalarWhereWithAggregatesInput = { + AND?: Prisma.DailyScheduleScalarWhereWithAggregatesInput | Prisma.DailyScheduleScalarWhereWithAggregatesInput[] + OR?: Prisma.DailyScheduleScalarWhereWithAggregatesInput[] + NOT?: Prisma.DailyScheduleScalarWhereWithAggregatesInput | Prisma.DailyScheduleScalarWhereWithAggregatesInput[] + id?: Prisma.StringWithAggregatesFilter<"DailySchedule"> | string + campaignId?: Prisma.StringWithAggregatesFilter<"DailySchedule"> | string + dayIndex?: Prisma.IntWithAggregatesFilter<"DailySchedule"> | number + date?: Prisma.DateTimeNullableWithAggregatesFilter<"DailySchedule"> | Date | string | null + plannedSends?: Prisma.IntWithAggregatesFilter<"DailySchedule"> | number + plannedReplies?: Prisma.IntWithAggregatesFilter<"DailySchedule"> | number + actualSends?: Prisma.IntWithAggregatesFilter<"DailySchedule"> | number + actualReplies?: Prisma.IntWithAggregatesFilter<"DailySchedule"> | number + spamDetected?: Prisma.IntWithAggregatesFilter<"DailySchedule"> | number + spamRescued?: Prisma.IntWithAggregatesFilter<"DailySchedule"> | number + createdAt?: Prisma.DateTimeWithAggregatesFilter<"DailySchedule"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"DailySchedule"> | Date | string +} + +export type DailyScheduleCreateInput = { + id?: string + dayIndex: number + date?: Date | string | null + plannedSends: number + plannedReplies: number + actualSends?: number + actualReplies?: number + spamDetected?: number + spamRescued?: number + createdAt?: Date | string + updatedAt?: Date | string + campaign: Prisma.WarmupCampaignCreateNestedOneWithoutDailySchedulesInput +} + +export type DailyScheduleUncheckedCreateInput = { + id?: string + campaignId: string + dayIndex: number + date?: Date | string | null + plannedSends: number + plannedReplies: number + actualSends?: number + actualReplies?: number + spamDetected?: number + spamRescued?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type DailyScheduleUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + campaign?: Prisma.WarmupCampaignUpdateOneRequiredWithoutDailySchedulesNestedInput +} + +export type DailyScheduleUncheckedUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.StringFieldUpdateOperationsInput | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type DailyScheduleCreateManyInput = { + id?: string + campaignId: string + dayIndex: number + date?: Date | string | null + plannedSends: number + plannedReplies: number + actualSends?: number + actualReplies?: number + spamDetected?: number + spamRescued?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type DailyScheduleUpdateManyMutationInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type DailyScheduleUncheckedUpdateManyInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.StringFieldUpdateOperationsInput | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type DailyScheduleListRelationFilter = { + every?: Prisma.DailyScheduleWhereInput + some?: Prisma.DailyScheduleWhereInput + none?: Prisma.DailyScheduleWhereInput +} + +export type DailyScheduleOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type DailyScheduleCampaignIdDayIndexCompoundUniqueInput = { + campaignId: string + dayIndex: number +} + +export type DailyScheduleCountOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + dayIndex?: Prisma.SortOrder + date?: Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type DailyScheduleAvgOrderByAggregateInput = { + dayIndex?: Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder +} + +export type DailyScheduleMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + dayIndex?: Prisma.SortOrder + date?: Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type DailyScheduleMinOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + dayIndex?: Prisma.SortOrder + date?: Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type DailyScheduleSumOrderByAggregateInput = { + dayIndex?: Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder +} + +export type DailyScheduleCreateNestedManyWithoutCampaignInput = { + create?: Prisma.XOR | Prisma.DailyScheduleCreateWithoutCampaignInput[] | Prisma.DailyScheduleUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.DailyScheduleCreateOrConnectWithoutCampaignInput | Prisma.DailyScheduleCreateOrConnectWithoutCampaignInput[] + createMany?: Prisma.DailyScheduleCreateManyCampaignInputEnvelope + connect?: Prisma.DailyScheduleWhereUniqueInput | Prisma.DailyScheduleWhereUniqueInput[] +} + +export type DailyScheduleUncheckedCreateNestedManyWithoutCampaignInput = { + create?: Prisma.XOR | Prisma.DailyScheduleCreateWithoutCampaignInput[] | Prisma.DailyScheduleUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.DailyScheduleCreateOrConnectWithoutCampaignInput | Prisma.DailyScheduleCreateOrConnectWithoutCampaignInput[] + createMany?: Prisma.DailyScheduleCreateManyCampaignInputEnvelope + connect?: Prisma.DailyScheduleWhereUniqueInput | Prisma.DailyScheduleWhereUniqueInput[] +} + +export type DailyScheduleUpdateManyWithoutCampaignNestedInput = { + create?: Prisma.XOR | Prisma.DailyScheduleCreateWithoutCampaignInput[] | Prisma.DailyScheduleUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.DailyScheduleCreateOrConnectWithoutCampaignInput | Prisma.DailyScheduleCreateOrConnectWithoutCampaignInput[] + upsert?: Prisma.DailyScheduleUpsertWithWhereUniqueWithoutCampaignInput | Prisma.DailyScheduleUpsertWithWhereUniqueWithoutCampaignInput[] + createMany?: Prisma.DailyScheduleCreateManyCampaignInputEnvelope + set?: Prisma.DailyScheduleWhereUniqueInput | Prisma.DailyScheduleWhereUniqueInput[] + disconnect?: Prisma.DailyScheduleWhereUniqueInput | Prisma.DailyScheduleWhereUniqueInput[] + delete?: Prisma.DailyScheduleWhereUniqueInput | Prisma.DailyScheduleWhereUniqueInput[] + connect?: Prisma.DailyScheduleWhereUniqueInput | Prisma.DailyScheduleWhereUniqueInput[] + update?: Prisma.DailyScheduleUpdateWithWhereUniqueWithoutCampaignInput | Prisma.DailyScheduleUpdateWithWhereUniqueWithoutCampaignInput[] + updateMany?: Prisma.DailyScheduleUpdateManyWithWhereWithoutCampaignInput | Prisma.DailyScheduleUpdateManyWithWhereWithoutCampaignInput[] + deleteMany?: Prisma.DailyScheduleScalarWhereInput | Prisma.DailyScheduleScalarWhereInput[] +} + +export type DailyScheduleUncheckedUpdateManyWithoutCampaignNestedInput = { + create?: Prisma.XOR | Prisma.DailyScheduleCreateWithoutCampaignInput[] | Prisma.DailyScheduleUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.DailyScheduleCreateOrConnectWithoutCampaignInput | Prisma.DailyScheduleCreateOrConnectWithoutCampaignInput[] + upsert?: Prisma.DailyScheduleUpsertWithWhereUniqueWithoutCampaignInput | Prisma.DailyScheduleUpsertWithWhereUniqueWithoutCampaignInput[] + createMany?: Prisma.DailyScheduleCreateManyCampaignInputEnvelope + set?: Prisma.DailyScheduleWhereUniqueInput | Prisma.DailyScheduleWhereUniqueInput[] + disconnect?: Prisma.DailyScheduleWhereUniqueInput | Prisma.DailyScheduleWhereUniqueInput[] + delete?: Prisma.DailyScheduleWhereUniqueInput | Prisma.DailyScheduleWhereUniqueInput[] + connect?: Prisma.DailyScheduleWhereUniqueInput | Prisma.DailyScheduleWhereUniqueInput[] + update?: Prisma.DailyScheduleUpdateWithWhereUniqueWithoutCampaignInput | Prisma.DailyScheduleUpdateWithWhereUniqueWithoutCampaignInput[] + updateMany?: Prisma.DailyScheduleUpdateManyWithWhereWithoutCampaignInput | Prisma.DailyScheduleUpdateManyWithWhereWithoutCampaignInput[] + deleteMany?: Prisma.DailyScheduleScalarWhereInput | Prisma.DailyScheduleScalarWhereInput[] +} + +export type DailyScheduleCreateWithoutCampaignInput = { + id?: string + dayIndex: number + date?: Date | string | null + plannedSends: number + plannedReplies: number + actualSends?: number + actualReplies?: number + spamDetected?: number + spamRescued?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type DailyScheduleUncheckedCreateWithoutCampaignInput = { + id?: string + dayIndex: number + date?: Date | string | null + plannedSends: number + plannedReplies: number + actualSends?: number + actualReplies?: number + spamDetected?: number + spamRescued?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type DailyScheduleCreateOrConnectWithoutCampaignInput = { + where: Prisma.DailyScheduleWhereUniqueInput + create: Prisma.XOR +} + +export type DailyScheduleCreateManyCampaignInputEnvelope = { + data: Prisma.DailyScheduleCreateManyCampaignInput | Prisma.DailyScheduleCreateManyCampaignInput[] +} + +export type DailyScheduleUpsertWithWhereUniqueWithoutCampaignInput = { + where: Prisma.DailyScheduleWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type DailyScheduleUpdateWithWhereUniqueWithoutCampaignInput = { + where: Prisma.DailyScheduleWhereUniqueInput + data: Prisma.XOR +} + +export type DailyScheduleUpdateManyWithWhereWithoutCampaignInput = { + where: Prisma.DailyScheduleScalarWhereInput + data: Prisma.XOR +} + +export type DailyScheduleScalarWhereInput = { + AND?: Prisma.DailyScheduleScalarWhereInput | Prisma.DailyScheduleScalarWhereInput[] + OR?: Prisma.DailyScheduleScalarWhereInput[] + NOT?: Prisma.DailyScheduleScalarWhereInput | Prisma.DailyScheduleScalarWhereInput[] + id?: Prisma.StringFilter<"DailySchedule"> | string + campaignId?: Prisma.StringFilter<"DailySchedule"> | string + dayIndex?: Prisma.IntFilter<"DailySchedule"> | number + date?: Prisma.DateTimeNullableFilter<"DailySchedule"> | Date | string | null + plannedSends?: Prisma.IntFilter<"DailySchedule"> | number + plannedReplies?: Prisma.IntFilter<"DailySchedule"> | number + actualSends?: Prisma.IntFilter<"DailySchedule"> | number + actualReplies?: Prisma.IntFilter<"DailySchedule"> | number + spamDetected?: Prisma.IntFilter<"DailySchedule"> | number + spamRescued?: Prisma.IntFilter<"DailySchedule"> | number + createdAt?: Prisma.DateTimeFilter<"DailySchedule"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"DailySchedule"> | Date | string +} + +export type DailyScheduleCreateManyCampaignInput = { + id?: string + dayIndex: number + date?: Date | string | null + plannedSends: number + plannedReplies: number + actualSends?: number + actualReplies?: number + spamDetected?: number + spamRescued?: number + createdAt?: Date | string + updatedAt?: Date | string +} + +export type DailyScheduleUpdateWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type DailyScheduleUncheckedUpdateWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type DailyScheduleUncheckedUpdateManyWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + date?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + + +export type DailyScheduleSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + dayIndex?: boolean + date?: boolean + plannedSends?: boolean + plannedReplies?: boolean + actualSends?: boolean + actualReplies?: boolean + spamDetected?: boolean + spamRescued?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +}, ExtArgs["result"]["dailySchedule"]> + +export type DailyScheduleSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + dayIndex?: boolean + date?: boolean + plannedSends?: boolean + plannedReplies?: boolean + actualSends?: boolean + actualReplies?: boolean + spamDetected?: boolean + spamRescued?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +}, ExtArgs["result"]["dailySchedule"]> + +export type DailyScheduleSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + dayIndex?: boolean + date?: boolean + plannedSends?: boolean + plannedReplies?: boolean + actualSends?: boolean + actualReplies?: boolean + spamDetected?: boolean + spamRescued?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +}, ExtArgs["result"]["dailySchedule"]> + +export type DailyScheduleSelectScalar = { + id?: boolean + campaignId?: boolean + dayIndex?: boolean + date?: boolean + plannedSends?: boolean + plannedReplies?: boolean + actualSends?: boolean + actualReplies?: boolean + spamDetected?: boolean + spamRescued?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type DailyScheduleOmit = runtime.Types.Extensions.GetOmit<"id" | "campaignId" | "dayIndex" | "date" | "plannedSends" | "plannedReplies" | "actualSends" | "actualReplies" | "spamDetected" | "spamRescued" | "createdAt" | "updatedAt", ExtArgs["result"]["dailySchedule"]> +export type DailyScheduleInclude = { + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +} +export type DailyScheduleIncludeCreateManyAndReturn = { + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +} +export type DailyScheduleIncludeUpdateManyAndReturn = { + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +} + +export type $DailySchedulePayload = { + name: "DailySchedule" + objects: { + campaign: Prisma.$WarmupCampaignPayload + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: string + campaignId: string + dayIndex: number + date: Date | null + plannedSends: number + plannedReplies: number + actualSends: number + actualReplies: number + spamDetected: number + spamRescued: number + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["dailySchedule"]> + composites: {} +} + +export type DailyScheduleGetPayload = runtime.Types.Result.GetResult + +export type DailyScheduleCountArgs = + Omit & { + select?: DailyScheduleCountAggregateInputType | true + } + +export interface DailyScheduleDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['DailySchedule'], meta: { name: 'DailySchedule' } } + /** + * Find zero or one DailySchedule that matches the filter. + * @param {DailyScheduleFindUniqueArgs} args - Arguments to find a DailySchedule + * @example + * // Get one DailySchedule + * const dailySchedule = await prisma.dailySchedule.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__DailyScheduleClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one DailySchedule that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {DailyScheduleFindUniqueOrThrowArgs} args - Arguments to find a DailySchedule + * @example + * // Get one DailySchedule + * const dailySchedule = await prisma.dailySchedule.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__DailyScheduleClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first DailySchedule that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyScheduleFindFirstArgs} args - Arguments to find a DailySchedule + * @example + * // Get one DailySchedule + * const dailySchedule = await prisma.dailySchedule.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__DailyScheduleClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first DailySchedule that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyScheduleFindFirstOrThrowArgs} args - Arguments to find a DailySchedule + * @example + * // Get one DailySchedule + * const dailySchedule = await prisma.dailySchedule.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__DailyScheduleClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more DailySchedules that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyScheduleFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all DailySchedules + * const dailySchedules = await prisma.dailySchedule.findMany() + * + * // Get first 10 DailySchedules + * const dailySchedules = await prisma.dailySchedule.findMany({ take: 10 }) + * + * // Only select the `id` + * const dailyScheduleWithIdOnly = await prisma.dailySchedule.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a DailySchedule. + * @param {DailyScheduleCreateArgs} args - Arguments to create a DailySchedule. + * @example + * // Create one DailySchedule + * const DailySchedule = await prisma.dailySchedule.create({ + * data: { + * // ... data to create a DailySchedule + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__DailyScheduleClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many DailySchedules. + * @param {DailyScheduleCreateManyArgs} args - Arguments to create many DailySchedules. + * @example + * // Create many DailySchedules + * const dailySchedule = await prisma.dailySchedule.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many DailySchedules and returns the data saved in the database. + * @param {DailyScheduleCreateManyAndReturnArgs} args - Arguments to create many DailySchedules. + * @example + * // Create many DailySchedules + * const dailySchedule = await prisma.dailySchedule.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many DailySchedules and only return the `id` + * const dailyScheduleWithIdOnly = await prisma.dailySchedule.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a DailySchedule. + * @param {DailyScheduleDeleteArgs} args - Arguments to delete one DailySchedule. + * @example + * // Delete one DailySchedule + * const DailySchedule = await prisma.dailySchedule.delete({ + * where: { + * // ... filter to delete one DailySchedule + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__DailyScheduleClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one DailySchedule. + * @param {DailyScheduleUpdateArgs} args - Arguments to update one DailySchedule. + * @example + * // Update one DailySchedule + * const dailySchedule = await prisma.dailySchedule.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__DailyScheduleClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more DailySchedules. + * @param {DailyScheduleDeleteManyArgs} args - Arguments to filter DailySchedules to delete. + * @example + * // Delete a few DailySchedules + * const { count } = await prisma.dailySchedule.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more DailySchedules. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyScheduleUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many DailySchedules + * const dailySchedule = await prisma.dailySchedule.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more DailySchedules and returns the data updated in the database. + * @param {DailyScheduleUpdateManyAndReturnArgs} args - Arguments to update many DailySchedules. + * @example + * // Update many DailySchedules + * const dailySchedule = await prisma.dailySchedule.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more DailySchedules and only return the `id` + * const dailyScheduleWithIdOnly = await prisma.dailySchedule.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one DailySchedule. + * @param {DailyScheduleUpsertArgs} args - Arguments to update or create a DailySchedule. + * @example + * // Update or create a DailySchedule + * const dailySchedule = await prisma.dailySchedule.upsert({ + * create: { + * // ... data to create a DailySchedule + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the DailySchedule we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__DailyScheduleClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of DailySchedules. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyScheduleCountArgs} args - Arguments to filter DailySchedules to count. + * @example + * // Count the number of DailySchedules + * const count = await prisma.dailySchedule.count({ + * where: { + * // ... the filter for the DailySchedules we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a DailySchedule. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyScheduleAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by DailySchedule. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyScheduleGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends DailyScheduleGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: DailyScheduleGroupByArgs['orderBy'] } + : { orderBy?: DailyScheduleGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetDailyScheduleGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the DailySchedule model + */ +readonly fields: DailyScheduleFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for DailySchedule. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__DailyScheduleClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + campaign = {}>(args?: Prisma.Subset>): Prisma.Prisma__WarmupCampaignClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the DailySchedule model + */ +export interface DailyScheduleFieldRefs { + readonly id: Prisma.FieldRef<"DailySchedule", 'String'> + readonly campaignId: Prisma.FieldRef<"DailySchedule", 'String'> + readonly dayIndex: Prisma.FieldRef<"DailySchedule", 'Int'> + readonly date: Prisma.FieldRef<"DailySchedule", 'DateTime'> + readonly plannedSends: Prisma.FieldRef<"DailySchedule", 'Int'> + readonly plannedReplies: Prisma.FieldRef<"DailySchedule", 'Int'> + readonly actualSends: Prisma.FieldRef<"DailySchedule", 'Int'> + readonly actualReplies: Prisma.FieldRef<"DailySchedule", 'Int'> + readonly spamDetected: Prisma.FieldRef<"DailySchedule", 'Int'> + readonly spamRescued: Prisma.FieldRef<"DailySchedule", 'Int'> + readonly createdAt: Prisma.FieldRef<"DailySchedule", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"DailySchedule", 'DateTime'> +} + + +// Custom InputTypes +/** + * DailySchedule findUnique + */ +export type DailyScheduleFindUniqueArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelect | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleInclude | null + /** + * Filter, which DailySchedule to fetch. + */ + where: Prisma.DailyScheduleWhereUniqueInput +} + +/** + * DailySchedule findUniqueOrThrow + */ +export type DailyScheduleFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelect | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleInclude | null + /** + * Filter, which DailySchedule to fetch. + */ + where: Prisma.DailyScheduleWhereUniqueInput +} + +/** + * DailySchedule findFirst + */ +export type DailyScheduleFindFirstArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelect | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleInclude | null + /** + * Filter, which DailySchedule to fetch. + */ + where?: Prisma.DailyScheduleWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of DailySchedules to fetch. + */ + orderBy?: Prisma.DailyScheduleOrderByWithRelationInput | Prisma.DailyScheduleOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for DailySchedules. + */ + cursor?: Prisma.DailyScheduleWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` DailySchedules from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` DailySchedules. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of DailySchedules. + */ + distinct?: Prisma.DailyScheduleScalarFieldEnum | Prisma.DailyScheduleScalarFieldEnum[] +} + +/** + * DailySchedule findFirstOrThrow + */ +export type DailyScheduleFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelect | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleInclude | null + /** + * Filter, which DailySchedule to fetch. + */ + where?: Prisma.DailyScheduleWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of DailySchedules to fetch. + */ + orderBy?: Prisma.DailyScheduleOrderByWithRelationInput | Prisma.DailyScheduleOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for DailySchedules. + */ + cursor?: Prisma.DailyScheduleWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` DailySchedules from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` DailySchedules. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of DailySchedules. + */ + distinct?: Prisma.DailyScheduleScalarFieldEnum | Prisma.DailyScheduleScalarFieldEnum[] +} + +/** + * DailySchedule findMany + */ +export type DailyScheduleFindManyArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelect | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleInclude | null + /** + * Filter, which DailySchedules to fetch. + */ + where?: Prisma.DailyScheduleWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of DailySchedules to fetch. + */ + orderBy?: Prisma.DailyScheduleOrderByWithRelationInput | Prisma.DailyScheduleOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing DailySchedules. + */ + cursor?: Prisma.DailyScheduleWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` DailySchedules from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` DailySchedules. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of DailySchedules. + */ + distinct?: Prisma.DailyScheduleScalarFieldEnum | Prisma.DailyScheduleScalarFieldEnum[] +} + +/** + * DailySchedule create + */ +export type DailyScheduleCreateArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelect | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleInclude | null + /** + * The data needed to create a DailySchedule. + */ + data: Prisma.XOR +} + +/** + * DailySchedule createMany + */ +export type DailyScheduleCreateManyArgs = { + /** + * The data used to create many DailySchedules. + */ + data: Prisma.DailyScheduleCreateManyInput | Prisma.DailyScheduleCreateManyInput[] +} + +/** + * DailySchedule createManyAndReturn + */ +export type DailyScheduleCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelectCreateManyAndReturn | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * The data used to create many DailySchedules. + */ + data: Prisma.DailyScheduleCreateManyInput | Prisma.DailyScheduleCreateManyInput[] + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleIncludeCreateManyAndReturn | null +} + +/** + * DailySchedule update + */ +export type DailyScheduleUpdateArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelect | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleInclude | null + /** + * The data needed to update a DailySchedule. + */ + data: Prisma.XOR + /** + * Choose, which DailySchedule to update. + */ + where: Prisma.DailyScheduleWhereUniqueInput +} + +/** + * DailySchedule updateMany + */ +export type DailyScheduleUpdateManyArgs = { + /** + * The data used to update DailySchedules. + */ + data: Prisma.XOR + /** + * Filter which DailySchedules to update + */ + where?: Prisma.DailyScheduleWhereInput + /** + * Limit how many DailySchedules to update. + */ + limit?: number +} + +/** + * DailySchedule updateManyAndReturn + */ +export type DailyScheduleUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * The data used to update DailySchedules. + */ + data: Prisma.XOR + /** + * Filter which DailySchedules to update + */ + where?: Prisma.DailyScheduleWhereInput + /** + * Limit how many DailySchedules to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleIncludeUpdateManyAndReturn | null +} + +/** + * DailySchedule upsert + */ +export type DailyScheduleUpsertArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelect | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleInclude | null + /** + * The filter to search for the DailySchedule to update in case it exists. + */ + where: Prisma.DailyScheduleWhereUniqueInput + /** + * In case the DailySchedule found by the `where` argument doesn't exist, create a new DailySchedule with this data. + */ + create: Prisma.XOR + /** + * In case the DailySchedule was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * DailySchedule delete + */ +export type DailyScheduleDeleteArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelect | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleInclude | null + /** + * Filter which DailySchedule to delete. + */ + where: Prisma.DailyScheduleWhereUniqueInput +} + +/** + * DailySchedule deleteMany + */ +export type DailyScheduleDeleteManyArgs = { + /** + * Filter which DailySchedules to delete + */ + where?: Prisma.DailyScheduleWhereInput + /** + * Limit how many DailySchedules to delete. + */ + limit?: number +} + +/** + * DailySchedule without action + */ +export type DailyScheduleDefaultArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelect | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleInclude | null +} diff --git a/apps/api/src/generated/prisma/models/DailyStat.ts b/apps/api/src/generated/prisma/models/DailyStat.ts new file mode 100644 index 0000000..1f9ede7 --- /dev/null +++ b/apps/api/src/generated/prisma/models/DailyStat.ts @@ -0,0 +1,1763 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `DailyStat` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums" +import type * as Prisma from "../internal/prismaNamespace" + +/** + * Model DailyStat + * + */ +export type DailyStatModel = runtime.Types.Result.DefaultSelection + +export type AggregateDailyStat = { + _count: DailyStatCountAggregateOutputType | null + _avg: DailyStatAvgAggregateOutputType | null + _sum: DailyStatSumAggregateOutputType | null + _min: DailyStatMinAggregateOutputType | null + _max: DailyStatMaxAggregateOutputType | null +} + +export type DailyStatAvgAggregateOutputType = { + dayIndex: number | null + plannedSends: number | null + plannedReplies: number | null + actualSends: number | null + actualReplies: number | null + spamDetected: number | null + spamRescued: number | null + replyRateActual: number | null + inboxPlacementRate: number | null +} + +export type DailyStatSumAggregateOutputType = { + dayIndex: number | null + plannedSends: number | null + plannedReplies: number | null + actualSends: number | null + actualReplies: number | null + spamDetected: number | null + spamRescued: number | null + replyRateActual: number | null + inboxPlacementRate: number | null +} + +export type DailyStatMinAggregateOutputType = { + id: string | null + campaignId: string | null + date: Date | null + dayIndex: number | null + plannedSends: number | null + plannedReplies: number | null + actualSends: number | null + actualReplies: number | null + spamDetected: number | null + spamRescued: number | null + replyRateActual: number | null + inboxPlacementRate: number | null + createdAt: Date | null + updatedAt: Date | null +} + +export type DailyStatMaxAggregateOutputType = { + id: string | null + campaignId: string | null + date: Date | null + dayIndex: number | null + plannedSends: number | null + plannedReplies: number | null + actualSends: number | null + actualReplies: number | null + spamDetected: number | null + spamRescued: number | null + replyRateActual: number | null + inboxPlacementRate: number | null + createdAt: Date | null + updatedAt: Date | null +} + +export type DailyStatCountAggregateOutputType = { + id: number + campaignId: number + date: number + dayIndex: number + plannedSends: number + plannedReplies: number + actualSends: number + actualReplies: number + spamDetected: number + spamRescued: number + replyRateActual: number + inboxPlacementRate: number + createdAt: number + updatedAt: number + _all: number +} + + +export type DailyStatAvgAggregateInputType = { + dayIndex?: true + plannedSends?: true + plannedReplies?: true + actualSends?: true + actualReplies?: true + spamDetected?: true + spamRescued?: true + replyRateActual?: true + inboxPlacementRate?: true +} + +export type DailyStatSumAggregateInputType = { + dayIndex?: true + plannedSends?: true + plannedReplies?: true + actualSends?: true + actualReplies?: true + spamDetected?: true + spamRescued?: true + replyRateActual?: true + inboxPlacementRate?: true +} + +export type DailyStatMinAggregateInputType = { + id?: true + campaignId?: true + date?: true + dayIndex?: true + plannedSends?: true + plannedReplies?: true + actualSends?: true + actualReplies?: true + spamDetected?: true + spamRescued?: true + replyRateActual?: true + inboxPlacementRate?: true + createdAt?: true + updatedAt?: true +} + +export type DailyStatMaxAggregateInputType = { + id?: true + campaignId?: true + date?: true + dayIndex?: true + plannedSends?: true + plannedReplies?: true + actualSends?: true + actualReplies?: true + spamDetected?: true + spamRescued?: true + replyRateActual?: true + inboxPlacementRate?: true + createdAt?: true + updatedAt?: true +} + +export type DailyStatCountAggregateInputType = { + id?: true + campaignId?: true + date?: true + dayIndex?: true + plannedSends?: true + plannedReplies?: true + actualSends?: true + actualReplies?: true + spamDetected?: true + spamRescued?: true + replyRateActual?: true + inboxPlacementRate?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type DailyStatAggregateArgs = { + /** + * Filter which DailyStat to aggregate. + */ + where?: Prisma.DailyStatWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of DailyStats to fetch. + */ + orderBy?: Prisma.DailyStatOrderByWithRelationInput | Prisma.DailyStatOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.DailyStatWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` DailyStats from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` DailyStats. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned DailyStats + **/ + _count?: true | DailyStatCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: DailyStatAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: DailyStatSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: DailyStatMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: DailyStatMaxAggregateInputType +} + +export type GetDailyStatAggregateType = { + [P in keyof T & keyof AggregateDailyStat]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type DailyStatGroupByArgs = { + where?: Prisma.DailyStatWhereInput + orderBy?: Prisma.DailyStatOrderByWithAggregationInput | Prisma.DailyStatOrderByWithAggregationInput[] + by: Prisma.DailyStatScalarFieldEnum[] | Prisma.DailyStatScalarFieldEnum + having?: Prisma.DailyStatScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: DailyStatCountAggregateInputType | true + _avg?: DailyStatAvgAggregateInputType + _sum?: DailyStatSumAggregateInputType + _min?: DailyStatMinAggregateInputType + _max?: DailyStatMaxAggregateInputType +} + +export type DailyStatGroupByOutputType = { + id: string + campaignId: string + date: Date + dayIndex: number + plannedSends: number + plannedReplies: number + actualSends: number + actualReplies: number + spamDetected: number + spamRescued: number + replyRateActual: number | null + inboxPlacementRate: number | null + createdAt: Date + updatedAt: Date + _count: DailyStatCountAggregateOutputType | null + _avg: DailyStatAvgAggregateOutputType | null + _sum: DailyStatSumAggregateOutputType | null + _min: DailyStatMinAggregateOutputType | null + _max: DailyStatMaxAggregateOutputType | null +} + +export type GetDailyStatGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof DailyStatGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type DailyStatWhereInput = { + AND?: Prisma.DailyStatWhereInput | Prisma.DailyStatWhereInput[] + OR?: Prisma.DailyStatWhereInput[] + NOT?: Prisma.DailyStatWhereInput | Prisma.DailyStatWhereInput[] + id?: Prisma.StringFilter<"DailyStat"> | string + campaignId?: Prisma.StringFilter<"DailyStat"> | string + date?: Prisma.DateTimeFilter<"DailyStat"> | Date | string + dayIndex?: Prisma.IntFilter<"DailyStat"> | number + plannedSends?: Prisma.IntFilter<"DailyStat"> | number + plannedReplies?: Prisma.IntFilter<"DailyStat"> | number + actualSends?: Prisma.IntFilter<"DailyStat"> | number + actualReplies?: Prisma.IntFilter<"DailyStat"> | number + spamDetected?: Prisma.IntFilter<"DailyStat"> | number + spamRescued?: Prisma.IntFilter<"DailyStat"> | number + replyRateActual?: Prisma.FloatNullableFilter<"DailyStat"> | number | null + inboxPlacementRate?: Prisma.FloatNullableFilter<"DailyStat"> | number | null + createdAt?: Prisma.DateTimeFilter<"DailyStat"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"DailyStat"> | Date | string + campaign?: Prisma.XOR +} + +export type DailyStatOrderByWithRelationInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + date?: Prisma.SortOrder + dayIndex?: Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder + replyRateActual?: Prisma.SortOrderInput | Prisma.SortOrder + inboxPlacementRate?: Prisma.SortOrderInput | Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + campaign?: Prisma.WarmupCampaignOrderByWithRelationInput +} + +export type DailyStatWhereUniqueInput = Prisma.AtLeast<{ + id?: string + campaignId_date?: Prisma.DailyStatCampaignIdDateCompoundUniqueInput + AND?: Prisma.DailyStatWhereInput | Prisma.DailyStatWhereInput[] + OR?: Prisma.DailyStatWhereInput[] + NOT?: Prisma.DailyStatWhereInput | Prisma.DailyStatWhereInput[] + campaignId?: Prisma.StringFilter<"DailyStat"> | string + date?: Prisma.DateTimeFilter<"DailyStat"> | Date | string + dayIndex?: Prisma.IntFilter<"DailyStat"> | number + plannedSends?: Prisma.IntFilter<"DailyStat"> | number + plannedReplies?: Prisma.IntFilter<"DailyStat"> | number + actualSends?: Prisma.IntFilter<"DailyStat"> | number + actualReplies?: Prisma.IntFilter<"DailyStat"> | number + spamDetected?: Prisma.IntFilter<"DailyStat"> | number + spamRescued?: Prisma.IntFilter<"DailyStat"> | number + replyRateActual?: Prisma.FloatNullableFilter<"DailyStat"> | number | null + inboxPlacementRate?: Prisma.FloatNullableFilter<"DailyStat"> | number | null + createdAt?: Prisma.DateTimeFilter<"DailyStat"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"DailyStat"> | Date | string + campaign?: Prisma.XOR +}, "id" | "campaignId_date"> + +export type DailyStatOrderByWithAggregationInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + date?: Prisma.SortOrder + dayIndex?: Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder + replyRateActual?: Prisma.SortOrderInput | Prisma.SortOrder + inboxPlacementRate?: Prisma.SortOrderInput | Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.DailyStatCountOrderByAggregateInput + _avg?: Prisma.DailyStatAvgOrderByAggregateInput + _max?: Prisma.DailyStatMaxOrderByAggregateInput + _min?: Prisma.DailyStatMinOrderByAggregateInput + _sum?: Prisma.DailyStatSumOrderByAggregateInput +} + +export type DailyStatScalarWhereWithAggregatesInput = { + AND?: Prisma.DailyStatScalarWhereWithAggregatesInput | Prisma.DailyStatScalarWhereWithAggregatesInput[] + OR?: Prisma.DailyStatScalarWhereWithAggregatesInput[] + NOT?: Prisma.DailyStatScalarWhereWithAggregatesInput | Prisma.DailyStatScalarWhereWithAggregatesInput[] + id?: Prisma.StringWithAggregatesFilter<"DailyStat"> | string + campaignId?: Prisma.StringWithAggregatesFilter<"DailyStat"> | string + date?: Prisma.DateTimeWithAggregatesFilter<"DailyStat"> | Date | string + dayIndex?: Prisma.IntWithAggregatesFilter<"DailyStat"> | number + plannedSends?: Prisma.IntWithAggregatesFilter<"DailyStat"> | number + plannedReplies?: Prisma.IntWithAggregatesFilter<"DailyStat"> | number + actualSends?: Prisma.IntWithAggregatesFilter<"DailyStat"> | number + actualReplies?: Prisma.IntWithAggregatesFilter<"DailyStat"> | number + spamDetected?: Prisma.IntWithAggregatesFilter<"DailyStat"> | number + spamRescued?: Prisma.IntWithAggregatesFilter<"DailyStat"> | number + replyRateActual?: Prisma.FloatNullableWithAggregatesFilter<"DailyStat"> | number | null + inboxPlacementRate?: Prisma.FloatNullableWithAggregatesFilter<"DailyStat"> | number | null + createdAt?: Prisma.DateTimeWithAggregatesFilter<"DailyStat"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"DailyStat"> | Date | string +} + +export type DailyStatCreateInput = { + id?: string + date: Date | string + dayIndex: number + plannedSends: number + plannedReplies: number + actualSends?: number + actualReplies?: number + spamDetected?: number + spamRescued?: number + replyRateActual?: number | null + inboxPlacementRate?: number | null + createdAt?: Date | string + updatedAt?: Date | string + campaign: Prisma.WarmupCampaignCreateNestedOneWithoutDailyStatsInput +} + +export type DailyStatUncheckedCreateInput = { + id?: string + campaignId: string + date: Date | string + dayIndex: number + plannedSends: number + plannedReplies: number + actualSends?: number + actualReplies?: number + spamDetected?: number + spamRescued?: number + replyRateActual?: number | null + inboxPlacementRate?: number | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type DailyStatUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + replyRateActual?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + inboxPlacementRate?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + campaign?: Prisma.WarmupCampaignUpdateOneRequiredWithoutDailyStatsNestedInput +} + +export type DailyStatUncheckedUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.StringFieldUpdateOperationsInput | string + date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + replyRateActual?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + inboxPlacementRate?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type DailyStatCreateManyInput = { + id?: string + campaignId: string + date: Date | string + dayIndex: number + plannedSends: number + plannedReplies: number + actualSends?: number + actualReplies?: number + spamDetected?: number + spamRescued?: number + replyRateActual?: number | null + inboxPlacementRate?: number | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type DailyStatUpdateManyMutationInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + replyRateActual?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + inboxPlacementRate?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type DailyStatUncheckedUpdateManyInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.StringFieldUpdateOperationsInput | string + date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + replyRateActual?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + inboxPlacementRate?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type DailyStatListRelationFilter = { + every?: Prisma.DailyStatWhereInput + some?: Prisma.DailyStatWhereInput + none?: Prisma.DailyStatWhereInput +} + +export type DailyStatOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type DailyStatCampaignIdDateCompoundUniqueInput = { + campaignId: string + date: Date | string +} + +export type DailyStatCountOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + date?: Prisma.SortOrder + dayIndex?: Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder + replyRateActual?: Prisma.SortOrder + inboxPlacementRate?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type DailyStatAvgOrderByAggregateInput = { + dayIndex?: Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder + replyRateActual?: Prisma.SortOrder + inboxPlacementRate?: Prisma.SortOrder +} + +export type DailyStatMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + date?: Prisma.SortOrder + dayIndex?: Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder + replyRateActual?: Prisma.SortOrder + inboxPlacementRate?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type DailyStatMinOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + date?: Prisma.SortOrder + dayIndex?: Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder + replyRateActual?: Prisma.SortOrder + inboxPlacementRate?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type DailyStatSumOrderByAggregateInput = { + dayIndex?: Prisma.SortOrder + plannedSends?: Prisma.SortOrder + plannedReplies?: Prisma.SortOrder + actualSends?: Prisma.SortOrder + actualReplies?: Prisma.SortOrder + spamDetected?: Prisma.SortOrder + spamRescued?: Prisma.SortOrder + replyRateActual?: Prisma.SortOrder + inboxPlacementRate?: Prisma.SortOrder +} + +export type DailyStatCreateNestedManyWithoutCampaignInput = { + create?: Prisma.XOR | Prisma.DailyStatCreateWithoutCampaignInput[] | Prisma.DailyStatUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.DailyStatCreateOrConnectWithoutCampaignInput | Prisma.DailyStatCreateOrConnectWithoutCampaignInput[] + createMany?: Prisma.DailyStatCreateManyCampaignInputEnvelope + connect?: Prisma.DailyStatWhereUniqueInput | Prisma.DailyStatWhereUniqueInput[] +} + +export type DailyStatUncheckedCreateNestedManyWithoutCampaignInput = { + create?: Prisma.XOR | Prisma.DailyStatCreateWithoutCampaignInput[] | Prisma.DailyStatUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.DailyStatCreateOrConnectWithoutCampaignInput | Prisma.DailyStatCreateOrConnectWithoutCampaignInput[] + createMany?: Prisma.DailyStatCreateManyCampaignInputEnvelope + connect?: Prisma.DailyStatWhereUniqueInput | Prisma.DailyStatWhereUniqueInput[] +} + +export type DailyStatUpdateManyWithoutCampaignNestedInput = { + create?: Prisma.XOR | Prisma.DailyStatCreateWithoutCampaignInput[] | Prisma.DailyStatUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.DailyStatCreateOrConnectWithoutCampaignInput | Prisma.DailyStatCreateOrConnectWithoutCampaignInput[] + upsert?: Prisma.DailyStatUpsertWithWhereUniqueWithoutCampaignInput | Prisma.DailyStatUpsertWithWhereUniqueWithoutCampaignInput[] + createMany?: Prisma.DailyStatCreateManyCampaignInputEnvelope + set?: Prisma.DailyStatWhereUniqueInput | Prisma.DailyStatWhereUniqueInput[] + disconnect?: Prisma.DailyStatWhereUniqueInput | Prisma.DailyStatWhereUniqueInput[] + delete?: Prisma.DailyStatWhereUniqueInput | Prisma.DailyStatWhereUniqueInput[] + connect?: Prisma.DailyStatWhereUniqueInput | Prisma.DailyStatWhereUniqueInput[] + update?: Prisma.DailyStatUpdateWithWhereUniqueWithoutCampaignInput | Prisma.DailyStatUpdateWithWhereUniqueWithoutCampaignInput[] + updateMany?: Prisma.DailyStatUpdateManyWithWhereWithoutCampaignInput | Prisma.DailyStatUpdateManyWithWhereWithoutCampaignInput[] + deleteMany?: Prisma.DailyStatScalarWhereInput | Prisma.DailyStatScalarWhereInput[] +} + +export type DailyStatUncheckedUpdateManyWithoutCampaignNestedInput = { + create?: Prisma.XOR | Prisma.DailyStatCreateWithoutCampaignInput[] | Prisma.DailyStatUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.DailyStatCreateOrConnectWithoutCampaignInput | Prisma.DailyStatCreateOrConnectWithoutCampaignInput[] + upsert?: Prisma.DailyStatUpsertWithWhereUniqueWithoutCampaignInput | Prisma.DailyStatUpsertWithWhereUniqueWithoutCampaignInput[] + createMany?: Prisma.DailyStatCreateManyCampaignInputEnvelope + set?: Prisma.DailyStatWhereUniqueInput | Prisma.DailyStatWhereUniqueInput[] + disconnect?: Prisma.DailyStatWhereUniqueInput | Prisma.DailyStatWhereUniqueInput[] + delete?: Prisma.DailyStatWhereUniqueInput | Prisma.DailyStatWhereUniqueInput[] + connect?: Prisma.DailyStatWhereUniqueInput | Prisma.DailyStatWhereUniqueInput[] + update?: Prisma.DailyStatUpdateWithWhereUniqueWithoutCampaignInput | Prisma.DailyStatUpdateWithWhereUniqueWithoutCampaignInput[] + updateMany?: Prisma.DailyStatUpdateManyWithWhereWithoutCampaignInput | Prisma.DailyStatUpdateManyWithWhereWithoutCampaignInput[] + deleteMany?: Prisma.DailyStatScalarWhereInput | Prisma.DailyStatScalarWhereInput[] +} + +export type NullableFloatFieldUpdateOperationsInput = { + set?: number | null + increment?: number + decrement?: number + multiply?: number + divide?: number +} + +export type DailyStatCreateWithoutCampaignInput = { + id?: string + date: Date | string + dayIndex: number + plannedSends: number + plannedReplies: number + actualSends?: number + actualReplies?: number + spamDetected?: number + spamRescued?: number + replyRateActual?: number | null + inboxPlacementRate?: number | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type DailyStatUncheckedCreateWithoutCampaignInput = { + id?: string + date: Date | string + dayIndex: number + plannedSends: number + plannedReplies: number + actualSends?: number + actualReplies?: number + spamDetected?: number + spamRescued?: number + replyRateActual?: number | null + inboxPlacementRate?: number | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type DailyStatCreateOrConnectWithoutCampaignInput = { + where: Prisma.DailyStatWhereUniqueInput + create: Prisma.XOR +} + +export type DailyStatCreateManyCampaignInputEnvelope = { + data: Prisma.DailyStatCreateManyCampaignInput | Prisma.DailyStatCreateManyCampaignInput[] +} + +export type DailyStatUpsertWithWhereUniqueWithoutCampaignInput = { + where: Prisma.DailyStatWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type DailyStatUpdateWithWhereUniqueWithoutCampaignInput = { + where: Prisma.DailyStatWhereUniqueInput + data: Prisma.XOR +} + +export type DailyStatUpdateManyWithWhereWithoutCampaignInput = { + where: Prisma.DailyStatScalarWhereInput + data: Prisma.XOR +} + +export type DailyStatScalarWhereInput = { + AND?: Prisma.DailyStatScalarWhereInput | Prisma.DailyStatScalarWhereInput[] + OR?: Prisma.DailyStatScalarWhereInput[] + NOT?: Prisma.DailyStatScalarWhereInput | Prisma.DailyStatScalarWhereInput[] + id?: Prisma.StringFilter<"DailyStat"> | string + campaignId?: Prisma.StringFilter<"DailyStat"> | string + date?: Prisma.DateTimeFilter<"DailyStat"> | Date | string + dayIndex?: Prisma.IntFilter<"DailyStat"> | number + plannedSends?: Prisma.IntFilter<"DailyStat"> | number + plannedReplies?: Prisma.IntFilter<"DailyStat"> | number + actualSends?: Prisma.IntFilter<"DailyStat"> | number + actualReplies?: Prisma.IntFilter<"DailyStat"> | number + spamDetected?: Prisma.IntFilter<"DailyStat"> | number + spamRescued?: Prisma.IntFilter<"DailyStat"> | number + replyRateActual?: Prisma.FloatNullableFilter<"DailyStat"> | number | null + inboxPlacementRate?: Prisma.FloatNullableFilter<"DailyStat"> | number | null + createdAt?: Prisma.DateTimeFilter<"DailyStat"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"DailyStat"> | Date | string +} + +export type DailyStatCreateManyCampaignInput = { + id?: string + date: Date | string + dayIndex: number + plannedSends: number + plannedReplies: number + actualSends?: number + actualReplies?: number + spamDetected?: number + spamRescued?: number + replyRateActual?: number | null + inboxPlacementRate?: number | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type DailyStatUpdateWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + replyRateActual?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + inboxPlacementRate?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type DailyStatUncheckedUpdateWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + replyRateActual?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + inboxPlacementRate?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type DailyStatUncheckedUpdateManyWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + date?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + dayIndex?: Prisma.IntFieldUpdateOperationsInput | number + plannedSends?: Prisma.IntFieldUpdateOperationsInput | number + plannedReplies?: Prisma.IntFieldUpdateOperationsInput | number + actualSends?: Prisma.IntFieldUpdateOperationsInput | number + actualReplies?: Prisma.IntFieldUpdateOperationsInput | number + spamDetected?: Prisma.IntFieldUpdateOperationsInput | number + spamRescued?: Prisma.IntFieldUpdateOperationsInput | number + replyRateActual?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + inboxPlacementRate?: Prisma.NullableFloatFieldUpdateOperationsInput | number | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + + +export type DailyStatSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + date?: boolean + dayIndex?: boolean + plannedSends?: boolean + plannedReplies?: boolean + actualSends?: boolean + actualReplies?: boolean + spamDetected?: boolean + spamRescued?: boolean + replyRateActual?: boolean + inboxPlacementRate?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +}, ExtArgs["result"]["dailyStat"]> + +export type DailyStatSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + date?: boolean + dayIndex?: boolean + plannedSends?: boolean + plannedReplies?: boolean + actualSends?: boolean + actualReplies?: boolean + spamDetected?: boolean + spamRescued?: boolean + replyRateActual?: boolean + inboxPlacementRate?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +}, ExtArgs["result"]["dailyStat"]> + +export type DailyStatSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + date?: boolean + dayIndex?: boolean + plannedSends?: boolean + plannedReplies?: boolean + actualSends?: boolean + actualReplies?: boolean + spamDetected?: boolean + spamRescued?: boolean + replyRateActual?: boolean + inboxPlacementRate?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +}, ExtArgs["result"]["dailyStat"]> + +export type DailyStatSelectScalar = { + id?: boolean + campaignId?: boolean + date?: boolean + dayIndex?: boolean + plannedSends?: boolean + plannedReplies?: boolean + actualSends?: boolean + actualReplies?: boolean + spamDetected?: boolean + spamRescued?: boolean + replyRateActual?: boolean + inboxPlacementRate?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type DailyStatOmit = runtime.Types.Extensions.GetOmit<"id" | "campaignId" | "date" | "dayIndex" | "plannedSends" | "plannedReplies" | "actualSends" | "actualReplies" | "spamDetected" | "spamRescued" | "replyRateActual" | "inboxPlacementRate" | "createdAt" | "updatedAt", ExtArgs["result"]["dailyStat"]> +export type DailyStatInclude = { + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +} +export type DailyStatIncludeCreateManyAndReturn = { + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +} +export type DailyStatIncludeUpdateManyAndReturn = { + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +} + +export type $DailyStatPayload = { + name: "DailyStat" + objects: { + campaign: Prisma.$WarmupCampaignPayload + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: string + campaignId: string + date: Date + dayIndex: number + plannedSends: number + plannedReplies: number + actualSends: number + actualReplies: number + spamDetected: number + spamRescued: number + replyRateActual: number | null + inboxPlacementRate: number | null + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["dailyStat"]> + composites: {} +} + +export type DailyStatGetPayload = runtime.Types.Result.GetResult + +export type DailyStatCountArgs = + Omit & { + select?: DailyStatCountAggregateInputType | true + } + +export interface DailyStatDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['DailyStat'], meta: { name: 'DailyStat' } } + /** + * Find zero or one DailyStat that matches the filter. + * @param {DailyStatFindUniqueArgs} args - Arguments to find a DailyStat + * @example + * // Get one DailyStat + * const dailyStat = await prisma.dailyStat.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__DailyStatClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one DailyStat that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {DailyStatFindUniqueOrThrowArgs} args - Arguments to find a DailyStat + * @example + * // Get one DailyStat + * const dailyStat = await prisma.dailyStat.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__DailyStatClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first DailyStat that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyStatFindFirstArgs} args - Arguments to find a DailyStat + * @example + * // Get one DailyStat + * const dailyStat = await prisma.dailyStat.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__DailyStatClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first DailyStat that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyStatFindFirstOrThrowArgs} args - Arguments to find a DailyStat + * @example + * // Get one DailyStat + * const dailyStat = await prisma.dailyStat.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__DailyStatClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more DailyStats that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyStatFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all DailyStats + * const dailyStats = await prisma.dailyStat.findMany() + * + * // Get first 10 DailyStats + * const dailyStats = await prisma.dailyStat.findMany({ take: 10 }) + * + * // Only select the `id` + * const dailyStatWithIdOnly = await prisma.dailyStat.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a DailyStat. + * @param {DailyStatCreateArgs} args - Arguments to create a DailyStat. + * @example + * // Create one DailyStat + * const DailyStat = await prisma.dailyStat.create({ + * data: { + * // ... data to create a DailyStat + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__DailyStatClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many DailyStats. + * @param {DailyStatCreateManyArgs} args - Arguments to create many DailyStats. + * @example + * // Create many DailyStats + * const dailyStat = await prisma.dailyStat.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many DailyStats and returns the data saved in the database. + * @param {DailyStatCreateManyAndReturnArgs} args - Arguments to create many DailyStats. + * @example + * // Create many DailyStats + * const dailyStat = await prisma.dailyStat.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many DailyStats and only return the `id` + * const dailyStatWithIdOnly = await prisma.dailyStat.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a DailyStat. + * @param {DailyStatDeleteArgs} args - Arguments to delete one DailyStat. + * @example + * // Delete one DailyStat + * const DailyStat = await prisma.dailyStat.delete({ + * where: { + * // ... filter to delete one DailyStat + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__DailyStatClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one DailyStat. + * @param {DailyStatUpdateArgs} args - Arguments to update one DailyStat. + * @example + * // Update one DailyStat + * const dailyStat = await prisma.dailyStat.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__DailyStatClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more DailyStats. + * @param {DailyStatDeleteManyArgs} args - Arguments to filter DailyStats to delete. + * @example + * // Delete a few DailyStats + * const { count } = await prisma.dailyStat.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more DailyStats. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyStatUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many DailyStats + * const dailyStat = await prisma.dailyStat.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more DailyStats and returns the data updated in the database. + * @param {DailyStatUpdateManyAndReturnArgs} args - Arguments to update many DailyStats. + * @example + * // Update many DailyStats + * const dailyStat = await prisma.dailyStat.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more DailyStats and only return the `id` + * const dailyStatWithIdOnly = await prisma.dailyStat.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one DailyStat. + * @param {DailyStatUpsertArgs} args - Arguments to update or create a DailyStat. + * @example + * // Update or create a DailyStat + * const dailyStat = await prisma.dailyStat.upsert({ + * create: { + * // ... data to create a DailyStat + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the DailyStat we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__DailyStatClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of DailyStats. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyStatCountArgs} args - Arguments to filter DailyStats to count. + * @example + * // Count the number of DailyStats + * const count = await prisma.dailyStat.count({ + * where: { + * // ... the filter for the DailyStats we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a DailyStat. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyStatAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by DailyStat. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {DailyStatGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends DailyStatGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: DailyStatGroupByArgs['orderBy'] } + : { orderBy?: DailyStatGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetDailyStatGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the DailyStat model + */ +readonly fields: DailyStatFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for DailyStat. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__DailyStatClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + campaign = {}>(args?: Prisma.Subset>): Prisma.Prisma__WarmupCampaignClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the DailyStat model + */ +export interface DailyStatFieldRefs { + readonly id: Prisma.FieldRef<"DailyStat", 'String'> + readonly campaignId: Prisma.FieldRef<"DailyStat", 'String'> + readonly date: Prisma.FieldRef<"DailyStat", 'DateTime'> + readonly dayIndex: Prisma.FieldRef<"DailyStat", 'Int'> + readonly plannedSends: Prisma.FieldRef<"DailyStat", 'Int'> + readonly plannedReplies: Prisma.FieldRef<"DailyStat", 'Int'> + readonly actualSends: Prisma.FieldRef<"DailyStat", 'Int'> + readonly actualReplies: Prisma.FieldRef<"DailyStat", 'Int'> + readonly spamDetected: Prisma.FieldRef<"DailyStat", 'Int'> + readonly spamRescued: Prisma.FieldRef<"DailyStat", 'Int'> + readonly replyRateActual: Prisma.FieldRef<"DailyStat", 'Float'> + readonly inboxPlacementRate: Prisma.FieldRef<"DailyStat", 'Float'> + readonly createdAt: Prisma.FieldRef<"DailyStat", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"DailyStat", 'DateTime'> +} + + +// Custom InputTypes +/** + * DailyStat findUnique + */ +export type DailyStatFindUniqueArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelect | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatInclude | null + /** + * Filter, which DailyStat to fetch. + */ + where: Prisma.DailyStatWhereUniqueInput +} + +/** + * DailyStat findUniqueOrThrow + */ +export type DailyStatFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelect | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatInclude | null + /** + * Filter, which DailyStat to fetch. + */ + where: Prisma.DailyStatWhereUniqueInput +} + +/** + * DailyStat findFirst + */ +export type DailyStatFindFirstArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelect | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatInclude | null + /** + * Filter, which DailyStat to fetch. + */ + where?: Prisma.DailyStatWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of DailyStats to fetch. + */ + orderBy?: Prisma.DailyStatOrderByWithRelationInput | Prisma.DailyStatOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for DailyStats. + */ + cursor?: Prisma.DailyStatWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` DailyStats from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` DailyStats. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of DailyStats. + */ + distinct?: Prisma.DailyStatScalarFieldEnum | Prisma.DailyStatScalarFieldEnum[] +} + +/** + * DailyStat findFirstOrThrow + */ +export type DailyStatFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelect | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatInclude | null + /** + * Filter, which DailyStat to fetch. + */ + where?: Prisma.DailyStatWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of DailyStats to fetch. + */ + orderBy?: Prisma.DailyStatOrderByWithRelationInput | Prisma.DailyStatOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for DailyStats. + */ + cursor?: Prisma.DailyStatWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` DailyStats from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` DailyStats. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of DailyStats. + */ + distinct?: Prisma.DailyStatScalarFieldEnum | Prisma.DailyStatScalarFieldEnum[] +} + +/** + * DailyStat findMany + */ +export type DailyStatFindManyArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelect | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatInclude | null + /** + * Filter, which DailyStats to fetch. + */ + where?: Prisma.DailyStatWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of DailyStats to fetch. + */ + orderBy?: Prisma.DailyStatOrderByWithRelationInput | Prisma.DailyStatOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing DailyStats. + */ + cursor?: Prisma.DailyStatWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` DailyStats from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` DailyStats. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of DailyStats. + */ + distinct?: Prisma.DailyStatScalarFieldEnum | Prisma.DailyStatScalarFieldEnum[] +} + +/** + * DailyStat create + */ +export type DailyStatCreateArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelect | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatInclude | null + /** + * The data needed to create a DailyStat. + */ + data: Prisma.XOR +} + +/** + * DailyStat createMany + */ +export type DailyStatCreateManyArgs = { + /** + * The data used to create many DailyStats. + */ + data: Prisma.DailyStatCreateManyInput | Prisma.DailyStatCreateManyInput[] +} + +/** + * DailyStat createManyAndReturn + */ +export type DailyStatCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelectCreateManyAndReturn | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * The data used to create many DailyStats. + */ + data: Prisma.DailyStatCreateManyInput | Prisma.DailyStatCreateManyInput[] + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatIncludeCreateManyAndReturn | null +} + +/** + * DailyStat update + */ +export type DailyStatUpdateArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelect | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatInclude | null + /** + * The data needed to update a DailyStat. + */ + data: Prisma.XOR + /** + * Choose, which DailyStat to update. + */ + where: Prisma.DailyStatWhereUniqueInput +} + +/** + * DailyStat updateMany + */ +export type DailyStatUpdateManyArgs = { + /** + * The data used to update DailyStats. + */ + data: Prisma.XOR + /** + * Filter which DailyStats to update + */ + where?: Prisma.DailyStatWhereInput + /** + * Limit how many DailyStats to update. + */ + limit?: number +} + +/** + * DailyStat updateManyAndReturn + */ +export type DailyStatUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * The data used to update DailyStats. + */ + data: Prisma.XOR + /** + * Filter which DailyStats to update + */ + where?: Prisma.DailyStatWhereInput + /** + * Limit how many DailyStats to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatIncludeUpdateManyAndReturn | null +} + +/** + * DailyStat upsert + */ +export type DailyStatUpsertArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelect | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatInclude | null + /** + * The filter to search for the DailyStat to update in case it exists. + */ + where: Prisma.DailyStatWhereUniqueInput + /** + * In case the DailyStat found by the `where` argument doesn't exist, create a new DailyStat with this data. + */ + create: Prisma.XOR + /** + * In case the DailyStat was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * DailyStat delete + */ +export type DailyStatDeleteArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelect | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatInclude | null + /** + * Filter which DailyStat to delete. + */ + where: Prisma.DailyStatWhereUniqueInput +} + +/** + * DailyStat deleteMany + */ +export type DailyStatDeleteManyArgs = { + /** + * Filter which DailyStats to delete + */ + where?: Prisma.DailyStatWhereInput + /** + * Limit how many DailyStats to delete. + */ + limit?: number +} + +/** + * DailyStat without action + */ +export type DailyStatDefaultArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelect | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatInclude | null +} diff --git a/apps/api/src/generated/prisma/models/Inbox.ts b/apps/api/src/generated/prisma/models/Inbox.ts new file mode 100644 index 0000000..7c6ca45 --- /dev/null +++ b/apps/api/src/generated/prisma/models/Inbox.ts @@ -0,0 +1,2969 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `Inbox` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums" +import type * as Prisma from "../internal/prismaNamespace" + +/** + * Model Inbox + * + */ +export type InboxModel = runtime.Types.Result.DefaultSelection + +export type AggregateInbox = { + _count: InboxCountAggregateOutputType | null + _avg: InboxAvgAggregateOutputType | null + _sum: InboxSumAggregateOutputType | null + _min: InboxMinAggregateOutputType | null + _max: InboxMaxAggregateOutputType | null +} + +export type InboxAvgAggregateOutputType = { + smtpPort: number | null + imapPort: number | null + dailyLimit: number | null + currentRamp: number | null +} + +export type InboxSumAggregateOutputType = { + smtpPort: number | null + imapPort: number | null + dailyLimit: number | null + currentRamp: number | null +} + +export type InboxMinAggregateOutputType = { + id: string | null + workspaceId: string | null + email: string | null + smtpHost: string | null + smtpPort: number | null + imapHost: string | null + imapPort: number | null + username: string | null + password: string | null + authType: $Enums.MailboxAuthType | null + oauthRefreshToken: string | null + role: $Enums.MailboxRole | null + provider: $Enums.MailboxProvider | null + status: $Enums.InboxStatus | null + dailyLimit: number | null + currentRamp: number | null + industry: string | null + senderName: string | null + companyName: string | null + lastError: string | null + errorAt: Date | null + spamFolderPath: string | null + lastSpamRescuedAt: Date | null + createdAt: Date | null + updatedAt: Date | null +} + +export type InboxMaxAggregateOutputType = { + id: string | null + workspaceId: string | null + email: string | null + smtpHost: string | null + smtpPort: number | null + imapHost: string | null + imapPort: number | null + username: string | null + password: string | null + authType: $Enums.MailboxAuthType | null + oauthRefreshToken: string | null + role: $Enums.MailboxRole | null + provider: $Enums.MailboxProvider | null + status: $Enums.InboxStatus | null + dailyLimit: number | null + currentRamp: number | null + industry: string | null + senderName: string | null + companyName: string | null + lastError: string | null + errorAt: Date | null + spamFolderPath: string | null + lastSpamRescuedAt: Date | null + createdAt: Date | null + updatedAt: Date | null +} + +export type InboxCountAggregateOutputType = { + id: number + workspaceId: number + email: number + smtpHost: number + smtpPort: number + imapHost: number + imapPort: number + username: number + password: number + authType: number + oauthRefreshToken: number + role: number + provider: number + status: number + dailyLimit: number + currentRamp: number + industry: number + senderName: number + companyName: number + lastError: number + errorAt: number + spamFolderPath: number + lastSpamRescuedAt: number + createdAt: number + updatedAt: number + _all: number +} + + +export type InboxAvgAggregateInputType = { + smtpPort?: true + imapPort?: true + dailyLimit?: true + currentRamp?: true +} + +export type InboxSumAggregateInputType = { + smtpPort?: true + imapPort?: true + dailyLimit?: true + currentRamp?: true +} + +export type InboxMinAggregateInputType = { + id?: true + workspaceId?: true + email?: true + smtpHost?: true + smtpPort?: true + imapHost?: true + imapPort?: true + username?: true + password?: true + authType?: true + oauthRefreshToken?: true + role?: true + provider?: true + status?: true + dailyLimit?: true + currentRamp?: true + industry?: true + senderName?: true + companyName?: true + lastError?: true + errorAt?: true + spamFolderPath?: true + lastSpamRescuedAt?: true + createdAt?: true + updatedAt?: true +} + +export type InboxMaxAggregateInputType = { + id?: true + workspaceId?: true + email?: true + smtpHost?: true + smtpPort?: true + imapHost?: true + imapPort?: true + username?: true + password?: true + authType?: true + oauthRefreshToken?: true + role?: true + provider?: true + status?: true + dailyLimit?: true + currentRamp?: true + industry?: true + senderName?: true + companyName?: true + lastError?: true + errorAt?: true + spamFolderPath?: true + lastSpamRescuedAt?: true + createdAt?: true + updatedAt?: true +} + +export type InboxCountAggregateInputType = { + id?: true + workspaceId?: true + email?: true + smtpHost?: true + smtpPort?: true + imapHost?: true + imapPort?: true + username?: true + password?: true + authType?: true + oauthRefreshToken?: true + role?: true + provider?: true + status?: true + dailyLimit?: true + currentRamp?: true + industry?: true + senderName?: true + companyName?: true + lastError?: true + errorAt?: true + spamFolderPath?: true + lastSpamRescuedAt?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type InboxAggregateArgs = { + /** + * Filter which Inbox to aggregate. + */ + where?: Prisma.InboxWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Inboxes to fetch. + */ + orderBy?: Prisma.InboxOrderByWithRelationInput | Prisma.InboxOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.InboxWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Inboxes from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Inboxes. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned Inboxes + **/ + _count?: true | InboxCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: InboxAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: InboxSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: InboxMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: InboxMaxAggregateInputType +} + +export type GetInboxAggregateType = { + [P in keyof T & keyof AggregateInbox]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type InboxGroupByArgs = { + where?: Prisma.InboxWhereInput + orderBy?: Prisma.InboxOrderByWithAggregationInput | Prisma.InboxOrderByWithAggregationInput[] + by: Prisma.InboxScalarFieldEnum[] | Prisma.InboxScalarFieldEnum + having?: Prisma.InboxScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: InboxCountAggregateInputType | true + _avg?: InboxAvgAggregateInputType + _sum?: InboxSumAggregateInputType + _min?: InboxMinAggregateInputType + _max?: InboxMaxAggregateInputType +} + +export type InboxGroupByOutputType = { + id: string + workspaceId: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType: $Enums.MailboxAuthType + oauthRefreshToken: string | null + role: $Enums.MailboxRole + provider: $Enums.MailboxProvider + status: $Enums.InboxStatus + dailyLimit: number + currentRamp: number + industry: string + senderName: string | null + companyName: string | null + lastError: string | null + errorAt: Date | null + spamFolderPath: string | null + lastSpamRescuedAt: Date | null + createdAt: Date + updatedAt: Date + _count: InboxCountAggregateOutputType | null + _avg: InboxAvgAggregateOutputType | null + _sum: InboxSumAggregateOutputType | null + _min: InboxMinAggregateOutputType | null + _max: InboxMaxAggregateOutputType | null +} + +export type GetInboxGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof InboxGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type InboxWhereInput = { + AND?: Prisma.InboxWhereInput | Prisma.InboxWhereInput[] + OR?: Prisma.InboxWhereInput[] + NOT?: Prisma.InboxWhereInput | Prisma.InboxWhereInput[] + id?: Prisma.StringFilter<"Inbox"> | string + workspaceId?: Prisma.StringFilter<"Inbox"> | string + email?: Prisma.StringFilter<"Inbox"> | string + smtpHost?: Prisma.StringFilter<"Inbox"> | string + smtpPort?: Prisma.IntFilter<"Inbox"> | number + imapHost?: Prisma.StringFilter<"Inbox"> | string + imapPort?: Prisma.IntFilter<"Inbox"> | number + username?: Prisma.StringFilter<"Inbox"> | string + password?: Prisma.StringFilter<"Inbox"> | string + authType?: Prisma.EnumMailboxAuthTypeFilter<"Inbox"> | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.StringNullableFilter<"Inbox"> | string | null + role?: Prisma.EnumMailboxRoleFilter<"Inbox"> | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFilter<"Inbox"> | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFilter<"Inbox"> | $Enums.InboxStatus + dailyLimit?: Prisma.IntFilter<"Inbox"> | number + currentRamp?: Prisma.IntFilter<"Inbox"> | number + industry?: Prisma.StringFilter<"Inbox"> | string + senderName?: Prisma.StringNullableFilter<"Inbox"> | string | null + companyName?: Prisma.StringNullableFilter<"Inbox"> | string | null + lastError?: Prisma.StringNullableFilter<"Inbox"> | string | null + errorAt?: Prisma.DateTimeNullableFilter<"Inbox"> | Date | string | null + spamFolderPath?: Prisma.StringNullableFilter<"Inbox"> | string | null + lastSpamRescuedAt?: Prisma.DateTimeNullableFilter<"Inbox"> | Date | string | null + createdAt?: Prisma.DateTimeFilter<"Inbox"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Inbox"> | Date | string + workspace?: Prisma.XOR + sentLogs?: Prisma.WarmupLogListRelationFilter + receivedLogs?: Prisma.WarmupLogListRelationFilter + primaryCampaigns?: Prisma.WarmupCampaignListRelationFilter + satelliteCampaigns?: Prisma.CampaignSatelliteListRelationFilter +} + +export type InboxOrderByWithRelationInput = { + id?: Prisma.SortOrder + workspaceId?: Prisma.SortOrder + email?: Prisma.SortOrder + smtpHost?: Prisma.SortOrder + smtpPort?: Prisma.SortOrder + imapHost?: Prisma.SortOrder + imapPort?: Prisma.SortOrder + username?: Prisma.SortOrder + password?: Prisma.SortOrder + authType?: Prisma.SortOrder + oauthRefreshToken?: Prisma.SortOrderInput | Prisma.SortOrder + role?: Prisma.SortOrder + provider?: Prisma.SortOrder + status?: Prisma.SortOrder + dailyLimit?: Prisma.SortOrder + currentRamp?: Prisma.SortOrder + industry?: Prisma.SortOrder + senderName?: Prisma.SortOrderInput | Prisma.SortOrder + companyName?: Prisma.SortOrderInput | Prisma.SortOrder + lastError?: Prisma.SortOrderInput | Prisma.SortOrder + errorAt?: Prisma.SortOrderInput | Prisma.SortOrder + spamFolderPath?: Prisma.SortOrderInput | Prisma.SortOrder + lastSpamRescuedAt?: Prisma.SortOrderInput | Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + workspace?: Prisma.WorkspaceOrderByWithRelationInput + sentLogs?: Prisma.WarmupLogOrderByRelationAggregateInput + receivedLogs?: Prisma.WarmupLogOrderByRelationAggregateInput + primaryCampaigns?: Prisma.WarmupCampaignOrderByRelationAggregateInput + satelliteCampaigns?: Prisma.CampaignSatelliteOrderByRelationAggregateInput +} + +export type InboxWhereUniqueInput = Prisma.AtLeast<{ + id?: string + workspaceId_email?: Prisma.InboxWorkspaceIdEmailCompoundUniqueInput + AND?: Prisma.InboxWhereInput | Prisma.InboxWhereInput[] + OR?: Prisma.InboxWhereInput[] + NOT?: Prisma.InboxWhereInput | Prisma.InboxWhereInput[] + workspaceId?: Prisma.StringFilter<"Inbox"> | string + email?: Prisma.StringFilter<"Inbox"> | string + smtpHost?: Prisma.StringFilter<"Inbox"> | string + smtpPort?: Prisma.IntFilter<"Inbox"> | number + imapHost?: Prisma.StringFilter<"Inbox"> | string + imapPort?: Prisma.IntFilter<"Inbox"> | number + username?: Prisma.StringFilter<"Inbox"> | string + password?: Prisma.StringFilter<"Inbox"> | string + authType?: Prisma.EnumMailboxAuthTypeFilter<"Inbox"> | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.StringNullableFilter<"Inbox"> | string | null + role?: Prisma.EnumMailboxRoleFilter<"Inbox"> | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFilter<"Inbox"> | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFilter<"Inbox"> | $Enums.InboxStatus + dailyLimit?: Prisma.IntFilter<"Inbox"> | number + currentRamp?: Prisma.IntFilter<"Inbox"> | number + industry?: Prisma.StringFilter<"Inbox"> | string + senderName?: Prisma.StringNullableFilter<"Inbox"> | string | null + companyName?: Prisma.StringNullableFilter<"Inbox"> | string | null + lastError?: Prisma.StringNullableFilter<"Inbox"> | string | null + errorAt?: Prisma.DateTimeNullableFilter<"Inbox"> | Date | string | null + spamFolderPath?: Prisma.StringNullableFilter<"Inbox"> | string | null + lastSpamRescuedAt?: Prisma.DateTimeNullableFilter<"Inbox"> | Date | string | null + createdAt?: Prisma.DateTimeFilter<"Inbox"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Inbox"> | Date | string + workspace?: Prisma.XOR + sentLogs?: Prisma.WarmupLogListRelationFilter + receivedLogs?: Prisma.WarmupLogListRelationFilter + primaryCampaigns?: Prisma.WarmupCampaignListRelationFilter + satelliteCampaigns?: Prisma.CampaignSatelliteListRelationFilter +}, "id" | "workspaceId_email"> + +export type InboxOrderByWithAggregationInput = { + id?: Prisma.SortOrder + workspaceId?: Prisma.SortOrder + email?: Prisma.SortOrder + smtpHost?: Prisma.SortOrder + smtpPort?: Prisma.SortOrder + imapHost?: Prisma.SortOrder + imapPort?: Prisma.SortOrder + username?: Prisma.SortOrder + password?: Prisma.SortOrder + authType?: Prisma.SortOrder + oauthRefreshToken?: Prisma.SortOrderInput | Prisma.SortOrder + role?: Prisma.SortOrder + provider?: Prisma.SortOrder + status?: Prisma.SortOrder + dailyLimit?: Prisma.SortOrder + currentRamp?: Prisma.SortOrder + industry?: Prisma.SortOrder + senderName?: Prisma.SortOrderInput | Prisma.SortOrder + companyName?: Prisma.SortOrderInput | Prisma.SortOrder + lastError?: Prisma.SortOrderInput | Prisma.SortOrder + errorAt?: Prisma.SortOrderInput | Prisma.SortOrder + spamFolderPath?: Prisma.SortOrderInput | Prisma.SortOrder + lastSpamRescuedAt?: Prisma.SortOrderInput | Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.InboxCountOrderByAggregateInput + _avg?: Prisma.InboxAvgOrderByAggregateInput + _max?: Prisma.InboxMaxOrderByAggregateInput + _min?: Prisma.InboxMinOrderByAggregateInput + _sum?: Prisma.InboxSumOrderByAggregateInput +} + +export type InboxScalarWhereWithAggregatesInput = { + AND?: Prisma.InboxScalarWhereWithAggregatesInput | Prisma.InboxScalarWhereWithAggregatesInput[] + OR?: Prisma.InboxScalarWhereWithAggregatesInput[] + NOT?: Prisma.InboxScalarWhereWithAggregatesInput | Prisma.InboxScalarWhereWithAggregatesInput[] + id?: Prisma.StringWithAggregatesFilter<"Inbox"> | string + workspaceId?: Prisma.StringWithAggregatesFilter<"Inbox"> | string + email?: Prisma.StringWithAggregatesFilter<"Inbox"> | string + smtpHost?: Prisma.StringWithAggregatesFilter<"Inbox"> | string + smtpPort?: Prisma.IntWithAggregatesFilter<"Inbox"> | number + imapHost?: Prisma.StringWithAggregatesFilter<"Inbox"> | string + imapPort?: Prisma.IntWithAggregatesFilter<"Inbox"> | number + username?: Prisma.StringWithAggregatesFilter<"Inbox"> | string + password?: Prisma.StringWithAggregatesFilter<"Inbox"> | string + authType?: Prisma.EnumMailboxAuthTypeWithAggregatesFilter<"Inbox"> | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.StringNullableWithAggregatesFilter<"Inbox"> | string | null + role?: Prisma.EnumMailboxRoleWithAggregatesFilter<"Inbox"> | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderWithAggregatesFilter<"Inbox"> | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusWithAggregatesFilter<"Inbox"> | $Enums.InboxStatus + dailyLimit?: Prisma.IntWithAggregatesFilter<"Inbox"> | number + currentRamp?: Prisma.IntWithAggregatesFilter<"Inbox"> | number + industry?: Prisma.StringWithAggregatesFilter<"Inbox"> | string + senderName?: Prisma.StringNullableWithAggregatesFilter<"Inbox"> | string | null + companyName?: Prisma.StringNullableWithAggregatesFilter<"Inbox"> | string | null + lastError?: Prisma.StringNullableWithAggregatesFilter<"Inbox"> | string | null + errorAt?: Prisma.DateTimeNullableWithAggregatesFilter<"Inbox"> | Date | string | null + spamFolderPath?: Prisma.StringNullableWithAggregatesFilter<"Inbox"> | string | null + lastSpamRescuedAt?: Prisma.DateTimeNullableWithAggregatesFilter<"Inbox"> | Date | string | null + createdAt?: Prisma.DateTimeWithAggregatesFilter<"Inbox"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Inbox"> | Date | string +} + +export type InboxCreateInput = { + id?: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + workspace: Prisma.WorkspaceCreateNestedOneWithoutInboxesInput + sentLogs?: Prisma.WarmupLogCreateNestedManyWithoutSenderInput + receivedLogs?: Prisma.WarmupLogCreateNestedManyWithoutReceiverInput + primaryCampaigns?: Prisma.WarmupCampaignCreateNestedManyWithoutPrimaryMailboxInput + satelliteCampaigns?: Prisma.CampaignSatelliteCreateNestedManyWithoutSatelliteMailboxInput +} + +export type InboxUncheckedCreateInput = { + id?: string + workspaceId: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + sentLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutSenderInput + receivedLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutReceiverInput + primaryCampaigns?: Prisma.WarmupCampaignUncheckedCreateNestedManyWithoutPrimaryMailboxInput + satelliteCampaigns?: Prisma.CampaignSatelliteUncheckedCreateNestedManyWithoutSatelliteMailboxInput +} + +export type InboxUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + workspace?: Prisma.WorkspaceUpdateOneRequiredWithoutInboxesNestedInput + sentLogs?: Prisma.WarmupLogUpdateManyWithoutSenderNestedInput + receivedLogs?: Prisma.WarmupLogUpdateManyWithoutReceiverNestedInput + primaryCampaigns?: Prisma.WarmupCampaignUpdateManyWithoutPrimaryMailboxNestedInput + satelliteCampaigns?: Prisma.CampaignSatelliteUpdateManyWithoutSatelliteMailboxNestedInput +} + +export type InboxUncheckedUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + sentLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutSenderNestedInput + receivedLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutReceiverNestedInput + primaryCampaigns?: Prisma.WarmupCampaignUncheckedUpdateManyWithoutPrimaryMailboxNestedInput + satelliteCampaigns?: Prisma.CampaignSatelliteUncheckedUpdateManyWithoutSatelliteMailboxNestedInput +} + +export type InboxCreateManyInput = { + id?: string + workspaceId: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type InboxUpdateManyMutationInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type InboxUncheckedUpdateManyInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type InboxListRelationFilter = { + every?: Prisma.InboxWhereInput + some?: Prisma.InboxWhereInput + none?: Prisma.InboxWhereInput +} + +export type InboxOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type InboxWorkspaceIdEmailCompoundUniqueInput = { + workspaceId: string + email: string +} + +export type InboxCountOrderByAggregateInput = { + id?: Prisma.SortOrder + workspaceId?: Prisma.SortOrder + email?: Prisma.SortOrder + smtpHost?: Prisma.SortOrder + smtpPort?: Prisma.SortOrder + imapHost?: Prisma.SortOrder + imapPort?: Prisma.SortOrder + username?: Prisma.SortOrder + password?: Prisma.SortOrder + authType?: Prisma.SortOrder + oauthRefreshToken?: Prisma.SortOrder + role?: Prisma.SortOrder + provider?: Prisma.SortOrder + status?: Prisma.SortOrder + dailyLimit?: Prisma.SortOrder + currentRamp?: Prisma.SortOrder + industry?: Prisma.SortOrder + senderName?: Prisma.SortOrder + companyName?: Prisma.SortOrder + lastError?: Prisma.SortOrder + errorAt?: Prisma.SortOrder + spamFolderPath?: Prisma.SortOrder + lastSpamRescuedAt?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type InboxAvgOrderByAggregateInput = { + smtpPort?: Prisma.SortOrder + imapPort?: Prisma.SortOrder + dailyLimit?: Prisma.SortOrder + currentRamp?: Prisma.SortOrder +} + +export type InboxMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + workspaceId?: Prisma.SortOrder + email?: Prisma.SortOrder + smtpHost?: Prisma.SortOrder + smtpPort?: Prisma.SortOrder + imapHost?: Prisma.SortOrder + imapPort?: Prisma.SortOrder + username?: Prisma.SortOrder + password?: Prisma.SortOrder + authType?: Prisma.SortOrder + oauthRefreshToken?: Prisma.SortOrder + role?: Prisma.SortOrder + provider?: Prisma.SortOrder + status?: Prisma.SortOrder + dailyLimit?: Prisma.SortOrder + currentRamp?: Prisma.SortOrder + industry?: Prisma.SortOrder + senderName?: Prisma.SortOrder + companyName?: Prisma.SortOrder + lastError?: Prisma.SortOrder + errorAt?: Prisma.SortOrder + spamFolderPath?: Prisma.SortOrder + lastSpamRescuedAt?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type InboxMinOrderByAggregateInput = { + id?: Prisma.SortOrder + workspaceId?: Prisma.SortOrder + email?: Prisma.SortOrder + smtpHost?: Prisma.SortOrder + smtpPort?: Prisma.SortOrder + imapHost?: Prisma.SortOrder + imapPort?: Prisma.SortOrder + username?: Prisma.SortOrder + password?: Prisma.SortOrder + authType?: Prisma.SortOrder + oauthRefreshToken?: Prisma.SortOrder + role?: Prisma.SortOrder + provider?: Prisma.SortOrder + status?: Prisma.SortOrder + dailyLimit?: Prisma.SortOrder + currentRamp?: Prisma.SortOrder + industry?: Prisma.SortOrder + senderName?: Prisma.SortOrder + companyName?: Prisma.SortOrder + lastError?: Prisma.SortOrder + errorAt?: Prisma.SortOrder + spamFolderPath?: Prisma.SortOrder + lastSpamRescuedAt?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type InboxSumOrderByAggregateInput = { + smtpPort?: Prisma.SortOrder + imapPort?: Prisma.SortOrder + dailyLimit?: Prisma.SortOrder + currentRamp?: Prisma.SortOrder +} + +export type InboxScalarRelationFilter = { + is?: Prisma.InboxWhereInput + isNot?: Prisma.InboxWhereInput +} + +export type InboxCreateNestedManyWithoutWorkspaceInput = { + create?: Prisma.XOR | Prisma.InboxCreateWithoutWorkspaceInput[] | Prisma.InboxUncheckedCreateWithoutWorkspaceInput[] + connectOrCreate?: Prisma.InboxCreateOrConnectWithoutWorkspaceInput | Prisma.InboxCreateOrConnectWithoutWorkspaceInput[] + createMany?: Prisma.InboxCreateManyWorkspaceInputEnvelope + connect?: Prisma.InboxWhereUniqueInput | Prisma.InboxWhereUniqueInput[] +} + +export type InboxUncheckedCreateNestedManyWithoutWorkspaceInput = { + create?: Prisma.XOR | Prisma.InboxCreateWithoutWorkspaceInput[] | Prisma.InboxUncheckedCreateWithoutWorkspaceInput[] + connectOrCreate?: Prisma.InboxCreateOrConnectWithoutWorkspaceInput | Prisma.InboxCreateOrConnectWithoutWorkspaceInput[] + createMany?: Prisma.InboxCreateManyWorkspaceInputEnvelope + connect?: Prisma.InboxWhereUniqueInput | Prisma.InboxWhereUniqueInput[] +} + +export type InboxUpdateManyWithoutWorkspaceNestedInput = { + create?: Prisma.XOR | Prisma.InboxCreateWithoutWorkspaceInput[] | Prisma.InboxUncheckedCreateWithoutWorkspaceInput[] + connectOrCreate?: Prisma.InboxCreateOrConnectWithoutWorkspaceInput | Prisma.InboxCreateOrConnectWithoutWorkspaceInput[] + upsert?: Prisma.InboxUpsertWithWhereUniqueWithoutWorkspaceInput | Prisma.InboxUpsertWithWhereUniqueWithoutWorkspaceInput[] + createMany?: Prisma.InboxCreateManyWorkspaceInputEnvelope + set?: Prisma.InboxWhereUniqueInput | Prisma.InboxWhereUniqueInput[] + disconnect?: Prisma.InboxWhereUniqueInput | Prisma.InboxWhereUniqueInput[] + delete?: Prisma.InboxWhereUniqueInput | Prisma.InboxWhereUniqueInput[] + connect?: Prisma.InboxWhereUniqueInput | Prisma.InboxWhereUniqueInput[] + update?: Prisma.InboxUpdateWithWhereUniqueWithoutWorkspaceInput | Prisma.InboxUpdateWithWhereUniqueWithoutWorkspaceInput[] + updateMany?: Prisma.InboxUpdateManyWithWhereWithoutWorkspaceInput | Prisma.InboxUpdateManyWithWhereWithoutWorkspaceInput[] + deleteMany?: Prisma.InboxScalarWhereInput | Prisma.InboxScalarWhereInput[] +} + +export type InboxUncheckedUpdateManyWithoutWorkspaceNestedInput = { + create?: Prisma.XOR | Prisma.InboxCreateWithoutWorkspaceInput[] | Prisma.InboxUncheckedCreateWithoutWorkspaceInput[] + connectOrCreate?: Prisma.InboxCreateOrConnectWithoutWorkspaceInput | Prisma.InboxCreateOrConnectWithoutWorkspaceInput[] + upsert?: Prisma.InboxUpsertWithWhereUniqueWithoutWorkspaceInput | Prisma.InboxUpsertWithWhereUniqueWithoutWorkspaceInput[] + createMany?: Prisma.InboxCreateManyWorkspaceInputEnvelope + set?: Prisma.InboxWhereUniqueInput | Prisma.InboxWhereUniqueInput[] + disconnect?: Prisma.InboxWhereUniqueInput | Prisma.InboxWhereUniqueInput[] + delete?: Prisma.InboxWhereUniqueInput | Prisma.InboxWhereUniqueInput[] + connect?: Prisma.InboxWhereUniqueInput | Prisma.InboxWhereUniqueInput[] + update?: Prisma.InboxUpdateWithWhereUniqueWithoutWorkspaceInput | Prisma.InboxUpdateWithWhereUniqueWithoutWorkspaceInput[] + updateMany?: Prisma.InboxUpdateManyWithWhereWithoutWorkspaceInput | Prisma.InboxUpdateManyWithWhereWithoutWorkspaceInput[] + deleteMany?: Prisma.InboxScalarWhereInput | Prisma.InboxScalarWhereInput[] +} + +export type IntFieldUpdateOperationsInput = { + set?: number + increment?: number + decrement?: number + multiply?: number + divide?: number +} + +export type EnumMailboxAuthTypeFieldUpdateOperationsInput = { + set?: $Enums.MailboxAuthType +} + +export type EnumMailboxRoleFieldUpdateOperationsInput = { + set?: $Enums.MailboxRole +} + +export type EnumMailboxProviderFieldUpdateOperationsInput = { + set?: $Enums.MailboxProvider +} + +export type EnumInboxStatusFieldUpdateOperationsInput = { + set?: $Enums.InboxStatus +} + +export type InboxCreateNestedOneWithoutPrimaryCampaignsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.InboxCreateOrConnectWithoutPrimaryCampaignsInput + connect?: Prisma.InboxWhereUniqueInput +} + +export type InboxUpdateOneRequiredWithoutPrimaryCampaignsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.InboxCreateOrConnectWithoutPrimaryCampaignsInput + upsert?: Prisma.InboxUpsertWithoutPrimaryCampaignsInput + connect?: Prisma.InboxWhereUniqueInput + update?: Prisma.XOR, Prisma.InboxUncheckedUpdateWithoutPrimaryCampaignsInput> +} + +export type InboxCreateNestedOneWithoutSatelliteCampaignsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.InboxCreateOrConnectWithoutSatelliteCampaignsInput + connect?: Prisma.InboxWhereUniqueInput +} + +export type InboxUpdateOneRequiredWithoutSatelliteCampaignsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.InboxCreateOrConnectWithoutSatelliteCampaignsInput + upsert?: Prisma.InboxUpsertWithoutSatelliteCampaignsInput + connect?: Prisma.InboxWhereUniqueInput + update?: Prisma.XOR, Prisma.InboxUncheckedUpdateWithoutSatelliteCampaignsInput> +} + +export type InboxCreateNestedOneWithoutSentLogsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.InboxCreateOrConnectWithoutSentLogsInput + connect?: Prisma.InboxWhereUniqueInput +} + +export type InboxCreateNestedOneWithoutReceivedLogsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.InboxCreateOrConnectWithoutReceivedLogsInput + connect?: Prisma.InboxWhereUniqueInput +} + +export type InboxUpdateOneRequiredWithoutSentLogsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.InboxCreateOrConnectWithoutSentLogsInput + upsert?: Prisma.InboxUpsertWithoutSentLogsInput + connect?: Prisma.InboxWhereUniqueInput + update?: Prisma.XOR, Prisma.InboxUncheckedUpdateWithoutSentLogsInput> +} + +export type InboxUpdateOneRequiredWithoutReceivedLogsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.InboxCreateOrConnectWithoutReceivedLogsInput + upsert?: Prisma.InboxUpsertWithoutReceivedLogsInput + connect?: Prisma.InboxWhereUniqueInput + update?: Prisma.XOR, Prisma.InboxUncheckedUpdateWithoutReceivedLogsInput> +} + +export type InboxCreateWithoutWorkspaceInput = { + id?: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + sentLogs?: Prisma.WarmupLogCreateNestedManyWithoutSenderInput + receivedLogs?: Prisma.WarmupLogCreateNestedManyWithoutReceiverInput + primaryCampaigns?: Prisma.WarmupCampaignCreateNestedManyWithoutPrimaryMailboxInput + satelliteCampaigns?: Prisma.CampaignSatelliteCreateNestedManyWithoutSatelliteMailboxInput +} + +export type InboxUncheckedCreateWithoutWorkspaceInput = { + id?: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + sentLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutSenderInput + receivedLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutReceiverInput + primaryCampaigns?: Prisma.WarmupCampaignUncheckedCreateNestedManyWithoutPrimaryMailboxInput + satelliteCampaigns?: Prisma.CampaignSatelliteUncheckedCreateNestedManyWithoutSatelliteMailboxInput +} + +export type InboxCreateOrConnectWithoutWorkspaceInput = { + where: Prisma.InboxWhereUniqueInput + create: Prisma.XOR +} + +export type InboxCreateManyWorkspaceInputEnvelope = { + data: Prisma.InboxCreateManyWorkspaceInput | Prisma.InboxCreateManyWorkspaceInput[] +} + +export type InboxUpsertWithWhereUniqueWithoutWorkspaceInput = { + where: Prisma.InboxWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type InboxUpdateWithWhereUniqueWithoutWorkspaceInput = { + where: Prisma.InboxWhereUniqueInput + data: Prisma.XOR +} + +export type InboxUpdateManyWithWhereWithoutWorkspaceInput = { + where: Prisma.InboxScalarWhereInput + data: Prisma.XOR +} + +export type InboxScalarWhereInput = { + AND?: Prisma.InboxScalarWhereInput | Prisma.InboxScalarWhereInput[] + OR?: Prisma.InboxScalarWhereInput[] + NOT?: Prisma.InboxScalarWhereInput | Prisma.InboxScalarWhereInput[] + id?: Prisma.StringFilter<"Inbox"> | string + workspaceId?: Prisma.StringFilter<"Inbox"> | string + email?: Prisma.StringFilter<"Inbox"> | string + smtpHost?: Prisma.StringFilter<"Inbox"> | string + smtpPort?: Prisma.IntFilter<"Inbox"> | number + imapHost?: Prisma.StringFilter<"Inbox"> | string + imapPort?: Prisma.IntFilter<"Inbox"> | number + username?: Prisma.StringFilter<"Inbox"> | string + password?: Prisma.StringFilter<"Inbox"> | string + authType?: Prisma.EnumMailboxAuthTypeFilter<"Inbox"> | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.StringNullableFilter<"Inbox"> | string | null + role?: Prisma.EnumMailboxRoleFilter<"Inbox"> | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFilter<"Inbox"> | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFilter<"Inbox"> | $Enums.InboxStatus + dailyLimit?: Prisma.IntFilter<"Inbox"> | number + currentRamp?: Prisma.IntFilter<"Inbox"> | number + industry?: Prisma.StringFilter<"Inbox"> | string + senderName?: Prisma.StringNullableFilter<"Inbox"> | string | null + companyName?: Prisma.StringNullableFilter<"Inbox"> | string | null + lastError?: Prisma.StringNullableFilter<"Inbox"> | string | null + errorAt?: Prisma.DateTimeNullableFilter<"Inbox"> | Date | string | null + spamFolderPath?: Prisma.StringNullableFilter<"Inbox"> | string | null + lastSpamRescuedAt?: Prisma.DateTimeNullableFilter<"Inbox"> | Date | string | null + createdAt?: Prisma.DateTimeFilter<"Inbox"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Inbox"> | Date | string +} + +export type InboxCreateWithoutPrimaryCampaignsInput = { + id?: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + workspace: Prisma.WorkspaceCreateNestedOneWithoutInboxesInput + sentLogs?: Prisma.WarmupLogCreateNestedManyWithoutSenderInput + receivedLogs?: Prisma.WarmupLogCreateNestedManyWithoutReceiverInput + satelliteCampaigns?: Prisma.CampaignSatelliteCreateNestedManyWithoutSatelliteMailboxInput +} + +export type InboxUncheckedCreateWithoutPrimaryCampaignsInput = { + id?: string + workspaceId: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + sentLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutSenderInput + receivedLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutReceiverInput + satelliteCampaigns?: Prisma.CampaignSatelliteUncheckedCreateNestedManyWithoutSatelliteMailboxInput +} + +export type InboxCreateOrConnectWithoutPrimaryCampaignsInput = { + where: Prisma.InboxWhereUniqueInput + create: Prisma.XOR +} + +export type InboxUpsertWithoutPrimaryCampaignsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.InboxWhereInput +} + +export type InboxUpdateToOneWithWhereWithoutPrimaryCampaignsInput = { + where?: Prisma.InboxWhereInput + data: Prisma.XOR +} + +export type InboxUpdateWithoutPrimaryCampaignsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + workspace?: Prisma.WorkspaceUpdateOneRequiredWithoutInboxesNestedInput + sentLogs?: Prisma.WarmupLogUpdateManyWithoutSenderNestedInput + receivedLogs?: Prisma.WarmupLogUpdateManyWithoutReceiverNestedInput + satelliteCampaigns?: Prisma.CampaignSatelliteUpdateManyWithoutSatelliteMailboxNestedInput +} + +export type InboxUncheckedUpdateWithoutPrimaryCampaignsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + sentLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutSenderNestedInput + receivedLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutReceiverNestedInput + satelliteCampaigns?: Prisma.CampaignSatelliteUncheckedUpdateManyWithoutSatelliteMailboxNestedInput +} + +export type InboxCreateWithoutSatelliteCampaignsInput = { + id?: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + workspace: Prisma.WorkspaceCreateNestedOneWithoutInboxesInput + sentLogs?: Prisma.WarmupLogCreateNestedManyWithoutSenderInput + receivedLogs?: Prisma.WarmupLogCreateNestedManyWithoutReceiverInput + primaryCampaigns?: Prisma.WarmupCampaignCreateNestedManyWithoutPrimaryMailboxInput +} + +export type InboxUncheckedCreateWithoutSatelliteCampaignsInput = { + id?: string + workspaceId: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + sentLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutSenderInput + receivedLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutReceiverInput + primaryCampaigns?: Prisma.WarmupCampaignUncheckedCreateNestedManyWithoutPrimaryMailboxInput +} + +export type InboxCreateOrConnectWithoutSatelliteCampaignsInput = { + where: Prisma.InboxWhereUniqueInput + create: Prisma.XOR +} + +export type InboxUpsertWithoutSatelliteCampaignsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.InboxWhereInput +} + +export type InboxUpdateToOneWithWhereWithoutSatelliteCampaignsInput = { + where?: Prisma.InboxWhereInput + data: Prisma.XOR +} + +export type InboxUpdateWithoutSatelliteCampaignsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + workspace?: Prisma.WorkspaceUpdateOneRequiredWithoutInboxesNestedInput + sentLogs?: Prisma.WarmupLogUpdateManyWithoutSenderNestedInput + receivedLogs?: Prisma.WarmupLogUpdateManyWithoutReceiverNestedInput + primaryCampaigns?: Prisma.WarmupCampaignUpdateManyWithoutPrimaryMailboxNestedInput +} + +export type InboxUncheckedUpdateWithoutSatelliteCampaignsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + sentLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutSenderNestedInput + receivedLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutReceiverNestedInput + primaryCampaigns?: Prisma.WarmupCampaignUncheckedUpdateManyWithoutPrimaryMailboxNestedInput +} + +export type InboxCreateWithoutSentLogsInput = { + id?: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + workspace: Prisma.WorkspaceCreateNestedOneWithoutInboxesInput + receivedLogs?: Prisma.WarmupLogCreateNestedManyWithoutReceiverInput + primaryCampaigns?: Prisma.WarmupCampaignCreateNestedManyWithoutPrimaryMailboxInput + satelliteCampaigns?: Prisma.CampaignSatelliteCreateNestedManyWithoutSatelliteMailboxInput +} + +export type InboxUncheckedCreateWithoutSentLogsInput = { + id?: string + workspaceId: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + receivedLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutReceiverInput + primaryCampaigns?: Prisma.WarmupCampaignUncheckedCreateNestedManyWithoutPrimaryMailboxInput + satelliteCampaigns?: Prisma.CampaignSatelliteUncheckedCreateNestedManyWithoutSatelliteMailboxInput +} + +export type InboxCreateOrConnectWithoutSentLogsInput = { + where: Prisma.InboxWhereUniqueInput + create: Prisma.XOR +} + +export type InboxCreateWithoutReceivedLogsInput = { + id?: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + workspace: Prisma.WorkspaceCreateNestedOneWithoutInboxesInput + sentLogs?: Prisma.WarmupLogCreateNestedManyWithoutSenderInput + primaryCampaigns?: Prisma.WarmupCampaignCreateNestedManyWithoutPrimaryMailboxInput + satelliteCampaigns?: Prisma.CampaignSatelliteCreateNestedManyWithoutSatelliteMailboxInput +} + +export type InboxUncheckedCreateWithoutReceivedLogsInput = { + id?: string + workspaceId: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + sentLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutSenderInput + primaryCampaigns?: Prisma.WarmupCampaignUncheckedCreateNestedManyWithoutPrimaryMailboxInput + satelliteCampaigns?: Prisma.CampaignSatelliteUncheckedCreateNestedManyWithoutSatelliteMailboxInput +} + +export type InboxCreateOrConnectWithoutReceivedLogsInput = { + where: Prisma.InboxWhereUniqueInput + create: Prisma.XOR +} + +export type InboxUpsertWithoutSentLogsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.InboxWhereInput +} + +export type InboxUpdateToOneWithWhereWithoutSentLogsInput = { + where?: Prisma.InboxWhereInput + data: Prisma.XOR +} + +export type InboxUpdateWithoutSentLogsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + workspace?: Prisma.WorkspaceUpdateOneRequiredWithoutInboxesNestedInput + receivedLogs?: Prisma.WarmupLogUpdateManyWithoutReceiverNestedInput + primaryCampaigns?: Prisma.WarmupCampaignUpdateManyWithoutPrimaryMailboxNestedInput + satelliteCampaigns?: Prisma.CampaignSatelliteUpdateManyWithoutSatelliteMailboxNestedInput +} + +export type InboxUncheckedUpdateWithoutSentLogsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + receivedLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutReceiverNestedInput + primaryCampaigns?: Prisma.WarmupCampaignUncheckedUpdateManyWithoutPrimaryMailboxNestedInput + satelliteCampaigns?: Prisma.CampaignSatelliteUncheckedUpdateManyWithoutSatelliteMailboxNestedInput +} + +export type InboxUpsertWithoutReceivedLogsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.InboxWhereInput +} + +export type InboxUpdateToOneWithWhereWithoutReceivedLogsInput = { + where?: Prisma.InboxWhereInput + data: Prisma.XOR +} + +export type InboxUpdateWithoutReceivedLogsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + workspace?: Prisma.WorkspaceUpdateOneRequiredWithoutInboxesNestedInput + sentLogs?: Prisma.WarmupLogUpdateManyWithoutSenderNestedInput + primaryCampaigns?: Prisma.WarmupCampaignUpdateManyWithoutPrimaryMailboxNestedInput + satelliteCampaigns?: Prisma.CampaignSatelliteUpdateManyWithoutSatelliteMailboxNestedInput +} + +export type InboxUncheckedUpdateWithoutReceivedLogsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + sentLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutSenderNestedInput + primaryCampaigns?: Prisma.WarmupCampaignUncheckedUpdateManyWithoutPrimaryMailboxNestedInput + satelliteCampaigns?: Prisma.CampaignSatelliteUncheckedUpdateManyWithoutSatelliteMailboxNestedInput +} + +export type InboxCreateManyWorkspaceInput = { + id?: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType?: $Enums.MailboxAuthType + oauthRefreshToken?: string | null + role?: $Enums.MailboxRole + provider?: $Enums.MailboxProvider + status?: $Enums.InboxStatus + dailyLimit?: number + currentRamp?: number + industry?: string + senderName?: string | null + companyName?: string | null + lastError?: string | null + errorAt?: Date | string | null + spamFolderPath?: string | null + lastSpamRescuedAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type InboxUpdateWithoutWorkspaceInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + sentLogs?: Prisma.WarmupLogUpdateManyWithoutSenderNestedInput + receivedLogs?: Prisma.WarmupLogUpdateManyWithoutReceiverNestedInput + primaryCampaigns?: Prisma.WarmupCampaignUpdateManyWithoutPrimaryMailboxNestedInput + satelliteCampaigns?: Prisma.CampaignSatelliteUpdateManyWithoutSatelliteMailboxNestedInput +} + +export type InboxUncheckedUpdateWithoutWorkspaceInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + sentLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutSenderNestedInput + receivedLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutReceiverNestedInput + primaryCampaigns?: Prisma.WarmupCampaignUncheckedUpdateManyWithoutPrimaryMailboxNestedInput + satelliteCampaigns?: Prisma.CampaignSatelliteUncheckedUpdateManyWithoutSatelliteMailboxNestedInput +} + +export type InboxUncheckedUpdateManyWithoutWorkspaceInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + email?: Prisma.StringFieldUpdateOperationsInput | string + smtpHost?: Prisma.StringFieldUpdateOperationsInput | string + smtpPort?: Prisma.IntFieldUpdateOperationsInput | number + imapHost?: Prisma.StringFieldUpdateOperationsInput | string + imapPort?: Prisma.IntFieldUpdateOperationsInput | number + username?: Prisma.StringFieldUpdateOperationsInput | string + password?: Prisma.StringFieldUpdateOperationsInput | string + authType?: Prisma.EnumMailboxAuthTypeFieldUpdateOperationsInput | $Enums.MailboxAuthType + oauthRefreshToken?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + role?: Prisma.EnumMailboxRoleFieldUpdateOperationsInput | $Enums.MailboxRole + provider?: Prisma.EnumMailboxProviderFieldUpdateOperationsInput | $Enums.MailboxProvider + status?: Prisma.EnumInboxStatusFieldUpdateOperationsInput | $Enums.InboxStatus + dailyLimit?: Prisma.IntFieldUpdateOperationsInput | number + currentRamp?: Prisma.IntFieldUpdateOperationsInput | number + industry?: Prisma.StringFieldUpdateOperationsInput | string + senderName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + companyName?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastError?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + errorAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + spamFolderPath?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + lastSpamRescuedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + +/** + * Count Type InboxCountOutputType + */ + +export type InboxCountOutputType = { + sentLogs: number + receivedLogs: number + primaryCampaigns: number + satelliteCampaigns: number +} + +export type InboxCountOutputTypeSelect = { + sentLogs?: boolean | InboxCountOutputTypeCountSentLogsArgs + receivedLogs?: boolean | InboxCountOutputTypeCountReceivedLogsArgs + primaryCampaigns?: boolean | InboxCountOutputTypeCountPrimaryCampaignsArgs + satelliteCampaigns?: boolean | InboxCountOutputTypeCountSatelliteCampaignsArgs +} + +/** + * InboxCountOutputType without action + */ +export type InboxCountOutputTypeDefaultArgs = { + /** + * Select specific fields to fetch from the InboxCountOutputType + */ + select?: Prisma.InboxCountOutputTypeSelect | null +} + +/** + * InboxCountOutputType without action + */ +export type InboxCountOutputTypeCountSentLogsArgs = { + where?: Prisma.WarmupLogWhereInput +} + +/** + * InboxCountOutputType without action + */ +export type InboxCountOutputTypeCountReceivedLogsArgs = { + where?: Prisma.WarmupLogWhereInput +} + +/** + * InboxCountOutputType without action + */ +export type InboxCountOutputTypeCountPrimaryCampaignsArgs = { + where?: Prisma.WarmupCampaignWhereInput +} + +/** + * InboxCountOutputType without action + */ +export type InboxCountOutputTypeCountSatelliteCampaignsArgs = { + where?: Prisma.CampaignSatelliteWhereInput +} + + +export type InboxSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + workspaceId?: boolean + email?: boolean + smtpHost?: boolean + smtpPort?: boolean + imapHost?: boolean + imapPort?: boolean + username?: boolean + password?: boolean + authType?: boolean + oauthRefreshToken?: boolean + role?: boolean + provider?: boolean + status?: boolean + dailyLimit?: boolean + currentRamp?: boolean + industry?: boolean + senderName?: boolean + companyName?: boolean + lastError?: boolean + errorAt?: boolean + spamFolderPath?: boolean + lastSpamRescuedAt?: boolean + createdAt?: boolean + updatedAt?: boolean + workspace?: boolean | Prisma.WorkspaceDefaultArgs + sentLogs?: boolean | Prisma.Inbox$sentLogsArgs + receivedLogs?: boolean | Prisma.Inbox$receivedLogsArgs + primaryCampaigns?: boolean | Prisma.Inbox$primaryCampaignsArgs + satelliteCampaigns?: boolean | Prisma.Inbox$satelliteCampaignsArgs + _count?: boolean | Prisma.InboxCountOutputTypeDefaultArgs +}, ExtArgs["result"]["inbox"]> + +export type InboxSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + workspaceId?: boolean + email?: boolean + smtpHost?: boolean + smtpPort?: boolean + imapHost?: boolean + imapPort?: boolean + username?: boolean + password?: boolean + authType?: boolean + oauthRefreshToken?: boolean + role?: boolean + provider?: boolean + status?: boolean + dailyLimit?: boolean + currentRamp?: boolean + industry?: boolean + senderName?: boolean + companyName?: boolean + lastError?: boolean + errorAt?: boolean + spamFolderPath?: boolean + lastSpamRescuedAt?: boolean + createdAt?: boolean + updatedAt?: boolean + workspace?: boolean | Prisma.WorkspaceDefaultArgs +}, ExtArgs["result"]["inbox"]> + +export type InboxSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + workspaceId?: boolean + email?: boolean + smtpHost?: boolean + smtpPort?: boolean + imapHost?: boolean + imapPort?: boolean + username?: boolean + password?: boolean + authType?: boolean + oauthRefreshToken?: boolean + role?: boolean + provider?: boolean + status?: boolean + dailyLimit?: boolean + currentRamp?: boolean + industry?: boolean + senderName?: boolean + companyName?: boolean + lastError?: boolean + errorAt?: boolean + spamFolderPath?: boolean + lastSpamRescuedAt?: boolean + createdAt?: boolean + updatedAt?: boolean + workspace?: boolean | Prisma.WorkspaceDefaultArgs +}, ExtArgs["result"]["inbox"]> + +export type InboxSelectScalar = { + id?: boolean + workspaceId?: boolean + email?: boolean + smtpHost?: boolean + smtpPort?: boolean + imapHost?: boolean + imapPort?: boolean + username?: boolean + password?: boolean + authType?: boolean + oauthRefreshToken?: boolean + role?: boolean + provider?: boolean + status?: boolean + dailyLimit?: boolean + currentRamp?: boolean + industry?: boolean + senderName?: boolean + companyName?: boolean + lastError?: boolean + errorAt?: boolean + spamFolderPath?: boolean + lastSpamRescuedAt?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type InboxOmit = runtime.Types.Extensions.GetOmit<"id" | "workspaceId" | "email" | "smtpHost" | "smtpPort" | "imapHost" | "imapPort" | "username" | "password" | "authType" | "oauthRefreshToken" | "role" | "provider" | "status" | "dailyLimit" | "currentRamp" | "industry" | "senderName" | "companyName" | "lastError" | "errorAt" | "spamFolderPath" | "lastSpamRescuedAt" | "createdAt" | "updatedAt", ExtArgs["result"]["inbox"]> +export type InboxInclude = { + workspace?: boolean | Prisma.WorkspaceDefaultArgs + sentLogs?: boolean | Prisma.Inbox$sentLogsArgs + receivedLogs?: boolean | Prisma.Inbox$receivedLogsArgs + primaryCampaigns?: boolean | Prisma.Inbox$primaryCampaignsArgs + satelliteCampaigns?: boolean | Prisma.Inbox$satelliteCampaignsArgs + _count?: boolean | Prisma.InboxCountOutputTypeDefaultArgs +} +export type InboxIncludeCreateManyAndReturn = { + workspace?: boolean | Prisma.WorkspaceDefaultArgs +} +export type InboxIncludeUpdateManyAndReturn = { + workspace?: boolean | Prisma.WorkspaceDefaultArgs +} + +export type $InboxPayload = { + name: "Inbox" + objects: { + workspace: Prisma.$WorkspacePayload + sentLogs: Prisma.$WarmupLogPayload[] + receivedLogs: Prisma.$WarmupLogPayload[] + primaryCampaigns: Prisma.$WarmupCampaignPayload[] + satelliteCampaigns: Prisma.$CampaignSatellitePayload[] + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: string + workspaceId: string + email: string + smtpHost: string + smtpPort: number + imapHost: string + imapPort: number + username: string + password: string + authType: $Enums.MailboxAuthType + oauthRefreshToken: string | null + role: $Enums.MailboxRole + provider: $Enums.MailboxProvider + status: $Enums.InboxStatus + dailyLimit: number + currentRamp: number + industry: string + senderName: string | null + companyName: string | null + lastError: string | null + errorAt: Date | null + spamFolderPath: string | null + lastSpamRescuedAt: Date | null + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["inbox"]> + composites: {} +} + +export type InboxGetPayload = runtime.Types.Result.GetResult + +export type InboxCountArgs = + Omit & { + select?: InboxCountAggregateInputType | true + } + +export interface InboxDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['Inbox'], meta: { name: 'Inbox' } } + /** + * Find zero or one Inbox that matches the filter. + * @param {InboxFindUniqueArgs} args - Arguments to find a Inbox + * @example + * // Get one Inbox + * const inbox = await prisma.inbox.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__InboxClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one Inbox that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {InboxFindUniqueOrThrowArgs} args - Arguments to find a Inbox + * @example + * // Get one Inbox + * const inbox = await prisma.inbox.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__InboxClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Inbox that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {InboxFindFirstArgs} args - Arguments to find a Inbox + * @example + * // Get one Inbox + * const inbox = await prisma.inbox.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__InboxClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Inbox that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {InboxFindFirstOrThrowArgs} args - Arguments to find a Inbox + * @example + * // Get one Inbox + * const inbox = await prisma.inbox.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__InboxClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more Inboxes that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {InboxFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all Inboxes + * const inboxes = await prisma.inbox.findMany() + * + * // Get first 10 Inboxes + * const inboxes = await prisma.inbox.findMany({ take: 10 }) + * + * // Only select the `id` + * const inboxWithIdOnly = await prisma.inbox.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a Inbox. + * @param {InboxCreateArgs} args - Arguments to create a Inbox. + * @example + * // Create one Inbox + * const Inbox = await prisma.inbox.create({ + * data: { + * // ... data to create a Inbox + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__InboxClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many Inboxes. + * @param {InboxCreateManyArgs} args - Arguments to create many Inboxes. + * @example + * // Create many Inboxes + * const inbox = await prisma.inbox.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many Inboxes and returns the data saved in the database. + * @param {InboxCreateManyAndReturnArgs} args - Arguments to create many Inboxes. + * @example + * // Create many Inboxes + * const inbox = await prisma.inbox.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Inboxes and only return the `id` + * const inboxWithIdOnly = await prisma.inbox.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a Inbox. + * @param {InboxDeleteArgs} args - Arguments to delete one Inbox. + * @example + * // Delete one Inbox + * const Inbox = await prisma.inbox.delete({ + * where: { + * // ... filter to delete one Inbox + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__InboxClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one Inbox. + * @param {InboxUpdateArgs} args - Arguments to update one Inbox. + * @example + * // Update one Inbox + * const inbox = await prisma.inbox.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__InboxClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more Inboxes. + * @param {InboxDeleteManyArgs} args - Arguments to filter Inboxes to delete. + * @example + * // Delete a few Inboxes + * const { count } = await prisma.inbox.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Inboxes. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {InboxUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many Inboxes + * const inbox = await prisma.inbox.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Inboxes and returns the data updated in the database. + * @param {InboxUpdateManyAndReturnArgs} args - Arguments to update many Inboxes. + * @example + * // Update many Inboxes + * const inbox = await prisma.inbox.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Inboxes and only return the `id` + * const inboxWithIdOnly = await prisma.inbox.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one Inbox. + * @param {InboxUpsertArgs} args - Arguments to update or create a Inbox. + * @example + * // Update or create a Inbox + * const inbox = await prisma.inbox.upsert({ + * create: { + * // ... data to create a Inbox + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the Inbox we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__InboxClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of Inboxes. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {InboxCountArgs} args - Arguments to filter Inboxes to count. + * @example + * // Count the number of Inboxes + * const count = await prisma.inbox.count({ + * where: { + * // ... the filter for the Inboxes we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a Inbox. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {InboxAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by Inbox. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {InboxGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends InboxGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: InboxGroupByArgs['orderBy'] } + : { orderBy?: InboxGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetInboxGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the Inbox model + */ +readonly fields: InboxFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for Inbox. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__InboxClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + workspace = {}>(args?: Prisma.Subset>): Prisma.Prisma__WorkspaceClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + sentLogs = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + receivedLogs = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + primaryCampaigns = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + satelliteCampaigns = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the Inbox model + */ +export interface InboxFieldRefs { + readonly id: Prisma.FieldRef<"Inbox", 'String'> + readonly workspaceId: Prisma.FieldRef<"Inbox", 'String'> + readonly email: Prisma.FieldRef<"Inbox", 'String'> + readonly smtpHost: Prisma.FieldRef<"Inbox", 'String'> + readonly smtpPort: Prisma.FieldRef<"Inbox", 'Int'> + readonly imapHost: Prisma.FieldRef<"Inbox", 'String'> + readonly imapPort: Prisma.FieldRef<"Inbox", 'Int'> + readonly username: Prisma.FieldRef<"Inbox", 'String'> + readonly password: Prisma.FieldRef<"Inbox", 'String'> + readonly authType: Prisma.FieldRef<"Inbox", 'MailboxAuthType'> + readonly oauthRefreshToken: Prisma.FieldRef<"Inbox", 'String'> + readonly role: Prisma.FieldRef<"Inbox", 'MailboxRole'> + readonly provider: Prisma.FieldRef<"Inbox", 'MailboxProvider'> + readonly status: Prisma.FieldRef<"Inbox", 'InboxStatus'> + readonly dailyLimit: Prisma.FieldRef<"Inbox", 'Int'> + readonly currentRamp: Prisma.FieldRef<"Inbox", 'Int'> + readonly industry: Prisma.FieldRef<"Inbox", 'String'> + readonly senderName: Prisma.FieldRef<"Inbox", 'String'> + readonly companyName: Prisma.FieldRef<"Inbox", 'String'> + readonly lastError: Prisma.FieldRef<"Inbox", 'String'> + readonly errorAt: Prisma.FieldRef<"Inbox", 'DateTime'> + readonly spamFolderPath: Prisma.FieldRef<"Inbox", 'String'> + readonly lastSpamRescuedAt: Prisma.FieldRef<"Inbox", 'DateTime'> + readonly createdAt: Prisma.FieldRef<"Inbox", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"Inbox", 'DateTime'> +} + + +// Custom InputTypes +/** + * Inbox findUnique + */ +export type InboxFindUniqueArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelect | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxInclude | null + /** + * Filter, which Inbox to fetch. + */ + where: Prisma.InboxWhereUniqueInput +} + +/** + * Inbox findUniqueOrThrow + */ +export type InboxFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelect | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxInclude | null + /** + * Filter, which Inbox to fetch. + */ + where: Prisma.InboxWhereUniqueInput +} + +/** + * Inbox findFirst + */ +export type InboxFindFirstArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelect | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxInclude | null + /** + * Filter, which Inbox to fetch. + */ + where?: Prisma.InboxWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Inboxes to fetch. + */ + orderBy?: Prisma.InboxOrderByWithRelationInput | Prisma.InboxOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Inboxes. + */ + cursor?: Prisma.InboxWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Inboxes from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Inboxes. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Inboxes. + */ + distinct?: Prisma.InboxScalarFieldEnum | Prisma.InboxScalarFieldEnum[] +} + +/** + * Inbox findFirstOrThrow + */ +export type InboxFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelect | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxInclude | null + /** + * Filter, which Inbox to fetch. + */ + where?: Prisma.InboxWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Inboxes to fetch. + */ + orderBy?: Prisma.InboxOrderByWithRelationInput | Prisma.InboxOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Inboxes. + */ + cursor?: Prisma.InboxWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Inboxes from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Inboxes. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Inboxes. + */ + distinct?: Prisma.InboxScalarFieldEnum | Prisma.InboxScalarFieldEnum[] +} + +/** + * Inbox findMany + */ +export type InboxFindManyArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelect | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxInclude | null + /** + * Filter, which Inboxes to fetch. + */ + where?: Prisma.InboxWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Inboxes to fetch. + */ + orderBy?: Prisma.InboxOrderByWithRelationInput | Prisma.InboxOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing Inboxes. + */ + cursor?: Prisma.InboxWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Inboxes from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Inboxes. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Inboxes. + */ + distinct?: Prisma.InboxScalarFieldEnum | Prisma.InboxScalarFieldEnum[] +} + +/** + * Inbox create + */ +export type InboxCreateArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelect | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxInclude | null + /** + * The data needed to create a Inbox. + */ + data: Prisma.XOR +} + +/** + * Inbox createMany + */ +export type InboxCreateManyArgs = { + /** + * The data used to create many Inboxes. + */ + data: Prisma.InboxCreateManyInput | Prisma.InboxCreateManyInput[] +} + +/** + * Inbox createManyAndReturn + */ +export type InboxCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelectCreateManyAndReturn | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * The data used to create many Inboxes. + */ + data: Prisma.InboxCreateManyInput | Prisma.InboxCreateManyInput[] + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxIncludeCreateManyAndReturn | null +} + +/** + * Inbox update + */ +export type InboxUpdateArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelect | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxInclude | null + /** + * The data needed to update a Inbox. + */ + data: Prisma.XOR + /** + * Choose, which Inbox to update. + */ + where: Prisma.InboxWhereUniqueInput +} + +/** + * Inbox updateMany + */ +export type InboxUpdateManyArgs = { + /** + * The data used to update Inboxes. + */ + data: Prisma.XOR + /** + * Filter which Inboxes to update + */ + where?: Prisma.InboxWhereInput + /** + * Limit how many Inboxes to update. + */ + limit?: number +} + +/** + * Inbox updateManyAndReturn + */ +export type InboxUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * The data used to update Inboxes. + */ + data: Prisma.XOR + /** + * Filter which Inboxes to update + */ + where?: Prisma.InboxWhereInput + /** + * Limit how many Inboxes to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxIncludeUpdateManyAndReturn | null +} + +/** + * Inbox upsert + */ +export type InboxUpsertArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelect | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxInclude | null + /** + * The filter to search for the Inbox to update in case it exists. + */ + where: Prisma.InboxWhereUniqueInput + /** + * In case the Inbox found by the `where` argument doesn't exist, create a new Inbox with this data. + */ + create: Prisma.XOR + /** + * In case the Inbox was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * Inbox delete + */ +export type InboxDeleteArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelect | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxInclude | null + /** + * Filter which Inbox to delete. + */ + where: Prisma.InboxWhereUniqueInput +} + +/** + * Inbox deleteMany + */ +export type InboxDeleteManyArgs = { + /** + * Filter which Inboxes to delete + */ + where?: Prisma.InboxWhereInput + /** + * Limit how many Inboxes to delete. + */ + limit?: number +} + +/** + * Inbox.sentLogs + */ +export type Inbox$sentLogsArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null + where?: Prisma.WarmupLogWhereInput + orderBy?: Prisma.WarmupLogOrderByWithRelationInput | Prisma.WarmupLogOrderByWithRelationInput[] + cursor?: Prisma.WarmupLogWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.WarmupLogScalarFieldEnum | Prisma.WarmupLogScalarFieldEnum[] +} + +/** + * Inbox.receivedLogs + */ +export type Inbox$receivedLogsArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null + where?: Prisma.WarmupLogWhereInput + orderBy?: Prisma.WarmupLogOrderByWithRelationInput | Prisma.WarmupLogOrderByWithRelationInput[] + cursor?: Prisma.WarmupLogWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.WarmupLogScalarFieldEnum | Prisma.WarmupLogScalarFieldEnum[] +} + +/** + * Inbox.primaryCampaigns + */ +export type Inbox$primaryCampaignsArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null + where?: Prisma.WarmupCampaignWhereInput + orderBy?: Prisma.WarmupCampaignOrderByWithRelationInput | Prisma.WarmupCampaignOrderByWithRelationInput[] + cursor?: Prisma.WarmupCampaignWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.WarmupCampaignScalarFieldEnum | Prisma.WarmupCampaignScalarFieldEnum[] +} + +/** + * Inbox.satelliteCampaigns + */ +export type Inbox$satelliteCampaignsArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelect | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteInclude | null + where?: Prisma.CampaignSatelliteWhereInput + orderBy?: Prisma.CampaignSatelliteOrderByWithRelationInput | Prisma.CampaignSatelliteOrderByWithRelationInput[] + cursor?: Prisma.CampaignSatelliteWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.CampaignSatelliteScalarFieldEnum | Prisma.CampaignSatelliteScalarFieldEnum[] +} + +/** + * Inbox without action + */ +export type InboxDefaultArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelect | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxInclude | null +} diff --git a/apps/api/src/generated/prisma/models/JobRunLog.ts b/apps/api/src/generated/prisma/models/JobRunLog.ts new file mode 100644 index 0000000..c6ce13c --- /dev/null +++ b/apps/api/src/generated/prisma/models/JobRunLog.ts @@ -0,0 +1,1151 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `JobRunLog` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums" +import type * as Prisma from "../internal/prismaNamespace" + +/** + * Model JobRunLog + * + */ +export type JobRunLogModel = runtime.Types.Result.DefaultSelection + +export type AggregateJobRunLog = { + _count: JobRunLogCountAggregateOutputType | null + _min: JobRunLogMinAggregateOutputType | null + _max: JobRunLogMaxAggregateOutputType | null +} + +export type JobRunLogMinAggregateOutputType = { + id: string | null + jobName: string | null + ranAt: Date | null + success: boolean | null + message: string | null +} + +export type JobRunLogMaxAggregateOutputType = { + id: string | null + jobName: string | null + ranAt: Date | null + success: boolean | null + message: string | null +} + +export type JobRunLogCountAggregateOutputType = { + id: number + jobName: number + ranAt: number + success: number + message: number + _all: number +} + + +export type JobRunLogMinAggregateInputType = { + id?: true + jobName?: true + ranAt?: true + success?: true + message?: true +} + +export type JobRunLogMaxAggregateInputType = { + id?: true + jobName?: true + ranAt?: true + success?: true + message?: true +} + +export type JobRunLogCountAggregateInputType = { + id?: true + jobName?: true + ranAt?: true + success?: true + message?: true + _all?: true +} + +export type JobRunLogAggregateArgs = { + /** + * Filter which JobRunLog to aggregate. + */ + where?: Prisma.JobRunLogWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of JobRunLogs to fetch. + */ + orderBy?: Prisma.JobRunLogOrderByWithRelationInput | Prisma.JobRunLogOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.JobRunLogWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` JobRunLogs from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` JobRunLogs. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned JobRunLogs + **/ + _count?: true | JobRunLogCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: JobRunLogMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: JobRunLogMaxAggregateInputType +} + +export type GetJobRunLogAggregateType = { + [P in keyof T & keyof AggregateJobRunLog]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type JobRunLogGroupByArgs = { + where?: Prisma.JobRunLogWhereInput + orderBy?: Prisma.JobRunLogOrderByWithAggregationInput | Prisma.JobRunLogOrderByWithAggregationInput[] + by: Prisma.JobRunLogScalarFieldEnum[] | Prisma.JobRunLogScalarFieldEnum + having?: Prisma.JobRunLogScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: JobRunLogCountAggregateInputType | true + _min?: JobRunLogMinAggregateInputType + _max?: JobRunLogMaxAggregateInputType +} + +export type JobRunLogGroupByOutputType = { + id: string + jobName: string + ranAt: Date + success: boolean + message: string | null + _count: JobRunLogCountAggregateOutputType | null + _min: JobRunLogMinAggregateOutputType | null + _max: JobRunLogMaxAggregateOutputType | null +} + +export type GetJobRunLogGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof JobRunLogGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type JobRunLogWhereInput = { + AND?: Prisma.JobRunLogWhereInput | Prisma.JobRunLogWhereInput[] + OR?: Prisma.JobRunLogWhereInput[] + NOT?: Prisma.JobRunLogWhereInput | Prisma.JobRunLogWhereInput[] + id?: Prisma.StringFilter<"JobRunLog"> | string + jobName?: Prisma.StringFilter<"JobRunLog"> | string + ranAt?: Prisma.DateTimeFilter<"JobRunLog"> | Date | string + success?: Prisma.BoolFilter<"JobRunLog"> | boolean + message?: Prisma.StringNullableFilter<"JobRunLog"> | string | null +} + +export type JobRunLogOrderByWithRelationInput = { + id?: Prisma.SortOrder + jobName?: Prisma.SortOrder + ranAt?: Prisma.SortOrder + success?: Prisma.SortOrder + message?: Prisma.SortOrderInput | Prisma.SortOrder +} + +export type JobRunLogWhereUniqueInput = Prisma.AtLeast<{ + id?: string + AND?: Prisma.JobRunLogWhereInput | Prisma.JobRunLogWhereInput[] + OR?: Prisma.JobRunLogWhereInput[] + NOT?: Prisma.JobRunLogWhereInput | Prisma.JobRunLogWhereInput[] + jobName?: Prisma.StringFilter<"JobRunLog"> | string + ranAt?: Prisma.DateTimeFilter<"JobRunLog"> | Date | string + success?: Prisma.BoolFilter<"JobRunLog"> | boolean + message?: Prisma.StringNullableFilter<"JobRunLog"> | string | null +}, "id"> + +export type JobRunLogOrderByWithAggregationInput = { + id?: Prisma.SortOrder + jobName?: Prisma.SortOrder + ranAt?: Prisma.SortOrder + success?: Prisma.SortOrder + message?: Prisma.SortOrderInput | Prisma.SortOrder + _count?: Prisma.JobRunLogCountOrderByAggregateInput + _max?: Prisma.JobRunLogMaxOrderByAggregateInput + _min?: Prisma.JobRunLogMinOrderByAggregateInput +} + +export type JobRunLogScalarWhereWithAggregatesInput = { + AND?: Prisma.JobRunLogScalarWhereWithAggregatesInput | Prisma.JobRunLogScalarWhereWithAggregatesInput[] + OR?: Prisma.JobRunLogScalarWhereWithAggregatesInput[] + NOT?: Prisma.JobRunLogScalarWhereWithAggregatesInput | Prisma.JobRunLogScalarWhereWithAggregatesInput[] + id?: Prisma.StringWithAggregatesFilter<"JobRunLog"> | string + jobName?: Prisma.StringWithAggregatesFilter<"JobRunLog"> | string + ranAt?: Prisma.DateTimeWithAggregatesFilter<"JobRunLog"> | Date | string + success?: Prisma.BoolWithAggregatesFilter<"JobRunLog"> | boolean + message?: Prisma.StringNullableWithAggregatesFilter<"JobRunLog"> | string | null +} + +export type JobRunLogCreateInput = { + id?: string + jobName: string + ranAt?: Date | string + success?: boolean + message?: string | null +} + +export type JobRunLogUncheckedCreateInput = { + id?: string + jobName: string + ranAt?: Date | string + success?: boolean + message?: string | null +} + +export type JobRunLogUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + jobName?: Prisma.StringFieldUpdateOperationsInput | string + ranAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + success?: Prisma.BoolFieldUpdateOperationsInput | boolean + message?: Prisma.NullableStringFieldUpdateOperationsInput | string | null +} + +export type JobRunLogUncheckedUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + jobName?: Prisma.StringFieldUpdateOperationsInput | string + ranAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + success?: Prisma.BoolFieldUpdateOperationsInput | boolean + message?: Prisma.NullableStringFieldUpdateOperationsInput | string | null +} + +export type JobRunLogCreateManyInput = { + id?: string + jobName: string + ranAt?: Date | string + success?: boolean + message?: string | null +} + +export type JobRunLogUpdateManyMutationInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + jobName?: Prisma.StringFieldUpdateOperationsInput | string + ranAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + success?: Prisma.BoolFieldUpdateOperationsInput | boolean + message?: Prisma.NullableStringFieldUpdateOperationsInput | string | null +} + +export type JobRunLogUncheckedUpdateManyInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + jobName?: Prisma.StringFieldUpdateOperationsInput | string + ranAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + success?: Prisma.BoolFieldUpdateOperationsInput | boolean + message?: Prisma.NullableStringFieldUpdateOperationsInput | string | null +} + +export type JobRunLogCountOrderByAggregateInput = { + id?: Prisma.SortOrder + jobName?: Prisma.SortOrder + ranAt?: Prisma.SortOrder + success?: Prisma.SortOrder + message?: Prisma.SortOrder +} + +export type JobRunLogMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + jobName?: Prisma.SortOrder + ranAt?: Prisma.SortOrder + success?: Prisma.SortOrder + message?: Prisma.SortOrder +} + +export type JobRunLogMinOrderByAggregateInput = { + id?: Prisma.SortOrder + jobName?: Prisma.SortOrder + ranAt?: Prisma.SortOrder + success?: Prisma.SortOrder + message?: Prisma.SortOrder +} + +export type BoolFieldUpdateOperationsInput = { + set?: boolean +} + + + +export type JobRunLogSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + jobName?: boolean + ranAt?: boolean + success?: boolean + message?: boolean +}, ExtArgs["result"]["jobRunLog"]> + +export type JobRunLogSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + jobName?: boolean + ranAt?: boolean + success?: boolean + message?: boolean +}, ExtArgs["result"]["jobRunLog"]> + +export type JobRunLogSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + jobName?: boolean + ranAt?: boolean + success?: boolean + message?: boolean +}, ExtArgs["result"]["jobRunLog"]> + +export type JobRunLogSelectScalar = { + id?: boolean + jobName?: boolean + ranAt?: boolean + success?: boolean + message?: boolean +} + +export type JobRunLogOmit = runtime.Types.Extensions.GetOmit<"id" | "jobName" | "ranAt" | "success" | "message", ExtArgs["result"]["jobRunLog"]> + +export type $JobRunLogPayload = { + name: "JobRunLog" + objects: {} + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: string + jobName: string + ranAt: Date + success: boolean + message: string | null + }, ExtArgs["result"]["jobRunLog"]> + composites: {} +} + +export type JobRunLogGetPayload = runtime.Types.Result.GetResult + +export type JobRunLogCountArgs = + Omit & { + select?: JobRunLogCountAggregateInputType | true + } + +export interface JobRunLogDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['JobRunLog'], meta: { name: 'JobRunLog' } } + /** + * Find zero or one JobRunLog that matches the filter. + * @param {JobRunLogFindUniqueArgs} args - Arguments to find a JobRunLog + * @example + * // Get one JobRunLog + * const jobRunLog = await prisma.jobRunLog.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__JobRunLogClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one JobRunLog that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {JobRunLogFindUniqueOrThrowArgs} args - Arguments to find a JobRunLog + * @example + * // Get one JobRunLog + * const jobRunLog = await prisma.jobRunLog.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__JobRunLogClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first JobRunLog that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {JobRunLogFindFirstArgs} args - Arguments to find a JobRunLog + * @example + * // Get one JobRunLog + * const jobRunLog = await prisma.jobRunLog.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__JobRunLogClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first JobRunLog that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {JobRunLogFindFirstOrThrowArgs} args - Arguments to find a JobRunLog + * @example + * // Get one JobRunLog + * const jobRunLog = await prisma.jobRunLog.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__JobRunLogClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more JobRunLogs that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {JobRunLogFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all JobRunLogs + * const jobRunLogs = await prisma.jobRunLog.findMany() + * + * // Get first 10 JobRunLogs + * const jobRunLogs = await prisma.jobRunLog.findMany({ take: 10 }) + * + * // Only select the `id` + * const jobRunLogWithIdOnly = await prisma.jobRunLog.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a JobRunLog. + * @param {JobRunLogCreateArgs} args - Arguments to create a JobRunLog. + * @example + * // Create one JobRunLog + * const JobRunLog = await prisma.jobRunLog.create({ + * data: { + * // ... data to create a JobRunLog + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__JobRunLogClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many JobRunLogs. + * @param {JobRunLogCreateManyArgs} args - Arguments to create many JobRunLogs. + * @example + * // Create many JobRunLogs + * const jobRunLog = await prisma.jobRunLog.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many JobRunLogs and returns the data saved in the database. + * @param {JobRunLogCreateManyAndReturnArgs} args - Arguments to create many JobRunLogs. + * @example + * // Create many JobRunLogs + * const jobRunLog = await prisma.jobRunLog.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many JobRunLogs and only return the `id` + * const jobRunLogWithIdOnly = await prisma.jobRunLog.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a JobRunLog. + * @param {JobRunLogDeleteArgs} args - Arguments to delete one JobRunLog. + * @example + * // Delete one JobRunLog + * const JobRunLog = await prisma.jobRunLog.delete({ + * where: { + * // ... filter to delete one JobRunLog + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__JobRunLogClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one JobRunLog. + * @param {JobRunLogUpdateArgs} args - Arguments to update one JobRunLog. + * @example + * // Update one JobRunLog + * const jobRunLog = await prisma.jobRunLog.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__JobRunLogClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more JobRunLogs. + * @param {JobRunLogDeleteManyArgs} args - Arguments to filter JobRunLogs to delete. + * @example + * // Delete a few JobRunLogs + * const { count } = await prisma.jobRunLog.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more JobRunLogs. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {JobRunLogUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many JobRunLogs + * const jobRunLog = await prisma.jobRunLog.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more JobRunLogs and returns the data updated in the database. + * @param {JobRunLogUpdateManyAndReturnArgs} args - Arguments to update many JobRunLogs. + * @example + * // Update many JobRunLogs + * const jobRunLog = await prisma.jobRunLog.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more JobRunLogs and only return the `id` + * const jobRunLogWithIdOnly = await prisma.jobRunLog.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one JobRunLog. + * @param {JobRunLogUpsertArgs} args - Arguments to update or create a JobRunLog. + * @example + * // Update or create a JobRunLog + * const jobRunLog = await prisma.jobRunLog.upsert({ + * create: { + * // ... data to create a JobRunLog + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the JobRunLog we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__JobRunLogClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of JobRunLogs. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {JobRunLogCountArgs} args - Arguments to filter JobRunLogs to count. + * @example + * // Count the number of JobRunLogs + * const count = await prisma.jobRunLog.count({ + * where: { + * // ... the filter for the JobRunLogs we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a JobRunLog. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {JobRunLogAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by JobRunLog. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {JobRunLogGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends JobRunLogGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: JobRunLogGroupByArgs['orderBy'] } + : { orderBy?: JobRunLogGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetJobRunLogGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the JobRunLog model + */ +readonly fields: JobRunLogFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for JobRunLog. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__JobRunLogClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the JobRunLog model + */ +export interface JobRunLogFieldRefs { + readonly id: Prisma.FieldRef<"JobRunLog", 'String'> + readonly jobName: Prisma.FieldRef<"JobRunLog", 'String'> + readonly ranAt: Prisma.FieldRef<"JobRunLog", 'DateTime'> + readonly success: Prisma.FieldRef<"JobRunLog", 'Boolean'> + readonly message: Prisma.FieldRef<"JobRunLog", 'String'> +} + + +// Custom InputTypes +/** + * JobRunLog findUnique + */ +export type JobRunLogFindUniqueArgs = { + /** + * Select specific fields to fetch from the JobRunLog + */ + select?: Prisma.JobRunLogSelect | null + /** + * Omit specific fields from the JobRunLog + */ + omit?: Prisma.JobRunLogOmit | null + /** + * Filter, which JobRunLog to fetch. + */ + where: Prisma.JobRunLogWhereUniqueInput +} + +/** + * JobRunLog findUniqueOrThrow + */ +export type JobRunLogFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the JobRunLog + */ + select?: Prisma.JobRunLogSelect | null + /** + * Omit specific fields from the JobRunLog + */ + omit?: Prisma.JobRunLogOmit | null + /** + * Filter, which JobRunLog to fetch. + */ + where: Prisma.JobRunLogWhereUniqueInput +} + +/** + * JobRunLog findFirst + */ +export type JobRunLogFindFirstArgs = { + /** + * Select specific fields to fetch from the JobRunLog + */ + select?: Prisma.JobRunLogSelect | null + /** + * Omit specific fields from the JobRunLog + */ + omit?: Prisma.JobRunLogOmit | null + /** + * Filter, which JobRunLog to fetch. + */ + where?: Prisma.JobRunLogWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of JobRunLogs to fetch. + */ + orderBy?: Prisma.JobRunLogOrderByWithRelationInput | Prisma.JobRunLogOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for JobRunLogs. + */ + cursor?: Prisma.JobRunLogWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` JobRunLogs from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` JobRunLogs. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of JobRunLogs. + */ + distinct?: Prisma.JobRunLogScalarFieldEnum | Prisma.JobRunLogScalarFieldEnum[] +} + +/** + * JobRunLog findFirstOrThrow + */ +export type JobRunLogFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the JobRunLog + */ + select?: Prisma.JobRunLogSelect | null + /** + * Omit specific fields from the JobRunLog + */ + omit?: Prisma.JobRunLogOmit | null + /** + * Filter, which JobRunLog to fetch. + */ + where?: Prisma.JobRunLogWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of JobRunLogs to fetch. + */ + orderBy?: Prisma.JobRunLogOrderByWithRelationInput | Prisma.JobRunLogOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for JobRunLogs. + */ + cursor?: Prisma.JobRunLogWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` JobRunLogs from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` JobRunLogs. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of JobRunLogs. + */ + distinct?: Prisma.JobRunLogScalarFieldEnum | Prisma.JobRunLogScalarFieldEnum[] +} + +/** + * JobRunLog findMany + */ +export type JobRunLogFindManyArgs = { + /** + * Select specific fields to fetch from the JobRunLog + */ + select?: Prisma.JobRunLogSelect | null + /** + * Omit specific fields from the JobRunLog + */ + omit?: Prisma.JobRunLogOmit | null + /** + * Filter, which JobRunLogs to fetch. + */ + where?: Prisma.JobRunLogWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of JobRunLogs to fetch. + */ + orderBy?: Prisma.JobRunLogOrderByWithRelationInput | Prisma.JobRunLogOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing JobRunLogs. + */ + cursor?: Prisma.JobRunLogWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` JobRunLogs from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` JobRunLogs. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of JobRunLogs. + */ + distinct?: Prisma.JobRunLogScalarFieldEnum | Prisma.JobRunLogScalarFieldEnum[] +} + +/** + * JobRunLog create + */ +export type JobRunLogCreateArgs = { + /** + * Select specific fields to fetch from the JobRunLog + */ + select?: Prisma.JobRunLogSelect | null + /** + * Omit specific fields from the JobRunLog + */ + omit?: Prisma.JobRunLogOmit | null + /** + * The data needed to create a JobRunLog. + */ + data: Prisma.XOR +} + +/** + * JobRunLog createMany + */ +export type JobRunLogCreateManyArgs = { + /** + * The data used to create many JobRunLogs. + */ + data: Prisma.JobRunLogCreateManyInput | Prisma.JobRunLogCreateManyInput[] +} + +/** + * JobRunLog createManyAndReturn + */ +export type JobRunLogCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the JobRunLog + */ + select?: Prisma.JobRunLogSelectCreateManyAndReturn | null + /** + * Omit specific fields from the JobRunLog + */ + omit?: Prisma.JobRunLogOmit | null + /** + * The data used to create many JobRunLogs. + */ + data: Prisma.JobRunLogCreateManyInput | Prisma.JobRunLogCreateManyInput[] +} + +/** + * JobRunLog update + */ +export type JobRunLogUpdateArgs = { + /** + * Select specific fields to fetch from the JobRunLog + */ + select?: Prisma.JobRunLogSelect | null + /** + * Omit specific fields from the JobRunLog + */ + omit?: Prisma.JobRunLogOmit | null + /** + * The data needed to update a JobRunLog. + */ + data: Prisma.XOR + /** + * Choose, which JobRunLog to update. + */ + where: Prisma.JobRunLogWhereUniqueInput +} + +/** + * JobRunLog updateMany + */ +export type JobRunLogUpdateManyArgs = { + /** + * The data used to update JobRunLogs. + */ + data: Prisma.XOR + /** + * Filter which JobRunLogs to update + */ + where?: Prisma.JobRunLogWhereInput + /** + * Limit how many JobRunLogs to update. + */ + limit?: number +} + +/** + * JobRunLog updateManyAndReturn + */ +export type JobRunLogUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the JobRunLog + */ + select?: Prisma.JobRunLogSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the JobRunLog + */ + omit?: Prisma.JobRunLogOmit | null + /** + * The data used to update JobRunLogs. + */ + data: Prisma.XOR + /** + * Filter which JobRunLogs to update + */ + where?: Prisma.JobRunLogWhereInput + /** + * Limit how many JobRunLogs to update. + */ + limit?: number +} + +/** + * JobRunLog upsert + */ +export type JobRunLogUpsertArgs = { + /** + * Select specific fields to fetch from the JobRunLog + */ + select?: Prisma.JobRunLogSelect | null + /** + * Omit specific fields from the JobRunLog + */ + omit?: Prisma.JobRunLogOmit | null + /** + * The filter to search for the JobRunLog to update in case it exists. + */ + where: Prisma.JobRunLogWhereUniqueInput + /** + * In case the JobRunLog found by the `where` argument doesn't exist, create a new JobRunLog with this data. + */ + create: Prisma.XOR + /** + * In case the JobRunLog was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * JobRunLog delete + */ +export type JobRunLogDeleteArgs = { + /** + * Select specific fields to fetch from the JobRunLog + */ + select?: Prisma.JobRunLogSelect | null + /** + * Omit specific fields from the JobRunLog + */ + omit?: Prisma.JobRunLogOmit | null + /** + * Filter which JobRunLog to delete. + */ + where: Prisma.JobRunLogWhereUniqueInput +} + +/** + * JobRunLog deleteMany + */ +export type JobRunLogDeleteManyArgs = { + /** + * Filter which JobRunLogs to delete + */ + where?: Prisma.JobRunLogWhereInput + /** + * Limit how many JobRunLogs to delete. + */ + limit?: number +} + +/** + * JobRunLog without action + */ +export type JobRunLogDefaultArgs = { + /** + * Select specific fields to fetch from the JobRunLog + */ + select?: Prisma.JobRunLogSelect | null + /** + * Omit specific fields from the JobRunLog + */ + omit?: Prisma.JobRunLogOmit | null +} diff --git a/apps/api/src/generated/prisma/models/SendJob.ts b/apps/api/src/generated/prisma/models/SendJob.ts new file mode 100644 index 0000000..45a110f --- /dev/null +++ b/apps/api/src/generated/prisma/models/SendJob.ts @@ -0,0 +1,1494 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `SendJob` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums" +import type * as Prisma from "../internal/prismaNamespace" + +/** + * Model SendJob + * + */ +export type SendJobModel = runtime.Types.Result.DefaultSelection + +export type AggregateSendJob = { + _count: SendJobCountAggregateOutputType | null + _min: SendJobMinAggregateOutputType | null + _max: SendJobMaxAggregateOutputType | null +} + +export type SendJobMinAggregateOutputType = { + id: string | null + campaignId: string | null + scheduledAt: Date | null + status: $Enums.SendJobStatus | null + idempotencyKey: string | null + processedAt: Date | null + errorMessage: string | null + createdAt: Date | null + updatedAt: Date | null +} + +export type SendJobMaxAggregateOutputType = { + id: string | null + campaignId: string | null + scheduledAt: Date | null + status: $Enums.SendJobStatus | null + idempotencyKey: string | null + processedAt: Date | null + errorMessage: string | null + createdAt: Date | null + updatedAt: Date | null +} + +export type SendJobCountAggregateOutputType = { + id: number + campaignId: number + scheduledAt: number + status: number + idempotencyKey: number + processedAt: number + errorMessage: number + createdAt: number + updatedAt: number + _all: number +} + + +export type SendJobMinAggregateInputType = { + id?: true + campaignId?: true + scheduledAt?: true + status?: true + idempotencyKey?: true + processedAt?: true + errorMessage?: true + createdAt?: true + updatedAt?: true +} + +export type SendJobMaxAggregateInputType = { + id?: true + campaignId?: true + scheduledAt?: true + status?: true + idempotencyKey?: true + processedAt?: true + errorMessage?: true + createdAt?: true + updatedAt?: true +} + +export type SendJobCountAggregateInputType = { + id?: true + campaignId?: true + scheduledAt?: true + status?: true + idempotencyKey?: true + processedAt?: true + errorMessage?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type SendJobAggregateArgs = { + /** + * Filter which SendJob to aggregate. + */ + where?: Prisma.SendJobWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of SendJobs to fetch. + */ + orderBy?: Prisma.SendJobOrderByWithRelationInput | Prisma.SendJobOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.SendJobWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` SendJobs from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` SendJobs. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned SendJobs + **/ + _count?: true | SendJobCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: SendJobMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: SendJobMaxAggregateInputType +} + +export type GetSendJobAggregateType = { + [P in keyof T & keyof AggregateSendJob]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type SendJobGroupByArgs = { + where?: Prisma.SendJobWhereInput + orderBy?: Prisma.SendJobOrderByWithAggregationInput | Prisma.SendJobOrderByWithAggregationInput[] + by: Prisma.SendJobScalarFieldEnum[] | Prisma.SendJobScalarFieldEnum + having?: Prisma.SendJobScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: SendJobCountAggregateInputType | true + _min?: SendJobMinAggregateInputType + _max?: SendJobMaxAggregateInputType +} + +export type SendJobGroupByOutputType = { + id: string + campaignId: string + scheduledAt: Date + status: $Enums.SendJobStatus + idempotencyKey: string + processedAt: Date | null + errorMessage: string | null + createdAt: Date + updatedAt: Date + _count: SendJobCountAggregateOutputType | null + _min: SendJobMinAggregateOutputType | null + _max: SendJobMaxAggregateOutputType | null +} + +export type GetSendJobGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof SendJobGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type SendJobWhereInput = { + AND?: Prisma.SendJobWhereInput | Prisma.SendJobWhereInput[] + OR?: Prisma.SendJobWhereInput[] + NOT?: Prisma.SendJobWhereInput | Prisma.SendJobWhereInput[] + id?: Prisma.StringFilter<"SendJob"> | string + campaignId?: Prisma.StringFilter<"SendJob"> | string + scheduledAt?: Prisma.DateTimeFilter<"SendJob"> | Date | string + status?: Prisma.EnumSendJobStatusFilter<"SendJob"> | $Enums.SendJobStatus + idempotencyKey?: Prisma.StringFilter<"SendJob"> | string + processedAt?: Prisma.DateTimeNullableFilter<"SendJob"> | Date | string | null + errorMessage?: Prisma.StringNullableFilter<"SendJob"> | string | null + createdAt?: Prisma.DateTimeFilter<"SendJob"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"SendJob"> | Date | string + campaign?: Prisma.XOR +} + +export type SendJobOrderByWithRelationInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + scheduledAt?: Prisma.SortOrder + status?: Prisma.SortOrder + idempotencyKey?: Prisma.SortOrder + processedAt?: Prisma.SortOrderInput | Prisma.SortOrder + errorMessage?: Prisma.SortOrderInput | Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + campaign?: Prisma.WarmupCampaignOrderByWithRelationInput +} + +export type SendJobWhereUniqueInput = Prisma.AtLeast<{ + id?: string + idempotencyKey?: string + AND?: Prisma.SendJobWhereInput | Prisma.SendJobWhereInput[] + OR?: Prisma.SendJobWhereInput[] + NOT?: Prisma.SendJobWhereInput | Prisma.SendJobWhereInput[] + campaignId?: Prisma.StringFilter<"SendJob"> | string + scheduledAt?: Prisma.DateTimeFilter<"SendJob"> | Date | string + status?: Prisma.EnumSendJobStatusFilter<"SendJob"> | $Enums.SendJobStatus + processedAt?: Prisma.DateTimeNullableFilter<"SendJob"> | Date | string | null + errorMessage?: Prisma.StringNullableFilter<"SendJob"> | string | null + createdAt?: Prisma.DateTimeFilter<"SendJob"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"SendJob"> | Date | string + campaign?: Prisma.XOR +}, "id" | "idempotencyKey"> + +export type SendJobOrderByWithAggregationInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + scheduledAt?: Prisma.SortOrder + status?: Prisma.SortOrder + idempotencyKey?: Prisma.SortOrder + processedAt?: Prisma.SortOrderInput | Prisma.SortOrder + errorMessage?: Prisma.SortOrderInput | Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.SendJobCountOrderByAggregateInput + _max?: Prisma.SendJobMaxOrderByAggregateInput + _min?: Prisma.SendJobMinOrderByAggregateInput +} + +export type SendJobScalarWhereWithAggregatesInput = { + AND?: Prisma.SendJobScalarWhereWithAggregatesInput | Prisma.SendJobScalarWhereWithAggregatesInput[] + OR?: Prisma.SendJobScalarWhereWithAggregatesInput[] + NOT?: Prisma.SendJobScalarWhereWithAggregatesInput | Prisma.SendJobScalarWhereWithAggregatesInput[] + id?: Prisma.StringWithAggregatesFilter<"SendJob"> | string + campaignId?: Prisma.StringWithAggregatesFilter<"SendJob"> | string + scheduledAt?: Prisma.DateTimeWithAggregatesFilter<"SendJob"> | Date | string + status?: Prisma.EnumSendJobStatusWithAggregatesFilter<"SendJob"> | $Enums.SendJobStatus + idempotencyKey?: Prisma.StringWithAggregatesFilter<"SendJob"> | string + processedAt?: Prisma.DateTimeNullableWithAggregatesFilter<"SendJob"> | Date | string | null + errorMessage?: Prisma.StringNullableWithAggregatesFilter<"SendJob"> | string | null + createdAt?: Prisma.DateTimeWithAggregatesFilter<"SendJob"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"SendJob"> | Date | string +} + +export type SendJobCreateInput = { + id?: string + scheduledAt: Date | string + status?: $Enums.SendJobStatus + idempotencyKey: string + processedAt?: Date | string | null + errorMessage?: string | null + createdAt?: Date | string + updatedAt?: Date | string + campaign: Prisma.WarmupCampaignCreateNestedOneWithoutSendJobsInput +} + +export type SendJobUncheckedCreateInput = { + id?: string + campaignId: string + scheduledAt: Date | string + status?: $Enums.SendJobStatus + idempotencyKey: string + processedAt?: Date | string | null + errorMessage?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type SendJobUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + scheduledAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + status?: Prisma.EnumSendJobStatusFieldUpdateOperationsInput | $Enums.SendJobStatus + idempotencyKey?: Prisma.StringFieldUpdateOperationsInput | string + processedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + errorMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + campaign?: Prisma.WarmupCampaignUpdateOneRequiredWithoutSendJobsNestedInput +} + +export type SendJobUncheckedUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.StringFieldUpdateOperationsInput | string + scheduledAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + status?: Prisma.EnumSendJobStatusFieldUpdateOperationsInput | $Enums.SendJobStatus + idempotencyKey?: Prisma.StringFieldUpdateOperationsInput | string + processedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + errorMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type SendJobCreateManyInput = { + id?: string + campaignId: string + scheduledAt: Date | string + status?: $Enums.SendJobStatus + idempotencyKey: string + processedAt?: Date | string | null + errorMessage?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type SendJobUpdateManyMutationInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + scheduledAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + status?: Prisma.EnumSendJobStatusFieldUpdateOperationsInput | $Enums.SendJobStatus + idempotencyKey?: Prisma.StringFieldUpdateOperationsInput | string + processedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + errorMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type SendJobUncheckedUpdateManyInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.StringFieldUpdateOperationsInput | string + scheduledAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + status?: Prisma.EnumSendJobStatusFieldUpdateOperationsInput | $Enums.SendJobStatus + idempotencyKey?: Prisma.StringFieldUpdateOperationsInput | string + processedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + errorMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type SendJobCountOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + scheduledAt?: Prisma.SortOrder + status?: Prisma.SortOrder + idempotencyKey?: Prisma.SortOrder + processedAt?: Prisma.SortOrder + errorMessage?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type SendJobMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + scheduledAt?: Prisma.SortOrder + status?: Prisma.SortOrder + idempotencyKey?: Prisma.SortOrder + processedAt?: Prisma.SortOrder + errorMessage?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type SendJobMinOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + scheduledAt?: Prisma.SortOrder + status?: Prisma.SortOrder + idempotencyKey?: Prisma.SortOrder + processedAt?: Prisma.SortOrder + errorMessage?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type SendJobListRelationFilter = { + every?: Prisma.SendJobWhereInput + some?: Prisma.SendJobWhereInput + none?: Prisma.SendJobWhereInput +} + +export type SendJobOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type EnumSendJobStatusFieldUpdateOperationsInput = { + set?: $Enums.SendJobStatus +} + +export type NullableDateTimeFieldUpdateOperationsInput = { + set?: Date | string | null +} + +export type NullableStringFieldUpdateOperationsInput = { + set?: string | null +} + +export type SendJobCreateNestedManyWithoutCampaignInput = { + create?: Prisma.XOR | Prisma.SendJobCreateWithoutCampaignInput[] | Prisma.SendJobUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.SendJobCreateOrConnectWithoutCampaignInput | Prisma.SendJobCreateOrConnectWithoutCampaignInput[] + createMany?: Prisma.SendJobCreateManyCampaignInputEnvelope + connect?: Prisma.SendJobWhereUniqueInput | Prisma.SendJobWhereUniqueInput[] +} + +export type SendJobUncheckedCreateNestedManyWithoutCampaignInput = { + create?: Prisma.XOR | Prisma.SendJobCreateWithoutCampaignInput[] | Prisma.SendJobUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.SendJobCreateOrConnectWithoutCampaignInput | Prisma.SendJobCreateOrConnectWithoutCampaignInput[] + createMany?: Prisma.SendJobCreateManyCampaignInputEnvelope + connect?: Prisma.SendJobWhereUniqueInput | Prisma.SendJobWhereUniqueInput[] +} + +export type SendJobUpdateManyWithoutCampaignNestedInput = { + create?: Prisma.XOR | Prisma.SendJobCreateWithoutCampaignInput[] | Prisma.SendJobUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.SendJobCreateOrConnectWithoutCampaignInput | Prisma.SendJobCreateOrConnectWithoutCampaignInput[] + upsert?: Prisma.SendJobUpsertWithWhereUniqueWithoutCampaignInput | Prisma.SendJobUpsertWithWhereUniqueWithoutCampaignInput[] + createMany?: Prisma.SendJobCreateManyCampaignInputEnvelope + set?: Prisma.SendJobWhereUniqueInput | Prisma.SendJobWhereUniqueInput[] + disconnect?: Prisma.SendJobWhereUniqueInput | Prisma.SendJobWhereUniqueInput[] + delete?: Prisma.SendJobWhereUniqueInput | Prisma.SendJobWhereUniqueInput[] + connect?: Prisma.SendJobWhereUniqueInput | Prisma.SendJobWhereUniqueInput[] + update?: Prisma.SendJobUpdateWithWhereUniqueWithoutCampaignInput | Prisma.SendJobUpdateWithWhereUniqueWithoutCampaignInput[] + updateMany?: Prisma.SendJobUpdateManyWithWhereWithoutCampaignInput | Prisma.SendJobUpdateManyWithWhereWithoutCampaignInput[] + deleteMany?: Prisma.SendJobScalarWhereInput | Prisma.SendJobScalarWhereInput[] +} + +export type SendJobUncheckedUpdateManyWithoutCampaignNestedInput = { + create?: Prisma.XOR | Prisma.SendJobCreateWithoutCampaignInput[] | Prisma.SendJobUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.SendJobCreateOrConnectWithoutCampaignInput | Prisma.SendJobCreateOrConnectWithoutCampaignInput[] + upsert?: Prisma.SendJobUpsertWithWhereUniqueWithoutCampaignInput | Prisma.SendJobUpsertWithWhereUniqueWithoutCampaignInput[] + createMany?: Prisma.SendJobCreateManyCampaignInputEnvelope + set?: Prisma.SendJobWhereUniqueInput | Prisma.SendJobWhereUniqueInput[] + disconnect?: Prisma.SendJobWhereUniqueInput | Prisma.SendJobWhereUniqueInput[] + delete?: Prisma.SendJobWhereUniqueInput | Prisma.SendJobWhereUniqueInput[] + connect?: Prisma.SendJobWhereUniqueInput | Prisma.SendJobWhereUniqueInput[] + update?: Prisma.SendJobUpdateWithWhereUniqueWithoutCampaignInput | Prisma.SendJobUpdateWithWhereUniqueWithoutCampaignInput[] + updateMany?: Prisma.SendJobUpdateManyWithWhereWithoutCampaignInput | Prisma.SendJobUpdateManyWithWhereWithoutCampaignInput[] + deleteMany?: Prisma.SendJobScalarWhereInput | Prisma.SendJobScalarWhereInput[] +} + +export type SendJobCreateWithoutCampaignInput = { + id?: string + scheduledAt: Date | string + status?: $Enums.SendJobStatus + idempotencyKey: string + processedAt?: Date | string | null + errorMessage?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type SendJobUncheckedCreateWithoutCampaignInput = { + id?: string + scheduledAt: Date | string + status?: $Enums.SendJobStatus + idempotencyKey: string + processedAt?: Date | string | null + errorMessage?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type SendJobCreateOrConnectWithoutCampaignInput = { + where: Prisma.SendJobWhereUniqueInput + create: Prisma.XOR +} + +export type SendJobCreateManyCampaignInputEnvelope = { + data: Prisma.SendJobCreateManyCampaignInput | Prisma.SendJobCreateManyCampaignInput[] +} + +export type SendJobUpsertWithWhereUniqueWithoutCampaignInput = { + where: Prisma.SendJobWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type SendJobUpdateWithWhereUniqueWithoutCampaignInput = { + where: Prisma.SendJobWhereUniqueInput + data: Prisma.XOR +} + +export type SendJobUpdateManyWithWhereWithoutCampaignInput = { + where: Prisma.SendJobScalarWhereInput + data: Prisma.XOR +} + +export type SendJobScalarWhereInput = { + AND?: Prisma.SendJobScalarWhereInput | Prisma.SendJobScalarWhereInput[] + OR?: Prisma.SendJobScalarWhereInput[] + NOT?: Prisma.SendJobScalarWhereInput | Prisma.SendJobScalarWhereInput[] + id?: Prisma.StringFilter<"SendJob"> | string + campaignId?: Prisma.StringFilter<"SendJob"> | string + scheduledAt?: Prisma.DateTimeFilter<"SendJob"> | Date | string + status?: Prisma.EnumSendJobStatusFilter<"SendJob"> | $Enums.SendJobStatus + idempotencyKey?: Prisma.StringFilter<"SendJob"> | string + processedAt?: Prisma.DateTimeNullableFilter<"SendJob"> | Date | string | null + errorMessage?: Prisma.StringNullableFilter<"SendJob"> | string | null + createdAt?: Prisma.DateTimeFilter<"SendJob"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"SendJob"> | Date | string +} + +export type SendJobCreateManyCampaignInput = { + id?: string + scheduledAt: Date | string + status?: $Enums.SendJobStatus + idempotencyKey: string + processedAt?: Date | string | null + errorMessage?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type SendJobUpdateWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + scheduledAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + status?: Prisma.EnumSendJobStatusFieldUpdateOperationsInput | $Enums.SendJobStatus + idempotencyKey?: Prisma.StringFieldUpdateOperationsInput | string + processedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + errorMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type SendJobUncheckedUpdateWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + scheduledAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + status?: Prisma.EnumSendJobStatusFieldUpdateOperationsInput | $Enums.SendJobStatus + idempotencyKey?: Prisma.StringFieldUpdateOperationsInput | string + processedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + errorMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type SendJobUncheckedUpdateManyWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + scheduledAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + status?: Prisma.EnumSendJobStatusFieldUpdateOperationsInput | $Enums.SendJobStatus + idempotencyKey?: Prisma.StringFieldUpdateOperationsInput | string + processedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + errorMessage?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + + +export type SendJobSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + scheduledAt?: boolean + status?: boolean + idempotencyKey?: boolean + processedAt?: boolean + errorMessage?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +}, ExtArgs["result"]["sendJob"]> + +export type SendJobSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + scheduledAt?: boolean + status?: boolean + idempotencyKey?: boolean + processedAt?: boolean + errorMessage?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +}, ExtArgs["result"]["sendJob"]> + +export type SendJobSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + scheduledAt?: boolean + status?: boolean + idempotencyKey?: boolean + processedAt?: boolean + errorMessage?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +}, ExtArgs["result"]["sendJob"]> + +export type SendJobSelectScalar = { + id?: boolean + campaignId?: boolean + scheduledAt?: boolean + status?: boolean + idempotencyKey?: boolean + processedAt?: boolean + errorMessage?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type SendJobOmit = runtime.Types.Extensions.GetOmit<"id" | "campaignId" | "scheduledAt" | "status" | "idempotencyKey" | "processedAt" | "errorMessage" | "createdAt" | "updatedAt", ExtArgs["result"]["sendJob"]> +export type SendJobInclude = { + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +} +export type SendJobIncludeCreateManyAndReturn = { + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +} +export type SendJobIncludeUpdateManyAndReturn = { + campaign?: boolean | Prisma.WarmupCampaignDefaultArgs +} + +export type $SendJobPayload = { + name: "SendJob" + objects: { + campaign: Prisma.$WarmupCampaignPayload + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: string + campaignId: string + scheduledAt: Date + status: $Enums.SendJobStatus + idempotencyKey: string + processedAt: Date | null + errorMessage: string | null + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["sendJob"]> + composites: {} +} + +export type SendJobGetPayload = runtime.Types.Result.GetResult + +export type SendJobCountArgs = + Omit & { + select?: SendJobCountAggregateInputType | true + } + +export interface SendJobDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['SendJob'], meta: { name: 'SendJob' } } + /** + * Find zero or one SendJob that matches the filter. + * @param {SendJobFindUniqueArgs} args - Arguments to find a SendJob + * @example + * // Get one SendJob + * const sendJob = await prisma.sendJob.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__SendJobClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one SendJob that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {SendJobFindUniqueOrThrowArgs} args - Arguments to find a SendJob + * @example + * // Get one SendJob + * const sendJob = await prisma.sendJob.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__SendJobClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first SendJob that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SendJobFindFirstArgs} args - Arguments to find a SendJob + * @example + * // Get one SendJob + * const sendJob = await prisma.sendJob.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__SendJobClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first SendJob that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SendJobFindFirstOrThrowArgs} args - Arguments to find a SendJob + * @example + * // Get one SendJob + * const sendJob = await prisma.sendJob.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__SendJobClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more SendJobs that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SendJobFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all SendJobs + * const sendJobs = await prisma.sendJob.findMany() + * + * // Get first 10 SendJobs + * const sendJobs = await prisma.sendJob.findMany({ take: 10 }) + * + * // Only select the `id` + * const sendJobWithIdOnly = await prisma.sendJob.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a SendJob. + * @param {SendJobCreateArgs} args - Arguments to create a SendJob. + * @example + * // Create one SendJob + * const SendJob = await prisma.sendJob.create({ + * data: { + * // ... data to create a SendJob + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__SendJobClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many SendJobs. + * @param {SendJobCreateManyArgs} args - Arguments to create many SendJobs. + * @example + * // Create many SendJobs + * const sendJob = await prisma.sendJob.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many SendJobs and returns the data saved in the database. + * @param {SendJobCreateManyAndReturnArgs} args - Arguments to create many SendJobs. + * @example + * // Create many SendJobs + * const sendJob = await prisma.sendJob.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many SendJobs and only return the `id` + * const sendJobWithIdOnly = await prisma.sendJob.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a SendJob. + * @param {SendJobDeleteArgs} args - Arguments to delete one SendJob. + * @example + * // Delete one SendJob + * const SendJob = await prisma.sendJob.delete({ + * where: { + * // ... filter to delete one SendJob + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__SendJobClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one SendJob. + * @param {SendJobUpdateArgs} args - Arguments to update one SendJob. + * @example + * // Update one SendJob + * const sendJob = await prisma.sendJob.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__SendJobClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more SendJobs. + * @param {SendJobDeleteManyArgs} args - Arguments to filter SendJobs to delete. + * @example + * // Delete a few SendJobs + * const { count } = await prisma.sendJob.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more SendJobs. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SendJobUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many SendJobs + * const sendJob = await prisma.sendJob.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more SendJobs and returns the data updated in the database. + * @param {SendJobUpdateManyAndReturnArgs} args - Arguments to update many SendJobs. + * @example + * // Update many SendJobs + * const sendJob = await prisma.sendJob.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more SendJobs and only return the `id` + * const sendJobWithIdOnly = await prisma.sendJob.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one SendJob. + * @param {SendJobUpsertArgs} args - Arguments to update or create a SendJob. + * @example + * // Update or create a SendJob + * const sendJob = await prisma.sendJob.upsert({ + * create: { + * // ... data to create a SendJob + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the SendJob we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__SendJobClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of SendJobs. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SendJobCountArgs} args - Arguments to filter SendJobs to count. + * @example + * // Count the number of SendJobs + * const count = await prisma.sendJob.count({ + * where: { + * // ... the filter for the SendJobs we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a SendJob. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SendJobAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by SendJob. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SendJobGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends SendJobGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: SendJobGroupByArgs['orderBy'] } + : { orderBy?: SendJobGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetSendJobGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the SendJob model + */ +readonly fields: SendJobFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for SendJob. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__SendJobClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + campaign = {}>(args?: Prisma.Subset>): Prisma.Prisma__WarmupCampaignClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the SendJob model + */ +export interface SendJobFieldRefs { + readonly id: Prisma.FieldRef<"SendJob", 'String'> + readonly campaignId: Prisma.FieldRef<"SendJob", 'String'> + readonly scheduledAt: Prisma.FieldRef<"SendJob", 'DateTime'> + readonly status: Prisma.FieldRef<"SendJob", 'SendJobStatus'> + readonly idempotencyKey: Prisma.FieldRef<"SendJob", 'String'> + readonly processedAt: Prisma.FieldRef<"SendJob", 'DateTime'> + readonly errorMessage: Prisma.FieldRef<"SendJob", 'String'> + readonly createdAt: Prisma.FieldRef<"SendJob", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"SendJob", 'DateTime'> +} + + +// Custom InputTypes +/** + * SendJob findUnique + */ +export type SendJobFindUniqueArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelect | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobInclude | null + /** + * Filter, which SendJob to fetch. + */ + where: Prisma.SendJobWhereUniqueInput +} + +/** + * SendJob findUniqueOrThrow + */ +export type SendJobFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelect | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobInclude | null + /** + * Filter, which SendJob to fetch. + */ + where: Prisma.SendJobWhereUniqueInput +} + +/** + * SendJob findFirst + */ +export type SendJobFindFirstArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelect | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobInclude | null + /** + * Filter, which SendJob to fetch. + */ + where?: Prisma.SendJobWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of SendJobs to fetch. + */ + orderBy?: Prisma.SendJobOrderByWithRelationInput | Prisma.SendJobOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for SendJobs. + */ + cursor?: Prisma.SendJobWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` SendJobs from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` SendJobs. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of SendJobs. + */ + distinct?: Prisma.SendJobScalarFieldEnum | Prisma.SendJobScalarFieldEnum[] +} + +/** + * SendJob findFirstOrThrow + */ +export type SendJobFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelect | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobInclude | null + /** + * Filter, which SendJob to fetch. + */ + where?: Prisma.SendJobWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of SendJobs to fetch. + */ + orderBy?: Prisma.SendJobOrderByWithRelationInput | Prisma.SendJobOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for SendJobs. + */ + cursor?: Prisma.SendJobWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` SendJobs from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` SendJobs. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of SendJobs. + */ + distinct?: Prisma.SendJobScalarFieldEnum | Prisma.SendJobScalarFieldEnum[] +} + +/** + * SendJob findMany + */ +export type SendJobFindManyArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelect | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobInclude | null + /** + * Filter, which SendJobs to fetch. + */ + where?: Prisma.SendJobWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of SendJobs to fetch. + */ + orderBy?: Prisma.SendJobOrderByWithRelationInput | Prisma.SendJobOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing SendJobs. + */ + cursor?: Prisma.SendJobWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` SendJobs from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` SendJobs. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of SendJobs. + */ + distinct?: Prisma.SendJobScalarFieldEnum | Prisma.SendJobScalarFieldEnum[] +} + +/** + * SendJob create + */ +export type SendJobCreateArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelect | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobInclude | null + /** + * The data needed to create a SendJob. + */ + data: Prisma.XOR +} + +/** + * SendJob createMany + */ +export type SendJobCreateManyArgs = { + /** + * The data used to create many SendJobs. + */ + data: Prisma.SendJobCreateManyInput | Prisma.SendJobCreateManyInput[] +} + +/** + * SendJob createManyAndReturn + */ +export type SendJobCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelectCreateManyAndReturn | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * The data used to create many SendJobs. + */ + data: Prisma.SendJobCreateManyInput | Prisma.SendJobCreateManyInput[] + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobIncludeCreateManyAndReturn | null +} + +/** + * SendJob update + */ +export type SendJobUpdateArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelect | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobInclude | null + /** + * The data needed to update a SendJob. + */ + data: Prisma.XOR + /** + * Choose, which SendJob to update. + */ + where: Prisma.SendJobWhereUniqueInput +} + +/** + * SendJob updateMany + */ +export type SendJobUpdateManyArgs = { + /** + * The data used to update SendJobs. + */ + data: Prisma.XOR + /** + * Filter which SendJobs to update + */ + where?: Prisma.SendJobWhereInput + /** + * Limit how many SendJobs to update. + */ + limit?: number +} + +/** + * SendJob updateManyAndReturn + */ +export type SendJobUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * The data used to update SendJobs. + */ + data: Prisma.XOR + /** + * Filter which SendJobs to update + */ + where?: Prisma.SendJobWhereInput + /** + * Limit how many SendJobs to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobIncludeUpdateManyAndReturn | null +} + +/** + * SendJob upsert + */ +export type SendJobUpsertArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelect | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobInclude | null + /** + * The filter to search for the SendJob to update in case it exists. + */ + where: Prisma.SendJobWhereUniqueInput + /** + * In case the SendJob found by the `where` argument doesn't exist, create a new SendJob with this data. + */ + create: Prisma.XOR + /** + * In case the SendJob was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * SendJob delete + */ +export type SendJobDeleteArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelect | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobInclude | null + /** + * Filter which SendJob to delete. + */ + where: Prisma.SendJobWhereUniqueInput +} + +/** + * SendJob deleteMany + */ +export type SendJobDeleteManyArgs = { + /** + * Filter which SendJobs to delete + */ + where?: Prisma.SendJobWhereInput + /** + * Limit how many SendJobs to delete. + */ + limit?: number +} + +/** + * SendJob without action + */ +export type SendJobDefaultArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelect | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobInclude | null +} diff --git a/apps/api/src/generated/prisma/models/Setting.ts b/apps/api/src/generated/prisma/models/Setting.ts new file mode 100644 index 0000000..6af9e44 --- /dev/null +++ b/apps/api/src/generated/prisma/models/Setting.ts @@ -0,0 +1,1147 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `Setting` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums" +import type * as Prisma from "../internal/prismaNamespace" + +/** + * Model Setting + * + */ +export type SettingModel = runtime.Types.Result.DefaultSelection + +export type AggregateSetting = { + _count: SettingCountAggregateOutputType | null + _min: SettingMinAggregateOutputType | null + _max: SettingMaxAggregateOutputType | null +} + +export type SettingMinAggregateOutputType = { + id: string | null + key: string | null + value: string | null + createdAt: Date | null + updatedAt: Date | null +} + +export type SettingMaxAggregateOutputType = { + id: string | null + key: string | null + value: string | null + createdAt: Date | null + updatedAt: Date | null +} + +export type SettingCountAggregateOutputType = { + id: number + key: number + value: number + createdAt: number + updatedAt: number + _all: number +} + + +export type SettingMinAggregateInputType = { + id?: true + key?: true + value?: true + createdAt?: true + updatedAt?: true +} + +export type SettingMaxAggregateInputType = { + id?: true + key?: true + value?: true + createdAt?: true + updatedAt?: true +} + +export type SettingCountAggregateInputType = { + id?: true + key?: true + value?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type SettingAggregateArgs = { + /** + * Filter which Setting to aggregate. + */ + where?: Prisma.SettingWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Settings to fetch. + */ + orderBy?: Prisma.SettingOrderByWithRelationInput | Prisma.SettingOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.SettingWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Settings from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Settings. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned Settings + **/ + _count?: true | SettingCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: SettingMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: SettingMaxAggregateInputType +} + +export type GetSettingAggregateType = { + [P in keyof T & keyof AggregateSetting]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type SettingGroupByArgs = { + where?: Prisma.SettingWhereInput + orderBy?: Prisma.SettingOrderByWithAggregationInput | Prisma.SettingOrderByWithAggregationInput[] + by: Prisma.SettingScalarFieldEnum[] | Prisma.SettingScalarFieldEnum + having?: Prisma.SettingScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: SettingCountAggregateInputType | true + _min?: SettingMinAggregateInputType + _max?: SettingMaxAggregateInputType +} + +export type SettingGroupByOutputType = { + id: string + key: string + value: string + createdAt: Date + updatedAt: Date + _count: SettingCountAggregateOutputType | null + _min: SettingMinAggregateOutputType | null + _max: SettingMaxAggregateOutputType | null +} + +export type GetSettingGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof SettingGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type SettingWhereInput = { + AND?: Prisma.SettingWhereInput | Prisma.SettingWhereInput[] + OR?: Prisma.SettingWhereInput[] + NOT?: Prisma.SettingWhereInput | Prisma.SettingWhereInput[] + id?: Prisma.StringFilter<"Setting"> | string + key?: Prisma.StringFilter<"Setting"> | string + value?: Prisma.StringFilter<"Setting"> | string + createdAt?: Prisma.DateTimeFilter<"Setting"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Setting"> | Date | string +} + +export type SettingOrderByWithRelationInput = { + id?: Prisma.SortOrder + key?: Prisma.SortOrder + value?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type SettingWhereUniqueInput = Prisma.AtLeast<{ + id?: string + key?: string + AND?: Prisma.SettingWhereInput | Prisma.SettingWhereInput[] + OR?: Prisma.SettingWhereInput[] + NOT?: Prisma.SettingWhereInput | Prisma.SettingWhereInput[] + value?: Prisma.StringFilter<"Setting"> | string + createdAt?: Prisma.DateTimeFilter<"Setting"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Setting"> | Date | string +}, "id" | "key"> + +export type SettingOrderByWithAggregationInput = { + id?: Prisma.SortOrder + key?: Prisma.SortOrder + value?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.SettingCountOrderByAggregateInput + _max?: Prisma.SettingMaxOrderByAggregateInput + _min?: Prisma.SettingMinOrderByAggregateInput +} + +export type SettingScalarWhereWithAggregatesInput = { + AND?: Prisma.SettingScalarWhereWithAggregatesInput | Prisma.SettingScalarWhereWithAggregatesInput[] + OR?: Prisma.SettingScalarWhereWithAggregatesInput[] + NOT?: Prisma.SettingScalarWhereWithAggregatesInput | Prisma.SettingScalarWhereWithAggregatesInput[] + id?: Prisma.StringWithAggregatesFilter<"Setting"> | string + key?: Prisma.StringWithAggregatesFilter<"Setting"> | string + value?: Prisma.StringWithAggregatesFilter<"Setting"> | string + createdAt?: Prisma.DateTimeWithAggregatesFilter<"Setting"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Setting"> | Date | string +} + +export type SettingCreateInput = { + id?: string + key: string + value: string + createdAt?: Date | string + updatedAt?: Date | string +} + +export type SettingUncheckedCreateInput = { + id?: string + key: string + value: string + createdAt?: Date | string + updatedAt?: Date | string +} + +export type SettingUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + key?: Prisma.StringFieldUpdateOperationsInput | string + value?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type SettingUncheckedUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + key?: Prisma.StringFieldUpdateOperationsInput | string + value?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type SettingCreateManyInput = { + id?: string + key: string + value: string + createdAt?: Date | string + updatedAt?: Date | string +} + +export type SettingUpdateManyMutationInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + key?: Prisma.StringFieldUpdateOperationsInput | string + value?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type SettingUncheckedUpdateManyInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + key?: Prisma.StringFieldUpdateOperationsInput | string + value?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type SettingCountOrderByAggregateInput = { + id?: Prisma.SortOrder + key?: Prisma.SortOrder + value?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type SettingMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + key?: Prisma.SortOrder + value?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type SettingMinOrderByAggregateInput = { + id?: Prisma.SortOrder + key?: Prisma.SortOrder + value?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + + + +export type SettingSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + key?: boolean + value?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["setting"]> + +export type SettingSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + key?: boolean + value?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["setting"]> + +export type SettingSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + key?: boolean + value?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["setting"]> + +export type SettingSelectScalar = { + id?: boolean + key?: boolean + value?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type SettingOmit = runtime.Types.Extensions.GetOmit<"id" | "key" | "value" | "createdAt" | "updatedAt", ExtArgs["result"]["setting"]> + +export type $SettingPayload = { + name: "Setting" + objects: {} + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: string + key: string + value: string + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["setting"]> + composites: {} +} + +export type SettingGetPayload = runtime.Types.Result.GetResult + +export type SettingCountArgs = + Omit & { + select?: SettingCountAggregateInputType | true + } + +export interface SettingDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['Setting'], meta: { name: 'Setting' } } + /** + * Find zero or one Setting that matches the filter. + * @param {SettingFindUniqueArgs} args - Arguments to find a Setting + * @example + * // Get one Setting + * const setting = await prisma.setting.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__SettingClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one Setting that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {SettingFindUniqueOrThrowArgs} args - Arguments to find a Setting + * @example + * // Get one Setting + * const setting = await prisma.setting.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__SettingClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Setting that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SettingFindFirstArgs} args - Arguments to find a Setting + * @example + * // Get one Setting + * const setting = await prisma.setting.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__SettingClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Setting that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SettingFindFirstOrThrowArgs} args - Arguments to find a Setting + * @example + * // Get one Setting + * const setting = await prisma.setting.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__SettingClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more Settings that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SettingFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all Settings + * const settings = await prisma.setting.findMany() + * + * // Get first 10 Settings + * const settings = await prisma.setting.findMany({ take: 10 }) + * + * // Only select the `id` + * const settingWithIdOnly = await prisma.setting.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a Setting. + * @param {SettingCreateArgs} args - Arguments to create a Setting. + * @example + * // Create one Setting + * const Setting = await prisma.setting.create({ + * data: { + * // ... data to create a Setting + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__SettingClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many Settings. + * @param {SettingCreateManyArgs} args - Arguments to create many Settings. + * @example + * // Create many Settings + * const setting = await prisma.setting.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many Settings and returns the data saved in the database. + * @param {SettingCreateManyAndReturnArgs} args - Arguments to create many Settings. + * @example + * // Create many Settings + * const setting = await prisma.setting.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Settings and only return the `id` + * const settingWithIdOnly = await prisma.setting.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a Setting. + * @param {SettingDeleteArgs} args - Arguments to delete one Setting. + * @example + * // Delete one Setting + * const Setting = await prisma.setting.delete({ + * where: { + * // ... filter to delete one Setting + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__SettingClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one Setting. + * @param {SettingUpdateArgs} args - Arguments to update one Setting. + * @example + * // Update one Setting + * const setting = await prisma.setting.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__SettingClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more Settings. + * @param {SettingDeleteManyArgs} args - Arguments to filter Settings to delete. + * @example + * // Delete a few Settings + * const { count } = await prisma.setting.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Settings. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SettingUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many Settings + * const setting = await prisma.setting.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Settings and returns the data updated in the database. + * @param {SettingUpdateManyAndReturnArgs} args - Arguments to update many Settings. + * @example + * // Update many Settings + * const setting = await prisma.setting.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Settings and only return the `id` + * const settingWithIdOnly = await prisma.setting.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one Setting. + * @param {SettingUpsertArgs} args - Arguments to update or create a Setting. + * @example + * // Update or create a Setting + * const setting = await prisma.setting.upsert({ + * create: { + * // ... data to create a Setting + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the Setting we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__SettingClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of Settings. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SettingCountArgs} args - Arguments to filter Settings to count. + * @example + * // Count the number of Settings + * const count = await prisma.setting.count({ + * where: { + * // ... the filter for the Settings we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a Setting. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SettingAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by Setting. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {SettingGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends SettingGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: SettingGroupByArgs['orderBy'] } + : { orderBy?: SettingGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetSettingGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the Setting model + */ +readonly fields: SettingFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for Setting. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__SettingClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the Setting model + */ +export interface SettingFieldRefs { + readonly id: Prisma.FieldRef<"Setting", 'String'> + readonly key: Prisma.FieldRef<"Setting", 'String'> + readonly value: Prisma.FieldRef<"Setting", 'String'> + readonly createdAt: Prisma.FieldRef<"Setting", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"Setting", 'DateTime'> +} + + +// Custom InputTypes +/** + * Setting findUnique + */ +export type SettingFindUniqueArgs = { + /** + * Select specific fields to fetch from the Setting + */ + select?: Prisma.SettingSelect | null + /** + * Omit specific fields from the Setting + */ + omit?: Prisma.SettingOmit | null + /** + * Filter, which Setting to fetch. + */ + where: Prisma.SettingWhereUniqueInput +} + +/** + * Setting findUniqueOrThrow + */ +export type SettingFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the Setting + */ + select?: Prisma.SettingSelect | null + /** + * Omit specific fields from the Setting + */ + omit?: Prisma.SettingOmit | null + /** + * Filter, which Setting to fetch. + */ + where: Prisma.SettingWhereUniqueInput +} + +/** + * Setting findFirst + */ +export type SettingFindFirstArgs = { + /** + * Select specific fields to fetch from the Setting + */ + select?: Prisma.SettingSelect | null + /** + * Omit specific fields from the Setting + */ + omit?: Prisma.SettingOmit | null + /** + * Filter, which Setting to fetch. + */ + where?: Prisma.SettingWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Settings to fetch. + */ + orderBy?: Prisma.SettingOrderByWithRelationInput | Prisma.SettingOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Settings. + */ + cursor?: Prisma.SettingWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Settings from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Settings. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Settings. + */ + distinct?: Prisma.SettingScalarFieldEnum | Prisma.SettingScalarFieldEnum[] +} + +/** + * Setting findFirstOrThrow + */ +export type SettingFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the Setting + */ + select?: Prisma.SettingSelect | null + /** + * Omit specific fields from the Setting + */ + omit?: Prisma.SettingOmit | null + /** + * Filter, which Setting to fetch. + */ + where?: Prisma.SettingWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Settings to fetch. + */ + orderBy?: Prisma.SettingOrderByWithRelationInput | Prisma.SettingOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Settings. + */ + cursor?: Prisma.SettingWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Settings from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Settings. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Settings. + */ + distinct?: Prisma.SettingScalarFieldEnum | Prisma.SettingScalarFieldEnum[] +} + +/** + * Setting findMany + */ +export type SettingFindManyArgs = { + /** + * Select specific fields to fetch from the Setting + */ + select?: Prisma.SettingSelect | null + /** + * Omit specific fields from the Setting + */ + omit?: Prisma.SettingOmit | null + /** + * Filter, which Settings to fetch. + */ + where?: Prisma.SettingWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Settings to fetch. + */ + orderBy?: Prisma.SettingOrderByWithRelationInput | Prisma.SettingOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing Settings. + */ + cursor?: Prisma.SettingWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Settings from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Settings. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Settings. + */ + distinct?: Prisma.SettingScalarFieldEnum | Prisma.SettingScalarFieldEnum[] +} + +/** + * Setting create + */ +export type SettingCreateArgs = { + /** + * Select specific fields to fetch from the Setting + */ + select?: Prisma.SettingSelect | null + /** + * Omit specific fields from the Setting + */ + omit?: Prisma.SettingOmit | null + /** + * The data needed to create a Setting. + */ + data: Prisma.XOR +} + +/** + * Setting createMany + */ +export type SettingCreateManyArgs = { + /** + * The data used to create many Settings. + */ + data: Prisma.SettingCreateManyInput | Prisma.SettingCreateManyInput[] +} + +/** + * Setting createManyAndReturn + */ +export type SettingCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Setting + */ + select?: Prisma.SettingSelectCreateManyAndReturn | null + /** + * Omit specific fields from the Setting + */ + omit?: Prisma.SettingOmit | null + /** + * The data used to create many Settings. + */ + data: Prisma.SettingCreateManyInput | Prisma.SettingCreateManyInput[] +} + +/** + * Setting update + */ +export type SettingUpdateArgs = { + /** + * Select specific fields to fetch from the Setting + */ + select?: Prisma.SettingSelect | null + /** + * Omit specific fields from the Setting + */ + omit?: Prisma.SettingOmit | null + /** + * The data needed to update a Setting. + */ + data: Prisma.XOR + /** + * Choose, which Setting to update. + */ + where: Prisma.SettingWhereUniqueInput +} + +/** + * Setting updateMany + */ +export type SettingUpdateManyArgs = { + /** + * The data used to update Settings. + */ + data: Prisma.XOR + /** + * Filter which Settings to update + */ + where?: Prisma.SettingWhereInput + /** + * Limit how many Settings to update. + */ + limit?: number +} + +/** + * Setting updateManyAndReturn + */ +export type SettingUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Setting + */ + select?: Prisma.SettingSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the Setting + */ + omit?: Prisma.SettingOmit | null + /** + * The data used to update Settings. + */ + data: Prisma.XOR + /** + * Filter which Settings to update + */ + where?: Prisma.SettingWhereInput + /** + * Limit how many Settings to update. + */ + limit?: number +} + +/** + * Setting upsert + */ +export type SettingUpsertArgs = { + /** + * Select specific fields to fetch from the Setting + */ + select?: Prisma.SettingSelect | null + /** + * Omit specific fields from the Setting + */ + omit?: Prisma.SettingOmit | null + /** + * The filter to search for the Setting to update in case it exists. + */ + where: Prisma.SettingWhereUniqueInput + /** + * In case the Setting found by the `where` argument doesn't exist, create a new Setting with this data. + */ + create: Prisma.XOR + /** + * In case the Setting was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * Setting delete + */ +export type SettingDeleteArgs = { + /** + * Select specific fields to fetch from the Setting + */ + select?: Prisma.SettingSelect | null + /** + * Omit specific fields from the Setting + */ + omit?: Prisma.SettingOmit | null + /** + * Filter which Setting to delete. + */ + where: Prisma.SettingWhereUniqueInput +} + +/** + * Setting deleteMany + */ +export type SettingDeleteManyArgs = { + /** + * Filter which Settings to delete + */ + where?: Prisma.SettingWhereInput + /** + * Limit how many Settings to delete. + */ + limit?: number +} + +/** + * Setting without action + */ +export type SettingDefaultArgs = { + /** + * Select specific fields to fetch from the Setting + */ + select?: Prisma.SettingSelect | null + /** + * Omit specific fields from the Setting + */ + omit?: Prisma.SettingOmit | null +} diff --git a/apps/api/src/generated/prisma/models/WarmupCampaign.ts b/apps/api/src/generated/prisma/models/WarmupCampaign.ts new file mode 100644 index 0000000..54853d7 --- /dev/null +++ b/apps/api/src/generated/prisma/models/WarmupCampaign.ts @@ -0,0 +1,3268 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `WarmupCampaign` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums" +import type * as Prisma from "../internal/prismaNamespace" + +/** + * Model WarmupCampaign + * + */ +export type WarmupCampaignModel = runtime.Types.Result.DefaultSelection + +export type AggregateWarmupCampaign = { + _count: WarmupCampaignCountAggregateOutputType | null + _avg: WarmupCampaignAvgAggregateOutputType | null + _sum: WarmupCampaignSumAggregateOutputType | null + _min: WarmupCampaignMinAggregateOutputType | null + _max: WarmupCampaignMaxAggregateOutputType | null +} + +export type WarmupCampaignAvgAggregateOutputType = { + durationDays: number | null + minEmailsPerDay: number | null + maxEmailsPerDay: number | null + replyRatePercent: number | null + maintenanceVolumePercent: number | null + currentDay: number | null +} + +export type WarmupCampaignSumAggregateOutputType = { + durationDays: number | null + minEmailsPerDay: number | null + maxEmailsPerDay: number | null + replyRatePercent: number | null + maintenanceVolumePercent: number | null + currentDay: number | null +} + +export type WarmupCampaignMinAggregateOutputType = { + id: string | null + workspaceId: string | null + primaryMailboxId: string | null + name: string | null + recipe: $Enums.CampaignRecipe | null + status: $Enums.CampaignStatus | null + durationDays: number | null + minEmailsPerDay: number | null + maxEmailsPerDay: number | null + replyRatePercent: number | null + maintenanceVolumePercent: number | null + timezone: string | null + sendWindowStart: string | null + sendWindowEnd: string | null + customSchedule: string | null + startedAt: Date | null + endsAt: Date | null + currentDay: number | null + pausedFromStatus: $Enums.CampaignStatus | null + lastSendAt: Date | null + createdAt: Date | null + updatedAt: Date | null +} + +export type WarmupCampaignMaxAggregateOutputType = { + id: string | null + workspaceId: string | null + primaryMailboxId: string | null + name: string | null + recipe: $Enums.CampaignRecipe | null + status: $Enums.CampaignStatus | null + durationDays: number | null + minEmailsPerDay: number | null + maxEmailsPerDay: number | null + replyRatePercent: number | null + maintenanceVolumePercent: number | null + timezone: string | null + sendWindowStart: string | null + sendWindowEnd: string | null + customSchedule: string | null + startedAt: Date | null + endsAt: Date | null + currentDay: number | null + pausedFromStatus: $Enums.CampaignStatus | null + lastSendAt: Date | null + createdAt: Date | null + updatedAt: Date | null +} + +export type WarmupCampaignCountAggregateOutputType = { + id: number + workspaceId: number + primaryMailboxId: number + name: number + recipe: number + status: number + durationDays: number + minEmailsPerDay: number + maxEmailsPerDay: number + replyRatePercent: number + maintenanceVolumePercent: number + timezone: number + sendWindowStart: number + sendWindowEnd: number + customSchedule: number + startedAt: number + endsAt: number + currentDay: number + pausedFromStatus: number + lastSendAt: number + createdAt: number + updatedAt: number + _all: number +} + + +export type WarmupCampaignAvgAggregateInputType = { + durationDays?: true + minEmailsPerDay?: true + maxEmailsPerDay?: true + replyRatePercent?: true + maintenanceVolumePercent?: true + currentDay?: true +} + +export type WarmupCampaignSumAggregateInputType = { + durationDays?: true + minEmailsPerDay?: true + maxEmailsPerDay?: true + replyRatePercent?: true + maintenanceVolumePercent?: true + currentDay?: true +} + +export type WarmupCampaignMinAggregateInputType = { + id?: true + workspaceId?: true + primaryMailboxId?: true + name?: true + recipe?: true + status?: true + durationDays?: true + minEmailsPerDay?: true + maxEmailsPerDay?: true + replyRatePercent?: true + maintenanceVolumePercent?: true + timezone?: true + sendWindowStart?: true + sendWindowEnd?: true + customSchedule?: true + startedAt?: true + endsAt?: true + currentDay?: true + pausedFromStatus?: true + lastSendAt?: true + createdAt?: true + updatedAt?: true +} + +export type WarmupCampaignMaxAggregateInputType = { + id?: true + workspaceId?: true + primaryMailboxId?: true + name?: true + recipe?: true + status?: true + durationDays?: true + minEmailsPerDay?: true + maxEmailsPerDay?: true + replyRatePercent?: true + maintenanceVolumePercent?: true + timezone?: true + sendWindowStart?: true + sendWindowEnd?: true + customSchedule?: true + startedAt?: true + endsAt?: true + currentDay?: true + pausedFromStatus?: true + lastSendAt?: true + createdAt?: true + updatedAt?: true +} + +export type WarmupCampaignCountAggregateInputType = { + id?: true + workspaceId?: true + primaryMailboxId?: true + name?: true + recipe?: true + status?: true + durationDays?: true + minEmailsPerDay?: true + maxEmailsPerDay?: true + replyRatePercent?: true + maintenanceVolumePercent?: true + timezone?: true + sendWindowStart?: true + sendWindowEnd?: true + customSchedule?: true + startedAt?: true + endsAt?: true + currentDay?: true + pausedFromStatus?: true + lastSendAt?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type WarmupCampaignAggregateArgs = { + /** + * Filter which WarmupCampaign to aggregate. + */ + where?: Prisma.WarmupCampaignWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of WarmupCampaigns to fetch. + */ + orderBy?: Prisma.WarmupCampaignOrderByWithRelationInput | Prisma.WarmupCampaignOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.WarmupCampaignWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` WarmupCampaigns from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` WarmupCampaigns. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned WarmupCampaigns + **/ + _count?: true | WarmupCampaignCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to average + **/ + _avg?: WarmupCampaignAvgAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to sum + **/ + _sum?: WarmupCampaignSumAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: WarmupCampaignMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: WarmupCampaignMaxAggregateInputType +} + +export type GetWarmupCampaignAggregateType = { + [P in keyof T & keyof AggregateWarmupCampaign]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type WarmupCampaignGroupByArgs = { + where?: Prisma.WarmupCampaignWhereInput + orderBy?: Prisma.WarmupCampaignOrderByWithAggregationInput | Prisma.WarmupCampaignOrderByWithAggregationInput[] + by: Prisma.WarmupCampaignScalarFieldEnum[] | Prisma.WarmupCampaignScalarFieldEnum + having?: Prisma.WarmupCampaignScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: WarmupCampaignCountAggregateInputType | true + _avg?: WarmupCampaignAvgAggregateInputType + _sum?: WarmupCampaignSumAggregateInputType + _min?: WarmupCampaignMinAggregateInputType + _max?: WarmupCampaignMaxAggregateInputType +} + +export type WarmupCampaignGroupByOutputType = { + id: string + workspaceId: string + primaryMailboxId: string + name: string + recipe: $Enums.CampaignRecipe + status: $Enums.CampaignStatus + durationDays: number + minEmailsPerDay: number + maxEmailsPerDay: number + replyRatePercent: number + maintenanceVolumePercent: number + timezone: string + sendWindowStart: string + sendWindowEnd: string + customSchedule: string | null + startedAt: Date | null + endsAt: Date | null + currentDay: number + pausedFromStatus: $Enums.CampaignStatus | null + lastSendAt: Date | null + createdAt: Date + updatedAt: Date + _count: WarmupCampaignCountAggregateOutputType | null + _avg: WarmupCampaignAvgAggregateOutputType | null + _sum: WarmupCampaignSumAggregateOutputType | null + _min: WarmupCampaignMinAggregateOutputType | null + _max: WarmupCampaignMaxAggregateOutputType | null +} + +export type GetWarmupCampaignGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof WarmupCampaignGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type WarmupCampaignWhereInput = { + AND?: Prisma.WarmupCampaignWhereInput | Prisma.WarmupCampaignWhereInput[] + OR?: Prisma.WarmupCampaignWhereInput[] + NOT?: Prisma.WarmupCampaignWhereInput | Prisma.WarmupCampaignWhereInput[] + id?: Prisma.StringFilter<"WarmupCampaign"> | string + workspaceId?: Prisma.StringFilter<"WarmupCampaign"> | string + primaryMailboxId?: Prisma.StringFilter<"WarmupCampaign"> | string + name?: Prisma.StringFilter<"WarmupCampaign"> | string + recipe?: Prisma.EnumCampaignRecipeFilter<"WarmupCampaign"> | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFilter<"WarmupCampaign"> | $Enums.CampaignStatus + durationDays?: Prisma.IntFilter<"WarmupCampaign"> | number + minEmailsPerDay?: Prisma.IntFilter<"WarmupCampaign"> | number + maxEmailsPerDay?: Prisma.IntFilter<"WarmupCampaign"> | number + replyRatePercent?: Prisma.IntFilter<"WarmupCampaign"> | number + maintenanceVolumePercent?: Prisma.IntFilter<"WarmupCampaign"> | number + timezone?: Prisma.StringFilter<"WarmupCampaign"> | string + sendWindowStart?: Prisma.StringFilter<"WarmupCampaign"> | string + sendWindowEnd?: Prisma.StringFilter<"WarmupCampaign"> | string + customSchedule?: Prisma.StringNullableFilter<"WarmupCampaign"> | string | null + startedAt?: Prisma.DateTimeNullableFilter<"WarmupCampaign"> | Date | string | null + endsAt?: Prisma.DateTimeNullableFilter<"WarmupCampaign"> | Date | string | null + currentDay?: Prisma.IntFilter<"WarmupCampaign"> | number + pausedFromStatus?: Prisma.EnumCampaignStatusNullableFilter<"WarmupCampaign"> | $Enums.CampaignStatus | null + lastSendAt?: Prisma.DateTimeNullableFilter<"WarmupCampaign"> | Date | string | null + createdAt?: Prisma.DateTimeFilter<"WarmupCampaign"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"WarmupCampaign"> | Date | string + workspace?: Prisma.XOR + primaryMailbox?: Prisma.XOR + satellites?: Prisma.CampaignSatelliteListRelationFilter + dailySchedules?: Prisma.DailyScheduleListRelationFilter + dailyStats?: Prisma.DailyStatListRelationFilter + sendJobs?: Prisma.SendJobListRelationFilter + warmupLogs?: Prisma.WarmupLogListRelationFilter +} + +export type WarmupCampaignOrderByWithRelationInput = { + id?: Prisma.SortOrder + workspaceId?: Prisma.SortOrder + primaryMailboxId?: Prisma.SortOrder + name?: Prisma.SortOrder + recipe?: Prisma.SortOrder + status?: Prisma.SortOrder + durationDays?: Prisma.SortOrder + minEmailsPerDay?: Prisma.SortOrder + maxEmailsPerDay?: Prisma.SortOrder + replyRatePercent?: Prisma.SortOrder + maintenanceVolumePercent?: Prisma.SortOrder + timezone?: Prisma.SortOrder + sendWindowStart?: Prisma.SortOrder + sendWindowEnd?: Prisma.SortOrder + customSchedule?: Prisma.SortOrderInput | Prisma.SortOrder + startedAt?: Prisma.SortOrderInput | Prisma.SortOrder + endsAt?: Prisma.SortOrderInput | Prisma.SortOrder + currentDay?: Prisma.SortOrder + pausedFromStatus?: Prisma.SortOrderInput | Prisma.SortOrder + lastSendAt?: Prisma.SortOrderInput | Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + workspace?: Prisma.WorkspaceOrderByWithRelationInput + primaryMailbox?: Prisma.InboxOrderByWithRelationInput + satellites?: Prisma.CampaignSatelliteOrderByRelationAggregateInput + dailySchedules?: Prisma.DailyScheduleOrderByRelationAggregateInput + dailyStats?: Prisma.DailyStatOrderByRelationAggregateInput + sendJobs?: Prisma.SendJobOrderByRelationAggregateInput + warmupLogs?: Prisma.WarmupLogOrderByRelationAggregateInput +} + +export type WarmupCampaignWhereUniqueInput = Prisma.AtLeast<{ + id?: string + AND?: Prisma.WarmupCampaignWhereInput | Prisma.WarmupCampaignWhereInput[] + OR?: Prisma.WarmupCampaignWhereInput[] + NOT?: Prisma.WarmupCampaignWhereInput | Prisma.WarmupCampaignWhereInput[] + workspaceId?: Prisma.StringFilter<"WarmupCampaign"> | string + primaryMailboxId?: Prisma.StringFilter<"WarmupCampaign"> | string + name?: Prisma.StringFilter<"WarmupCampaign"> | string + recipe?: Prisma.EnumCampaignRecipeFilter<"WarmupCampaign"> | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFilter<"WarmupCampaign"> | $Enums.CampaignStatus + durationDays?: Prisma.IntFilter<"WarmupCampaign"> | number + minEmailsPerDay?: Prisma.IntFilter<"WarmupCampaign"> | number + maxEmailsPerDay?: Prisma.IntFilter<"WarmupCampaign"> | number + replyRatePercent?: Prisma.IntFilter<"WarmupCampaign"> | number + maintenanceVolumePercent?: Prisma.IntFilter<"WarmupCampaign"> | number + timezone?: Prisma.StringFilter<"WarmupCampaign"> | string + sendWindowStart?: Prisma.StringFilter<"WarmupCampaign"> | string + sendWindowEnd?: Prisma.StringFilter<"WarmupCampaign"> | string + customSchedule?: Prisma.StringNullableFilter<"WarmupCampaign"> | string | null + startedAt?: Prisma.DateTimeNullableFilter<"WarmupCampaign"> | Date | string | null + endsAt?: Prisma.DateTimeNullableFilter<"WarmupCampaign"> | Date | string | null + currentDay?: Prisma.IntFilter<"WarmupCampaign"> | number + pausedFromStatus?: Prisma.EnumCampaignStatusNullableFilter<"WarmupCampaign"> | $Enums.CampaignStatus | null + lastSendAt?: Prisma.DateTimeNullableFilter<"WarmupCampaign"> | Date | string | null + createdAt?: Prisma.DateTimeFilter<"WarmupCampaign"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"WarmupCampaign"> | Date | string + workspace?: Prisma.XOR + primaryMailbox?: Prisma.XOR + satellites?: Prisma.CampaignSatelliteListRelationFilter + dailySchedules?: Prisma.DailyScheduleListRelationFilter + dailyStats?: Prisma.DailyStatListRelationFilter + sendJobs?: Prisma.SendJobListRelationFilter + warmupLogs?: Prisma.WarmupLogListRelationFilter +}, "id"> + +export type WarmupCampaignOrderByWithAggregationInput = { + id?: Prisma.SortOrder + workspaceId?: Prisma.SortOrder + primaryMailboxId?: Prisma.SortOrder + name?: Prisma.SortOrder + recipe?: Prisma.SortOrder + status?: Prisma.SortOrder + durationDays?: Prisma.SortOrder + minEmailsPerDay?: Prisma.SortOrder + maxEmailsPerDay?: Prisma.SortOrder + replyRatePercent?: Prisma.SortOrder + maintenanceVolumePercent?: Prisma.SortOrder + timezone?: Prisma.SortOrder + sendWindowStart?: Prisma.SortOrder + sendWindowEnd?: Prisma.SortOrder + customSchedule?: Prisma.SortOrderInput | Prisma.SortOrder + startedAt?: Prisma.SortOrderInput | Prisma.SortOrder + endsAt?: Prisma.SortOrderInput | Prisma.SortOrder + currentDay?: Prisma.SortOrder + pausedFromStatus?: Prisma.SortOrderInput | Prisma.SortOrder + lastSendAt?: Prisma.SortOrderInput | Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.WarmupCampaignCountOrderByAggregateInput + _avg?: Prisma.WarmupCampaignAvgOrderByAggregateInput + _max?: Prisma.WarmupCampaignMaxOrderByAggregateInput + _min?: Prisma.WarmupCampaignMinOrderByAggregateInput + _sum?: Prisma.WarmupCampaignSumOrderByAggregateInput +} + +export type WarmupCampaignScalarWhereWithAggregatesInput = { + AND?: Prisma.WarmupCampaignScalarWhereWithAggregatesInput | Prisma.WarmupCampaignScalarWhereWithAggregatesInput[] + OR?: Prisma.WarmupCampaignScalarWhereWithAggregatesInput[] + NOT?: Prisma.WarmupCampaignScalarWhereWithAggregatesInput | Prisma.WarmupCampaignScalarWhereWithAggregatesInput[] + id?: Prisma.StringWithAggregatesFilter<"WarmupCampaign"> | string + workspaceId?: Prisma.StringWithAggregatesFilter<"WarmupCampaign"> | string + primaryMailboxId?: Prisma.StringWithAggregatesFilter<"WarmupCampaign"> | string + name?: Prisma.StringWithAggregatesFilter<"WarmupCampaign"> | string + recipe?: Prisma.EnumCampaignRecipeWithAggregatesFilter<"WarmupCampaign"> | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusWithAggregatesFilter<"WarmupCampaign"> | $Enums.CampaignStatus + durationDays?: Prisma.IntWithAggregatesFilter<"WarmupCampaign"> | number + minEmailsPerDay?: Prisma.IntWithAggregatesFilter<"WarmupCampaign"> | number + maxEmailsPerDay?: Prisma.IntWithAggregatesFilter<"WarmupCampaign"> | number + replyRatePercent?: Prisma.IntWithAggregatesFilter<"WarmupCampaign"> | number + maintenanceVolumePercent?: Prisma.IntWithAggregatesFilter<"WarmupCampaign"> | number + timezone?: Prisma.StringWithAggregatesFilter<"WarmupCampaign"> | string + sendWindowStart?: Prisma.StringWithAggregatesFilter<"WarmupCampaign"> | string + sendWindowEnd?: Prisma.StringWithAggregatesFilter<"WarmupCampaign"> | string + customSchedule?: Prisma.StringNullableWithAggregatesFilter<"WarmupCampaign"> | string | null + startedAt?: Prisma.DateTimeNullableWithAggregatesFilter<"WarmupCampaign"> | Date | string | null + endsAt?: Prisma.DateTimeNullableWithAggregatesFilter<"WarmupCampaign"> | Date | string | null + currentDay?: Prisma.IntWithAggregatesFilter<"WarmupCampaign"> | number + pausedFromStatus?: Prisma.EnumCampaignStatusNullableWithAggregatesFilter<"WarmupCampaign"> | $Enums.CampaignStatus | null + lastSendAt?: Prisma.DateTimeNullableWithAggregatesFilter<"WarmupCampaign"> | Date | string | null + createdAt?: Prisma.DateTimeWithAggregatesFilter<"WarmupCampaign"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"WarmupCampaign"> | Date | string +} + +export type WarmupCampaignCreateInput = { + id?: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + workspace: Prisma.WorkspaceCreateNestedOneWithoutCampaignsInput + primaryMailbox: Prisma.InboxCreateNestedOneWithoutPrimaryCampaignsInput + satellites?: Prisma.CampaignSatelliteCreateNestedManyWithoutCampaignInput + dailySchedules?: Prisma.DailyScheduleCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignUncheckedCreateInput = { + id?: string + workspaceId: string + primaryMailboxId: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + satellites?: Prisma.CampaignSatelliteUncheckedCreateNestedManyWithoutCampaignInput + dailySchedules?: Prisma.DailyScheduleUncheckedCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatUncheckedCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobUncheckedCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + workspace?: Prisma.WorkspaceUpdateOneRequiredWithoutCampaignsNestedInput + primaryMailbox?: Prisma.InboxUpdateOneRequiredWithoutPrimaryCampaignsNestedInput + satellites?: Prisma.CampaignSatelliteUpdateManyWithoutCampaignNestedInput + dailySchedules?: Prisma.DailyScheduleUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignUncheckedUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + primaryMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + satellites?: Prisma.CampaignSatelliteUncheckedUpdateManyWithoutCampaignNestedInput + dailySchedules?: Prisma.DailyScheduleUncheckedUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUncheckedUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUncheckedUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignCreateManyInput = { + id?: string + workspaceId: string + primaryMailboxId: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type WarmupCampaignUpdateManyMutationInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WarmupCampaignUncheckedUpdateManyInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + primaryMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WarmupCampaignListRelationFilter = { + every?: Prisma.WarmupCampaignWhereInput + some?: Prisma.WarmupCampaignWhereInput + none?: Prisma.WarmupCampaignWhereInput +} + +export type WarmupCampaignOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type WarmupCampaignScalarRelationFilter = { + is?: Prisma.WarmupCampaignWhereInput + isNot?: Prisma.WarmupCampaignWhereInput +} + +export type WarmupCampaignCountOrderByAggregateInput = { + id?: Prisma.SortOrder + workspaceId?: Prisma.SortOrder + primaryMailboxId?: Prisma.SortOrder + name?: Prisma.SortOrder + recipe?: Prisma.SortOrder + status?: Prisma.SortOrder + durationDays?: Prisma.SortOrder + minEmailsPerDay?: Prisma.SortOrder + maxEmailsPerDay?: Prisma.SortOrder + replyRatePercent?: Prisma.SortOrder + maintenanceVolumePercent?: Prisma.SortOrder + timezone?: Prisma.SortOrder + sendWindowStart?: Prisma.SortOrder + sendWindowEnd?: Prisma.SortOrder + customSchedule?: Prisma.SortOrder + startedAt?: Prisma.SortOrder + endsAt?: Prisma.SortOrder + currentDay?: Prisma.SortOrder + pausedFromStatus?: Prisma.SortOrder + lastSendAt?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type WarmupCampaignAvgOrderByAggregateInput = { + durationDays?: Prisma.SortOrder + minEmailsPerDay?: Prisma.SortOrder + maxEmailsPerDay?: Prisma.SortOrder + replyRatePercent?: Prisma.SortOrder + maintenanceVolumePercent?: Prisma.SortOrder + currentDay?: Prisma.SortOrder +} + +export type WarmupCampaignMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + workspaceId?: Prisma.SortOrder + primaryMailboxId?: Prisma.SortOrder + name?: Prisma.SortOrder + recipe?: Prisma.SortOrder + status?: Prisma.SortOrder + durationDays?: Prisma.SortOrder + minEmailsPerDay?: Prisma.SortOrder + maxEmailsPerDay?: Prisma.SortOrder + replyRatePercent?: Prisma.SortOrder + maintenanceVolumePercent?: Prisma.SortOrder + timezone?: Prisma.SortOrder + sendWindowStart?: Prisma.SortOrder + sendWindowEnd?: Prisma.SortOrder + customSchedule?: Prisma.SortOrder + startedAt?: Prisma.SortOrder + endsAt?: Prisma.SortOrder + currentDay?: Prisma.SortOrder + pausedFromStatus?: Prisma.SortOrder + lastSendAt?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type WarmupCampaignMinOrderByAggregateInput = { + id?: Prisma.SortOrder + workspaceId?: Prisma.SortOrder + primaryMailboxId?: Prisma.SortOrder + name?: Prisma.SortOrder + recipe?: Prisma.SortOrder + status?: Prisma.SortOrder + durationDays?: Prisma.SortOrder + minEmailsPerDay?: Prisma.SortOrder + maxEmailsPerDay?: Prisma.SortOrder + replyRatePercent?: Prisma.SortOrder + maintenanceVolumePercent?: Prisma.SortOrder + timezone?: Prisma.SortOrder + sendWindowStart?: Prisma.SortOrder + sendWindowEnd?: Prisma.SortOrder + customSchedule?: Prisma.SortOrder + startedAt?: Prisma.SortOrder + endsAt?: Prisma.SortOrder + currentDay?: Prisma.SortOrder + pausedFromStatus?: Prisma.SortOrder + lastSendAt?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type WarmupCampaignSumOrderByAggregateInput = { + durationDays?: Prisma.SortOrder + minEmailsPerDay?: Prisma.SortOrder + maxEmailsPerDay?: Prisma.SortOrder + replyRatePercent?: Prisma.SortOrder + maintenanceVolumePercent?: Prisma.SortOrder + currentDay?: Prisma.SortOrder +} + +export type WarmupCampaignNullableScalarRelationFilter = { + is?: Prisma.WarmupCampaignWhereInput | null + isNot?: Prisma.WarmupCampaignWhereInput | null +} + +export type WarmupCampaignCreateNestedManyWithoutWorkspaceInput = { + create?: Prisma.XOR | Prisma.WarmupCampaignCreateWithoutWorkspaceInput[] | Prisma.WarmupCampaignUncheckedCreateWithoutWorkspaceInput[] + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutWorkspaceInput | Prisma.WarmupCampaignCreateOrConnectWithoutWorkspaceInput[] + createMany?: Prisma.WarmupCampaignCreateManyWorkspaceInputEnvelope + connect?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] +} + +export type WarmupCampaignUncheckedCreateNestedManyWithoutWorkspaceInput = { + create?: Prisma.XOR | Prisma.WarmupCampaignCreateWithoutWorkspaceInput[] | Prisma.WarmupCampaignUncheckedCreateWithoutWorkspaceInput[] + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutWorkspaceInput | Prisma.WarmupCampaignCreateOrConnectWithoutWorkspaceInput[] + createMany?: Prisma.WarmupCampaignCreateManyWorkspaceInputEnvelope + connect?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] +} + +export type WarmupCampaignUpdateManyWithoutWorkspaceNestedInput = { + create?: Prisma.XOR | Prisma.WarmupCampaignCreateWithoutWorkspaceInput[] | Prisma.WarmupCampaignUncheckedCreateWithoutWorkspaceInput[] + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutWorkspaceInput | Prisma.WarmupCampaignCreateOrConnectWithoutWorkspaceInput[] + upsert?: Prisma.WarmupCampaignUpsertWithWhereUniqueWithoutWorkspaceInput | Prisma.WarmupCampaignUpsertWithWhereUniqueWithoutWorkspaceInput[] + createMany?: Prisma.WarmupCampaignCreateManyWorkspaceInputEnvelope + set?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + disconnect?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + delete?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + connect?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + update?: Prisma.WarmupCampaignUpdateWithWhereUniqueWithoutWorkspaceInput | Prisma.WarmupCampaignUpdateWithWhereUniqueWithoutWorkspaceInput[] + updateMany?: Prisma.WarmupCampaignUpdateManyWithWhereWithoutWorkspaceInput | Prisma.WarmupCampaignUpdateManyWithWhereWithoutWorkspaceInput[] + deleteMany?: Prisma.WarmupCampaignScalarWhereInput | Prisma.WarmupCampaignScalarWhereInput[] +} + +export type WarmupCampaignUncheckedUpdateManyWithoutWorkspaceNestedInput = { + create?: Prisma.XOR | Prisma.WarmupCampaignCreateWithoutWorkspaceInput[] | Prisma.WarmupCampaignUncheckedCreateWithoutWorkspaceInput[] + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutWorkspaceInput | Prisma.WarmupCampaignCreateOrConnectWithoutWorkspaceInput[] + upsert?: Prisma.WarmupCampaignUpsertWithWhereUniqueWithoutWorkspaceInput | Prisma.WarmupCampaignUpsertWithWhereUniqueWithoutWorkspaceInput[] + createMany?: Prisma.WarmupCampaignCreateManyWorkspaceInputEnvelope + set?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + disconnect?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + delete?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + connect?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + update?: Prisma.WarmupCampaignUpdateWithWhereUniqueWithoutWorkspaceInput | Prisma.WarmupCampaignUpdateWithWhereUniqueWithoutWorkspaceInput[] + updateMany?: Prisma.WarmupCampaignUpdateManyWithWhereWithoutWorkspaceInput | Prisma.WarmupCampaignUpdateManyWithWhereWithoutWorkspaceInput[] + deleteMany?: Prisma.WarmupCampaignScalarWhereInput | Prisma.WarmupCampaignScalarWhereInput[] +} + +export type WarmupCampaignCreateNestedOneWithoutSendJobsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutSendJobsInput + connect?: Prisma.WarmupCampaignWhereUniqueInput +} + +export type WarmupCampaignUpdateOneRequiredWithoutSendJobsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutSendJobsInput + upsert?: Prisma.WarmupCampaignUpsertWithoutSendJobsInput + connect?: Prisma.WarmupCampaignWhereUniqueInput + update?: Prisma.XOR, Prisma.WarmupCampaignUncheckedUpdateWithoutSendJobsInput> +} + +export type WarmupCampaignCreateNestedManyWithoutPrimaryMailboxInput = { + create?: Prisma.XOR | Prisma.WarmupCampaignCreateWithoutPrimaryMailboxInput[] | Prisma.WarmupCampaignUncheckedCreateWithoutPrimaryMailboxInput[] + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutPrimaryMailboxInput | Prisma.WarmupCampaignCreateOrConnectWithoutPrimaryMailboxInput[] + createMany?: Prisma.WarmupCampaignCreateManyPrimaryMailboxInputEnvelope + connect?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] +} + +export type WarmupCampaignUncheckedCreateNestedManyWithoutPrimaryMailboxInput = { + create?: Prisma.XOR | Prisma.WarmupCampaignCreateWithoutPrimaryMailboxInput[] | Prisma.WarmupCampaignUncheckedCreateWithoutPrimaryMailboxInput[] + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutPrimaryMailboxInput | Prisma.WarmupCampaignCreateOrConnectWithoutPrimaryMailboxInput[] + createMany?: Prisma.WarmupCampaignCreateManyPrimaryMailboxInputEnvelope + connect?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] +} + +export type WarmupCampaignUpdateManyWithoutPrimaryMailboxNestedInput = { + create?: Prisma.XOR | Prisma.WarmupCampaignCreateWithoutPrimaryMailboxInput[] | Prisma.WarmupCampaignUncheckedCreateWithoutPrimaryMailboxInput[] + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutPrimaryMailboxInput | Prisma.WarmupCampaignCreateOrConnectWithoutPrimaryMailboxInput[] + upsert?: Prisma.WarmupCampaignUpsertWithWhereUniqueWithoutPrimaryMailboxInput | Prisma.WarmupCampaignUpsertWithWhereUniqueWithoutPrimaryMailboxInput[] + createMany?: Prisma.WarmupCampaignCreateManyPrimaryMailboxInputEnvelope + set?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + disconnect?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + delete?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + connect?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + update?: Prisma.WarmupCampaignUpdateWithWhereUniqueWithoutPrimaryMailboxInput | Prisma.WarmupCampaignUpdateWithWhereUniqueWithoutPrimaryMailboxInput[] + updateMany?: Prisma.WarmupCampaignUpdateManyWithWhereWithoutPrimaryMailboxInput | Prisma.WarmupCampaignUpdateManyWithWhereWithoutPrimaryMailboxInput[] + deleteMany?: Prisma.WarmupCampaignScalarWhereInput | Prisma.WarmupCampaignScalarWhereInput[] +} + +export type WarmupCampaignUncheckedUpdateManyWithoutPrimaryMailboxNestedInput = { + create?: Prisma.XOR | Prisma.WarmupCampaignCreateWithoutPrimaryMailboxInput[] | Prisma.WarmupCampaignUncheckedCreateWithoutPrimaryMailboxInput[] + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutPrimaryMailboxInput | Prisma.WarmupCampaignCreateOrConnectWithoutPrimaryMailboxInput[] + upsert?: Prisma.WarmupCampaignUpsertWithWhereUniqueWithoutPrimaryMailboxInput | Prisma.WarmupCampaignUpsertWithWhereUniqueWithoutPrimaryMailboxInput[] + createMany?: Prisma.WarmupCampaignCreateManyPrimaryMailboxInputEnvelope + set?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + disconnect?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + delete?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + connect?: Prisma.WarmupCampaignWhereUniqueInput | Prisma.WarmupCampaignWhereUniqueInput[] + update?: Prisma.WarmupCampaignUpdateWithWhereUniqueWithoutPrimaryMailboxInput | Prisma.WarmupCampaignUpdateWithWhereUniqueWithoutPrimaryMailboxInput[] + updateMany?: Prisma.WarmupCampaignUpdateManyWithWhereWithoutPrimaryMailboxInput | Prisma.WarmupCampaignUpdateManyWithWhereWithoutPrimaryMailboxInput[] + deleteMany?: Prisma.WarmupCampaignScalarWhereInput | Prisma.WarmupCampaignScalarWhereInput[] +} + +export type EnumCampaignRecipeFieldUpdateOperationsInput = { + set?: $Enums.CampaignRecipe +} + +export type EnumCampaignStatusFieldUpdateOperationsInput = { + set?: $Enums.CampaignStatus +} + +export type NullableEnumCampaignStatusFieldUpdateOperationsInput = { + set?: $Enums.CampaignStatus | null +} + +export type WarmupCampaignCreateNestedOneWithoutSatellitesInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutSatellitesInput + connect?: Prisma.WarmupCampaignWhereUniqueInput +} + +export type WarmupCampaignUpdateOneRequiredWithoutSatellitesNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutSatellitesInput + upsert?: Prisma.WarmupCampaignUpsertWithoutSatellitesInput + connect?: Prisma.WarmupCampaignWhereUniqueInput + update?: Prisma.XOR, Prisma.WarmupCampaignUncheckedUpdateWithoutSatellitesInput> +} + +export type WarmupCampaignCreateNestedOneWithoutDailySchedulesInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutDailySchedulesInput + connect?: Prisma.WarmupCampaignWhereUniqueInput +} + +export type WarmupCampaignUpdateOneRequiredWithoutDailySchedulesNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutDailySchedulesInput + upsert?: Prisma.WarmupCampaignUpsertWithoutDailySchedulesInput + connect?: Prisma.WarmupCampaignWhereUniqueInput + update?: Prisma.XOR, Prisma.WarmupCampaignUncheckedUpdateWithoutDailySchedulesInput> +} + +export type WarmupCampaignCreateNestedOneWithoutDailyStatsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutDailyStatsInput + connect?: Prisma.WarmupCampaignWhereUniqueInput +} + +export type WarmupCampaignUpdateOneRequiredWithoutDailyStatsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutDailyStatsInput + upsert?: Prisma.WarmupCampaignUpsertWithoutDailyStatsInput + connect?: Prisma.WarmupCampaignWhereUniqueInput + update?: Prisma.XOR, Prisma.WarmupCampaignUncheckedUpdateWithoutDailyStatsInput> +} + +export type WarmupCampaignCreateNestedOneWithoutWarmupLogsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutWarmupLogsInput + connect?: Prisma.WarmupCampaignWhereUniqueInput +} + +export type WarmupCampaignUpdateOneWithoutWarmupLogsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WarmupCampaignCreateOrConnectWithoutWarmupLogsInput + upsert?: Prisma.WarmupCampaignUpsertWithoutWarmupLogsInput + disconnect?: Prisma.WarmupCampaignWhereInput | boolean + delete?: Prisma.WarmupCampaignWhereInput | boolean + connect?: Prisma.WarmupCampaignWhereUniqueInput + update?: Prisma.XOR, Prisma.WarmupCampaignUncheckedUpdateWithoutWarmupLogsInput> +} + +export type WarmupCampaignCreateWithoutWorkspaceInput = { + id?: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + primaryMailbox: Prisma.InboxCreateNestedOneWithoutPrimaryCampaignsInput + satellites?: Prisma.CampaignSatelliteCreateNestedManyWithoutCampaignInput + dailySchedules?: Prisma.DailyScheduleCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignUncheckedCreateWithoutWorkspaceInput = { + id?: string + primaryMailboxId: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + satellites?: Prisma.CampaignSatelliteUncheckedCreateNestedManyWithoutCampaignInput + dailySchedules?: Prisma.DailyScheduleUncheckedCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatUncheckedCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobUncheckedCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignCreateOrConnectWithoutWorkspaceInput = { + where: Prisma.WarmupCampaignWhereUniqueInput + create: Prisma.XOR +} + +export type WarmupCampaignCreateManyWorkspaceInputEnvelope = { + data: Prisma.WarmupCampaignCreateManyWorkspaceInput | Prisma.WarmupCampaignCreateManyWorkspaceInput[] +} + +export type WarmupCampaignUpsertWithWhereUniqueWithoutWorkspaceInput = { + where: Prisma.WarmupCampaignWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type WarmupCampaignUpdateWithWhereUniqueWithoutWorkspaceInput = { + where: Prisma.WarmupCampaignWhereUniqueInput + data: Prisma.XOR +} + +export type WarmupCampaignUpdateManyWithWhereWithoutWorkspaceInput = { + where: Prisma.WarmupCampaignScalarWhereInput + data: Prisma.XOR +} + +export type WarmupCampaignScalarWhereInput = { + AND?: Prisma.WarmupCampaignScalarWhereInput | Prisma.WarmupCampaignScalarWhereInput[] + OR?: Prisma.WarmupCampaignScalarWhereInput[] + NOT?: Prisma.WarmupCampaignScalarWhereInput | Prisma.WarmupCampaignScalarWhereInput[] + id?: Prisma.StringFilter<"WarmupCampaign"> | string + workspaceId?: Prisma.StringFilter<"WarmupCampaign"> | string + primaryMailboxId?: Prisma.StringFilter<"WarmupCampaign"> | string + name?: Prisma.StringFilter<"WarmupCampaign"> | string + recipe?: Prisma.EnumCampaignRecipeFilter<"WarmupCampaign"> | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFilter<"WarmupCampaign"> | $Enums.CampaignStatus + durationDays?: Prisma.IntFilter<"WarmupCampaign"> | number + minEmailsPerDay?: Prisma.IntFilter<"WarmupCampaign"> | number + maxEmailsPerDay?: Prisma.IntFilter<"WarmupCampaign"> | number + replyRatePercent?: Prisma.IntFilter<"WarmupCampaign"> | number + maintenanceVolumePercent?: Prisma.IntFilter<"WarmupCampaign"> | number + timezone?: Prisma.StringFilter<"WarmupCampaign"> | string + sendWindowStart?: Prisma.StringFilter<"WarmupCampaign"> | string + sendWindowEnd?: Prisma.StringFilter<"WarmupCampaign"> | string + customSchedule?: Prisma.StringNullableFilter<"WarmupCampaign"> | string | null + startedAt?: Prisma.DateTimeNullableFilter<"WarmupCampaign"> | Date | string | null + endsAt?: Prisma.DateTimeNullableFilter<"WarmupCampaign"> | Date | string | null + currentDay?: Prisma.IntFilter<"WarmupCampaign"> | number + pausedFromStatus?: Prisma.EnumCampaignStatusNullableFilter<"WarmupCampaign"> | $Enums.CampaignStatus | null + lastSendAt?: Prisma.DateTimeNullableFilter<"WarmupCampaign"> | Date | string | null + createdAt?: Prisma.DateTimeFilter<"WarmupCampaign"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"WarmupCampaign"> | Date | string +} + +export type WarmupCampaignCreateWithoutSendJobsInput = { + id?: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + workspace: Prisma.WorkspaceCreateNestedOneWithoutCampaignsInput + primaryMailbox: Prisma.InboxCreateNestedOneWithoutPrimaryCampaignsInput + satellites?: Prisma.CampaignSatelliteCreateNestedManyWithoutCampaignInput + dailySchedules?: Prisma.DailyScheduleCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignUncheckedCreateWithoutSendJobsInput = { + id?: string + workspaceId: string + primaryMailboxId: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + satellites?: Prisma.CampaignSatelliteUncheckedCreateNestedManyWithoutCampaignInput + dailySchedules?: Prisma.DailyScheduleUncheckedCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatUncheckedCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignCreateOrConnectWithoutSendJobsInput = { + where: Prisma.WarmupCampaignWhereUniqueInput + create: Prisma.XOR +} + +export type WarmupCampaignUpsertWithoutSendJobsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.WarmupCampaignWhereInput +} + +export type WarmupCampaignUpdateToOneWithWhereWithoutSendJobsInput = { + where?: Prisma.WarmupCampaignWhereInput + data: Prisma.XOR +} + +export type WarmupCampaignUpdateWithoutSendJobsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + workspace?: Prisma.WorkspaceUpdateOneRequiredWithoutCampaignsNestedInput + primaryMailbox?: Prisma.InboxUpdateOneRequiredWithoutPrimaryCampaignsNestedInput + satellites?: Prisma.CampaignSatelliteUpdateManyWithoutCampaignNestedInput + dailySchedules?: Prisma.DailyScheduleUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignUncheckedUpdateWithoutSendJobsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + primaryMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + satellites?: Prisma.CampaignSatelliteUncheckedUpdateManyWithoutCampaignNestedInput + dailySchedules?: Prisma.DailyScheduleUncheckedUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUncheckedUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignCreateWithoutPrimaryMailboxInput = { + id?: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + workspace: Prisma.WorkspaceCreateNestedOneWithoutCampaignsInput + satellites?: Prisma.CampaignSatelliteCreateNestedManyWithoutCampaignInput + dailySchedules?: Prisma.DailyScheduleCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignUncheckedCreateWithoutPrimaryMailboxInput = { + id?: string + workspaceId: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + satellites?: Prisma.CampaignSatelliteUncheckedCreateNestedManyWithoutCampaignInput + dailySchedules?: Prisma.DailyScheduleUncheckedCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatUncheckedCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobUncheckedCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignCreateOrConnectWithoutPrimaryMailboxInput = { + where: Prisma.WarmupCampaignWhereUniqueInput + create: Prisma.XOR +} + +export type WarmupCampaignCreateManyPrimaryMailboxInputEnvelope = { + data: Prisma.WarmupCampaignCreateManyPrimaryMailboxInput | Prisma.WarmupCampaignCreateManyPrimaryMailboxInput[] +} + +export type WarmupCampaignUpsertWithWhereUniqueWithoutPrimaryMailboxInput = { + where: Prisma.WarmupCampaignWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type WarmupCampaignUpdateWithWhereUniqueWithoutPrimaryMailboxInput = { + where: Prisma.WarmupCampaignWhereUniqueInput + data: Prisma.XOR +} + +export type WarmupCampaignUpdateManyWithWhereWithoutPrimaryMailboxInput = { + where: Prisma.WarmupCampaignScalarWhereInput + data: Prisma.XOR +} + +export type WarmupCampaignCreateWithoutSatellitesInput = { + id?: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + workspace: Prisma.WorkspaceCreateNestedOneWithoutCampaignsInput + primaryMailbox: Prisma.InboxCreateNestedOneWithoutPrimaryCampaignsInput + dailySchedules?: Prisma.DailyScheduleCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignUncheckedCreateWithoutSatellitesInput = { + id?: string + workspaceId: string + primaryMailboxId: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + dailySchedules?: Prisma.DailyScheduleUncheckedCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatUncheckedCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobUncheckedCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignCreateOrConnectWithoutSatellitesInput = { + where: Prisma.WarmupCampaignWhereUniqueInput + create: Prisma.XOR +} + +export type WarmupCampaignUpsertWithoutSatellitesInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.WarmupCampaignWhereInput +} + +export type WarmupCampaignUpdateToOneWithWhereWithoutSatellitesInput = { + where?: Prisma.WarmupCampaignWhereInput + data: Prisma.XOR +} + +export type WarmupCampaignUpdateWithoutSatellitesInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + workspace?: Prisma.WorkspaceUpdateOneRequiredWithoutCampaignsNestedInput + primaryMailbox?: Prisma.InboxUpdateOneRequiredWithoutPrimaryCampaignsNestedInput + dailySchedules?: Prisma.DailyScheduleUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignUncheckedUpdateWithoutSatellitesInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + primaryMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + dailySchedules?: Prisma.DailyScheduleUncheckedUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUncheckedUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUncheckedUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignCreateWithoutDailySchedulesInput = { + id?: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + workspace: Prisma.WorkspaceCreateNestedOneWithoutCampaignsInput + primaryMailbox: Prisma.InboxCreateNestedOneWithoutPrimaryCampaignsInput + satellites?: Prisma.CampaignSatelliteCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignUncheckedCreateWithoutDailySchedulesInput = { + id?: string + workspaceId: string + primaryMailboxId: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + satellites?: Prisma.CampaignSatelliteUncheckedCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatUncheckedCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobUncheckedCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignCreateOrConnectWithoutDailySchedulesInput = { + where: Prisma.WarmupCampaignWhereUniqueInput + create: Prisma.XOR +} + +export type WarmupCampaignUpsertWithoutDailySchedulesInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.WarmupCampaignWhereInput +} + +export type WarmupCampaignUpdateToOneWithWhereWithoutDailySchedulesInput = { + where?: Prisma.WarmupCampaignWhereInput + data: Prisma.XOR +} + +export type WarmupCampaignUpdateWithoutDailySchedulesInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + workspace?: Prisma.WorkspaceUpdateOneRequiredWithoutCampaignsNestedInput + primaryMailbox?: Prisma.InboxUpdateOneRequiredWithoutPrimaryCampaignsNestedInput + satellites?: Prisma.CampaignSatelliteUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignUncheckedUpdateWithoutDailySchedulesInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + primaryMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + satellites?: Prisma.CampaignSatelliteUncheckedUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUncheckedUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUncheckedUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignCreateWithoutDailyStatsInput = { + id?: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + workspace: Prisma.WorkspaceCreateNestedOneWithoutCampaignsInput + primaryMailbox: Prisma.InboxCreateNestedOneWithoutPrimaryCampaignsInput + satellites?: Prisma.CampaignSatelliteCreateNestedManyWithoutCampaignInput + dailySchedules?: Prisma.DailyScheduleCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignUncheckedCreateWithoutDailyStatsInput = { + id?: string + workspaceId: string + primaryMailboxId: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + satellites?: Prisma.CampaignSatelliteUncheckedCreateNestedManyWithoutCampaignInput + dailySchedules?: Prisma.DailyScheduleUncheckedCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobUncheckedCreateNestedManyWithoutCampaignInput + warmupLogs?: Prisma.WarmupLogUncheckedCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignCreateOrConnectWithoutDailyStatsInput = { + where: Prisma.WarmupCampaignWhereUniqueInput + create: Prisma.XOR +} + +export type WarmupCampaignUpsertWithoutDailyStatsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.WarmupCampaignWhereInput +} + +export type WarmupCampaignUpdateToOneWithWhereWithoutDailyStatsInput = { + where?: Prisma.WarmupCampaignWhereInput + data: Prisma.XOR +} + +export type WarmupCampaignUpdateWithoutDailyStatsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + workspace?: Prisma.WorkspaceUpdateOneRequiredWithoutCampaignsNestedInput + primaryMailbox?: Prisma.InboxUpdateOneRequiredWithoutPrimaryCampaignsNestedInput + satellites?: Prisma.CampaignSatelliteUpdateManyWithoutCampaignNestedInput + dailySchedules?: Prisma.DailyScheduleUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignUncheckedUpdateWithoutDailyStatsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + primaryMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + satellites?: Prisma.CampaignSatelliteUncheckedUpdateManyWithoutCampaignNestedInput + dailySchedules?: Prisma.DailyScheduleUncheckedUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUncheckedUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignCreateWithoutWarmupLogsInput = { + id?: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + workspace: Prisma.WorkspaceCreateNestedOneWithoutCampaignsInput + primaryMailbox: Prisma.InboxCreateNestedOneWithoutPrimaryCampaignsInput + satellites?: Prisma.CampaignSatelliteCreateNestedManyWithoutCampaignInput + dailySchedules?: Prisma.DailyScheduleCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignUncheckedCreateWithoutWarmupLogsInput = { + id?: string + workspaceId: string + primaryMailboxId: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string + satellites?: Prisma.CampaignSatelliteUncheckedCreateNestedManyWithoutCampaignInput + dailySchedules?: Prisma.DailyScheduleUncheckedCreateNestedManyWithoutCampaignInput + dailyStats?: Prisma.DailyStatUncheckedCreateNestedManyWithoutCampaignInput + sendJobs?: Prisma.SendJobUncheckedCreateNestedManyWithoutCampaignInput +} + +export type WarmupCampaignCreateOrConnectWithoutWarmupLogsInput = { + where: Prisma.WarmupCampaignWhereUniqueInput + create: Prisma.XOR +} + +export type WarmupCampaignUpsertWithoutWarmupLogsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.WarmupCampaignWhereInput +} + +export type WarmupCampaignUpdateToOneWithWhereWithoutWarmupLogsInput = { + where?: Prisma.WarmupCampaignWhereInput + data: Prisma.XOR +} + +export type WarmupCampaignUpdateWithoutWarmupLogsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + workspace?: Prisma.WorkspaceUpdateOneRequiredWithoutCampaignsNestedInput + primaryMailbox?: Prisma.InboxUpdateOneRequiredWithoutPrimaryCampaignsNestedInput + satellites?: Prisma.CampaignSatelliteUpdateManyWithoutCampaignNestedInput + dailySchedules?: Prisma.DailyScheduleUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignUncheckedUpdateWithoutWarmupLogsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + primaryMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + satellites?: Prisma.CampaignSatelliteUncheckedUpdateManyWithoutCampaignNestedInput + dailySchedules?: Prisma.DailyScheduleUncheckedUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUncheckedUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUncheckedUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignCreateManyWorkspaceInput = { + id?: string + primaryMailboxId: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type WarmupCampaignUpdateWithoutWorkspaceInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + primaryMailbox?: Prisma.InboxUpdateOneRequiredWithoutPrimaryCampaignsNestedInput + satellites?: Prisma.CampaignSatelliteUpdateManyWithoutCampaignNestedInput + dailySchedules?: Prisma.DailyScheduleUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignUncheckedUpdateWithoutWorkspaceInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + primaryMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + satellites?: Prisma.CampaignSatelliteUncheckedUpdateManyWithoutCampaignNestedInput + dailySchedules?: Prisma.DailyScheduleUncheckedUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUncheckedUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUncheckedUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignUncheckedUpdateManyWithoutWorkspaceInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + primaryMailboxId?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WarmupCampaignCreateManyPrimaryMailboxInput = { + id?: string + workspaceId: string + name: string + recipe?: $Enums.CampaignRecipe + status?: $Enums.CampaignStatus + durationDays?: number + minEmailsPerDay?: number + maxEmailsPerDay?: number + replyRatePercent?: number + maintenanceVolumePercent?: number + timezone?: string + sendWindowStart?: string + sendWindowEnd?: string + customSchedule?: string | null + startedAt?: Date | string | null + endsAt?: Date | string | null + currentDay?: number + pausedFromStatus?: $Enums.CampaignStatus | null + lastSendAt?: Date | string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type WarmupCampaignUpdateWithoutPrimaryMailboxInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + workspace?: Prisma.WorkspaceUpdateOneRequiredWithoutCampaignsNestedInput + satellites?: Prisma.CampaignSatelliteUpdateManyWithoutCampaignNestedInput + dailySchedules?: Prisma.DailyScheduleUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignUncheckedUpdateWithoutPrimaryMailboxInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + satellites?: Prisma.CampaignSatelliteUncheckedUpdateManyWithoutCampaignNestedInput + dailySchedules?: Prisma.DailyScheduleUncheckedUpdateManyWithoutCampaignNestedInput + dailyStats?: Prisma.DailyStatUncheckedUpdateManyWithoutCampaignNestedInput + sendJobs?: Prisma.SendJobUncheckedUpdateManyWithoutCampaignNestedInput + warmupLogs?: Prisma.WarmupLogUncheckedUpdateManyWithoutCampaignNestedInput +} + +export type WarmupCampaignUncheckedUpdateManyWithoutPrimaryMailboxInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + workspaceId?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + recipe?: Prisma.EnumCampaignRecipeFieldUpdateOperationsInput | $Enums.CampaignRecipe + status?: Prisma.EnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus + durationDays?: Prisma.IntFieldUpdateOperationsInput | number + minEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + maxEmailsPerDay?: Prisma.IntFieldUpdateOperationsInput | number + replyRatePercent?: Prisma.IntFieldUpdateOperationsInput | number + maintenanceVolumePercent?: Prisma.IntFieldUpdateOperationsInput | number + timezone?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowStart?: Prisma.StringFieldUpdateOperationsInput | string + sendWindowEnd?: Prisma.StringFieldUpdateOperationsInput | string + customSchedule?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + startedAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + endsAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + currentDay?: Prisma.IntFieldUpdateOperationsInput | number + pausedFromStatus?: Prisma.NullableEnumCampaignStatusFieldUpdateOperationsInput | $Enums.CampaignStatus | null + lastSendAt?: Prisma.NullableDateTimeFieldUpdateOperationsInput | Date | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + +/** + * Count Type WarmupCampaignCountOutputType + */ + +export type WarmupCampaignCountOutputType = { + satellites: number + dailySchedules: number + dailyStats: number + sendJobs: number + warmupLogs: number +} + +export type WarmupCampaignCountOutputTypeSelect = { + satellites?: boolean | WarmupCampaignCountOutputTypeCountSatellitesArgs + dailySchedules?: boolean | WarmupCampaignCountOutputTypeCountDailySchedulesArgs + dailyStats?: boolean | WarmupCampaignCountOutputTypeCountDailyStatsArgs + sendJobs?: boolean | WarmupCampaignCountOutputTypeCountSendJobsArgs + warmupLogs?: boolean | WarmupCampaignCountOutputTypeCountWarmupLogsArgs +} + +/** + * WarmupCampaignCountOutputType without action + */ +export type WarmupCampaignCountOutputTypeDefaultArgs = { + /** + * Select specific fields to fetch from the WarmupCampaignCountOutputType + */ + select?: Prisma.WarmupCampaignCountOutputTypeSelect | null +} + +/** + * WarmupCampaignCountOutputType without action + */ +export type WarmupCampaignCountOutputTypeCountSatellitesArgs = { + where?: Prisma.CampaignSatelliteWhereInput +} + +/** + * WarmupCampaignCountOutputType without action + */ +export type WarmupCampaignCountOutputTypeCountDailySchedulesArgs = { + where?: Prisma.DailyScheduleWhereInput +} + +/** + * WarmupCampaignCountOutputType without action + */ +export type WarmupCampaignCountOutputTypeCountDailyStatsArgs = { + where?: Prisma.DailyStatWhereInput +} + +/** + * WarmupCampaignCountOutputType without action + */ +export type WarmupCampaignCountOutputTypeCountSendJobsArgs = { + where?: Prisma.SendJobWhereInput +} + +/** + * WarmupCampaignCountOutputType without action + */ +export type WarmupCampaignCountOutputTypeCountWarmupLogsArgs = { + where?: Prisma.WarmupLogWhereInput +} + + +export type WarmupCampaignSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + workspaceId?: boolean + primaryMailboxId?: boolean + name?: boolean + recipe?: boolean + status?: boolean + durationDays?: boolean + minEmailsPerDay?: boolean + maxEmailsPerDay?: boolean + replyRatePercent?: boolean + maintenanceVolumePercent?: boolean + timezone?: boolean + sendWindowStart?: boolean + sendWindowEnd?: boolean + customSchedule?: boolean + startedAt?: boolean + endsAt?: boolean + currentDay?: boolean + pausedFromStatus?: boolean + lastSendAt?: boolean + createdAt?: boolean + updatedAt?: boolean + workspace?: boolean | Prisma.WorkspaceDefaultArgs + primaryMailbox?: boolean | Prisma.InboxDefaultArgs + satellites?: boolean | Prisma.WarmupCampaign$satellitesArgs + dailySchedules?: boolean | Prisma.WarmupCampaign$dailySchedulesArgs + dailyStats?: boolean | Prisma.WarmupCampaign$dailyStatsArgs + sendJobs?: boolean | Prisma.WarmupCampaign$sendJobsArgs + warmupLogs?: boolean | Prisma.WarmupCampaign$warmupLogsArgs + _count?: boolean | Prisma.WarmupCampaignCountOutputTypeDefaultArgs +}, ExtArgs["result"]["warmupCampaign"]> + +export type WarmupCampaignSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + workspaceId?: boolean + primaryMailboxId?: boolean + name?: boolean + recipe?: boolean + status?: boolean + durationDays?: boolean + minEmailsPerDay?: boolean + maxEmailsPerDay?: boolean + replyRatePercent?: boolean + maintenanceVolumePercent?: boolean + timezone?: boolean + sendWindowStart?: boolean + sendWindowEnd?: boolean + customSchedule?: boolean + startedAt?: boolean + endsAt?: boolean + currentDay?: boolean + pausedFromStatus?: boolean + lastSendAt?: boolean + createdAt?: boolean + updatedAt?: boolean + workspace?: boolean | Prisma.WorkspaceDefaultArgs + primaryMailbox?: boolean | Prisma.InboxDefaultArgs +}, ExtArgs["result"]["warmupCampaign"]> + +export type WarmupCampaignSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + workspaceId?: boolean + primaryMailboxId?: boolean + name?: boolean + recipe?: boolean + status?: boolean + durationDays?: boolean + minEmailsPerDay?: boolean + maxEmailsPerDay?: boolean + replyRatePercent?: boolean + maintenanceVolumePercent?: boolean + timezone?: boolean + sendWindowStart?: boolean + sendWindowEnd?: boolean + customSchedule?: boolean + startedAt?: boolean + endsAt?: boolean + currentDay?: boolean + pausedFromStatus?: boolean + lastSendAt?: boolean + createdAt?: boolean + updatedAt?: boolean + workspace?: boolean | Prisma.WorkspaceDefaultArgs + primaryMailbox?: boolean | Prisma.InboxDefaultArgs +}, ExtArgs["result"]["warmupCampaign"]> + +export type WarmupCampaignSelectScalar = { + id?: boolean + workspaceId?: boolean + primaryMailboxId?: boolean + name?: boolean + recipe?: boolean + status?: boolean + durationDays?: boolean + minEmailsPerDay?: boolean + maxEmailsPerDay?: boolean + replyRatePercent?: boolean + maintenanceVolumePercent?: boolean + timezone?: boolean + sendWindowStart?: boolean + sendWindowEnd?: boolean + customSchedule?: boolean + startedAt?: boolean + endsAt?: boolean + currentDay?: boolean + pausedFromStatus?: boolean + lastSendAt?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type WarmupCampaignOmit = runtime.Types.Extensions.GetOmit<"id" | "workspaceId" | "primaryMailboxId" | "name" | "recipe" | "status" | "durationDays" | "minEmailsPerDay" | "maxEmailsPerDay" | "replyRatePercent" | "maintenanceVolumePercent" | "timezone" | "sendWindowStart" | "sendWindowEnd" | "customSchedule" | "startedAt" | "endsAt" | "currentDay" | "pausedFromStatus" | "lastSendAt" | "createdAt" | "updatedAt", ExtArgs["result"]["warmupCampaign"]> +export type WarmupCampaignInclude = { + workspace?: boolean | Prisma.WorkspaceDefaultArgs + primaryMailbox?: boolean | Prisma.InboxDefaultArgs + satellites?: boolean | Prisma.WarmupCampaign$satellitesArgs + dailySchedules?: boolean | Prisma.WarmupCampaign$dailySchedulesArgs + dailyStats?: boolean | Prisma.WarmupCampaign$dailyStatsArgs + sendJobs?: boolean | Prisma.WarmupCampaign$sendJobsArgs + warmupLogs?: boolean | Prisma.WarmupCampaign$warmupLogsArgs + _count?: boolean | Prisma.WarmupCampaignCountOutputTypeDefaultArgs +} +export type WarmupCampaignIncludeCreateManyAndReturn = { + workspace?: boolean | Prisma.WorkspaceDefaultArgs + primaryMailbox?: boolean | Prisma.InboxDefaultArgs +} +export type WarmupCampaignIncludeUpdateManyAndReturn = { + workspace?: boolean | Prisma.WorkspaceDefaultArgs + primaryMailbox?: boolean | Prisma.InboxDefaultArgs +} + +export type $WarmupCampaignPayload = { + name: "WarmupCampaign" + objects: { + workspace: Prisma.$WorkspacePayload + primaryMailbox: Prisma.$InboxPayload + satellites: Prisma.$CampaignSatellitePayload[] + dailySchedules: Prisma.$DailySchedulePayload[] + dailyStats: Prisma.$DailyStatPayload[] + sendJobs: Prisma.$SendJobPayload[] + warmupLogs: Prisma.$WarmupLogPayload[] + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: string + workspaceId: string + primaryMailboxId: string + name: string + recipe: $Enums.CampaignRecipe + status: $Enums.CampaignStatus + durationDays: number + minEmailsPerDay: number + maxEmailsPerDay: number + replyRatePercent: number + maintenanceVolumePercent: number + timezone: string + sendWindowStart: string + sendWindowEnd: string + customSchedule: string | null + startedAt: Date | null + endsAt: Date | null + currentDay: number + pausedFromStatus: $Enums.CampaignStatus | null + lastSendAt: Date | null + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["warmupCampaign"]> + composites: {} +} + +export type WarmupCampaignGetPayload = runtime.Types.Result.GetResult + +export type WarmupCampaignCountArgs = + Omit & { + select?: WarmupCampaignCountAggregateInputType | true + } + +export interface WarmupCampaignDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['WarmupCampaign'], meta: { name: 'WarmupCampaign' } } + /** + * Find zero or one WarmupCampaign that matches the filter. + * @param {WarmupCampaignFindUniqueArgs} args - Arguments to find a WarmupCampaign + * @example + * // Get one WarmupCampaign + * const warmupCampaign = await prisma.warmupCampaign.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__WarmupCampaignClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one WarmupCampaign that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {WarmupCampaignFindUniqueOrThrowArgs} args - Arguments to find a WarmupCampaign + * @example + * // Get one WarmupCampaign + * const warmupCampaign = await prisma.warmupCampaign.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__WarmupCampaignClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first WarmupCampaign that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupCampaignFindFirstArgs} args - Arguments to find a WarmupCampaign + * @example + * // Get one WarmupCampaign + * const warmupCampaign = await prisma.warmupCampaign.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__WarmupCampaignClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first WarmupCampaign that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupCampaignFindFirstOrThrowArgs} args - Arguments to find a WarmupCampaign + * @example + * // Get one WarmupCampaign + * const warmupCampaign = await prisma.warmupCampaign.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__WarmupCampaignClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more WarmupCampaigns that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupCampaignFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all WarmupCampaigns + * const warmupCampaigns = await prisma.warmupCampaign.findMany() + * + * // Get first 10 WarmupCampaigns + * const warmupCampaigns = await prisma.warmupCampaign.findMany({ take: 10 }) + * + * // Only select the `id` + * const warmupCampaignWithIdOnly = await prisma.warmupCampaign.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a WarmupCampaign. + * @param {WarmupCampaignCreateArgs} args - Arguments to create a WarmupCampaign. + * @example + * // Create one WarmupCampaign + * const WarmupCampaign = await prisma.warmupCampaign.create({ + * data: { + * // ... data to create a WarmupCampaign + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__WarmupCampaignClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many WarmupCampaigns. + * @param {WarmupCampaignCreateManyArgs} args - Arguments to create many WarmupCampaigns. + * @example + * // Create many WarmupCampaigns + * const warmupCampaign = await prisma.warmupCampaign.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many WarmupCampaigns and returns the data saved in the database. + * @param {WarmupCampaignCreateManyAndReturnArgs} args - Arguments to create many WarmupCampaigns. + * @example + * // Create many WarmupCampaigns + * const warmupCampaign = await prisma.warmupCampaign.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many WarmupCampaigns and only return the `id` + * const warmupCampaignWithIdOnly = await prisma.warmupCampaign.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a WarmupCampaign. + * @param {WarmupCampaignDeleteArgs} args - Arguments to delete one WarmupCampaign. + * @example + * // Delete one WarmupCampaign + * const WarmupCampaign = await prisma.warmupCampaign.delete({ + * where: { + * // ... filter to delete one WarmupCampaign + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__WarmupCampaignClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one WarmupCampaign. + * @param {WarmupCampaignUpdateArgs} args - Arguments to update one WarmupCampaign. + * @example + * // Update one WarmupCampaign + * const warmupCampaign = await prisma.warmupCampaign.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__WarmupCampaignClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more WarmupCampaigns. + * @param {WarmupCampaignDeleteManyArgs} args - Arguments to filter WarmupCampaigns to delete. + * @example + * // Delete a few WarmupCampaigns + * const { count } = await prisma.warmupCampaign.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more WarmupCampaigns. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupCampaignUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many WarmupCampaigns + * const warmupCampaign = await prisma.warmupCampaign.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more WarmupCampaigns and returns the data updated in the database. + * @param {WarmupCampaignUpdateManyAndReturnArgs} args - Arguments to update many WarmupCampaigns. + * @example + * // Update many WarmupCampaigns + * const warmupCampaign = await prisma.warmupCampaign.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more WarmupCampaigns and only return the `id` + * const warmupCampaignWithIdOnly = await prisma.warmupCampaign.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one WarmupCampaign. + * @param {WarmupCampaignUpsertArgs} args - Arguments to update or create a WarmupCampaign. + * @example + * // Update or create a WarmupCampaign + * const warmupCampaign = await prisma.warmupCampaign.upsert({ + * create: { + * // ... data to create a WarmupCampaign + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the WarmupCampaign we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__WarmupCampaignClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of WarmupCampaigns. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupCampaignCountArgs} args - Arguments to filter WarmupCampaigns to count. + * @example + * // Count the number of WarmupCampaigns + * const count = await prisma.warmupCampaign.count({ + * where: { + * // ... the filter for the WarmupCampaigns we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a WarmupCampaign. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupCampaignAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by WarmupCampaign. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupCampaignGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends WarmupCampaignGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: WarmupCampaignGroupByArgs['orderBy'] } + : { orderBy?: WarmupCampaignGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetWarmupCampaignGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the WarmupCampaign model + */ +readonly fields: WarmupCampaignFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for WarmupCampaign. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__WarmupCampaignClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + workspace = {}>(args?: Prisma.Subset>): Prisma.Prisma__WorkspaceClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + primaryMailbox = {}>(args?: Prisma.Subset>): Prisma.Prisma__InboxClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + satellites = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + dailySchedules = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + dailyStats = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + sendJobs = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + warmupLogs = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the WarmupCampaign model + */ +export interface WarmupCampaignFieldRefs { + readonly id: Prisma.FieldRef<"WarmupCampaign", 'String'> + readonly workspaceId: Prisma.FieldRef<"WarmupCampaign", 'String'> + readonly primaryMailboxId: Prisma.FieldRef<"WarmupCampaign", 'String'> + readonly name: Prisma.FieldRef<"WarmupCampaign", 'String'> + readonly recipe: Prisma.FieldRef<"WarmupCampaign", 'CampaignRecipe'> + readonly status: Prisma.FieldRef<"WarmupCampaign", 'CampaignStatus'> + readonly durationDays: Prisma.FieldRef<"WarmupCampaign", 'Int'> + readonly minEmailsPerDay: Prisma.FieldRef<"WarmupCampaign", 'Int'> + readonly maxEmailsPerDay: Prisma.FieldRef<"WarmupCampaign", 'Int'> + readonly replyRatePercent: Prisma.FieldRef<"WarmupCampaign", 'Int'> + readonly maintenanceVolumePercent: Prisma.FieldRef<"WarmupCampaign", 'Int'> + readonly timezone: Prisma.FieldRef<"WarmupCampaign", 'String'> + readonly sendWindowStart: Prisma.FieldRef<"WarmupCampaign", 'String'> + readonly sendWindowEnd: Prisma.FieldRef<"WarmupCampaign", 'String'> + readonly customSchedule: Prisma.FieldRef<"WarmupCampaign", 'String'> + readonly startedAt: Prisma.FieldRef<"WarmupCampaign", 'DateTime'> + readonly endsAt: Prisma.FieldRef<"WarmupCampaign", 'DateTime'> + readonly currentDay: Prisma.FieldRef<"WarmupCampaign", 'Int'> + readonly pausedFromStatus: Prisma.FieldRef<"WarmupCampaign", 'CampaignStatus'> + readonly lastSendAt: Prisma.FieldRef<"WarmupCampaign", 'DateTime'> + readonly createdAt: Prisma.FieldRef<"WarmupCampaign", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"WarmupCampaign", 'DateTime'> +} + + +// Custom InputTypes +/** + * WarmupCampaign findUnique + */ +export type WarmupCampaignFindUniqueArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null + /** + * Filter, which WarmupCampaign to fetch. + */ + where: Prisma.WarmupCampaignWhereUniqueInput +} + +/** + * WarmupCampaign findUniqueOrThrow + */ +export type WarmupCampaignFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null + /** + * Filter, which WarmupCampaign to fetch. + */ + where: Prisma.WarmupCampaignWhereUniqueInput +} + +/** + * WarmupCampaign findFirst + */ +export type WarmupCampaignFindFirstArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null + /** + * Filter, which WarmupCampaign to fetch. + */ + where?: Prisma.WarmupCampaignWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of WarmupCampaigns to fetch. + */ + orderBy?: Prisma.WarmupCampaignOrderByWithRelationInput | Prisma.WarmupCampaignOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for WarmupCampaigns. + */ + cursor?: Prisma.WarmupCampaignWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` WarmupCampaigns from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` WarmupCampaigns. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of WarmupCampaigns. + */ + distinct?: Prisma.WarmupCampaignScalarFieldEnum | Prisma.WarmupCampaignScalarFieldEnum[] +} + +/** + * WarmupCampaign findFirstOrThrow + */ +export type WarmupCampaignFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null + /** + * Filter, which WarmupCampaign to fetch. + */ + where?: Prisma.WarmupCampaignWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of WarmupCampaigns to fetch. + */ + orderBy?: Prisma.WarmupCampaignOrderByWithRelationInput | Prisma.WarmupCampaignOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for WarmupCampaigns. + */ + cursor?: Prisma.WarmupCampaignWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` WarmupCampaigns from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` WarmupCampaigns. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of WarmupCampaigns. + */ + distinct?: Prisma.WarmupCampaignScalarFieldEnum | Prisma.WarmupCampaignScalarFieldEnum[] +} + +/** + * WarmupCampaign findMany + */ +export type WarmupCampaignFindManyArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null + /** + * Filter, which WarmupCampaigns to fetch. + */ + where?: Prisma.WarmupCampaignWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of WarmupCampaigns to fetch. + */ + orderBy?: Prisma.WarmupCampaignOrderByWithRelationInput | Prisma.WarmupCampaignOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing WarmupCampaigns. + */ + cursor?: Prisma.WarmupCampaignWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` WarmupCampaigns from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` WarmupCampaigns. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of WarmupCampaigns. + */ + distinct?: Prisma.WarmupCampaignScalarFieldEnum | Prisma.WarmupCampaignScalarFieldEnum[] +} + +/** + * WarmupCampaign create + */ +export type WarmupCampaignCreateArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null + /** + * The data needed to create a WarmupCampaign. + */ + data: Prisma.XOR +} + +/** + * WarmupCampaign createMany + */ +export type WarmupCampaignCreateManyArgs = { + /** + * The data used to create many WarmupCampaigns. + */ + data: Prisma.WarmupCampaignCreateManyInput | Prisma.WarmupCampaignCreateManyInput[] +} + +/** + * WarmupCampaign createManyAndReturn + */ +export type WarmupCampaignCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelectCreateManyAndReturn | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * The data used to create many WarmupCampaigns. + */ + data: Prisma.WarmupCampaignCreateManyInput | Prisma.WarmupCampaignCreateManyInput[] + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignIncludeCreateManyAndReturn | null +} + +/** + * WarmupCampaign update + */ +export type WarmupCampaignUpdateArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null + /** + * The data needed to update a WarmupCampaign. + */ + data: Prisma.XOR + /** + * Choose, which WarmupCampaign to update. + */ + where: Prisma.WarmupCampaignWhereUniqueInput +} + +/** + * WarmupCampaign updateMany + */ +export type WarmupCampaignUpdateManyArgs = { + /** + * The data used to update WarmupCampaigns. + */ + data: Prisma.XOR + /** + * Filter which WarmupCampaigns to update + */ + where?: Prisma.WarmupCampaignWhereInput + /** + * Limit how many WarmupCampaigns to update. + */ + limit?: number +} + +/** + * WarmupCampaign updateManyAndReturn + */ +export type WarmupCampaignUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * The data used to update WarmupCampaigns. + */ + data: Prisma.XOR + /** + * Filter which WarmupCampaigns to update + */ + where?: Prisma.WarmupCampaignWhereInput + /** + * Limit how many WarmupCampaigns to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignIncludeUpdateManyAndReturn | null +} + +/** + * WarmupCampaign upsert + */ +export type WarmupCampaignUpsertArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null + /** + * The filter to search for the WarmupCampaign to update in case it exists. + */ + where: Prisma.WarmupCampaignWhereUniqueInput + /** + * In case the WarmupCampaign found by the `where` argument doesn't exist, create a new WarmupCampaign with this data. + */ + create: Prisma.XOR + /** + * In case the WarmupCampaign was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * WarmupCampaign delete + */ +export type WarmupCampaignDeleteArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null + /** + * Filter which WarmupCampaign to delete. + */ + where: Prisma.WarmupCampaignWhereUniqueInput +} + +/** + * WarmupCampaign deleteMany + */ +export type WarmupCampaignDeleteManyArgs = { + /** + * Filter which WarmupCampaigns to delete + */ + where?: Prisma.WarmupCampaignWhereInput + /** + * Limit how many WarmupCampaigns to delete. + */ + limit?: number +} + +/** + * WarmupCampaign.satellites + */ +export type WarmupCampaign$satellitesArgs = { + /** + * Select specific fields to fetch from the CampaignSatellite + */ + select?: Prisma.CampaignSatelliteSelect | null + /** + * Omit specific fields from the CampaignSatellite + */ + omit?: Prisma.CampaignSatelliteOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.CampaignSatelliteInclude | null + where?: Prisma.CampaignSatelliteWhereInput + orderBy?: Prisma.CampaignSatelliteOrderByWithRelationInput | Prisma.CampaignSatelliteOrderByWithRelationInput[] + cursor?: Prisma.CampaignSatelliteWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.CampaignSatelliteScalarFieldEnum | Prisma.CampaignSatelliteScalarFieldEnum[] +} + +/** + * WarmupCampaign.dailySchedules + */ +export type WarmupCampaign$dailySchedulesArgs = { + /** + * Select specific fields to fetch from the DailySchedule + */ + select?: Prisma.DailyScheduleSelect | null + /** + * Omit specific fields from the DailySchedule + */ + omit?: Prisma.DailyScheduleOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyScheduleInclude | null + where?: Prisma.DailyScheduleWhereInput + orderBy?: Prisma.DailyScheduleOrderByWithRelationInput | Prisma.DailyScheduleOrderByWithRelationInput[] + cursor?: Prisma.DailyScheduleWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.DailyScheduleScalarFieldEnum | Prisma.DailyScheduleScalarFieldEnum[] +} + +/** + * WarmupCampaign.dailyStats + */ +export type WarmupCampaign$dailyStatsArgs = { + /** + * Select specific fields to fetch from the DailyStat + */ + select?: Prisma.DailyStatSelect | null + /** + * Omit specific fields from the DailyStat + */ + omit?: Prisma.DailyStatOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.DailyStatInclude | null + where?: Prisma.DailyStatWhereInput + orderBy?: Prisma.DailyStatOrderByWithRelationInput | Prisma.DailyStatOrderByWithRelationInput[] + cursor?: Prisma.DailyStatWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.DailyStatScalarFieldEnum | Prisma.DailyStatScalarFieldEnum[] +} + +/** + * WarmupCampaign.sendJobs + */ +export type WarmupCampaign$sendJobsArgs = { + /** + * Select specific fields to fetch from the SendJob + */ + select?: Prisma.SendJobSelect | null + /** + * Omit specific fields from the SendJob + */ + omit?: Prisma.SendJobOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.SendJobInclude | null + where?: Prisma.SendJobWhereInput + orderBy?: Prisma.SendJobOrderByWithRelationInput | Prisma.SendJobOrderByWithRelationInput[] + cursor?: Prisma.SendJobWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.SendJobScalarFieldEnum | Prisma.SendJobScalarFieldEnum[] +} + +/** + * WarmupCampaign.warmupLogs + */ +export type WarmupCampaign$warmupLogsArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null + where?: Prisma.WarmupLogWhereInput + orderBy?: Prisma.WarmupLogOrderByWithRelationInput | Prisma.WarmupLogOrderByWithRelationInput[] + cursor?: Prisma.WarmupLogWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.WarmupLogScalarFieldEnum | Prisma.WarmupLogScalarFieldEnum[] +} + +/** + * WarmupCampaign without action + */ +export type WarmupCampaignDefaultArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null +} diff --git a/apps/api/src/generated/prisma/models/WarmupLog.ts b/apps/api/src/generated/prisma/models/WarmupLog.ts new file mode 100644 index 0000000..0aafe0f --- /dev/null +++ b/apps/api/src/generated/prisma/models/WarmupLog.ts @@ -0,0 +1,2026 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `WarmupLog` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums" +import type * as Prisma from "../internal/prismaNamespace" + +/** + * Model WarmupLog + * + */ +export type WarmupLogModel = runtime.Types.Result.DefaultSelection + +export type AggregateWarmupLog = { + _count: WarmupLogCountAggregateOutputType | null + _min: WarmupLogMinAggregateOutputType | null + _max: WarmupLogMaxAggregateOutputType | null +} + +export type WarmupLogMinAggregateOutputType = { + id: string | null + campaignId: string | null + senderId: string | null + receiverId: string | null + messageId: string | null + inReplyTo: string | null + subject: string | null + body: string | null + status: $Enums.WarmupStatus | null + placement: string | null + rescuedFromSpam: boolean | null + validationResult: string | null + createdAt: Date | null + updatedAt: Date | null +} + +export type WarmupLogMaxAggregateOutputType = { + id: string | null + campaignId: string | null + senderId: string | null + receiverId: string | null + messageId: string | null + inReplyTo: string | null + subject: string | null + body: string | null + status: $Enums.WarmupStatus | null + placement: string | null + rescuedFromSpam: boolean | null + validationResult: string | null + createdAt: Date | null + updatedAt: Date | null +} + +export type WarmupLogCountAggregateOutputType = { + id: number + campaignId: number + senderId: number + receiverId: number + messageId: number + inReplyTo: number + subject: number + body: number + status: number + placement: number + rescuedFromSpam: number + validationResult: number + createdAt: number + updatedAt: number + _all: number +} + + +export type WarmupLogMinAggregateInputType = { + id?: true + campaignId?: true + senderId?: true + receiverId?: true + messageId?: true + inReplyTo?: true + subject?: true + body?: true + status?: true + placement?: true + rescuedFromSpam?: true + validationResult?: true + createdAt?: true + updatedAt?: true +} + +export type WarmupLogMaxAggregateInputType = { + id?: true + campaignId?: true + senderId?: true + receiverId?: true + messageId?: true + inReplyTo?: true + subject?: true + body?: true + status?: true + placement?: true + rescuedFromSpam?: true + validationResult?: true + createdAt?: true + updatedAt?: true +} + +export type WarmupLogCountAggregateInputType = { + id?: true + campaignId?: true + senderId?: true + receiverId?: true + messageId?: true + inReplyTo?: true + subject?: true + body?: true + status?: true + placement?: true + rescuedFromSpam?: true + validationResult?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type WarmupLogAggregateArgs = { + /** + * Filter which WarmupLog to aggregate. + */ + where?: Prisma.WarmupLogWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of WarmupLogs to fetch. + */ + orderBy?: Prisma.WarmupLogOrderByWithRelationInput | Prisma.WarmupLogOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.WarmupLogWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` WarmupLogs from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` WarmupLogs. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned WarmupLogs + **/ + _count?: true | WarmupLogCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: WarmupLogMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: WarmupLogMaxAggregateInputType +} + +export type GetWarmupLogAggregateType = { + [P in keyof T & keyof AggregateWarmupLog]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type WarmupLogGroupByArgs = { + where?: Prisma.WarmupLogWhereInput + orderBy?: Prisma.WarmupLogOrderByWithAggregationInput | Prisma.WarmupLogOrderByWithAggregationInput[] + by: Prisma.WarmupLogScalarFieldEnum[] | Prisma.WarmupLogScalarFieldEnum + having?: Prisma.WarmupLogScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: WarmupLogCountAggregateInputType | true + _min?: WarmupLogMinAggregateInputType + _max?: WarmupLogMaxAggregateInputType +} + +export type WarmupLogGroupByOutputType = { + id: string + campaignId: string | null + senderId: string + receiverId: string + messageId: string | null + inReplyTo: string | null + subject: string + body: string + status: $Enums.WarmupStatus + placement: string | null + rescuedFromSpam: boolean + validationResult: string | null + createdAt: Date + updatedAt: Date + _count: WarmupLogCountAggregateOutputType | null + _min: WarmupLogMinAggregateOutputType | null + _max: WarmupLogMaxAggregateOutputType | null +} + +export type GetWarmupLogGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof WarmupLogGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type WarmupLogWhereInput = { + AND?: Prisma.WarmupLogWhereInput | Prisma.WarmupLogWhereInput[] + OR?: Prisma.WarmupLogWhereInput[] + NOT?: Prisma.WarmupLogWhereInput | Prisma.WarmupLogWhereInput[] + id?: Prisma.StringFilter<"WarmupLog"> | string + campaignId?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + senderId?: Prisma.StringFilter<"WarmupLog"> | string + receiverId?: Prisma.StringFilter<"WarmupLog"> | string + messageId?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + inReplyTo?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + subject?: Prisma.StringFilter<"WarmupLog"> | string + body?: Prisma.StringFilter<"WarmupLog"> | string + status?: Prisma.EnumWarmupStatusFilter<"WarmupLog"> | $Enums.WarmupStatus + placement?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + rescuedFromSpam?: Prisma.BoolFilter<"WarmupLog"> | boolean + validationResult?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + createdAt?: Prisma.DateTimeFilter<"WarmupLog"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"WarmupLog"> | Date | string + campaign?: Prisma.XOR | null + sender?: Prisma.XOR + receiver?: Prisma.XOR +} + +export type WarmupLogOrderByWithRelationInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrderInput | Prisma.SortOrder + senderId?: Prisma.SortOrder + receiverId?: Prisma.SortOrder + messageId?: Prisma.SortOrderInput | Prisma.SortOrder + inReplyTo?: Prisma.SortOrderInput | Prisma.SortOrder + subject?: Prisma.SortOrder + body?: Prisma.SortOrder + status?: Prisma.SortOrder + placement?: Prisma.SortOrderInput | Prisma.SortOrder + rescuedFromSpam?: Prisma.SortOrder + validationResult?: Prisma.SortOrderInput | Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + campaign?: Prisma.WarmupCampaignOrderByWithRelationInput + sender?: Prisma.InboxOrderByWithRelationInput + receiver?: Prisma.InboxOrderByWithRelationInput +} + +export type WarmupLogWhereUniqueInput = Prisma.AtLeast<{ + id?: string + AND?: Prisma.WarmupLogWhereInput | Prisma.WarmupLogWhereInput[] + OR?: Prisma.WarmupLogWhereInput[] + NOT?: Prisma.WarmupLogWhereInput | Prisma.WarmupLogWhereInput[] + campaignId?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + senderId?: Prisma.StringFilter<"WarmupLog"> | string + receiverId?: Prisma.StringFilter<"WarmupLog"> | string + messageId?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + inReplyTo?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + subject?: Prisma.StringFilter<"WarmupLog"> | string + body?: Prisma.StringFilter<"WarmupLog"> | string + status?: Prisma.EnumWarmupStatusFilter<"WarmupLog"> | $Enums.WarmupStatus + placement?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + rescuedFromSpam?: Prisma.BoolFilter<"WarmupLog"> | boolean + validationResult?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + createdAt?: Prisma.DateTimeFilter<"WarmupLog"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"WarmupLog"> | Date | string + campaign?: Prisma.XOR | null + sender?: Prisma.XOR + receiver?: Prisma.XOR +}, "id"> + +export type WarmupLogOrderByWithAggregationInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrderInput | Prisma.SortOrder + senderId?: Prisma.SortOrder + receiverId?: Prisma.SortOrder + messageId?: Prisma.SortOrderInput | Prisma.SortOrder + inReplyTo?: Prisma.SortOrderInput | Prisma.SortOrder + subject?: Prisma.SortOrder + body?: Prisma.SortOrder + status?: Prisma.SortOrder + placement?: Prisma.SortOrderInput | Prisma.SortOrder + rescuedFromSpam?: Prisma.SortOrder + validationResult?: Prisma.SortOrderInput | Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.WarmupLogCountOrderByAggregateInput + _max?: Prisma.WarmupLogMaxOrderByAggregateInput + _min?: Prisma.WarmupLogMinOrderByAggregateInput +} + +export type WarmupLogScalarWhereWithAggregatesInput = { + AND?: Prisma.WarmupLogScalarWhereWithAggregatesInput | Prisma.WarmupLogScalarWhereWithAggregatesInput[] + OR?: Prisma.WarmupLogScalarWhereWithAggregatesInput[] + NOT?: Prisma.WarmupLogScalarWhereWithAggregatesInput | Prisma.WarmupLogScalarWhereWithAggregatesInput[] + id?: Prisma.StringWithAggregatesFilter<"WarmupLog"> | string + campaignId?: Prisma.StringNullableWithAggregatesFilter<"WarmupLog"> | string | null + senderId?: Prisma.StringWithAggregatesFilter<"WarmupLog"> | string + receiverId?: Prisma.StringWithAggregatesFilter<"WarmupLog"> | string + messageId?: Prisma.StringNullableWithAggregatesFilter<"WarmupLog"> | string | null + inReplyTo?: Prisma.StringNullableWithAggregatesFilter<"WarmupLog"> | string | null + subject?: Prisma.StringWithAggregatesFilter<"WarmupLog"> | string + body?: Prisma.StringWithAggregatesFilter<"WarmupLog"> | string + status?: Prisma.EnumWarmupStatusWithAggregatesFilter<"WarmupLog"> | $Enums.WarmupStatus + placement?: Prisma.StringNullableWithAggregatesFilter<"WarmupLog"> | string | null + rescuedFromSpam?: Prisma.BoolWithAggregatesFilter<"WarmupLog"> | boolean + validationResult?: Prisma.StringNullableWithAggregatesFilter<"WarmupLog"> | string | null + createdAt?: Prisma.DateTimeWithAggregatesFilter<"WarmupLog"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"WarmupLog"> | Date | string +} + +export type WarmupLogCreateInput = { + id?: string + messageId?: string | null + inReplyTo?: string | null + subject: string + body: string + status?: $Enums.WarmupStatus + placement?: string | null + rescuedFromSpam?: boolean + validationResult?: string | null + createdAt?: Date | string + updatedAt?: Date | string + campaign?: Prisma.WarmupCampaignCreateNestedOneWithoutWarmupLogsInput + sender: Prisma.InboxCreateNestedOneWithoutSentLogsInput + receiver: Prisma.InboxCreateNestedOneWithoutReceivedLogsInput +} + +export type WarmupLogUncheckedCreateInput = { + id?: string + campaignId?: string | null + senderId: string + receiverId: string + messageId?: string | null + inReplyTo?: string | null + subject: string + body: string + status?: $Enums.WarmupStatus + placement?: string | null + rescuedFromSpam?: boolean + validationResult?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type WarmupLogUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + campaign?: Prisma.WarmupCampaignUpdateOneWithoutWarmupLogsNestedInput + sender?: Prisma.InboxUpdateOneRequiredWithoutSentLogsNestedInput + receiver?: Prisma.InboxUpdateOneRequiredWithoutReceivedLogsNestedInput +} + +export type WarmupLogUncheckedUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + senderId?: Prisma.StringFieldUpdateOperationsInput | string + receiverId?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WarmupLogCreateManyInput = { + id?: string + campaignId?: string | null + senderId: string + receiverId: string + messageId?: string | null + inReplyTo?: string | null + subject: string + body: string + status?: $Enums.WarmupStatus + placement?: string | null + rescuedFromSpam?: boolean + validationResult?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type WarmupLogUpdateManyMutationInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WarmupLogUncheckedUpdateManyInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + senderId?: Prisma.StringFieldUpdateOperationsInput | string + receiverId?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WarmupLogListRelationFilter = { + every?: Prisma.WarmupLogWhereInput + some?: Prisma.WarmupLogWhereInput + none?: Prisma.WarmupLogWhereInput +} + +export type WarmupLogOrderByRelationAggregateInput = { + _count?: Prisma.SortOrder +} + +export type WarmupLogCountOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + senderId?: Prisma.SortOrder + receiverId?: Prisma.SortOrder + messageId?: Prisma.SortOrder + inReplyTo?: Prisma.SortOrder + subject?: Prisma.SortOrder + body?: Prisma.SortOrder + status?: Prisma.SortOrder + placement?: Prisma.SortOrder + rescuedFromSpam?: Prisma.SortOrder + validationResult?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type WarmupLogMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + senderId?: Prisma.SortOrder + receiverId?: Prisma.SortOrder + messageId?: Prisma.SortOrder + inReplyTo?: Prisma.SortOrder + subject?: Prisma.SortOrder + body?: Prisma.SortOrder + status?: Prisma.SortOrder + placement?: Prisma.SortOrder + rescuedFromSpam?: Prisma.SortOrder + validationResult?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type WarmupLogMinOrderByAggregateInput = { + id?: Prisma.SortOrder + campaignId?: Prisma.SortOrder + senderId?: Prisma.SortOrder + receiverId?: Prisma.SortOrder + messageId?: Prisma.SortOrder + inReplyTo?: Prisma.SortOrder + subject?: Prisma.SortOrder + body?: Prisma.SortOrder + status?: Prisma.SortOrder + placement?: Prisma.SortOrder + rescuedFromSpam?: Prisma.SortOrder + validationResult?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type WarmupLogCreateNestedManyWithoutSenderInput = { + create?: Prisma.XOR | Prisma.WarmupLogCreateWithoutSenderInput[] | Prisma.WarmupLogUncheckedCreateWithoutSenderInput[] + connectOrCreate?: Prisma.WarmupLogCreateOrConnectWithoutSenderInput | Prisma.WarmupLogCreateOrConnectWithoutSenderInput[] + createMany?: Prisma.WarmupLogCreateManySenderInputEnvelope + connect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] +} + +export type WarmupLogCreateNestedManyWithoutReceiverInput = { + create?: Prisma.XOR | Prisma.WarmupLogCreateWithoutReceiverInput[] | Prisma.WarmupLogUncheckedCreateWithoutReceiverInput[] + connectOrCreate?: Prisma.WarmupLogCreateOrConnectWithoutReceiverInput | Prisma.WarmupLogCreateOrConnectWithoutReceiverInput[] + createMany?: Prisma.WarmupLogCreateManyReceiverInputEnvelope + connect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] +} + +export type WarmupLogUncheckedCreateNestedManyWithoutSenderInput = { + create?: Prisma.XOR | Prisma.WarmupLogCreateWithoutSenderInput[] | Prisma.WarmupLogUncheckedCreateWithoutSenderInput[] + connectOrCreate?: Prisma.WarmupLogCreateOrConnectWithoutSenderInput | Prisma.WarmupLogCreateOrConnectWithoutSenderInput[] + createMany?: Prisma.WarmupLogCreateManySenderInputEnvelope + connect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] +} + +export type WarmupLogUncheckedCreateNestedManyWithoutReceiverInput = { + create?: Prisma.XOR | Prisma.WarmupLogCreateWithoutReceiverInput[] | Prisma.WarmupLogUncheckedCreateWithoutReceiverInput[] + connectOrCreate?: Prisma.WarmupLogCreateOrConnectWithoutReceiverInput | Prisma.WarmupLogCreateOrConnectWithoutReceiverInput[] + createMany?: Prisma.WarmupLogCreateManyReceiverInputEnvelope + connect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] +} + +export type WarmupLogUpdateManyWithoutSenderNestedInput = { + create?: Prisma.XOR | Prisma.WarmupLogCreateWithoutSenderInput[] | Prisma.WarmupLogUncheckedCreateWithoutSenderInput[] + connectOrCreate?: Prisma.WarmupLogCreateOrConnectWithoutSenderInput | Prisma.WarmupLogCreateOrConnectWithoutSenderInput[] + upsert?: Prisma.WarmupLogUpsertWithWhereUniqueWithoutSenderInput | Prisma.WarmupLogUpsertWithWhereUniqueWithoutSenderInput[] + createMany?: Prisma.WarmupLogCreateManySenderInputEnvelope + set?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + disconnect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + delete?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + connect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + update?: Prisma.WarmupLogUpdateWithWhereUniqueWithoutSenderInput | Prisma.WarmupLogUpdateWithWhereUniqueWithoutSenderInput[] + updateMany?: Prisma.WarmupLogUpdateManyWithWhereWithoutSenderInput | Prisma.WarmupLogUpdateManyWithWhereWithoutSenderInput[] + deleteMany?: Prisma.WarmupLogScalarWhereInput | Prisma.WarmupLogScalarWhereInput[] +} + +export type WarmupLogUpdateManyWithoutReceiverNestedInput = { + create?: Prisma.XOR | Prisma.WarmupLogCreateWithoutReceiverInput[] | Prisma.WarmupLogUncheckedCreateWithoutReceiverInput[] + connectOrCreate?: Prisma.WarmupLogCreateOrConnectWithoutReceiverInput | Prisma.WarmupLogCreateOrConnectWithoutReceiverInput[] + upsert?: Prisma.WarmupLogUpsertWithWhereUniqueWithoutReceiverInput | Prisma.WarmupLogUpsertWithWhereUniqueWithoutReceiverInput[] + createMany?: Prisma.WarmupLogCreateManyReceiverInputEnvelope + set?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + disconnect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + delete?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + connect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + update?: Prisma.WarmupLogUpdateWithWhereUniqueWithoutReceiverInput | Prisma.WarmupLogUpdateWithWhereUniqueWithoutReceiverInput[] + updateMany?: Prisma.WarmupLogUpdateManyWithWhereWithoutReceiverInput | Prisma.WarmupLogUpdateManyWithWhereWithoutReceiverInput[] + deleteMany?: Prisma.WarmupLogScalarWhereInput | Prisma.WarmupLogScalarWhereInput[] +} + +export type WarmupLogUncheckedUpdateManyWithoutSenderNestedInput = { + create?: Prisma.XOR | Prisma.WarmupLogCreateWithoutSenderInput[] | Prisma.WarmupLogUncheckedCreateWithoutSenderInput[] + connectOrCreate?: Prisma.WarmupLogCreateOrConnectWithoutSenderInput | Prisma.WarmupLogCreateOrConnectWithoutSenderInput[] + upsert?: Prisma.WarmupLogUpsertWithWhereUniqueWithoutSenderInput | Prisma.WarmupLogUpsertWithWhereUniqueWithoutSenderInput[] + createMany?: Prisma.WarmupLogCreateManySenderInputEnvelope + set?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + disconnect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + delete?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + connect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + update?: Prisma.WarmupLogUpdateWithWhereUniqueWithoutSenderInput | Prisma.WarmupLogUpdateWithWhereUniqueWithoutSenderInput[] + updateMany?: Prisma.WarmupLogUpdateManyWithWhereWithoutSenderInput | Prisma.WarmupLogUpdateManyWithWhereWithoutSenderInput[] + deleteMany?: Prisma.WarmupLogScalarWhereInput | Prisma.WarmupLogScalarWhereInput[] +} + +export type WarmupLogUncheckedUpdateManyWithoutReceiverNestedInput = { + create?: Prisma.XOR | Prisma.WarmupLogCreateWithoutReceiverInput[] | Prisma.WarmupLogUncheckedCreateWithoutReceiverInput[] + connectOrCreate?: Prisma.WarmupLogCreateOrConnectWithoutReceiverInput | Prisma.WarmupLogCreateOrConnectWithoutReceiverInput[] + upsert?: Prisma.WarmupLogUpsertWithWhereUniqueWithoutReceiverInput | Prisma.WarmupLogUpsertWithWhereUniqueWithoutReceiverInput[] + createMany?: Prisma.WarmupLogCreateManyReceiverInputEnvelope + set?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + disconnect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + delete?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + connect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + update?: Prisma.WarmupLogUpdateWithWhereUniqueWithoutReceiverInput | Prisma.WarmupLogUpdateWithWhereUniqueWithoutReceiverInput[] + updateMany?: Prisma.WarmupLogUpdateManyWithWhereWithoutReceiverInput | Prisma.WarmupLogUpdateManyWithWhereWithoutReceiverInput[] + deleteMany?: Prisma.WarmupLogScalarWhereInput | Prisma.WarmupLogScalarWhereInput[] +} + +export type WarmupLogCreateNestedManyWithoutCampaignInput = { + create?: Prisma.XOR | Prisma.WarmupLogCreateWithoutCampaignInput[] | Prisma.WarmupLogUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.WarmupLogCreateOrConnectWithoutCampaignInput | Prisma.WarmupLogCreateOrConnectWithoutCampaignInput[] + createMany?: Prisma.WarmupLogCreateManyCampaignInputEnvelope + connect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] +} + +export type WarmupLogUncheckedCreateNestedManyWithoutCampaignInput = { + create?: Prisma.XOR | Prisma.WarmupLogCreateWithoutCampaignInput[] | Prisma.WarmupLogUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.WarmupLogCreateOrConnectWithoutCampaignInput | Prisma.WarmupLogCreateOrConnectWithoutCampaignInput[] + createMany?: Prisma.WarmupLogCreateManyCampaignInputEnvelope + connect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] +} + +export type WarmupLogUpdateManyWithoutCampaignNestedInput = { + create?: Prisma.XOR | Prisma.WarmupLogCreateWithoutCampaignInput[] | Prisma.WarmupLogUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.WarmupLogCreateOrConnectWithoutCampaignInput | Prisma.WarmupLogCreateOrConnectWithoutCampaignInput[] + upsert?: Prisma.WarmupLogUpsertWithWhereUniqueWithoutCampaignInput | Prisma.WarmupLogUpsertWithWhereUniqueWithoutCampaignInput[] + createMany?: Prisma.WarmupLogCreateManyCampaignInputEnvelope + set?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + disconnect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + delete?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + connect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + update?: Prisma.WarmupLogUpdateWithWhereUniqueWithoutCampaignInput | Prisma.WarmupLogUpdateWithWhereUniqueWithoutCampaignInput[] + updateMany?: Prisma.WarmupLogUpdateManyWithWhereWithoutCampaignInput | Prisma.WarmupLogUpdateManyWithWhereWithoutCampaignInput[] + deleteMany?: Prisma.WarmupLogScalarWhereInput | Prisma.WarmupLogScalarWhereInput[] +} + +export type WarmupLogUncheckedUpdateManyWithoutCampaignNestedInput = { + create?: Prisma.XOR | Prisma.WarmupLogCreateWithoutCampaignInput[] | Prisma.WarmupLogUncheckedCreateWithoutCampaignInput[] + connectOrCreate?: Prisma.WarmupLogCreateOrConnectWithoutCampaignInput | Prisma.WarmupLogCreateOrConnectWithoutCampaignInput[] + upsert?: Prisma.WarmupLogUpsertWithWhereUniqueWithoutCampaignInput | Prisma.WarmupLogUpsertWithWhereUniqueWithoutCampaignInput[] + createMany?: Prisma.WarmupLogCreateManyCampaignInputEnvelope + set?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + disconnect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + delete?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + connect?: Prisma.WarmupLogWhereUniqueInput | Prisma.WarmupLogWhereUniqueInput[] + update?: Prisma.WarmupLogUpdateWithWhereUniqueWithoutCampaignInput | Prisma.WarmupLogUpdateWithWhereUniqueWithoutCampaignInput[] + updateMany?: Prisma.WarmupLogUpdateManyWithWhereWithoutCampaignInput | Prisma.WarmupLogUpdateManyWithWhereWithoutCampaignInput[] + deleteMany?: Prisma.WarmupLogScalarWhereInput | Prisma.WarmupLogScalarWhereInput[] +} + +export type EnumWarmupStatusFieldUpdateOperationsInput = { + set?: $Enums.WarmupStatus +} + +export type WarmupLogCreateWithoutSenderInput = { + id?: string + messageId?: string | null + inReplyTo?: string | null + subject: string + body: string + status?: $Enums.WarmupStatus + placement?: string | null + rescuedFromSpam?: boolean + validationResult?: string | null + createdAt?: Date | string + updatedAt?: Date | string + campaign?: Prisma.WarmupCampaignCreateNestedOneWithoutWarmupLogsInput + receiver: Prisma.InboxCreateNestedOneWithoutReceivedLogsInput +} + +export type WarmupLogUncheckedCreateWithoutSenderInput = { + id?: string + campaignId?: string | null + receiverId: string + messageId?: string | null + inReplyTo?: string | null + subject: string + body: string + status?: $Enums.WarmupStatus + placement?: string | null + rescuedFromSpam?: boolean + validationResult?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type WarmupLogCreateOrConnectWithoutSenderInput = { + where: Prisma.WarmupLogWhereUniqueInput + create: Prisma.XOR +} + +export type WarmupLogCreateManySenderInputEnvelope = { + data: Prisma.WarmupLogCreateManySenderInput | Prisma.WarmupLogCreateManySenderInput[] +} + +export type WarmupLogCreateWithoutReceiverInput = { + id?: string + messageId?: string | null + inReplyTo?: string | null + subject: string + body: string + status?: $Enums.WarmupStatus + placement?: string | null + rescuedFromSpam?: boolean + validationResult?: string | null + createdAt?: Date | string + updatedAt?: Date | string + campaign?: Prisma.WarmupCampaignCreateNestedOneWithoutWarmupLogsInput + sender: Prisma.InboxCreateNestedOneWithoutSentLogsInput +} + +export type WarmupLogUncheckedCreateWithoutReceiverInput = { + id?: string + campaignId?: string | null + senderId: string + messageId?: string | null + inReplyTo?: string | null + subject: string + body: string + status?: $Enums.WarmupStatus + placement?: string | null + rescuedFromSpam?: boolean + validationResult?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type WarmupLogCreateOrConnectWithoutReceiverInput = { + where: Prisma.WarmupLogWhereUniqueInput + create: Prisma.XOR +} + +export type WarmupLogCreateManyReceiverInputEnvelope = { + data: Prisma.WarmupLogCreateManyReceiverInput | Prisma.WarmupLogCreateManyReceiverInput[] +} + +export type WarmupLogUpsertWithWhereUniqueWithoutSenderInput = { + where: Prisma.WarmupLogWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type WarmupLogUpdateWithWhereUniqueWithoutSenderInput = { + where: Prisma.WarmupLogWhereUniqueInput + data: Prisma.XOR +} + +export type WarmupLogUpdateManyWithWhereWithoutSenderInput = { + where: Prisma.WarmupLogScalarWhereInput + data: Prisma.XOR +} + +export type WarmupLogScalarWhereInput = { + AND?: Prisma.WarmupLogScalarWhereInput | Prisma.WarmupLogScalarWhereInput[] + OR?: Prisma.WarmupLogScalarWhereInput[] + NOT?: Prisma.WarmupLogScalarWhereInput | Prisma.WarmupLogScalarWhereInput[] + id?: Prisma.StringFilter<"WarmupLog"> | string + campaignId?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + senderId?: Prisma.StringFilter<"WarmupLog"> | string + receiverId?: Prisma.StringFilter<"WarmupLog"> | string + messageId?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + inReplyTo?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + subject?: Prisma.StringFilter<"WarmupLog"> | string + body?: Prisma.StringFilter<"WarmupLog"> | string + status?: Prisma.EnumWarmupStatusFilter<"WarmupLog"> | $Enums.WarmupStatus + placement?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + rescuedFromSpam?: Prisma.BoolFilter<"WarmupLog"> | boolean + validationResult?: Prisma.StringNullableFilter<"WarmupLog"> | string | null + createdAt?: Prisma.DateTimeFilter<"WarmupLog"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"WarmupLog"> | Date | string +} + +export type WarmupLogUpsertWithWhereUniqueWithoutReceiverInput = { + where: Prisma.WarmupLogWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type WarmupLogUpdateWithWhereUniqueWithoutReceiverInput = { + where: Prisma.WarmupLogWhereUniqueInput + data: Prisma.XOR +} + +export type WarmupLogUpdateManyWithWhereWithoutReceiverInput = { + where: Prisma.WarmupLogScalarWhereInput + data: Prisma.XOR +} + +export type WarmupLogCreateWithoutCampaignInput = { + id?: string + messageId?: string | null + inReplyTo?: string | null + subject: string + body: string + status?: $Enums.WarmupStatus + placement?: string | null + rescuedFromSpam?: boolean + validationResult?: string | null + createdAt?: Date | string + updatedAt?: Date | string + sender: Prisma.InboxCreateNestedOneWithoutSentLogsInput + receiver: Prisma.InboxCreateNestedOneWithoutReceivedLogsInput +} + +export type WarmupLogUncheckedCreateWithoutCampaignInput = { + id?: string + senderId: string + receiverId: string + messageId?: string | null + inReplyTo?: string | null + subject: string + body: string + status?: $Enums.WarmupStatus + placement?: string | null + rescuedFromSpam?: boolean + validationResult?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type WarmupLogCreateOrConnectWithoutCampaignInput = { + where: Prisma.WarmupLogWhereUniqueInput + create: Prisma.XOR +} + +export type WarmupLogCreateManyCampaignInputEnvelope = { + data: Prisma.WarmupLogCreateManyCampaignInput | Prisma.WarmupLogCreateManyCampaignInput[] +} + +export type WarmupLogUpsertWithWhereUniqueWithoutCampaignInput = { + where: Prisma.WarmupLogWhereUniqueInput + update: Prisma.XOR + create: Prisma.XOR +} + +export type WarmupLogUpdateWithWhereUniqueWithoutCampaignInput = { + where: Prisma.WarmupLogWhereUniqueInput + data: Prisma.XOR +} + +export type WarmupLogUpdateManyWithWhereWithoutCampaignInput = { + where: Prisma.WarmupLogScalarWhereInput + data: Prisma.XOR +} + +export type WarmupLogCreateManySenderInput = { + id?: string + campaignId?: string | null + receiverId: string + messageId?: string | null + inReplyTo?: string | null + subject: string + body: string + status?: $Enums.WarmupStatus + placement?: string | null + rescuedFromSpam?: boolean + validationResult?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type WarmupLogCreateManyReceiverInput = { + id?: string + campaignId?: string | null + senderId: string + messageId?: string | null + inReplyTo?: string | null + subject: string + body: string + status?: $Enums.WarmupStatus + placement?: string | null + rescuedFromSpam?: boolean + validationResult?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type WarmupLogUpdateWithoutSenderInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + campaign?: Prisma.WarmupCampaignUpdateOneWithoutWarmupLogsNestedInput + receiver?: Prisma.InboxUpdateOneRequiredWithoutReceivedLogsNestedInput +} + +export type WarmupLogUncheckedUpdateWithoutSenderInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + receiverId?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WarmupLogUncheckedUpdateManyWithoutSenderInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + receiverId?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WarmupLogUpdateWithoutReceiverInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + campaign?: Prisma.WarmupCampaignUpdateOneWithoutWarmupLogsNestedInput + sender?: Prisma.InboxUpdateOneRequiredWithoutSentLogsNestedInput +} + +export type WarmupLogUncheckedUpdateWithoutReceiverInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + senderId?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WarmupLogUncheckedUpdateManyWithoutReceiverInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + campaignId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + senderId?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WarmupLogCreateManyCampaignInput = { + id?: string + senderId: string + receiverId: string + messageId?: string | null + inReplyTo?: string | null + subject: string + body: string + status?: $Enums.WarmupStatus + placement?: string | null + rescuedFromSpam?: boolean + validationResult?: string | null + createdAt?: Date | string + updatedAt?: Date | string +} + +export type WarmupLogUpdateWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + sender?: Prisma.InboxUpdateOneRequiredWithoutSentLogsNestedInput + receiver?: Prisma.InboxUpdateOneRequiredWithoutReceivedLogsNestedInput +} + +export type WarmupLogUncheckedUpdateWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + senderId?: Prisma.StringFieldUpdateOperationsInput | string + receiverId?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WarmupLogUncheckedUpdateManyWithoutCampaignInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + senderId?: Prisma.StringFieldUpdateOperationsInput | string + receiverId?: Prisma.StringFieldUpdateOperationsInput | string + messageId?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + inReplyTo?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + subject?: Prisma.StringFieldUpdateOperationsInput | string + body?: Prisma.StringFieldUpdateOperationsInput | string + status?: Prisma.EnumWarmupStatusFieldUpdateOperationsInput | $Enums.WarmupStatus + placement?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + rescuedFromSpam?: Prisma.BoolFieldUpdateOperationsInput | boolean + validationResult?: Prisma.NullableStringFieldUpdateOperationsInput | string | null + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + + + +export type WarmupLogSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + senderId?: boolean + receiverId?: boolean + messageId?: boolean + inReplyTo?: boolean + subject?: boolean + body?: boolean + status?: boolean + placement?: boolean + rescuedFromSpam?: boolean + validationResult?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupLog$campaignArgs + sender?: boolean | Prisma.InboxDefaultArgs + receiver?: boolean | Prisma.InboxDefaultArgs +}, ExtArgs["result"]["warmupLog"]> + +export type WarmupLogSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + senderId?: boolean + receiverId?: boolean + messageId?: boolean + inReplyTo?: boolean + subject?: boolean + body?: boolean + status?: boolean + placement?: boolean + rescuedFromSpam?: boolean + validationResult?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupLog$campaignArgs + sender?: boolean | Prisma.InboxDefaultArgs + receiver?: boolean | Prisma.InboxDefaultArgs +}, ExtArgs["result"]["warmupLog"]> + +export type WarmupLogSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + campaignId?: boolean + senderId?: boolean + receiverId?: boolean + messageId?: boolean + inReplyTo?: boolean + subject?: boolean + body?: boolean + status?: boolean + placement?: boolean + rescuedFromSpam?: boolean + validationResult?: boolean + createdAt?: boolean + updatedAt?: boolean + campaign?: boolean | Prisma.WarmupLog$campaignArgs + sender?: boolean | Prisma.InboxDefaultArgs + receiver?: boolean | Prisma.InboxDefaultArgs +}, ExtArgs["result"]["warmupLog"]> + +export type WarmupLogSelectScalar = { + id?: boolean + campaignId?: boolean + senderId?: boolean + receiverId?: boolean + messageId?: boolean + inReplyTo?: boolean + subject?: boolean + body?: boolean + status?: boolean + placement?: boolean + rescuedFromSpam?: boolean + validationResult?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type WarmupLogOmit = runtime.Types.Extensions.GetOmit<"id" | "campaignId" | "senderId" | "receiverId" | "messageId" | "inReplyTo" | "subject" | "body" | "status" | "placement" | "rescuedFromSpam" | "validationResult" | "createdAt" | "updatedAt", ExtArgs["result"]["warmupLog"]> +export type WarmupLogInclude = { + campaign?: boolean | Prisma.WarmupLog$campaignArgs + sender?: boolean | Prisma.InboxDefaultArgs + receiver?: boolean | Prisma.InboxDefaultArgs +} +export type WarmupLogIncludeCreateManyAndReturn = { + campaign?: boolean | Prisma.WarmupLog$campaignArgs + sender?: boolean | Prisma.InboxDefaultArgs + receiver?: boolean | Prisma.InboxDefaultArgs +} +export type WarmupLogIncludeUpdateManyAndReturn = { + campaign?: boolean | Prisma.WarmupLog$campaignArgs + sender?: boolean | Prisma.InboxDefaultArgs + receiver?: boolean | Prisma.InboxDefaultArgs +} + +export type $WarmupLogPayload = { + name: "WarmupLog" + objects: { + campaign: Prisma.$WarmupCampaignPayload | null + sender: Prisma.$InboxPayload + receiver: Prisma.$InboxPayload + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: string + campaignId: string | null + senderId: string + receiverId: string + messageId: string | null + inReplyTo: string | null + subject: string + body: string + status: $Enums.WarmupStatus + placement: string | null + rescuedFromSpam: boolean + validationResult: string | null + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["warmupLog"]> + composites: {} +} + +export type WarmupLogGetPayload = runtime.Types.Result.GetResult + +export type WarmupLogCountArgs = + Omit & { + select?: WarmupLogCountAggregateInputType | true + } + +export interface WarmupLogDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['WarmupLog'], meta: { name: 'WarmupLog' } } + /** + * Find zero or one WarmupLog that matches the filter. + * @param {WarmupLogFindUniqueArgs} args - Arguments to find a WarmupLog + * @example + * // Get one WarmupLog + * const warmupLog = await prisma.warmupLog.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__WarmupLogClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one WarmupLog that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {WarmupLogFindUniqueOrThrowArgs} args - Arguments to find a WarmupLog + * @example + * // Get one WarmupLog + * const warmupLog = await prisma.warmupLog.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__WarmupLogClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first WarmupLog that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupLogFindFirstArgs} args - Arguments to find a WarmupLog + * @example + * // Get one WarmupLog + * const warmupLog = await prisma.warmupLog.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__WarmupLogClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first WarmupLog that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupLogFindFirstOrThrowArgs} args - Arguments to find a WarmupLog + * @example + * // Get one WarmupLog + * const warmupLog = await prisma.warmupLog.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__WarmupLogClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more WarmupLogs that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupLogFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all WarmupLogs + * const warmupLogs = await prisma.warmupLog.findMany() + * + * // Get first 10 WarmupLogs + * const warmupLogs = await prisma.warmupLog.findMany({ take: 10 }) + * + * // Only select the `id` + * const warmupLogWithIdOnly = await prisma.warmupLog.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a WarmupLog. + * @param {WarmupLogCreateArgs} args - Arguments to create a WarmupLog. + * @example + * // Create one WarmupLog + * const WarmupLog = await prisma.warmupLog.create({ + * data: { + * // ... data to create a WarmupLog + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__WarmupLogClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many WarmupLogs. + * @param {WarmupLogCreateManyArgs} args - Arguments to create many WarmupLogs. + * @example + * // Create many WarmupLogs + * const warmupLog = await prisma.warmupLog.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many WarmupLogs and returns the data saved in the database. + * @param {WarmupLogCreateManyAndReturnArgs} args - Arguments to create many WarmupLogs. + * @example + * // Create many WarmupLogs + * const warmupLog = await prisma.warmupLog.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many WarmupLogs and only return the `id` + * const warmupLogWithIdOnly = await prisma.warmupLog.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a WarmupLog. + * @param {WarmupLogDeleteArgs} args - Arguments to delete one WarmupLog. + * @example + * // Delete one WarmupLog + * const WarmupLog = await prisma.warmupLog.delete({ + * where: { + * // ... filter to delete one WarmupLog + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__WarmupLogClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one WarmupLog. + * @param {WarmupLogUpdateArgs} args - Arguments to update one WarmupLog. + * @example + * // Update one WarmupLog + * const warmupLog = await prisma.warmupLog.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__WarmupLogClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more WarmupLogs. + * @param {WarmupLogDeleteManyArgs} args - Arguments to filter WarmupLogs to delete. + * @example + * // Delete a few WarmupLogs + * const { count } = await prisma.warmupLog.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more WarmupLogs. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupLogUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many WarmupLogs + * const warmupLog = await prisma.warmupLog.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more WarmupLogs and returns the data updated in the database. + * @param {WarmupLogUpdateManyAndReturnArgs} args - Arguments to update many WarmupLogs. + * @example + * // Update many WarmupLogs + * const warmupLog = await prisma.warmupLog.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more WarmupLogs and only return the `id` + * const warmupLogWithIdOnly = await prisma.warmupLog.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one WarmupLog. + * @param {WarmupLogUpsertArgs} args - Arguments to update or create a WarmupLog. + * @example + * // Update or create a WarmupLog + * const warmupLog = await prisma.warmupLog.upsert({ + * create: { + * // ... data to create a WarmupLog + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the WarmupLog we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__WarmupLogClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of WarmupLogs. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupLogCountArgs} args - Arguments to filter WarmupLogs to count. + * @example + * // Count the number of WarmupLogs + * const count = await prisma.warmupLog.count({ + * where: { + * // ... the filter for the WarmupLogs we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a WarmupLog. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupLogAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by WarmupLog. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WarmupLogGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends WarmupLogGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: WarmupLogGroupByArgs['orderBy'] } + : { orderBy?: WarmupLogGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetWarmupLogGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the WarmupLog model + */ +readonly fields: WarmupLogFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for WarmupLog. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__WarmupLogClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + campaign = {}>(args?: Prisma.Subset>): Prisma.Prisma__WarmupCampaignClient, T, "findUniqueOrThrow", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + sender = {}>(args?: Prisma.Subset>): Prisma.Prisma__InboxClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + receiver = {}>(args?: Prisma.Subset>): Prisma.Prisma__InboxClient, T, "findUniqueOrThrow", GlobalOmitOptions> | Null, Null, ExtArgs, GlobalOmitOptions> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the WarmupLog model + */ +export interface WarmupLogFieldRefs { + readonly id: Prisma.FieldRef<"WarmupLog", 'String'> + readonly campaignId: Prisma.FieldRef<"WarmupLog", 'String'> + readonly senderId: Prisma.FieldRef<"WarmupLog", 'String'> + readonly receiverId: Prisma.FieldRef<"WarmupLog", 'String'> + readonly messageId: Prisma.FieldRef<"WarmupLog", 'String'> + readonly inReplyTo: Prisma.FieldRef<"WarmupLog", 'String'> + readonly subject: Prisma.FieldRef<"WarmupLog", 'String'> + readonly body: Prisma.FieldRef<"WarmupLog", 'String'> + readonly status: Prisma.FieldRef<"WarmupLog", 'WarmupStatus'> + readonly placement: Prisma.FieldRef<"WarmupLog", 'String'> + readonly rescuedFromSpam: Prisma.FieldRef<"WarmupLog", 'Boolean'> + readonly validationResult: Prisma.FieldRef<"WarmupLog", 'String'> + readonly createdAt: Prisma.FieldRef<"WarmupLog", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"WarmupLog", 'DateTime'> +} + + +// Custom InputTypes +/** + * WarmupLog findUnique + */ +export type WarmupLogFindUniqueArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null + /** + * Filter, which WarmupLog to fetch. + */ + where: Prisma.WarmupLogWhereUniqueInput +} + +/** + * WarmupLog findUniqueOrThrow + */ +export type WarmupLogFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null + /** + * Filter, which WarmupLog to fetch. + */ + where: Prisma.WarmupLogWhereUniqueInput +} + +/** + * WarmupLog findFirst + */ +export type WarmupLogFindFirstArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null + /** + * Filter, which WarmupLog to fetch. + */ + where?: Prisma.WarmupLogWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of WarmupLogs to fetch. + */ + orderBy?: Prisma.WarmupLogOrderByWithRelationInput | Prisma.WarmupLogOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for WarmupLogs. + */ + cursor?: Prisma.WarmupLogWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` WarmupLogs from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` WarmupLogs. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of WarmupLogs. + */ + distinct?: Prisma.WarmupLogScalarFieldEnum | Prisma.WarmupLogScalarFieldEnum[] +} + +/** + * WarmupLog findFirstOrThrow + */ +export type WarmupLogFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null + /** + * Filter, which WarmupLog to fetch. + */ + where?: Prisma.WarmupLogWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of WarmupLogs to fetch. + */ + orderBy?: Prisma.WarmupLogOrderByWithRelationInput | Prisma.WarmupLogOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for WarmupLogs. + */ + cursor?: Prisma.WarmupLogWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` WarmupLogs from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` WarmupLogs. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of WarmupLogs. + */ + distinct?: Prisma.WarmupLogScalarFieldEnum | Prisma.WarmupLogScalarFieldEnum[] +} + +/** + * WarmupLog findMany + */ +export type WarmupLogFindManyArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null + /** + * Filter, which WarmupLogs to fetch. + */ + where?: Prisma.WarmupLogWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of WarmupLogs to fetch. + */ + orderBy?: Prisma.WarmupLogOrderByWithRelationInput | Prisma.WarmupLogOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing WarmupLogs. + */ + cursor?: Prisma.WarmupLogWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` WarmupLogs from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` WarmupLogs. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of WarmupLogs. + */ + distinct?: Prisma.WarmupLogScalarFieldEnum | Prisma.WarmupLogScalarFieldEnum[] +} + +/** + * WarmupLog create + */ +export type WarmupLogCreateArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null + /** + * The data needed to create a WarmupLog. + */ + data: Prisma.XOR +} + +/** + * WarmupLog createMany + */ +export type WarmupLogCreateManyArgs = { + /** + * The data used to create many WarmupLogs. + */ + data: Prisma.WarmupLogCreateManyInput | Prisma.WarmupLogCreateManyInput[] +} + +/** + * WarmupLog createManyAndReturn + */ +export type WarmupLogCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelectCreateManyAndReturn | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * The data used to create many WarmupLogs. + */ + data: Prisma.WarmupLogCreateManyInput | Prisma.WarmupLogCreateManyInput[] + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogIncludeCreateManyAndReturn | null +} + +/** + * WarmupLog update + */ +export type WarmupLogUpdateArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null + /** + * The data needed to update a WarmupLog. + */ + data: Prisma.XOR + /** + * Choose, which WarmupLog to update. + */ + where: Prisma.WarmupLogWhereUniqueInput +} + +/** + * WarmupLog updateMany + */ +export type WarmupLogUpdateManyArgs = { + /** + * The data used to update WarmupLogs. + */ + data: Prisma.XOR + /** + * Filter which WarmupLogs to update + */ + where?: Prisma.WarmupLogWhereInput + /** + * Limit how many WarmupLogs to update. + */ + limit?: number +} + +/** + * WarmupLog updateManyAndReturn + */ +export type WarmupLogUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * The data used to update WarmupLogs. + */ + data: Prisma.XOR + /** + * Filter which WarmupLogs to update + */ + where?: Prisma.WarmupLogWhereInput + /** + * Limit how many WarmupLogs to update. + */ + limit?: number + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogIncludeUpdateManyAndReturn | null +} + +/** + * WarmupLog upsert + */ +export type WarmupLogUpsertArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null + /** + * The filter to search for the WarmupLog to update in case it exists. + */ + where: Prisma.WarmupLogWhereUniqueInput + /** + * In case the WarmupLog found by the `where` argument doesn't exist, create a new WarmupLog with this data. + */ + create: Prisma.XOR + /** + * In case the WarmupLog was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * WarmupLog delete + */ +export type WarmupLogDeleteArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null + /** + * Filter which WarmupLog to delete. + */ + where: Prisma.WarmupLogWhereUniqueInput +} + +/** + * WarmupLog deleteMany + */ +export type WarmupLogDeleteManyArgs = { + /** + * Filter which WarmupLogs to delete + */ + where?: Prisma.WarmupLogWhereInput + /** + * Limit how many WarmupLogs to delete. + */ + limit?: number +} + +/** + * WarmupLog.campaign + */ +export type WarmupLog$campaignArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null + where?: Prisma.WarmupCampaignWhereInput +} + +/** + * WarmupLog without action + */ +export type WarmupLogDefaultArgs = { + /** + * Select specific fields to fetch from the WarmupLog + */ + select?: Prisma.WarmupLogSelect | null + /** + * Omit specific fields from the WarmupLog + */ + omit?: Prisma.WarmupLogOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupLogInclude | null +} diff --git a/apps/api/src/generated/prisma/models/Workspace.ts b/apps/api/src/generated/prisma/models/Workspace.ts new file mode 100644 index 0000000..9243b57 --- /dev/null +++ b/apps/api/src/generated/prisma/models/Workspace.ts @@ -0,0 +1,1411 @@ + +/* !!! This is code generated by Prisma. Do not edit directly. !!! */ +/* eslint-disable */ +// biome-ignore-all lint: generated file +// @ts-nocheck +/* + * This file exports the `Workspace` model and its related types. + * + * 🟢 You can import this file directly. + */ +import type * as runtime from "@prisma/client/runtime/client" +import type * as $Enums from "../enums" +import type * as Prisma from "../internal/prismaNamespace" + +/** + * Model Workspace + * + */ +export type WorkspaceModel = runtime.Types.Result.DefaultSelection + +export type AggregateWorkspace = { + _count: WorkspaceCountAggregateOutputType | null + _min: WorkspaceMinAggregateOutputType | null + _max: WorkspaceMaxAggregateOutputType | null +} + +export type WorkspaceMinAggregateOutputType = { + id: string | null + name: string | null + createdAt: Date | null + updatedAt: Date | null +} + +export type WorkspaceMaxAggregateOutputType = { + id: string | null + name: string | null + createdAt: Date | null + updatedAt: Date | null +} + +export type WorkspaceCountAggregateOutputType = { + id: number + name: number + createdAt: number + updatedAt: number + _all: number +} + + +export type WorkspaceMinAggregateInputType = { + id?: true + name?: true + createdAt?: true + updatedAt?: true +} + +export type WorkspaceMaxAggregateInputType = { + id?: true + name?: true + createdAt?: true + updatedAt?: true +} + +export type WorkspaceCountAggregateInputType = { + id?: true + name?: true + createdAt?: true + updatedAt?: true + _all?: true +} + +export type WorkspaceAggregateArgs = { + /** + * Filter which Workspace to aggregate. + */ + where?: Prisma.WorkspaceWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Workspaces to fetch. + */ + orderBy?: Prisma.WorkspaceOrderByWithRelationInput | Prisma.WorkspaceOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the start position + */ + cursor?: Prisma.WorkspaceWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Workspaces from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Workspaces. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Count returned Workspaces + **/ + _count?: true | WorkspaceCountAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the minimum value + **/ + _min?: WorkspaceMinAggregateInputType + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/aggregations Aggregation Docs} + * + * Select which fields to find the maximum value + **/ + _max?: WorkspaceMaxAggregateInputType +} + +export type GetWorkspaceAggregateType = { + [P in keyof T & keyof AggregateWorkspace]: P extends '_count' | 'count' + ? T[P] extends true + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType +} + + + + +export type WorkspaceGroupByArgs = { + where?: Prisma.WorkspaceWhereInput + orderBy?: Prisma.WorkspaceOrderByWithAggregationInput | Prisma.WorkspaceOrderByWithAggregationInput[] + by: Prisma.WorkspaceScalarFieldEnum[] | Prisma.WorkspaceScalarFieldEnum + having?: Prisma.WorkspaceScalarWhereWithAggregatesInput + take?: number + skip?: number + _count?: WorkspaceCountAggregateInputType | true + _min?: WorkspaceMinAggregateInputType + _max?: WorkspaceMaxAggregateInputType +} + +export type WorkspaceGroupByOutputType = { + id: string + name: string + createdAt: Date + updatedAt: Date + _count: WorkspaceCountAggregateOutputType | null + _min: WorkspaceMinAggregateOutputType | null + _max: WorkspaceMaxAggregateOutputType | null +} + +export type GetWorkspaceGroupByPayload = Prisma.PrismaPromise< + Array< + Prisma.PickEnumerable & + { + [P in ((keyof T) & (keyof WorkspaceGroupByOutputType))]: P extends '_count' + ? T[P] extends boolean + ? number + : Prisma.GetScalarType + : Prisma.GetScalarType + } + > + > + + + +export type WorkspaceWhereInput = { + AND?: Prisma.WorkspaceWhereInput | Prisma.WorkspaceWhereInput[] + OR?: Prisma.WorkspaceWhereInput[] + NOT?: Prisma.WorkspaceWhereInput | Prisma.WorkspaceWhereInput[] + id?: Prisma.StringFilter<"Workspace"> | string + name?: Prisma.StringFilter<"Workspace"> | string + createdAt?: Prisma.DateTimeFilter<"Workspace"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Workspace"> | Date | string + inboxes?: Prisma.InboxListRelationFilter + campaigns?: Prisma.WarmupCampaignListRelationFilter +} + +export type WorkspaceOrderByWithRelationInput = { + id?: Prisma.SortOrder + name?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + inboxes?: Prisma.InboxOrderByRelationAggregateInput + campaigns?: Prisma.WarmupCampaignOrderByRelationAggregateInput +} + +export type WorkspaceWhereUniqueInput = Prisma.AtLeast<{ + id?: string + name?: string + AND?: Prisma.WorkspaceWhereInput | Prisma.WorkspaceWhereInput[] + OR?: Prisma.WorkspaceWhereInput[] + NOT?: Prisma.WorkspaceWhereInput | Prisma.WorkspaceWhereInput[] + createdAt?: Prisma.DateTimeFilter<"Workspace"> | Date | string + updatedAt?: Prisma.DateTimeFilter<"Workspace"> | Date | string + inboxes?: Prisma.InboxListRelationFilter + campaigns?: Prisma.WarmupCampaignListRelationFilter +}, "id" | "name"> + +export type WorkspaceOrderByWithAggregationInput = { + id?: Prisma.SortOrder + name?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder + _count?: Prisma.WorkspaceCountOrderByAggregateInput + _max?: Prisma.WorkspaceMaxOrderByAggregateInput + _min?: Prisma.WorkspaceMinOrderByAggregateInput +} + +export type WorkspaceScalarWhereWithAggregatesInput = { + AND?: Prisma.WorkspaceScalarWhereWithAggregatesInput | Prisma.WorkspaceScalarWhereWithAggregatesInput[] + OR?: Prisma.WorkspaceScalarWhereWithAggregatesInput[] + NOT?: Prisma.WorkspaceScalarWhereWithAggregatesInput | Prisma.WorkspaceScalarWhereWithAggregatesInput[] + id?: Prisma.StringWithAggregatesFilter<"Workspace"> | string + name?: Prisma.StringWithAggregatesFilter<"Workspace"> | string + createdAt?: Prisma.DateTimeWithAggregatesFilter<"Workspace"> | Date | string + updatedAt?: Prisma.DateTimeWithAggregatesFilter<"Workspace"> | Date | string +} + +export type WorkspaceCreateInput = { + id?: string + name: string + createdAt?: Date | string + updatedAt?: Date | string + inboxes?: Prisma.InboxCreateNestedManyWithoutWorkspaceInput + campaigns?: Prisma.WarmupCampaignCreateNestedManyWithoutWorkspaceInput +} + +export type WorkspaceUncheckedCreateInput = { + id?: string + name: string + createdAt?: Date | string + updatedAt?: Date | string + inboxes?: Prisma.InboxUncheckedCreateNestedManyWithoutWorkspaceInput + campaigns?: Prisma.WarmupCampaignUncheckedCreateNestedManyWithoutWorkspaceInput +} + +export type WorkspaceUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + inboxes?: Prisma.InboxUpdateManyWithoutWorkspaceNestedInput + campaigns?: Prisma.WarmupCampaignUpdateManyWithoutWorkspaceNestedInput +} + +export type WorkspaceUncheckedUpdateInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + inboxes?: Prisma.InboxUncheckedUpdateManyWithoutWorkspaceNestedInput + campaigns?: Prisma.WarmupCampaignUncheckedUpdateManyWithoutWorkspaceNestedInput +} + +export type WorkspaceCreateManyInput = { + id?: string + name: string + createdAt?: Date | string + updatedAt?: Date | string +} + +export type WorkspaceUpdateManyMutationInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WorkspaceUncheckedUpdateManyInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string +} + +export type WorkspaceCountOrderByAggregateInput = { + id?: Prisma.SortOrder + name?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type WorkspaceMaxOrderByAggregateInput = { + id?: Prisma.SortOrder + name?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type WorkspaceMinOrderByAggregateInput = { + id?: Prisma.SortOrder + name?: Prisma.SortOrder + createdAt?: Prisma.SortOrder + updatedAt?: Prisma.SortOrder +} + +export type WorkspaceScalarRelationFilter = { + is?: Prisma.WorkspaceWhereInput + isNot?: Prisma.WorkspaceWhereInput +} + +export type StringFieldUpdateOperationsInput = { + set?: string +} + +export type DateTimeFieldUpdateOperationsInput = { + set?: Date | string +} + +export type WorkspaceCreateNestedOneWithoutInboxesInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WorkspaceCreateOrConnectWithoutInboxesInput + connect?: Prisma.WorkspaceWhereUniqueInput +} + +export type WorkspaceUpdateOneRequiredWithoutInboxesNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WorkspaceCreateOrConnectWithoutInboxesInput + upsert?: Prisma.WorkspaceUpsertWithoutInboxesInput + connect?: Prisma.WorkspaceWhereUniqueInput + update?: Prisma.XOR, Prisma.WorkspaceUncheckedUpdateWithoutInboxesInput> +} + +export type WorkspaceCreateNestedOneWithoutCampaignsInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WorkspaceCreateOrConnectWithoutCampaignsInput + connect?: Prisma.WorkspaceWhereUniqueInput +} + +export type WorkspaceUpdateOneRequiredWithoutCampaignsNestedInput = { + create?: Prisma.XOR + connectOrCreate?: Prisma.WorkspaceCreateOrConnectWithoutCampaignsInput + upsert?: Prisma.WorkspaceUpsertWithoutCampaignsInput + connect?: Prisma.WorkspaceWhereUniqueInput + update?: Prisma.XOR, Prisma.WorkspaceUncheckedUpdateWithoutCampaignsInput> +} + +export type WorkspaceCreateWithoutInboxesInput = { + id?: string + name: string + createdAt?: Date | string + updatedAt?: Date | string + campaigns?: Prisma.WarmupCampaignCreateNestedManyWithoutWorkspaceInput +} + +export type WorkspaceUncheckedCreateWithoutInboxesInput = { + id?: string + name: string + createdAt?: Date | string + updatedAt?: Date | string + campaigns?: Prisma.WarmupCampaignUncheckedCreateNestedManyWithoutWorkspaceInput +} + +export type WorkspaceCreateOrConnectWithoutInboxesInput = { + where: Prisma.WorkspaceWhereUniqueInput + create: Prisma.XOR +} + +export type WorkspaceUpsertWithoutInboxesInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.WorkspaceWhereInput +} + +export type WorkspaceUpdateToOneWithWhereWithoutInboxesInput = { + where?: Prisma.WorkspaceWhereInput + data: Prisma.XOR +} + +export type WorkspaceUpdateWithoutInboxesInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + campaigns?: Prisma.WarmupCampaignUpdateManyWithoutWorkspaceNestedInput +} + +export type WorkspaceUncheckedUpdateWithoutInboxesInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + campaigns?: Prisma.WarmupCampaignUncheckedUpdateManyWithoutWorkspaceNestedInput +} + +export type WorkspaceCreateWithoutCampaignsInput = { + id?: string + name: string + createdAt?: Date | string + updatedAt?: Date | string + inboxes?: Prisma.InboxCreateNestedManyWithoutWorkspaceInput +} + +export type WorkspaceUncheckedCreateWithoutCampaignsInput = { + id?: string + name: string + createdAt?: Date | string + updatedAt?: Date | string + inboxes?: Prisma.InboxUncheckedCreateNestedManyWithoutWorkspaceInput +} + +export type WorkspaceCreateOrConnectWithoutCampaignsInput = { + where: Prisma.WorkspaceWhereUniqueInput + create: Prisma.XOR +} + +export type WorkspaceUpsertWithoutCampaignsInput = { + update: Prisma.XOR + create: Prisma.XOR + where?: Prisma.WorkspaceWhereInput +} + +export type WorkspaceUpdateToOneWithWhereWithoutCampaignsInput = { + where?: Prisma.WorkspaceWhereInput + data: Prisma.XOR +} + +export type WorkspaceUpdateWithoutCampaignsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + inboxes?: Prisma.InboxUpdateManyWithoutWorkspaceNestedInput +} + +export type WorkspaceUncheckedUpdateWithoutCampaignsInput = { + id?: Prisma.StringFieldUpdateOperationsInput | string + name?: Prisma.StringFieldUpdateOperationsInput | string + createdAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + updatedAt?: Prisma.DateTimeFieldUpdateOperationsInput | Date | string + inboxes?: Prisma.InboxUncheckedUpdateManyWithoutWorkspaceNestedInput +} + + +/** + * Count Type WorkspaceCountOutputType + */ + +export type WorkspaceCountOutputType = { + inboxes: number + campaigns: number +} + +export type WorkspaceCountOutputTypeSelect = { + inboxes?: boolean | WorkspaceCountOutputTypeCountInboxesArgs + campaigns?: boolean | WorkspaceCountOutputTypeCountCampaignsArgs +} + +/** + * WorkspaceCountOutputType without action + */ +export type WorkspaceCountOutputTypeDefaultArgs = { + /** + * Select specific fields to fetch from the WorkspaceCountOutputType + */ + select?: Prisma.WorkspaceCountOutputTypeSelect | null +} + +/** + * WorkspaceCountOutputType without action + */ +export type WorkspaceCountOutputTypeCountInboxesArgs = { + where?: Prisma.InboxWhereInput +} + +/** + * WorkspaceCountOutputType without action + */ +export type WorkspaceCountOutputTypeCountCampaignsArgs = { + where?: Prisma.WarmupCampaignWhereInput +} + + +export type WorkspaceSelect = runtime.Types.Extensions.GetSelect<{ + id?: boolean + name?: boolean + createdAt?: boolean + updatedAt?: boolean + inboxes?: boolean | Prisma.Workspace$inboxesArgs + campaigns?: boolean | Prisma.Workspace$campaignsArgs + _count?: boolean | Prisma.WorkspaceCountOutputTypeDefaultArgs +}, ExtArgs["result"]["workspace"]> + +export type WorkspaceSelectCreateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + name?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["workspace"]> + +export type WorkspaceSelectUpdateManyAndReturn = runtime.Types.Extensions.GetSelect<{ + id?: boolean + name?: boolean + createdAt?: boolean + updatedAt?: boolean +}, ExtArgs["result"]["workspace"]> + +export type WorkspaceSelectScalar = { + id?: boolean + name?: boolean + createdAt?: boolean + updatedAt?: boolean +} + +export type WorkspaceOmit = runtime.Types.Extensions.GetOmit<"id" | "name" | "createdAt" | "updatedAt", ExtArgs["result"]["workspace"]> +export type WorkspaceInclude = { + inboxes?: boolean | Prisma.Workspace$inboxesArgs + campaigns?: boolean | Prisma.Workspace$campaignsArgs + _count?: boolean | Prisma.WorkspaceCountOutputTypeDefaultArgs +} +export type WorkspaceIncludeCreateManyAndReturn = {} +export type WorkspaceIncludeUpdateManyAndReturn = {} + +export type $WorkspacePayload = { + name: "Workspace" + objects: { + inboxes: Prisma.$InboxPayload[] + campaigns: Prisma.$WarmupCampaignPayload[] + } + scalars: runtime.Types.Extensions.GetPayloadResult<{ + id: string + name: string + createdAt: Date + updatedAt: Date + }, ExtArgs["result"]["workspace"]> + composites: {} +} + +export type WorkspaceGetPayload = runtime.Types.Result.GetResult + +export type WorkspaceCountArgs = + Omit & { + select?: WorkspaceCountAggregateInputType | true + } + +export interface WorkspaceDelegate { + [K: symbol]: { types: Prisma.TypeMap['model']['Workspace'], meta: { name: 'Workspace' } } + /** + * Find zero or one Workspace that matches the filter. + * @param {WorkspaceFindUniqueArgs} args - Arguments to find a Workspace + * @example + * // Get one Workspace + * const workspace = await prisma.workspace.findUnique({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUnique(args: Prisma.SelectSubset>): Prisma.Prisma__WorkspaceClient, T, "findUnique", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find one Workspace that matches the filter or throw an error with `error.code='P2025'` + * if no matches were found. + * @param {WorkspaceFindUniqueOrThrowArgs} args - Arguments to find a Workspace + * @example + * // Get one Workspace + * const workspace = await prisma.workspace.findUniqueOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findUniqueOrThrow(args: Prisma.SelectSubset>): Prisma.Prisma__WorkspaceClient, T, "findUniqueOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Workspace that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WorkspaceFindFirstArgs} args - Arguments to find a Workspace + * @example + * // Get one Workspace + * const workspace = await prisma.workspace.findFirst({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirst(args?: Prisma.SelectSubset>): Prisma.Prisma__WorkspaceClient, T, "findFirst", GlobalOmitOptions> | null, null, ExtArgs, GlobalOmitOptions> + + /** + * Find the first Workspace that matches the filter or + * throw `PrismaKnownClientError` with `P2025` code if no matches were found. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WorkspaceFindFirstOrThrowArgs} args - Arguments to find a Workspace + * @example + * // Get one Workspace + * const workspace = await prisma.workspace.findFirstOrThrow({ + * where: { + * // ... provide filter here + * } + * }) + */ + findFirstOrThrow(args?: Prisma.SelectSubset>): Prisma.Prisma__WorkspaceClient, T, "findFirstOrThrow", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Find zero or more Workspaces that matches the filter. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WorkspaceFindManyArgs} args - Arguments to filter and select certain fields only. + * @example + * // Get all Workspaces + * const workspaces = await prisma.workspace.findMany() + * + * // Get first 10 Workspaces + * const workspaces = await prisma.workspace.findMany({ take: 10 }) + * + * // Only select the `id` + * const workspaceWithIdOnly = await prisma.workspace.findMany({ select: { id: true } }) + * + */ + findMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions>> + + /** + * Create a Workspace. + * @param {WorkspaceCreateArgs} args - Arguments to create a Workspace. + * @example + * // Create one Workspace + * const Workspace = await prisma.workspace.create({ + * data: { + * // ... data to create a Workspace + * } + * }) + * + */ + create(args: Prisma.SelectSubset>): Prisma.Prisma__WorkspaceClient, T, "create", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Create many Workspaces. + * @param {WorkspaceCreateManyArgs} args - Arguments to create many Workspaces. + * @example + * // Create many Workspaces + * const workspace = await prisma.workspace.createMany({ + * data: [ + * // ... provide data here + * ] + * }) + * + */ + createMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Create many Workspaces and returns the data saved in the database. + * @param {WorkspaceCreateManyAndReturnArgs} args - Arguments to create many Workspaces. + * @example + * // Create many Workspaces + * const workspace = await prisma.workspace.createManyAndReturn({ + * data: [ + * // ... provide data here + * ] + * }) + * + * // Create many Workspaces and only return the `id` + * const workspaceWithIdOnly = await prisma.workspace.createManyAndReturn({ + * select: { id: true }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + createManyAndReturn(args?: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "createManyAndReturn", GlobalOmitOptions>> + + /** + * Delete a Workspace. + * @param {WorkspaceDeleteArgs} args - Arguments to delete one Workspace. + * @example + * // Delete one Workspace + * const Workspace = await prisma.workspace.delete({ + * where: { + * // ... filter to delete one Workspace + * } + * }) + * + */ + delete(args: Prisma.SelectSubset>): Prisma.Prisma__WorkspaceClient, T, "delete", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Update one Workspace. + * @param {WorkspaceUpdateArgs} args - Arguments to update one Workspace. + * @example + * // Update one Workspace + * const workspace = await prisma.workspace.update({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + update(args: Prisma.SelectSubset>): Prisma.Prisma__WorkspaceClient, T, "update", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + /** + * Delete zero or more Workspaces. + * @param {WorkspaceDeleteManyArgs} args - Arguments to filter Workspaces to delete. + * @example + * // Delete a few Workspaces + * const { count } = await prisma.workspace.deleteMany({ + * where: { + * // ... provide filter here + * } + * }) + * + */ + deleteMany(args?: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Workspaces. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WorkspaceUpdateManyArgs} args - Arguments to update one or more rows. + * @example + * // Update many Workspaces + * const workspace = await prisma.workspace.updateMany({ + * where: { + * // ... provide filter here + * }, + * data: { + * // ... provide data here + * } + * }) + * + */ + updateMany(args: Prisma.SelectSubset>): Prisma.PrismaPromise + + /** + * Update zero or more Workspaces and returns the data updated in the database. + * @param {WorkspaceUpdateManyAndReturnArgs} args - Arguments to update many Workspaces. + * @example + * // Update many Workspaces + * const workspace = await prisma.workspace.updateManyAndReturn({ + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * + * // Update zero or more Workspaces and only return the `id` + * const workspaceWithIdOnly = await prisma.workspace.updateManyAndReturn({ + * select: { id: true }, + * where: { + * // ... provide filter here + * }, + * data: [ + * // ... provide data here + * ] + * }) + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * + */ + updateManyAndReturn(args: Prisma.SelectSubset>): Prisma.PrismaPromise, T, "updateManyAndReturn", GlobalOmitOptions>> + + /** + * Create or update one Workspace. + * @param {WorkspaceUpsertArgs} args - Arguments to update or create a Workspace. + * @example + * // Update or create a Workspace + * const workspace = await prisma.workspace.upsert({ + * create: { + * // ... data to create a Workspace + * }, + * update: { + * // ... in case it already exists, update + * }, + * where: { + * // ... the filter for the Workspace we want to update + * } + * }) + */ + upsert(args: Prisma.SelectSubset>): Prisma.Prisma__WorkspaceClient, T, "upsert", GlobalOmitOptions>, never, ExtArgs, GlobalOmitOptions> + + + /** + * Count the number of Workspaces. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WorkspaceCountArgs} args - Arguments to filter Workspaces to count. + * @example + * // Count the number of Workspaces + * const count = await prisma.workspace.count({ + * where: { + * // ... the filter for the Workspaces we want to count + * } + * }) + **/ + count( + args?: Prisma.Subset, + ): Prisma.PrismaPromise< + T extends runtime.Types.Utils.Record<'select', any> + ? T['select'] extends true + ? number + : Prisma.GetScalarType + : number + > + + /** + * Allows you to perform aggregations operations on a Workspace. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WorkspaceAggregateArgs} args - Select which aggregations you would like to apply and on what fields. + * @example + * // Ordered by age ascending + * // Where email contains prisma.io + * // Limited to the 10 users + * const aggregations = await prisma.user.aggregate({ + * _avg: { + * age: true, + * }, + * where: { + * email: { + * contains: "prisma.io", + * }, + * }, + * orderBy: { + * age: "asc", + * }, + * take: 10, + * }) + **/ + aggregate(args: Prisma.Subset): Prisma.PrismaPromise> + + /** + * Group by Workspace. + * Note, that providing `undefined` is treated as the value not being there. + * Read more here: https://pris.ly/d/null-undefined + * @param {WorkspaceGroupByArgs} args - Group by arguments. + * @example + * // Group by city, order by createdAt, get count + * const result = await prisma.user.groupBy({ + * by: ['city', 'createdAt'], + * orderBy: { + * createdAt: true + * }, + * _count: { + * _all: true + * }, + * }) + * + **/ + groupBy< + T extends WorkspaceGroupByArgs, + HasSelectOrTake extends Prisma.Or< + Prisma.Extends<'skip', Prisma.Keys>, + Prisma.Extends<'take', Prisma.Keys> + >, + OrderByArg extends Prisma.True extends HasSelectOrTake + ? { orderBy: WorkspaceGroupByArgs['orderBy'] } + : { orderBy?: WorkspaceGroupByArgs['orderBy'] }, + OrderFields extends Prisma.ExcludeUnderscoreKeys>>, + ByFields extends Prisma.MaybeTupleToUnion, + ByValid extends Prisma.Has, + HavingFields extends Prisma.GetHavingFields, + HavingValid extends Prisma.Has, + ByEmpty extends T['by'] extends never[] ? Prisma.True : Prisma.False, + InputErrors extends ByEmpty extends Prisma.True + ? `Error: "by" must not be empty.` + : HavingValid extends Prisma.False + ? { + [P in HavingFields]: P extends ByFields + ? never + : P extends string + ? `Error: Field "${P}" used in "having" needs to be provided in "by".` + : [ + Error, + 'Field ', + P, + ` in "having" needs to be provided in "by"`, + ] + }[HavingFields] + : 'take' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "take", you also need to provide "orderBy"' + : 'skip' extends Prisma.Keys + ? 'orderBy' extends Prisma.Keys + ? ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + : 'Error: If you provide "skip", you also need to provide "orderBy"' + : ByValid extends Prisma.True + ? {} + : { + [P in OrderFields]: P extends ByFields + ? never + : `Error: Field "${P}" in "orderBy" needs to be provided in "by"` + }[OrderFields] + >(args: Prisma.SubsetIntersection & InputErrors): {} extends InputErrors ? GetWorkspaceGroupByPayload : Prisma.PrismaPromise +/** + * Fields of the Workspace model + */ +readonly fields: WorkspaceFieldRefs; +} + +/** + * The delegate class that acts as a "Promise-like" for Workspace. + * Why is this prefixed with `Prisma__`? + * Because we want to prevent naming conflicts as mentioned in + * https://github.com/prisma/prisma-client-js/issues/707 + */ +export interface Prisma__WorkspaceClient extends Prisma.PrismaPromise { + readonly [Symbol.toStringTag]: "PrismaPromise" + inboxes = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + campaigns = {}>(args?: Prisma.Subset>): Prisma.PrismaPromise, T, "findMany", GlobalOmitOptions> | Null> + /** + * Attaches callbacks for the resolution and/or rejection of the Promise. + * @param onfulfilled The callback to execute when the Promise is resolved. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of which ever callback is executed. + */ + then(onfulfilled?: ((value: T) => TResult1 | PromiseLike) | undefined | null, onrejected?: ((reason: any) => TResult2 | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback for only the rejection of the Promise. + * @param onrejected The callback to execute when the Promise is rejected. + * @returns A Promise for the completion of the callback. + */ + catch(onrejected?: ((reason: any) => TResult | PromiseLike) | undefined | null): runtime.Types.Utils.JsPromise + /** + * Attaches a callback that is invoked when the Promise is settled (fulfilled or rejected). The + * resolved value cannot be modified from the callback. + * @param onfinally The callback to execute when the Promise is settled (fulfilled or rejected). + * @returns A Promise for the completion of the callback. + */ + finally(onfinally?: (() => void) | undefined | null): runtime.Types.Utils.JsPromise +} + + + + +/** + * Fields of the Workspace model + */ +export interface WorkspaceFieldRefs { + readonly id: Prisma.FieldRef<"Workspace", 'String'> + readonly name: Prisma.FieldRef<"Workspace", 'String'> + readonly createdAt: Prisma.FieldRef<"Workspace", 'DateTime'> + readonly updatedAt: Prisma.FieldRef<"Workspace", 'DateTime'> +} + + +// Custom InputTypes +/** + * Workspace findUnique + */ +export type WorkspaceFindUniqueArgs = { + /** + * Select specific fields to fetch from the Workspace + */ + select?: Prisma.WorkspaceSelect | null + /** + * Omit specific fields from the Workspace + */ + omit?: Prisma.WorkspaceOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WorkspaceInclude | null + /** + * Filter, which Workspace to fetch. + */ + where: Prisma.WorkspaceWhereUniqueInput +} + +/** + * Workspace findUniqueOrThrow + */ +export type WorkspaceFindUniqueOrThrowArgs = { + /** + * Select specific fields to fetch from the Workspace + */ + select?: Prisma.WorkspaceSelect | null + /** + * Omit specific fields from the Workspace + */ + omit?: Prisma.WorkspaceOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WorkspaceInclude | null + /** + * Filter, which Workspace to fetch. + */ + where: Prisma.WorkspaceWhereUniqueInput +} + +/** + * Workspace findFirst + */ +export type WorkspaceFindFirstArgs = { + /** + * Select specific fields to fetch from the Workspace + */ + select?: Prisma.WorkspaceSelect | null + /** + * Omit specific fields from the Workspace + */ + omit?: Prisma.WorkspaceOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WorkspaceInclude | null + /** + * Filter, which Workspace to fetch. + */ + where?: Prisma.WorkspaceWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Workspaces to fetch. + */ + orderBy?: Prisma.WorkspaceOrderByWithRelationInput | Prisma.WorkspaceOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Workspaces. + */ + cursor?: Prisma.WorkspaceWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Workspaces from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Workspaces. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Workspaces. + */ + distinct?: Prisma.WorkspaceScalarFieldEnum | Prisma.WorkspaceScalarFieldEnum[] +} + +/** + * Workspace findFirstOrThrow + */ +export type WorkspaceFindFirstOrThrowArgs = { + /** + * Select specific fields to fetch from the Workspace + */ + select?: Prisma.WorkspaceSelect | null + /** + * Omit specific fields from the Workspace + */ + omit?: Prisma.WorkspaceOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WorkspaceInclude | null + /** + * Filter, which Workspace to fetch. + */ + where?: Prisma.WorkspaceWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Workspaces to fetch. + */ + orderBy?: Prisma.WorkspaceOrderByWithRelationInput | Prisma.WorkspaceOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for searching for Workspaces. + */ + cursor?: Prisma.WorkspaceWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Workspaces from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Workspaces. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Workspaces. + */ + distinct?: Prisma.WorkspaceScalarFieldEnum | Prisma.WorkspaceScalarFieldEnum[] +} + +/** + * Workspace findMany + */ +export type WorkspaceFindManyArgs = { + /** + * Select specific fields to fetch from the Workspace + */ + select?: Prisma.WorkspaceSelect | null + /** + * Omit specific fields from the Workspace + */ + omit?: Prisma.WorkspaceOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WorkspaceInclude | null + /** + * Filter, which Workspaces to fetch. + */ + where?: Prisma.WorkspaceWhereInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/sorting Sorting Docs} + * + * Determine the order of Workspaces to fetch. + */ + orderBy?: Prisma.WorkspaceOrderByWithRelationInput | Prisma.WorkspaceOrderByWithRelationInput[] + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination#cursor-based-pagination Cursor Docs} + * + * Sets the position for listing Workspaces. + */ + cursor?: Prisma.WorkspaceWhereUniqueInput + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Take `±n` Workspaces from the position of the cursor. + */ + take?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/pagination Pagination Docs} + * + * Skip the first `n` Workspaces. + */ + skip?: number + /** + * {@link https://www.prisma.io/docs/concepts/components/prisma-client/distinct Distinct Docs} + * + * Filter by unique combinations of Workspaces. + */ + distinct?: Prisma.WorkspaceScalarFieldEnum | Prisma.WorkspaceScalarFieldEnum[] +} + +/** + * Workspace create + */ +export type WorkspaceCreateArgs = { + /** + * Select specific fields to fetch from the Workspace + */ + select?: Prisma.WorkspaceSelect | null + /** + * Omit specific fields from the Workspace + */ + omit?: Prisma.WorkspaceOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WorkspaceInclude | null + /** + * The data needed to create a Workspace. + */ + data: Prisma.XOR +} + +/** + * Workspace createMany + */ +export type WorkspaceCreateManyArgs = { + /** + * The data used to create many Workspaces. + */ + data: Prisma.WorkspaceCreateManyInput | Prisma.WorkspaceCreateManyInput[] +} + +/** + * Workspace createManyAndReturn + */ +export type WorkspaceCreateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Workspace + */ + select?: Prisma.WorkspaceSelectCreateManyAndReturn | null + /** + * Omit specific fields from the Workspace + */ + omit?: Prisma.WorkspaceOmit | null + /** + * The data used to create many Workspaces. + */ + data: Prisma.WorkspaceCreateManyInput | Prisma.WorkspaceCreateManyInput[] +} + +/** + * Workspace update + */ +export type WorkspaceUpdateArgs = { + /** + * Select specific fields to fetch from the Workspace + */ + select?: Prisma.WorkspaceSelect | null + /** + * Omit specific fields from the Workspace + */ + omit?: Prisma.WorkspaceOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WorkspaceInclude | null + /** + * The data needed to update a Workspace. + */ + data: Prisma.XOR + /** + * Choose, which Workspace to update. + */ + where: Prisma.WorkspaceWhereUniqueInput +} + +/** + * Workspace updateMany + */ +export type WorkspaceUpdateManyArgs = { + /** + * The data used to update Workspaces. + */ + data: Prisma.XOR + /** + * Filter which Workspaces to update + */ + where?: Prisma.WorkspaceWhereInput + /** + * Limit how many Workspaces to update. + */ + limit?: number +} + +/** + * Workspace updateManyAndReturn + */ +export type WorkspaceUpdateManyAndReturnArgs = { + /** + * Select specific fields to fetch from the Workspace + */ + select?: Prisma.WorkspaceSelectUpdateManyAndReturn | null + /** + * Omit specific fields from the Workspace + */ + omit?: Prisma.WorkspaceOmit | null + /** + * The data used to update Workspaces. + */ + data: Prisma.XOR + /** + * Filter which Workspaces to update + */ + where?: Prisma.WorkspaceWhereInput + /** + * Limit how many Workspaces to update. + */ + limit?: number +} + +/** + * Workspace upsert + */ +export type WorkspaceUpsertArgs = { + /** + * Select specific fields to fetch from the Workspace + */ + select?: Prisma.WorkspaceSelect | null + /** + * Omit specific fields from the Workspace + */ + omit?: Prisma.WorkspaceOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WorkspaceInclude | null + /** + * The filter to search for the Workspace to update in case it exists. + */ + where: Prisma.WorkspaceWhereUniqueInput + /** + * In case the Workspace found by the `where` argument doesn't exist, create a new Workspace with this data. + */ + create: Prisma.XOR + /** + * In case the Workspace was found with the provided `where` argument, update it with this data. + */ + update: Prisma.XOR +} + +/** + * Workspace delete + */ +export type WorkspaceDeleteArgs = { + /** + * Select specific fields to fetch from the Workspace + */ + select?: Prisma.WorkspaceSelect | null + /** + * Omit specific fields from the Workspace + */ + omit?: Prisma.WorkspaceOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WorkspaceInclude | null + /** + * Filter which Workspace to delete. + */ + where: Prisma.WorkspaceWhereUniqueInput +} + +/** + * Workspace deleteMany + */ +export type WorkspaceDeleteManyArgs = { + /** + * Filter which Workspaces to delete + */ + where?: Prisma.WorkspaceWhereInput + /** + * Limit how many Workspaces to delete. + */ + limit?: number +} + +/** + * Workspace.inboxes + */ +export type Workspace$inboxesArgs = { + /** + * Select specific fields to fetch from the Inbox + */ + select?: Prisma.InboxSelect | null + /** + * Omit specific fields from the Inbox + */ + omit?: Prisma.InboxOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.InboxInclude | null + where?: Prisma.InboxWhereInput + orderBy?: Prisma.InboxOrderByWithRelationInput | Prisma.InboxOrderByWithRelationInput[] + cursor?: Prisma.InboxWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.InboxScalarFieldEnum | Prisma.InboxScalarFieldEnum[] +} + +/** + * Workspace.campaigns + */ +export type Workspace$campaignsArgs = { + /** + * Select specific fields to fetch from the WarmupCampaign + */ + select?: Prisma.WarmupCampaignSelect | null + /** + * Omit specific fields from the WarmupCampaign + */ + omit?: Prisma.WarmupCampaignOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WarmupCampaignInclude | null + where?: Prisma.WarmupCampaignWhereInput + orderBy?: Prisma.WarmupCampaignOrderByWithRelationInput | Prisma.WarmupCampaignOrderByWithRelationInput[] + cursor?: Prisma.WarmupCampaignWhereUniqueInput + take?: number + skip?: number + distinct?: Prisma.WarmupCampaignScalarFieldEnum | Prisma.WarmupCampaignScalarFieldEnum[] +} + +/** + * Workspace without action + */ +export type WorkspaceDefaultArgs = { + /** + * Select specific fields to fetch from the Workspace + */ + select?: Prisma.WorkspaceSelect | null + /** + * Omit specific fields from the Workspace + */ + omit?: Prisma.WorkspaceOmit | null + /** + * Choose, which related nodes to fetch as well + */ + include?: Prisma.WorkspaceInclude | null +} diff --git a/apps/api/src/index.ts b/apps/api/src/index.ts new file mode 100644 index 0000000..55ea283 --- /dev/null +++ b/apps/api/src/index.ts @@ -0,0 +1,82 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import cors from 'cors'; +import express from 'express'; +import { config } from './config'; +import { errorHandler } from './middleware/errorHandler'; +import { apiKeyAuth } from './middleware/apiKeyAuth'; +import inboxesRouter from './routes/inboxes.routes'; +import campaignsRouter from './routes/campaigns.routes'; +import recipesRouter from './routes/recipes.routes'; +import aiRouter from './routes/ai.routes'; +import logsRouter from './routes/logs.routes'; +import settingsRouter from './routes/settings.routes'; +import jobsRouter from './routes/jobs.routes'; +import integrationsRouter, { + googleOAuthCallback, + microsoftOAuthCallback, +} from './routes/integrations.routes'; +import statsRouter, { workspaceRouter } from './routes/stats.routes'; +import { startScheduler } from './jobs/scheduler'; +import { logger } from './lib/logger'; + +const app = express(); + +if (config.NODE_ENV === 'development') { + app.use( + cors({ + origin: ['http://localhost:5173', 'http://127.0.0.1:5173'], + credentials: true, + }), + ); +} + +app.use(express.json()); + +app.get('/api/integrations/google/callback', googleOAuthCallback); +app.get('/api/integrations/microsoft/callback', microsoftOAuthCallback); + +app.use('/api', apiKeyAuth); + +app.get('/health', (_req, res) => { + res.json({ status: 'ok' }); +}); + +app.use('/api/inboxes', inboxesRouter); +app.use('/api/mailboxes', inboxesRouter); +app.use('/api/campaigns', campaignsRouter); +app.use('/api/campaigns', statsRouter); +app.use('/api/workspace', workspaceRouter); +app.use('/api/recipes', recipesRouter); +app.use('/api/ai', aiRouter); +app.use('/api/logs', logsRouter); +app.use('/api/settings', settingsRouter); +app.use('/api/jobs', jobsRouter); +app.use('/api/integrations', integrationsRouter); + +const webDist = path.resolve(__dirname, '../../web/dist'); +if (fs.existsSync(webDist)) { + app.use(express.static(webDist)); + app.get('*', (req, res, next) => { + if (req.path.startsWith('/api') || req.path === '/health') { + next(); + return; + } + res.sendFile(path.join(webDist, 'index.html')); + }); +} + +app.use(errorHandler); + +async function main(): Promise { + await startScheduler(); + + app.listen(config.PORT, () => { + logger.info({ port: config.PORT, webDist: fs.existsSync(webDist) }, 'Warmbox server started'); + }); +} + +main().catch((err) => { + logger.error({ err }, 'Failed to start server'); + process.exit(1); +}); diff --git a/apps/api/src/jobs/campaign-dispatcher.job.ts b/apps/api/src/jobs/campaign-dispatcher.job.ts new file mode 100644 index 0000000..48be468 --- /dev/null +++ b/apps/api/src/jobs/campaign-dispatcher.job.ts @@ -0,0 +1,220 @@ +import type { CampaignSatellite, Inbox } from '../generated/prisma/client'; +import { CampaignStatus, InboxStatus, WarmupStatus, prisma } from '../lib/prisma'; +import { withInboxGuard } from '../lib/inboxGuard'; +import { logger } from '../lib/logger'; +import { isWithinSendWindow } from '../lib/send-window'; +import * as aiService from '../services/ai.service'; +import { syncCampaignDay } from '../services/campaign.service'; +import * as emailService from '../services/email.service'; +import { + claimDueSendJob, + completeSendJob, + enqueueSendJob, +} from '../services/job-queue.service'; + +const MIN_JITTER_MS = 45 * 60 * 1000; + +type SatelliteWithMailbox = CampaignSatellite & { satelliteMailbox: Inbox }; + +function pickWeightedSatellite(satellites: SatelliteWithMailbox[]): Inbox { + const totalWeight = satellites.reduce((sum, sat) => sum + sat.weight, 0); + let remaining = Math.random() * totalWeight; + + for (const satellite of satellites) { + remaining -= satellite.weight; + if (remaining <= 0) { + return satellite.satelliteMailbox; + } + } + + return satellites[satellites.length - 1]!.satelliteMailbox; +} + +function hasElapsedMinJitter(lastSendAt: Date | null): boolean { + if (!lastSendAt) return true; + return Date.now() - lastSendAt.getTime() >= MIN_JITTER_MS; +} + +async function executeCampaignSend( + campaignId: string, + jobId: string | null, +): Promise { + const campaign = await prisma.warmupCampaign.findUnique({ + where: { id: campaignId }, + include: { + primaryMailbox: true, + satellites: { + where: { active: true }, + include: { satelliteMailbox: true }, + }, + }, + }); + + if (!campaign) return; + + const activeSatellites = campaign.satellites.filter( + (sat) => sat.satelliteMailbox.status === InboxStatus.active, + ); + if (activeSatellites.length === 0) { + logger.warn({ campaignId }, 'Campaign has no active satellites'); + if (jobId) await completeSendJob(jobId, false, 'No active satellites'); + return; + } + + const schedule = await prisma.dailySchedule.findUnique({ + where: { + campaignId_dayIndex: { + campaignId: campaign.id, + dayIndex: campaign.currentDay, + }, + }, + }); + + if (!schedule || schedule.actualSends >= schedule.plannedSends) { + if (jobId) await completeSendJob(jobId, true, 'Daily send quota reached'); + return; + } + + const satellite = pickWeightedSatellite(activeSatellites); + const primary = campaign.primaryMailbox; + + if (primary.status !== InboxStatus.active) { + logger.warn({ campaignId }, 'Primary mailbox is not active'); + if (jobId) await completeSendJob(jobId, false, 'Primary mailbox inactive'); + return; + } + + await withInboxGuard(satellite, async () => { + const { subject, body, validationResult } = + await aiService.generateInitialEmailWithValidation(primary.industry, { + senderName: satellite.senderName, + companyName: satellite.companyName, + }); + + const { messageId } = await emailService.sendMail(satellite, { + to: primary.email, + subject, + body, + }); + + await prisma.$transaction(async (tx) => { + const current = await tx.dailySchedule.findUnique({ + where: { id: schedule.id }, + }); + if (!current || current.actualSends >= current.plannedSends) { + return; + } + + await tx.warmupLog.create({ + data: { + campaignId: campaign.id, + senderId: satellite.id, + receiverId: primary.id, + messageId, + subject, + body, + status: WarmupStatus.sent, + validationResult: JSON.stringify(validationResult), + }, + }); + + await tx.dailySchedule.update({ + where: { id: schedule.id }, + data: { actualSends: { increment: 1 } }, + }); + + await tx.warmupCampaign.update({ + where: { id: campaign.id }, + data: { lastSendAt: new Date() }, + }); + }); + + if (jobId) await completeSendJob(jobId, true); + + logger.info( + { + campaignId: campaign.id, + satellite: satellite.email, + primary: primary.email, + messageId, + }, + 'Campaign warmup email dispatched', + ); + }); +} + +export async function runCampaignDispatcherJob(): Promise { + logger.info('Campaign dispatcher job started'); + + const campaigns = await prisma.warmupCampaign.findMany({ + where: { + status: { in: [CampaignStatus.active, CampaignStatus.maintenance] }, + }, + include: { + primaryMailbox: true, + satellites: { + where: { active: true }, + include: { satelliteMailbox: true }, + }, + }, + }); + + if (campaigns.length === 0) { + logger.info('Campaign dispatcher skipped: no active campaigns'); + return 'No active campaigns'; + } + + let processed = 0; + + for (const campaign of campaigns) { + try { + await syncCampaignDay(campaign.id); + + const refreshed = await prisma.warmupCampaign.findUnique({ + where: { id: campaign.id }, + }); + if (!refreshed) continue; + + if (!hasElapsedMinJitter(refreshed.lastSendAt)) { + logger.debug({ campaignId: campaign.id }, 'Campaign within jitter window'); + continue; + } + + if ( + !isWithinSendWindow( + refreshed.timezone, + refreshed.sendWindowStart, + refreshed.sendWindowEnd, + ) + ) { + logger.debug({ campaignId: campaign.id }, 'Campaign outside send window'); + continue; + } + + const schedule = await prisma.dailySchedule.findUnique({ + where: { + campaignId_dayIndex: { + campaignId: campaign.id, + dayIndex: refreshed.currentDay, + }, + }, + }); + + if (!schedule || schedule.actualSends >= schedule.plannedSends) { + continue; + } + + await enqueueSendJob(campaign.id, new Date()); + const job = await claimDueSendJob(campaign.id); + if (!job) continue; + + await executeCampaignSend(campaign.id, job.id); + processed += 1; + } catch (err) { + logger.error({ err, campaignId: campaign.id }, 'Campaign dispatcher failed'); + } + } + + logger.info('Campaign dispatcher job finished'); + return `Processed ${processed} send job(s)`; +} diff --git a/apps/api/src/jobs/campaign-rollover.job.ts b/apps/api/src/jobs/campaign-rollover.job.ts new file mode 100644 index 0000000..e54e4e1 --- /dev/null +++ b/apps/api/src/jobs/campaign-rollover.job.ts @@ -0,0 +1,24 @@ +import { CampaignStatus, prisma } from '../lib/prisma'; +import { logger } from '../lib/logger'; +import { syncCampaignDay } from '../services/campaign.service'; + +export async function runCampaignRolloverJob(): Promise { + logger.info('Campaign rollover job started'); + + const campaigns = await prisma.warmupCampaign.findMany({ + where: { + status: { in: [CampaignStatus.active, CampaignStatus.maintenance] }, + }, + select: { id: true }, + }); + + for (const campaign of campaigns) { + try { + await syncCampaignDay(campaign.id); + } catch (err) { + logger.error({ err, campaignId: campaign.id }, 'Campaign rollover failed'); + } + } + + logger.info('Campaign rollover job finished'); +} diff --git a/src/jobs/dispatcher.job.ts b/apps/api/src/jobs/dispatcher.job.ts similarity index 90% rename from src/jobs/dispatcher.job.ts rename to apps/api/src/jobs/dispatcher.job.ts index 101adc6..9004db8 100644 --- a/src/jobs/dispatcher.job.ts +++ b/apps/api/src/jobs/dispatcher.job.ts @@ -5,6 +5,7 @@ import { logger } from '../lib/logger'; import * as aiService from '../services/ai.service'; import * as emailService from '../services/email.service'; import { getSettingOrDefault } from '../services/settings.service'; +import { hasAnyCampaign } from '../services/campaign.service'; function startOfToday(): Date { const d = new Date(); @@ -51,6 +52,12 @@ async function maybeBumpRamp(): Promise { export async function runDispatcherJob(): Promise { logger.info('Dispatcher job started'); + + if (await hasAnyCampaign()) { + logger.info('Legacy flat dispatcher skipped: campaigns exist'); + return; + } + await maybeBumpRamp(); const inboxes: Inbox[] = await prisma.inbox.findMany({ @@ -74,7 +81,10 @@ export async function runDispatcherJob(): Promise { const receiver = pickRandom(receivers); await withInboxGuard(sender, async () => { - const { subject, body } = await aiService.generateInitialEmail(sender.industry); + const { subject, body } = await aiService.generateInitialEmail(sender.industry, { + senderName: sender.senderName, + companyName: sender.companyName, + }); const { messageId } = await emailService.sendMail(sender, { to: receiver.email, subject, diff --git a/apps/api/src/jobs/job-runner.ts b/apps/api/src/jobs/job-runner.ts new file mode 100644 index 0000000..1acceba --- /dev/null +++ b/apps/api/src/jobs/job-runner.ts @@ -0,0 +1,17 @@ +import { logger } from '../lib/logger'; +import { logJobRun } from '../services/job-queue.service'; + +export async function runLoggedJob( + jobName: string, + fn: () => Promise, +): Promise { + try { + const message = await fn(); + await logJobRun(jobName, true, message ?? undefined); + } catch (err) { + const message = err instanceof Error ? err.message : 'Job failed'; + await logJobRun(jobName, false, message); + logger.error({ err, jobName }, 'Background job failed'); + throw err; + } +} diff --git a/apps/api/src/jobs/rescuer.job.ts b/apps/api/src/jobs/rescuer.job.ts new file mode 100644 index 0000000..27bfcab --- /dev/null +++ b/apps/api/src/jobs/rescuer.job.ts @@ -0,0 +1,464 @@ +import { simpleParser } from 'mailparser'; +import { ImapFlow } from 'imapflow'; +import type { Inbox, WarmupCampaign, WarmupLog } from '../generated/prisma/client'; +import { CampaignStatus, InboxStatus, WarmupStatus } from '../lib/prisma'; +import { prisma } from '../lib/prisma'; +import { withInboxGuard } from '../lib/inboxGuard'; +import { logger } from '../lib/logger'; +import * as aiService from '../services/ai.service'; +import { hasAnyCampaign } from '../services/campaign.service'; +import * as emailService from '../services/email.service'; +import { withImapClient } from '../services/imap.service'; +import { resolveSpamFolder } from '../services/spam-folder.service'; +import { + buildShouldReplyInput, + getTodayReplyStats, + shouldReply, +} from '../services/reply-rate.service'; +import { checkCampaignSpamThresholds } from '../services/spam-threshold.service'; + +type PendingLog = WarmupLog & { sender: Inbox }; + +type CampaignContext = { + campaign: Pick; + schedule: { plannedReplies: number; actualReplies: number }; +}; + +function normalizeMessageId(id: string | undefined | null): string | null { + if (!id) return null; + return id.replace(/^<|>$/g, '').trim(); +} + +function extractFromAddress(fromHeader: string | undefined): string | null { + if (!fromHeader) return null; + const match = fromHeader.match(/<([^>]+)>/) ?? fromHeader.match(/([\w.+-]+@[\w.-]+)/); + return match?.[1]?.toLowerCase() ?? null; +} + +async function findMatchingLog( + logs: PendingLog[], + messageId: string | null, + fromEmail: string | null, + subject: string | undefined, +): Promise { + if (messageId) { + const normalized = normalizeMessageId(messageId); + const byId = logs.find( + (l) => l.messageId && normalizeMessageId(l.messageId) === normalized, + ); + if (byId) return byId; + } + + if (fromEmail && subject) { + return logs.find( + (l) => + l.sender.email.toLowerCase() === fromEmail && + l.subject.trim() === subject.replace(/^Re:\s*/i, '').trim(), + ); + } + + return undefined; +} + +async function incrementActualReplies(campaignId: string, currentDay: number): Promise { + await prisma.dailySchedule.updateMany({ + where: { campaignId, dayIndex: currentDay }, + data: { actualReplies: { increment: 1 } }, + }); +} + +async function incrementSpamCounters( + campaignId: string, + currentDay: number, + rescued: boolean, +): Promise { + await prisma.dailySchedule.updateMany({ + where: { campaignId, dayIndex: currentDay }, + data: { + spamDetected: { increment: 1 }, + ...(rescued ? { spamRescued: { increment: 1 } } : {}), + }, + }); +} + +async function markEngagedNoReply(logId: string, placement: 'inbox' | 'spam'): Promise { + await prisma.warmupLog.update({ + where: { id: logId }, + data: { + status: WarmupStatus.engaged_no_reply, + placement, + rescuedFromSpam: placement === 'spam', + }, + }); + await prisma.warmupLog.update({ + where: { id: logId }, + data: { status: WarmupStatus.complete }, + }); +} + +async function processMessage( + receiver: Inbox, + uid: number, + client: ImapFlow, + pendingLogs: PendingLog[], + options?: { + campaignContext?: CampaignContext; + placement?: 'inbox' | 'spam'; + }, +): Promise { + const msg = await client.fetchOne( + String(uid), + { + envelope: true, + source: true, + headers: ['message-id', 'from', 'subject'], + uid: true, + }, + { uid: true }, + ); + + if (!msg || !msg.source) return; + + const parsed = await simpleParser(msg.source); + const fromEmail = extractFromAddress(parsed.from?.text ?? msg.envelope?.from?.[0]?.address); + const messageId = parsed.messageId ?? msg.envelope?.messageId ?? null; + const subject = parsed.subject ?? msg.envelope?.subject ?? ''; + + const log = await findMatchingLog(pendingLogs, messageId, fromEmail, subject); + if (!log) return; + + const placement = options?.placement ?? 'inbox'; + + await client.messageFlagsAdd(String(uid), ['\\Seen', '\\Flagged'], { uid: true }); + + try { + await client.messageFlagsAdd(String(uid), ['\\Important'], { uid: true, useLabels: true }); + } catch { + logger.debug({ inboxId: receiver.id }, 'Important flag not supported'); + } + + if (placement === 'inbox') { + await prisma.warmupLog.update({ + where: { id: log.id }, + data: { placement: 'inbox' }, + }); + } + + if (options?.campaignContext) { + const { campaign, schedule } = options.campaignContext; + const statsToday = await getTodayReplyStats(campaign.id, campaign.primaryMailboxId); + const replyInput = buildShouldReplyInput(campaign, schedule); + const willReply = shouldReply(replyInput, statsToday); + + if (!willReply) { + await markEngagedNoReply(log.id, placement); + logger.info( + { receiver: receiver.email, sender: log.sender.email, logId: log.id }, + 'Warmup engaged without reply', + ); + const idx = pendingLogs.findIndex((l) => l.id === log.id); + if (idx >= 0) pendingLogs.splice(idx, 1); + return; + } + } + + const incomingBody = parsed.text ?? parsed.html?.toString() ?? ''; + const { subject: replySubject, body: replyBody } = await aiService.generateReply( + receiver.industry, + incomingBody, + log.subject, + { senderName: receiver.senderName, companyName: receiver.companyName }, + ); + + const originalMessageId = log.messageId ?? messageId ?? undefined; + + await withInboxGuard(receiver, async () => { + const { messageId: replyMessageId } = await emailService.sendMail(receiver, { + to: log.sender.email, + subject: replySubject, + body: replyBody, + inReplyTo: originalMessageId, + references: originalMessageId, + }); + + await prisma.warmupLog.create({ + data: { + campaignId: options?.campaignContext?.campaign.id ?? log.campaignId, + senderId: receiver.id, + receiverId: log.senderId, + messageId: replyMessageId, + inReplyTo: originalMessageId, + subject: replySubject, + body: replyBody, + status: WarmupStatus.replied, + }, + }); + + await prisma.warmupLog.update({ + where: { id: log.id }, + data: { status: WarmupStatus.replied, inReplyTo: replyMessageId, placement }, + }); + + await prisma.warmupLog.update({ + where: { id: log.id }, + data: { status: WarmupStatus.complete }, + }); + + if (options?.campaignContext) { + await incrementActualReplies( + options.campaignContext.campaign.id, + options.campaignContext.campaign.currentDay, + ); + options.campaignContext.schedule.actualReplies += 1; + } + + logger.info( + { receiver: receiver.email, sender: log.sender.email, logId: log.id }, + 'Warmup reply sent', + ); + }); + + const idx = pendingLogs.findIndex((l) => l.id === log.id); + if (idx >= 0) pendingLogs.splice(idx, 1); +} + +async function rescueUidFromSpam( + receiver: Inbox, + client: ImapFlow, + uid: number, + pendingLogs: PendingLog[], + campaignContext?: CampaignContext, +): Promise { + const msg = await client.fetchOne( + String(uid), + { + envelope: true, + source: true, + headers: ['message-id', 'from', 'subject'], + uid: true, + }, + { uid: true }, + ); + + if (!msg || !msg.source) return false; + + const parsed = await simpleParser(msg.source); + const fromEmail = extractFromAddress(parsed.from?.text ?? msg.envelope?.from?.[0]?.address); + const messageId = parsed.messageId ?? msg.envelope?.messageId ?? null; + const subject = parsed.subject ?? msg.envelope?.subject ?? ''; + + const log = await findMatchingLog(pendingLogs, messageId, fromEmail, subject); + if (!log) return false; + + await client.messageMove([uid], 'INBOX', { uid: true }); + + await prisma.warmupLog.update({ + where: { id: log.id }, + data: { + status: WarmupStatus.rescued_from_spam, + rescuedFromSpam: true, + placement: 'spam', + }, + }); + log.status = WarmupStatus.rescued_from_spam; + + if (campaignContext) { + await incrementSpamCounters( + campaignContext.campaign.id, + campaignContext.campaign.currentDay, + true, + ); + } + + await prisma.inbox.update({ + where: { id: receiver.id }, + data: { lastSpamRescuedAt: new Date() }, + }); + + logger.info( + { receiver: receiver.email, sender: log.sender.email, logId: log.id, uid }, + 'Rescued email from spam (per-UID match)', + ); + + return true; +} + +async function rescueFromSpam( + receiver: Inbox, + client: ImapFlow, + pendingLogs: PendingLog[], + campaignContext?: CampaignContext, +): Promise { + const spamFolder = await resolveSpamFolder(client, receiver); + if (!spamFolder) return; + + await client.mailboxOpen(spamFolder); + + const senderEmails = [...new Set(pendingLogs.map((l) => l.sender.email))]; + + for (const senderEmail of senderEmails) { + const uids = await client.search({ seen: false, from: senderEmail }, { uid: true }); + if (!uids || uids.length === 0) continue; + + for (const uid of uids) { + try { + await rescueUidFromSpam(receiver, client, uid, pendingLogs, campaignContext); + } catch (err) { + logger.error({ err, receiverId: receiver.id, uid }, 'Per-UID spam rescue failed'); + } + } + } +} + +async function processInboxMessages( + receiver: Inbox, + pendingLogs: PendingLog[], + campaignContext?: CampaignContext, +): Promise { + if (pendingLogs.length === 0) return; + + await withInboxGuard(receiver, async () => { + await withImapClient(receiver, async (client) => { + await rescueFromSpam(receiver, client, pendingLogs, campaignContext); + + await client.mailboxOpen('INBOX'); + const senderEmails: string[] = [ + ...new Set(pendingLogs.map((l: PendingLog) => l.sender.email)), + ]; + + for (const senderEmail of senderEmails) { + const uids = await client.search({ seen: false, from: senderEmail }, { uid: true }); + if (!uids || uids.length === 0) continue; + + for (const uid of uids) { + try { + const log = pendingLogs.find( + (l) => l.sender.email.toLowerCase() === senderEmail.toLowerCase(), + ); + const placement = + log?.status === WarmupStatus.rescued_from_spam ? 'spam' : 'inbox'; + await processMessage(receiver, uid, client, pendingLogs, { + campaignContext, + placement, + }); + } catch (err) { + logger.error({ err, receiverId: receiver.id, uid }, 'Failed to process message'); + } + } + } + }); + }); +} + +async function processCampaignPrimary(receiver: Inbox): Promise { + const campaign = await prisma.warmupCampaign.findFirst({ + where: { + primaryMailboxId: receiver.id, + status: { in: [CampaignStatus.active, CampaignStatus.maintenance] }, + }, + include: { + satellites: { + where: { active: true }, + include: { satelliteMailbox: true }, + }, + }, + }); + if (!campaign) return; + + const schedule = await prisma.dailySchedule.findUnique({ + where: { + campaignId_dayIndex: { campaignId: campaign.id, dayIndex: campaign.currentDay }, + }, + }); + if (!schedule) return; + + const allowedEmails = new Set( + campaign.satellites.map((sat) => sat.satelliteMailbox.email.toLowerCase()), + ); + + const pendingLogs = (await prisma.warmupLog.findMany({ + where: { + campaignId: campaign.id, + receiverId: receiver.id, + status: { in: [WarmupStatus.sent, WarmupStatus.rescued_from_spam] }, + }, + include: { sender: true }, + })) as PendingLog[]; + + const filtered = pendingLogs.filter((log) => + allowedEmails.has(log.sender.email.toLowerCase()), + ); + + const campaignContext: CampaignContext = { + campaign: { + id: campaign.id, + replyRatePercent: campaign.replyRatePercent, + primaryMailboxId: campaign.primaryMailboxId, + currentDay: campaign.currentDay, + }, + schedule: { + plannedReplies: schedule.plannedReplies, + actualReplies: schedule.actualReplies, + }, + }; + + await processInboxMessages(receiver, filtered, campaignContext); +} + +async function processLegacyInbox(receiver: Inbox): Promise { + const pendingLogs = (await prisma.warmupLog.findMany({ + where: { + campaignId: null, + receiverId: receiver.id, + status: { in: [WarmupStatus.sent, WarmupStatus.rescued_from_spam] }, + }, + include: { sender: true }, + })) as PendingLog[]; + + await processInboxMessages(receiver, pendingLogs); +} + +export async function runRescuerJob(): Promise { + logger.info('Rescuer job started'); + + if (await hasAnyCampaign()) { + const campaigns = await prisma.warmupCampaign.findMany({ + where: { + status: { in: [CampaignStatus.active, CampaignStatus.maintenance] }, + }, + select: { primaryMailboxId: true }, + }); + + const primaryIds = [...new Set(campaigns.map((c) => c.primaryMailboxId))]; + + for (const primaryMailboxId of primaryIds) { + const inbox = await prisma.inbox.findUnique({ where: { id: primaryMailboxId } }); + if (!inbox || inbox.status !== InboxStatus.active) continue; + + try { + await processCampaignPrimary(inbox); + } catch (err) { + logger.error({ err, inboxId: inbox.id }, 'Campaign rescuer failed for primary'); + } + } + } else { + const inboxes = await prisma.inbox.findMany({ + where: { status: InboxStatus.active }, + }); + + for (const inbox of inboxes) { + try { + await processLegacyInbox(inbox); + } catch (err) { + logger.error({ err, inboxId: inbox.id }, 'Rescuer failed for inbox'); + } + } + } + + const paused = await checkCampaignSpamThresholds(); + if (paused > 0) { + logger.warn({ paused }, 'Auto-paused campaigns after rescuer spam check'); + } + + logger.info('Rescuer job finished'); +} diff --git a/src/jobs/scheduler.ts b/apps/api/src/jobs/scheduler.ts similarity index 53% rename from src/jobs/scheduler.ts rename to apps/api/src/jobs/scheduler.ts index c2b53bf..0428f88 100644 --- a/src/jobs/scheduler.ts +++ b/apps/api/src/jobs/scheduler.ts @@ -1,27 +1,49 @@ import cron from 'node-cron'; import { logger } from '../lib/logger'; import { seedDefaultSettings, getSettingOrDefault } from '../services/settings.service'; +import { seedDefaultWorkspace } from '../services/workspace.service'; +import { runCampaignDispatcherJob } from './campaign-dispatcher.job'; +import { runCampaignRolloverJob } from './campaign-rollover.job'; +import { runStatsRollupJob } from './stats-rollup.job'; import { runDispatcherJob } from './dispatcher.job'; import { runRescuerJob } from './rescuer.job'; +import { runLoggedJob } from './job-runner'; let dispatcherTask: cron.ScheduledTask | null = null; let rescuerTask: cron.ScheduledTask | null = null; +let rolloverTask: cron.ScheduledTask | null = null; +let rollupTask: cron.ScheduledTask | null = null; let rampTask: cron.ScheduledTask | null = null; async function scheduleJobs(): Promise { if (dispatcherTask) dispatcherTask.stop(); if (rescuerTask) rescuerTask.stop(); + if (rolloverTask) rolloverTask.stop(); + if (rollupTask) rollupTask.stop(); if (rampTask) rampTask.stop(); const dispatcherCron = await getSettingOrDefault('dispatcher_cron'); const rescuerCron = await getSettingOrDefault('rescuer_cron'); dispatcherTask = cron.schedule(dispatcherCron, () => { - runDispatcherJob().catch((err) => logger.error({ err }, 'Dispatcher cron error')); + runLoggedJob('campaign-dispatcher', runCampaignDispatcherJob).catch(() => undefined); + runDispatcherJob().catch((err) => logger.error({ err }, 'Legacy dispatcher cron error')); }); rescuerTask = cron.schedule(rescuerCron, () => { - runRescuerJob().catch((err) => logger.error({ err }, 'Rescuer cron error')); + runLoggedJob('rescuer', async () => { + await runRescuerJob(); + }).catch(() => undefined); + }); + + rolloverTask = cron.schedule('0 * * * *', () => { + runLoggedJob('campaign-rollover', async () => { + await runCampaignRolloverJob(); + }).catch(() => undefined); + }); + + rollupTask = cron.schedule('5 * * * *', () => { + runLoggedJob('stats-rollup', runStatsRollupJob).catch(() => undefined); }); rampTask = cron.schedule('0 0 * * *', () => { @@ -34,6 +56,7 @@ async function scheduleJobs(): Promise { } export async function startScheduler(): Promise { + await seedDefaultWorkspace(); await seedDefaultSettings(); await scheduleJobs(); } diff --git a/apps/api/src/jobs/stats-rollup.job.ts b/apps/api/src/jobs/stats-rollup.job.ts new file mode 100644 index 0000000..aa6e1c3 --- /dev/null +++ b/apps/api/src/jobs/stats-rollup.job.ts @@ -0,0 +1,50 @@ +import { DateTime } from 'luxon'; +import { logger } from '../lib/logger'; +import { prisma } from '../lib/prisma'; +import { finalizeDailyStatForSchedule } from '../services/stats.service'; +import { checkCampaignSpamThresholds } from '../services/spam-threshold.service'; + +export async function runStatsRollupJob(): Promise { + logger.info('Stats rollup job started'); + + const campaigns = await prisma.warmupCampaign.findMany({ + where: { currentDay: { gt: 0 } }, + include: { + dailySchedules: { orderBy: { dayIndex: 'asc' } }, + }, + }); + + let finalized = 0; + + for (const campaign of campaigns) { + try { + const localNow = DateTime.now().setZone(campaign.timezone); + const hour = localNow.hour; + + if (hour !== 0) continue; + + const yesterday = localNow.minus({ days: 1 }).startOf('day'); + const schedule = campaign.dailySchedules.find((row) => { + if (!row.date) return false; + const rowDay = DateTime.fromJSDate(row.date).setZone(campaign.timezone).startOf('day'); + return rowDay.toMillis() === yesterday.toMillis(); + }); + + if (!schedule) continue; + + await finalizeDailyStatForSchedule(campaign, schedule); + finalized += 1; + logger.info( + { campaignId: campaign.id, dayIndex: schedule.dayIndex }, + 'Finalized daily stat rollup', + ); + } catch (err) { + logger.error({ err, campaignId: campaign.id }, 'Stats rollup failed'); + } + } + + const paused = await checkCampaignSpamThresholds(); + + logger.info('Stats rollup job finished'); + return `Finalized ${finalized} day(s); auto-paused ${paused} campaign(s)`; +} diff --git a/apps/api/src/lib/dns-check.ts b/apps/api/src/lib/dns-check.ts new file mode 100644 index 0000000..7e6ed3f --- /dev/null +++ b/apps/api/src/lib/dns-check.ts @@ -0,0 +1,70 @@ +import { promises as dns } from 'node:dns'; + +export type DnsCheckResult = { + spf: 'pass' | 'warn' | 'fail'; + dkim: 'pass' | 'warn' | 'fail'; + dmarc: 'pass' | 'warn' | 'fail'; + details: string[]; +}; + +function extractDomain(email: string): string { + const parts = email.split('@'); + return parts[1]?.toLowerCase() ?? email.toLowerCase(); +} + +async function resolveTxtRecords(host: string): Promise { + try { + const records = await dns.resolveTxt(host); + return records.map((chunks) => chunks.join('')); + } catch { + return []; + } +} + +export async function checkDomainDns(domain: string): Promise { + const details: string[] = []; + let spf: DnsCheckResult['spf'] = 'warn'; + let dkim: DnsCheckResult['spf'] = 'warn'; + let dmarc: DnsCheckResult['spf'] = 'warn'; + + const rootTxt = await resolveTxtRecords(domain); + const spfRecord = rootTxt.find((r) => r.toLowerCase().startsWith('v=spf1')); + if (spfRecord) { + spf = 'pass'; + details.push(`SPF found: ${spfRecord.slice(0, 80)}`); + } else { + spf = 'fail'; + details.push('No SPF TXT record found'); + } + + const dmarcTxt = await resolveTxtRecords(`_dmarc.${domain}`); + const dmarcRecord = dmarcTxt.find((r) => r.toLowerCase().startsWith('v=dmarc1')); + if (dmarcRecord) { + dmarc = 'pass'; + details.push(`DMARC found: ${dmarcRecord.slice(0, 80)}`); + } else { + dmarc = 'warn'; + details.push('No DMARC record found at _dmarc'); + } + + const dkimSelectors = ['google', 'selector1', 'selector2', 'default']; + let dkimFound = false; + for (const selector of dkimSelectors) { + const dkimTxt = await resolveTxtRecords(`${selector}._domainkey.${domain}`); + if (dkimTxt.some((r) => r.includes('v=DKIM1') || r.includes('k=rsa'))) { + dkimFound = true; + details.push(`DKIM found for selector ${selector}`); + break; + } + } + dkim = dkimFound ? 'pass' : 'warn'; + if (!dkimFound) { + details.push('No DKIM record found for common selectors'); + } + + return { spf, dkim, dmarc, details }; +} + +export async function checkMailboxDns(email: string): Promise { + return checkDomainDns(extractDomain(email)); +} diff --git a/apps/api/src/lib/domain-age.ts b/apps/api/src/lib/domain-age.ts new file mode 100644 index 0000000..2f79dfa --- /dev/null +++ b/apps/api/src/lib/domain-age.ts @@ -0,0 +1,47 @@ +export type DomainAgeResult = { + domain: string; + registeredAt: string | null; + ageDays: number | null; + source: 'rdap' | 'unavailable'; +}; + +type RdapEvent = { eventAction?: string; eventDate?: string }; + +async function fetchRdap(domain: string): Promise { + const response = await fetch(`https://rdap.org/domain/${encodeURIComponent(domain)}`, { + headers: { Accept: 'application/rdap+json' }, + }); + + if (!response.ok) { + return { domain, registeredAt: null, ageDays: null, source: 'unavailable' }; + } + + const data = (await response.json()) as { events?: RdapEvent[] }; + const registration = data.events?.find((event) => event.eventAction === 'registration'); + + if (!registration?.eventDate) { + return { domain, registeredAt: null, ageDays: null, source: 'unavailable' }; + } + + const registeredAt = new Date(registration.eventDate); + if (Number.isNaN(registeredAt.getTime())) { + return { domain, registeredAt: null, ageDays: null, source: 'unavailable' }; + } + + const ageDays = Math.floor((Date.now() - registeredAt.getTime()) / (1000 * 60 * 60 * 24)); + + return { + domain, + registeredAt: registeredAt.toISOString(), + ageDays, + source: 'rdap', + }; +} + +export async function checkDomainAge(emailOrDomain: string): Promise { + const domain = emailOrDomain.includes('@') + ? emailOrDomain.split('@')[1]!.toLowerCase() + : emailOrDomain.toLowerCase(); + + return fetchRdap(domain); +} diff --git a/src/lib/inboxGuard.ts b/apps/api/src/lib/inboxGuard.ts similarity index 79% rename from src/lib/inboxGuard.ts rename to apps/api/src/lib/inboxGuard.ts index 27984c2..f6d09ab 100644 --- a/src/lib/inboxGuard.ts +++ b/apps/api/src/lib/inboxGuard.ts @@ -31,9 +31,9 @@ export async function withInboxGuard( } } -export function stripPassword( +export function stripPassword( inbox: T, -): Omit { - const { password: _, ...rest } = inbox; +): Omit { + const { password: _, oauthRefreshToken: __, ...rest } = inbox; return rest; } diff --git a/src/lib/logger.ts b/apps/api/src/lib/logger.ts similarity index 100% rename from src/lib/logger.ts rename to apps/api/src/lib/logger.ts diff --git a/src/lib/prisma.ts b/apps/api/src/lib/prisma.ts similarity index 68% rename from src/lib/prisma.ts rename to apps/api/src/lib/prisma.ts index 58fc9df..017b4e7 100644 --- a/src/lib/prisma.ts +++ b/apps/api/src/lib/prisma.ts @@ -22,5 +22,22 @@ if (config.NODE_ENV !== 'production') { } export { PrismaClient } from '../generated/prisma/client'; -export type { Inbox, WarmupLog, Setting } from '../generated/prisma/client'; -export { InboxStatus, WarmupStatus } from '../generated/prisma/client'; +export type { + Inbox, + WarmupLog, + Setting, + WarmupCampaign, + Workspace, + DailySchedule, + CampaignSatellite, +} from '../generated/prisma/client'; +export { + InboxStatus, + WarmupStatus, + MailboxRole, + MailboxProvider, + CampaignRecipe, + CampaignStatus, + SendJobStatus, + MailboxAuthType, +} from '../generated/prisma/client'; diff --git a/apps/api/src/lib/send-window.ts b/apps/api/src/lib/send-window.ts new file mode 100644 index 0000000..1152c1b --- /dev/null +++ b/apps/api/src/lib/send-window.ts @@ -0,0 +1,56 @@ +import { DateTime } from 'luxon'; + +function parseTimeParts(time: string): { hour: number; minute: number } { + const [hourStr, minuteStr] = time.split(':'); + const hour = Number(hourStr); + const minute = Number(minuteStr ?? 0); + if ( + Number.isNaN(hour) || + Number.isNaN(minute) || + hour < 0 || + hour > 23 || + minute < 0 || + minute > 59 + ) { + throw new Error(`Invalid time format: ${time}`); + } + return { hour, minute }; +} + +export function isWithinSendWindow( + timezone: string, + sendWindowStart: string, + sendWindowEnd: string, + now: DateTime = DateTime.now(), +): boolean { + const local = now.setZone(timezone); + if (!local.isValid) { + return false; + } + + const startParts = parseTimeParts(sendWindowStart); + const endParts = parseTimeParts(sendWindowEnd); + + const start = local.set({ + hour: startParts.hour, + minute: startParts.minute, + second: 0, + millisecond: 0, + }); + const end = local.set({ + hour: endParts.hour, + minute: endParts.minute, + second: 59, + millisecond: 999, + }); + + if (end < start) { + return local >= start || local <= end; + } + + return local >= start && local <= end; +} + +export function calendarDayInTimezone(timezone: string, date: Date = new Date()): DateTime { + return DateTime.fromJSDate(date).setZone(timezone).startOf('day'); +} diff --git a/src/middleware/apiKeyAuth.ts b/apps/api/src/middleware/apiKeyAuth.ts similarity index 100% rename from src/middleware/apiKeyAuth.ts rename to apps/api/src/middleware/apiKeyAuth.ts diff --git a/src/middleware/errorHandler.ts b/apps/api/src/middleware/errorHandler.ts similarity index 100% rename from src/middleware/errorHandler.ts rename to apps/api/src/middleware/errorHandler.ts diff --git a/apps/api/src/models/campaign.schemas.ts b/apps/api/src/models/campaign.schemas.ts new file mode 100644 index 0000000..eba47eb --- /dev/null +++ b/apps/api/src/models/campaign.schemas.ts @@ -0,0 +1,75 @@ +import { z } from 'zod'; +import { CampaignRecipe, CampaignStatus, MailboxRole } from '../lib/prisma'; + +export const createCampaignSchema = z.object({ + workspaceId: z.string().optional(), + primaryMailboxId: z.string().min(1), + name: z.string().min(1), + recipe: z.nativeEnum(CampaignRecipe).default(CampaignRecipe.grow), + durationDays: z.coerce.number().int().min(14).max(45).default(30), + minEmailsPerDay: z.coerce.number().int().min(1).default(1), + maxEmailsPerDay: z.coerce.number().int().max(50).default(40), + replyRatePercent: z.coerce.number().int().min(10).max(45).default(30), + maintenanceVolumePercent: z.coerce.number().int().min(1).max(100).default(20), + timezone: z.string().default('America/Vancouver'), + sendWindowStart: z.string().default('08:00'), + sendWindowEnd: z.string().default('18:00'), + customSchedule: z + .array( + z.object({ + day: z.coerce.number().int().min(1), + sends: z.coerce.number().int().min(1), + }), + ) + .optional(), + satelliteMailboxIds: z.array(z.string()).optional(), +}); + +export const updateCampaignSchema = z.object({ + name: z.string().min(1).optional(), + recipe: z.nativeEnum(CampaignRecipe).optional(), + durationDays: z.coerce.number().int().min(14).max(45).optional(), + minEmailsPerDay: z.coerce.number().int().min(1).optional(), + maxEmailsPerDay: z.coerce.number().int().max(50).optional(), + replyRatePercent: z.coerce.number().int().min(10).max(45).optional(), + maintenanceVolumePercent: z.coerce.number().int().min(1).max(100).optional(), + timezone: z.string().optional(), + sendWindowStart: z.string().optional(), + sendWindowEnd: z.string().optional(), + customSchedule: z + .array( + z.object({ + day: z.coerce.number().int().min(1), + sends: z.coerce.number().int().min(1), + }), + ) + .optional(), +}); + +export const assignSatelliteSchema = z.object({ + mailboxId: z.string().min(1), + weight: z.coerce.number().int().positive().default(1), +}); + +export const recipePreviewSchema = z.object({ + recipe: z.nativeEnum(CampaignRecipe).default(CampaignRecipe.grow), + durationDays: z.coerce.number().int().min(14).max(45).default(30), + minEmailsPerDay: z.coerce.number().int().min(1).default(1), + maxEmailsPerDay: z.coerce.number().int().max(50).default(40), + replyRatePercent: z.coerce.number().int().min(10).max(45).default(30), + customSchedule: z + .array( + z.object({ + day: z.coerce.number().int().min(1), + sends: z.coerce.number().int().min(1), + }), + ) + .optional(), +}); + +export const campaignListQuerySchema = z.object({ + status: z.nativeEnum(CampaignStatus).optional(), + workspaceId: z.string().optional(), +}); + +export const mailboxRoleSchema = z.nativeEnum(MailboxRole); diff --git a/src/models/enums.ts b/apps/api/src/models/enums.ts similarity index 72% rename from src/models/enums.ts rename to apps/api/src/models/enums.ts index 03b537b..99f3d97 100644 --- a/src/models/enums.ts +++ b/apps/api/src/models/enums.ts @@ -1,4 +1,11 @@ -export { InboxStatus, WarmupStatus } from '../lib/prisma'; +export { + InboxStatus, + WarmupStatus, + MailboxRole, + MailboxProvider, + CampaignRecipe, + CampaignStatus, +} from '../lib/prisma'; export const SPAM_FOLDERS = ['[Gmail]/Spam', 'Junk', 'Spam'] as const; diff --git a/apps/api/src/models/provider-presets.ts b/apps/api/src/models/provider-presets.ts new file mode 100644 index 0000000..b13fbe1 --- /dev/null +++ b/apps/api/src/models/provider-presets.ts @@ -0,0 +1,50 @@ +import { MailboxProvider } from '../lib/prisma'; + +export type ProviderPreset = { + smtpHost: string; + smtpPort: number; + imapHost: string; + imapPort: number; +}; + +export const PROVIDER_PRESETS: Record = { + google_workspace: { + smtpHost: 'smtp.gmail.com', + smtpPort: 587, + imapHost: 'imap.gmail.com', + imapPort: 993, + }, + gmail: { + smtpHost: 'smtp.gmail.com', + smtpPort: 587, + imapHost: 'imap.gmail.com', + imapPort: 993, + }, + yahoo: { + smtpHost: 'smtp.mail.yahoo.com', + smtpPort: 587, + imapHost: 'imap.mail.yahoo.com', + imapPort: 993, + }, + outlook: { + smtpHost: 'smtp-mail.outlook.com', + smtpPort: 587, + imapHost: 'outlook.office365.com', + imapPort: 993, + }, + custom: null, +}; + +export function applyProviderPreset( + provider: MailboxProvider, + data: Partial, +): Partial { + const preset = PROVIDER_PRESETS[provider]; + if (!preset) return data; + return { + smtpHost: data.smtpHost ?? preset.smtpHost, + smtpPort: data.smtpPort ?? preset.smtpPort, + imapHost: data.imapHost ?? preset.imapHost, + imapPort: data.imapPort ?? preset.imapPort, + }; +} diff --git a/src/models/types.ts b/apps/api/src/models/types.ts similarity index 100% rename from src/models/types.ts rename to apps/api/src/models/types.ts diff --git a/apps/api/src/models/workspace.constants.ts b/apps/api/src/models/workspace.constants.ts new file mode 100644 index 0000000..b91acb0 --- /dev/null +++ b/apps/api/src/models/workspace.constants.ts @@ -0,0 +1,2 @@ +export const DEFAULT_WORKSPACE_NAME = 'default'; +export const DEFAULT_WORKSPACE_ID = 'ws_default'; diff --git a/apps/api/src/routes/ai.routes.ts b/apps/api/src/routes/ai.routes.ts new file mode 100644 index 0000000..27ec1c7 --- /dev/null +++ b/apps/api/src/routes/ai.routes.ts @@ -0,0 +1,46 @@ +import { Router, Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import * as aiService from '../services/ai.service'; +import { ContentValidationError } from '../services/content-validator.service'; +import { AppError } from '../middleware/errorHandler'; + +const router = Router(); + +const testGenerateSchema = z.object({ + industry: z.string().default('SaaS'), + senderName: z.string().optional(), + companyName: z.string().optional(), + type: z.enum(['initial', 'reply']).default('initial'), + incomingBody: z.string().optional(), + threadSubject: z.string().optional(), +}); + +router.post('/test-generate', async (req: Request, res: Response, next: NextFunction) => { + try { + const data = testGenerateSchema.parse(req.body); + const profile = { + senderName: data.senderName, + companyName: data.companyName, + }; + + const email = + data.type === 'reply' + ? await aiService.generateReply( + data.industry, + data.incomingBody ?? 'Thanks for reaching out about the project timeline.', + data.threadSubject ?? 'Project follow-up', + profile, + ) + : await aiService.generateInitialEmail(data.industry, profile); + + res.json(email); + } catch (err) { + if (err instanceof ContentValidationError) { + next(new AppError(422, err.message)); + return; + } + next(err); + } +}); + +export default router; diff --git a/apps/api/src/routes/campaigns.routes.ts b/apps/api/src/routes/campaigns.routes.ts new file mode 100644 index 0000000..2fc6c89 --- /dev/null +++ b/apps/api/src/routes/campaigns.routes.ts @@ -0,0 +1,183 @@ +import { Router, Request, Response, NextFunction } from 'express'; +import { stripPassword } from '../lib/inboxGuard'; +import { AppError } from '../middleware/errorHandler'; +import { + assignSatelliteSchema, + campaignListQuerySchema, + createCampaignSchema, + updateCampaignSchema, +} from '../models/campaign.schemas'; +import { + assignSatellite, + CampaignError, + createCampaign, + deleteCampaign, + getCampaignById, + listCampaigns, + listSatellites, + pauseCampaign, + resumeCampaign, + startCampaign, + unassignSatellite, + updateCampaign, +} from '../services/campaign.service'; + +const router = Router(); + +function handleCampaignError(err: unknown, next: NextFunction) { + if (err instanceof CampaignError) { + next(new AppError(err.statusCode, err.message)); + return; + } + next(err); +} + +function sanitizeCampaign(campaign: Awaited>) { + return { + ...campaign, + primaryMailbox: stripPassword(campaign.primaryMailbox), + satellites: campaign.satellites.map((s) => ({ + ...s, + satelliteMailbox: stripPassword(s.satelliteMailbox), + })), + }; +} + +router.post('/', async (req: Request, res: Response, next: NextFunction) => { + try { + const data = createCampaignSchema.parse(req.body); + const campaign = await createCampaign(data); + res.status(201).json(sanitizeCampaign(campaign)); + } catch (err) { + handleCampaignError(err, next); + } +}); + +router.get('/', async (req: Request, res: Response, next: NextFunction) => { + try { + const filters = campaignListQuerySchema.parse(req.query); + const campaigns = await listCampaigns(filters); + res.json( + campaigns.map((c) => ({ + ...c, + primaryMailbox: stripPassword(c.primaryMailbox), + satellites: c.satellites.map((s) => ({ + ...s, + satelliteMailbox: stripPassword(s.satelliteMailbox), + })), + })), + ); + } catch (err) { + next(err); + } +}); + +router.get('/:id', async (req: Request, res: Response, next: NextFunction) => { + try { + const campaign = await getCampaignById(req.params.id); + res.json(sanitizeCampaign(campaign)); + } catch (err) { + handleCampaignError(err, next); + } +}); + +router.patch('/:id', async (req: Request, res: Response, next: NextFunction) => { + try { + const data = updateCampaignSchema.parse(req.body); + const campaign = await updateCampaign(req.params.id, data); + res.json({ + ...campaign, + primaryMailbox: stripPassword(campaign.primaryMailbox), + satellites: campaign.satellites.map((s) => ({ + ...s, + satelliteMailbox: stripPassword(s.satelliteMailbox), + })), + }); + } catch (err) { + handleCampaignError(err, next); + } +}); + +router.delete('/:id', async (req: Request, res: Response, next: NextFunction) => { + try { + await deleteCampaign(req.params.id); + res.status(204).send(); + } catch (err) { + handleCampaignError(err, next); + } +}); + +router.post('/:id/start', async (req: Request, res: Response, next: NextFunction) => { + try { + const campaign = await startCampaign(req.params.id); + res.json({ + ...campaign, + primaryMailbox: stripPassword(campaign.primaryMailbox), + satellites: campaign.satellites.map((s) => ({ + ...s, + satelliteMailbox: stripPassword(s.satelliteMailbox), + })), + }); + } catch (err) { + handleCampaignError(err, next); + } +}); + +router.post('/:id/pause', async (req: Request, res: Response, next: NextFunction) => { + try { + const campaign = await pauseCampaign(req.params.id); + res.json(sanitizeCampaign(campaign)); + } catch (err) { + handleCampaignError(err, next); + } +}); + +router.post('/:id/resume', async (req: Request, res: Response, next: NextFunction) => { + try { + const campaign = await resumeCampaign(req.params.id); + res.json(sanitizeCampaign(campaign)); + } catch (err) { + handleCampaignError(err, next); + } +}); + +router.get('/:id/satellites', async (req: Request, res: Response, next: NextFunction) => { + try { + const satellites = await listSatellites(req.params.id); + res.json( + satellites.map((s) => ({ + ...s, + satelliteMailbox: stripPassword(s.satelliteMailbox), + })), + ); + } catch (err) { + handleCampaignError(err, next); + } +}); + +router.post('/:id/satellites', async (req: Request, res: Response, next: NextFunction) => { + try { + const { mailboxId, weight } = assignSatelliteSchema.parse(req.body); + const link = await assignSatellite(req.params.id, mailboxId, weight); + res.status(201).json({ + ...link, + satelliteMailbox: stripPassword(link.satelliteMailbox), + }); + } catch (err) { + handleCampaignError(err, next); + } +}); + +router.delete( + '/:id/satellites/:mailboxId', + async (req: Request, res: Response, next: NextFunction) => { + try { + await unassignSatellite(req.params.id, req.params.mailboxId); + res.status(204).send(); + } catch (err) { + handleCampaignError(err, next); + } + }, +); + +export default router; diff --git a/apps/api/src/routes/inboxes.routes.ts b/apps/api/src/routes/inboxes.routes.ts new file mode 100644 index 0000000..e488f95 --- /dev/null +++ b/apps/api/src/routes/inboxes.routes.ts @@ -0,0 +1,367 @@ +import { Router, Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { InboxStatus, MailboxAuthType, MailboxProvider, MailboxRole } from '../lib/prisma'; +import { prisma } from '../lib/prisma'; +import { encrypt } from '../services/crypto.service'; +import { testConnection } from '../services/email.service'; +import { isMicrosoftConsumerEmail } from '../services/microsoft-oauth.service'; +import { stripPassword } from '../lib/inboxGuard'; +import { AppError } from '../middleware/errorHandler'; +import { applyProviderPreset, PROVIDER_PRESETS } from '../models/provider-presets'; +import { checkMailboxDns } from '../lib/dns-check'; +import { resolveWorkspaceId } from '../services/workspace.service'; + +const router = Router(); + +const createInboxSchema = z.object({ + email: z.string().email(), + smtpHost: z.string().min(1).optional(), + smtpPort: z.coerce.number().int().positive().optional(), + imapHost: z.string().min(1).optional(), + imapPort: z.coerce.number().int().positive().optional(), + username: z.string().min(1), + password: z.string().optional(), + authType: z.nativeEnum(MailboxAuthType).optional(), + oauthRefreshToken: z.string().optional(), + workspaceId: z.string().optional(), + role: z.nativeEnum(MailboxRole).optional(), + provider: z.nativeEnum(MailboxProvider).default(MailboxProvider.custom), + dailyLimit: z.coerce.number().int().positive().optional(), + currentRamp: z.coerce.number().int().positive().optional(), + industry: z.string().min(1).optional(), + senderName: z.string().optional(), + companyName: z.string().optional(), + status: z.nativeEnum(InboxStatus).optional(), +}); + +const updateInboxSchema = z.object({ + email: z.string().email().optional(), + smtpHost: z.string().min(1).optional(), + smtpPort: z.coerce.number().int().positive().optional(), + imapHost: z.string().min(1).optional(), + imapPort: z.coerce.number().int().positive().optional(), + username: z.string().min(1).optional(), + password: z.string().optional(), + authType: z.nativeEnum(MailboxAuthType).optional(), + oauthRefreshToken: z.string().optional(), + role: z.nativeEnum(MailboxRole).optional(), + provider: z.nativeEnum(MailboxProvider).optional(), + dailyLimit: z.coerce.number().int().positive().optional(), + currentRamp: z.coerce.number().int().positive().optional(), + industry: z.string().min(1).optional(), + senderName: z.string().optional(), + companyName: z.string().optional(), + status: z.nativeEnum(InboxStatus).optional(), +}); + +const listQuerySchema = z.object({ + role: z.nativeEnum(MailboxRole).optional(), + workspaceId: z.string().optional(), +}); + +router.get('/provider-presets', (_req: Request, res: Response) => { + res.json(PROVIDER_PRESETS); +}); + +function resolveAuthType( + data: z.infer, + provider: MailboxProvider, +): MailboxAuthType { + if (data.authType) return data.authType; + if (provider === MailboxProvider.outlook && data.oauthRefreshToken) { + return MailboxAuthType.microsoft_oauth; + } + return MailboxAuthType.password; +} + +async function validateRoleChange(inboxId: string, newRole: MailboxRole): Promise { + const inbox = await prisma.inbox.findUnique({ where: { id: inboxId } }); + if (!inbox) throw new AppError(404, 'Inbox not found'); + + if (newRole === inbox.role) return; + + if (newRole === MailboxRole.primary) { + throw new AppError(400, 'Set a mailbox as primary by creating or editing a campaign'); + } + + if (inbox.role === MailboxRole.primary) { + const activeCampaign = await prisma.warmupCampaign.findFirst({ + where: { + primaryMailboxId: inboxId, + status: { in: ['active', 'maintenance', 'paused'] }, + }, + }); + if (activeCampaign) { + throw new AppError(400, 'Cannot change role while this mailbox is a campaign primary'); + } + } + + if (newRole === MailboxRole.unassigned && inbox.role === MailboxRole.satellite) { + const assignments = await prisma.campaignSatellite.count({ + where: { satelliteMailboxId: inboxId }, + }); + if (assignments > 0) { + throw new AppError( + 400, + 'Remove this mailbox from all campaigns before setting role to unassigned', + ); + } + } +} + +router.post('/', async (req: Request, res: Response, next: NextFunction) => { + try { + const data = createInboxSchema.parse(req.body); + const workspaceId = await resolveWorkspaceId(data.workspaceId); + const provider = data.provider ?? MailboxProvider.custom; + const authType = resolveAuthType(data, provider); + + if (authType === MailboxAuthType.password && !data.password) { + throw new AppError(400, 'Password is required for password authentication'); + } + if (authType === MailboxAuthType.microsoft_oauth && !data.oauthRefreshToken) { + throw new AppError( + 400, + 'Microsoft OAuth is required for @live.com/@outlook.com accounts. Use Connect Microsoft.', + ); + } + + const presetFields = applyProviderPreset(provider, { + smtpHost: data.smtpHost, + smtpPort: data.smtpPort, + imapHost: data.imapHost, + imapPort: data.imapPort, + }); + + if (!presetFields.smtpHost || !presetFields.imapHost || !presetFields.smtpPort || !presetFields.imapPort) { + throw new AppError(400, 'smtpHost, smtpPort, imapHost, and imapPort are required for custom provider'); + } + + if ( + authType === MailboxAuthType.password && + provider === MailboxProvider.outlook && + isMicrosoftConsumerEmail(data.email) + ) { + throw new AppError( + 400, + 'Microsoft consumer accounts (@live.com, @outlook.com) require OAuth. Use Connect Microsoft instead of an app password.', + ); + } + + const candidate = { + id: 'test', + workspaceId, + email: data.email, + smtpHost: presetFields.smtpHost, + smtpPort: presetFields.smtpPort, + imapHost: presetFields.imapHost, + imapPort: presetFields.imapPort, + username: data.username, + password: encrypt(data.password ?? 'oauth-placeholder'), + authType, + oauthRefreshToken: data.oauthRefreshToken ? encrypt(data.oauthRefreshToken) : null, + role: data.role ?? MailboxRole.unassigned, + provider, + status: InboxStatus.active, + dailyLimit: data.dailyLimit ?? 40, + currentRamp: data.currentRamp ?? 5, + industry: data.industry ?? 'SaaS', + senderName: data.senderName ?? null, + companyName: data.companyName ?? null, + lastError: null, + errorAt: null, + spamFolderPath: null, + lastSpamRescuedAt: null, + createdAt: new Date(), + updatedAt: new Date(), + }; + + const connection = await testConnection(candidate); + if (!connection.smtp.ok || !connection.imap.ok) { + const errors = [ + !connection.smtp.ok ? `SMTP: ${connection.smtp.error}` : null, + !connection.imap.ok ? `IMAP: ${connection.imap.error}` : null, + ].filter(Boolean); + throw new AppError(400, `Connection test failed. ${errors.join(' ')}`); + } + + const inbox = await prisma.inbox.create({ + data: { + workspaceId, + email: data.email, + smtpHost: presetFields.smtpHost, + smtpPort: presetFields.smtpPort, + imapHost: presetFields.imapHost, + imapPort: presetFields.imapPort, + username: data.username, + password: encrypt(data.password ?? 'oauth-placeholder'), + authType, + oauthRefreshToken: data.oauthRefreshToken ? encrypt(data.oauthRefreshToken) : null, + role: data.role ?? MailboxRole.unassigned, + provider, + dailyLimit: data.dailyLimit ?? 40, + currentRamp: data.currentRamp ?? 5, + industry: data.industry ?? 'SaaS', + senderName: data.senderName, + companyName: data.companyName, + status: InboxStatus.active, + }, + }); + res.status(201).json(stripPassword(inbox)); + } catch (err) { + next(err); + } +}); + +router.get('/', async (req: Request, res: Response, next: NextFunction) => { + try { + const { role, workspaceId: wsId } = listQuerySchema.parse(req.query); + const workspaceId = wsId ? await resolveWorkspaceId(wsId) : undefined; + + const inboxes = await prisma.inbox.findMany({ + where: { + ...(role ? { role } : {}), + ...(workspaceId ? { workspaceId } : {}), + }, + orderBy: { createdAt: 'desc' }, + }); + res.json(inboxes.map(stripPassword)); + } catch (err) { + next(err); + } +}); + +router.get('/:id', async (req: Request, res: Response, next: NextFunction) => { + try { + const inbox = await prisma.inbox.findUnique({ where: { id: req.params.id } }); + if (!inbox) { + throw new AppError(404, 'Inbox not found'); + } + res.json(stripPassword(inbox)); + } catch (err) { + next(err); + } +}); + +router.patch('/:id', async (req: Request, res: Response, next: NextFunction) => { + try { + const data = updateInboxSchema.parse(req.body); + const { password, provider, oauthRefreshToken, role, authType, ...rest } = data; + + if (role) { + await validateRoleChange(req.params.id, role); + } + + let connectionUpdates = {}; + if (provider) { + connectionUpdates = applyProviderPreset(provider, { + smtpHost: data.smtpHost, + smtpPort: data.smtpPort, + imapHost: data.imapHost, + imapPort: data.imapPort, + }); + } + + const existing = await prisma.inbox.findUnique({ where: { id: req.params.id } }); + if (!existing) throw new AppError(404, 'Inbox not found'); + + const nextAuthType = authType ?? existing.authType; + const nextPassword = password ? encrypt(password) : existing.password; + const nextOAuth = oauthRefreshToken ? encrypt(oauthRefreshToken) : existing.oauthRefreshToken; + + if (password || oauthRefreshToken || authType || provider) { + const candidate = { + ...existing, + ...rest, + ...(provider ? { provider } : {}), + ...connectionUpdates, + password: nextPassword, + authType: nextAuthType, + oauthRefreshToken: nextOAuth, + }; + const connection = await testConnection(candidate); + if (!connection.smtp.ok || !connection.imap.ok) { + const errors = [ + !connection.smtp.ok ? `SMTP: ${connection.smtp.error}` : null, + !connection.imap.ok ? `IMAP: ${connection.imap.error}` : null, + ].filter(Boolean); + throw new AppError(400, `Connection test failed. ${errors.join(' ')}`); + } + } + + const inbox = await prisma.inbox.update({ + where: { id: req.params.id }, + data: { + ...rest, + ...(role ? { role } : {}), + ...(provider ? { provider } : {}), + ...(authType ? { authType } : {}), + ...connectionUpdates, + ...(password ? { password: encrypt(password) } : {}), + ...(oauthRefreshToken ? { oauthRefreshToken: encrypt(oauthRefreshToken) } : {}), + ...(rest.status === InboxStatus.active + ? { lastError: null, errorAt: null } + : {}), + }, + }); + res.json(stripPassword(inbox)); + } catch (err) { + next(err); + } +}); + +router.delete('/:id', async (req: Request, res: Response, next: NextFunction) => { + try { + await prisma.inbox.delete({ where: { id: req.params.id } }); + res.status(204).send(); + } catch (err) { + next(err); + } +}); + +router.post('/:id/dns-check', async (req: Request, res: Response, next: NextFunction) => { + try { + const inbox = await prisma.inbox.findUnique({ where: { id: req.params.id } }); + if (!inbox) { + throw new AppError(404, 'Inbox not found'); + } + const result = await checkMailboxDns(inbox.email); + res.json(result); + } catch (err) { + next(err); + } +}); + +router.post('/:id/test', async (req: Request, res: Response, next: NextFunction) => { + try { + const inbox = await prisma.inbox.findUnique({ where: { id: req.params.id } }); + if (!inbox) { + throw new AppError(404, 'Inbox not found'); + } + + const result = await testConnection(inbox); + const ok = result.smtp.ok && result.imap.ok; + + if (!ok) { + await prisma.inbox.update({ + where: { id: inbox.id }, + data: { + status: InboxStatus.error, + lastError: result.smtp.error ?? result.imap.error ?? 'Connection test failed', + errorAt: new Date(), + }, + }); + } else if (inbox.status === InboxStatus.error) { + await prisma.inbox.update({ + where: { id: inbox.id }, + data: { status: InboxStatus.active, lastError: null, errorAt: null }, + }); + } + + res.json({ ok, ...result }); + } catch (err) { + next(err); + } +}); + +export default router; diff --git a/apps/api/src/routes/integrations.routes.ts b/apps/api/src/routes/integrations.routes.ts new file mode 100644 index 0000000..b17853d --- /dev/null +++ b/apps/api/src/routes/integrations.routes.ts @@ -0,0 +1,159 @@ +import { Router, Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { AppError } from '../middleware/errorHandler'; +import { + buildGoogleAuthUrl, + exchangeGoogleAuthCode, + getGoogleOAuthSettings, + getPostmasterStatus, +} from '../services/postmaster.service'; +import { + buildMicrosoftAuthUrl, + exchangeMicrosoftAuthCode, + getMicrosoftOAuthSettings, +} from '../services/microsoft-oauth.service'; +import { upsertSetting } from '../services/settings.service'; + +const router = Router(); + +const googleSettingsSchema = z.object({ + google_oauth_client_id: z.string().min(1), + google_oauth_client_secret: z.string().min(1), +}); + +router.get('/google/status', async (_req: Request, res: Response, next: NextFunction) => { + try { + const status = await getPostmasterStatus(); + const { clientId } = await getGoogleOAuthSettings(); + res.json({ + ...status, + hasClientId: Boolean(clientId), + redirectUri: undefined, + }); + } catch (err) { + next(err); + } +}); + +router.post('/google/credentials', async (req: Request, res: Response, next: NextFunction) => { + try { + const data = googleSettingsSchema.parse(req.body); + await upsertSetting('google_oauth_client_id', data.google_oauth_client_id); + await upsertSetting('google_oauth_client_secret', data.google_oauth_client_secret); + const status = await getPostmasterStatus(); + res.json(status); + } catch (err) { + next(err); + } +}); + +router.get('/google/auth-url', async (_req: Request, res: Response, next: NextFunction) => { + try { + const { clientId } = await getGoogleOAuthSettings(); + if (!clientId) { + throw new AppError(400, 'Configure Google OAuth client ID and secret first'); + } + res.json({ url: buildGoogleAuthUrl(clientId) }); + } catch (err) { + next(err); + } +}); + +const microsoftSettingsSchema = z.object({ + microsoft_oauth_client_id: z.string().min(1), + microsoft_oauth_client_secret: z.string().min(1), +}); + +router.get('/microsoft/status', async (_req: Request, res: Response, next: NextFunction) => { + try { + const { clientId, clientSecret } = await getMicrosoftOAuthSettings(); + res.json({ + configured: Boolean(clientId && clientSecret), + hasClientId: Boolean(clientId), + }); + } catch (err) { + next(err); + } +}); + +router.post('/microsoft/credentials', async (req: Request, res: Response, next: NextFunction) => { + try { + const data = microsoftSettingsSchema.parse(req.body); + await upsertSetting('microsoft_oauth_client_id', data.microsoft_oauth_client_id); + await upsertSetting('microsoft_oauth_client_secret', data.microsoft_oauth_client_secret); + res.json({ configured: true }); + } catch (err) { + next(err); + } +}); + +router.get('/microsoft/auth-url', async (req: Request, res: Response, next: NextFunction) => { + try { + const { clientId } = await getMicrosoftOAuthSettings(); + if (!clientId) { + throw new AppError(400, 'Configure Microsoft OAuth client ID and secret first'); + } + const state = typeof req.query.state === 'string' ? req.query.state : 'mailbox'; + res.json({ url: buildMicrosoftAuthUrl(clientId, state) }); + } catch (err) { + next(err); + } +}); + +export async function microsoftOAuthCallback(req: Request, res: Response): Promise { + try { + const code = typeof req.query.code === 'string' ? req.query.code : null; + if (!code) { + res.status(400).send('Missing authorization code'); + return; + } + + const { refreshToken } = await exchangeMicrosoftAuthCode(code); + + res.send(` + + +

Microsoft account connected

+

You can close this tab and return to Warmbox.

+ + + `); + } catch (err) { + const message = err instanceof Error ? err.message : 'OAuth callback failed'; + res.status(500).send(message); + } +} + +export async function googleOAuthCallback(req: Request, res: Response): Promise { + try { + const code = typeof req.query.code === 'string' ? req.query.code : null; + if (!code) { + res.status(400).send('Missing authorization code'); + return; + } + + const refreshToken = await exchangeGoogleAuthCode(code); + await upsertSetting('google_postmaster_refresh_token', refreshToken); + + res.send(` + + +

Google Postmaster connected

+

Warmbox can now verify your domains against Postmaster Tools.

+

You can close this tab and return to the dashboard Settings page.

+ + `); + } catch (err) { + const message = err instanceof Error ? err.message : 'OAuth callback failed'; + res.status(500).send(message); + } +} + +export default router; diff --git a/apps/api/src/routes/jobs.routes.ts b/apps/api/src/routes/jobs.routes.ts new file mode 100644 index 0000000..3e0f04d --- /dev/null +++ b/apps/api/src/routes/jobs.routes.ts @@ -0,0 +1,60 @@ +import { Router, Request, Response, NextFunction } from 'express'; +import { runCampaignDispatcherJob } from '../jobs/campaign-dispatcher.job'; +import { runCampaignRolloverJob } from '../jobs/campaign-rollover.job'; +import { runStatsRollupJob } from '../jobs/stats-rollup.job'; +import { runRescuerJob } from '../jobs/rescuer.job'; +import { runLoggedJob } from '../jobs/job-runner'; +import { getJobStatuses } from '../services/job-queue.service'; + +const router = Router(); + +router.get('/status', async (_req: Request, res: Response, next: NextFunction) => { + try { + const jobs = await getJobStatuses(); + res.json({ jobs }); + } catch (err) { + next(err); + } +}); + +router.post('/dispatcher/run', async (_req: Request, res: Response, next: NextFunction) => { + try { + await runLoggedJob('campaign-dispatcher', runCampaignDispatcherJob); + res.json({ ok: true, job: 'campaign-dispatcher' }); + } catch (err) { + next(err); + } +}); + +router.post('/rollover/run', async (_req: Request, res: Response, next: NextFunction) => { + try { + await runLoggedJob('campaign-rollover', async () => { + await runCampaignRolloverJob(); + }); + res.json({ ok: true, job: 'campaign-rollover' }); + } catch (err) { + next(err); + } +}); + +router.post('/rescuer/run', async (_req: Request, res: Response, next: NextFunction) => { + try { + await runLoggedJob('rescuer', async () => { + await runRescuerJob(); + }); + res.json({ ok: true, job: 'rescuer' }); + } catch (err) { + next(err); + } +}); + +router.post('/rollup/run', async (_req: Request, res: Response, next: NextFunction) => { + try { + await runLoggedJob('stats-rollup', runStatsRollupJob); + res.json({ ok: true, job: 'stats-rollup' }); + } catch (err) { + next(err); + } +}); + +export default router; diff --git a/src/routes/logs.routes.ts b/apps/api/src/routes/logs.routes.ts similarity index 87% rename from src/routes/logs.routes.ts rename to apps/api/src/routes/logs.routes.ts index c148f66..0fa05f3 100644 --- a/src/routes/logs.routes.ts +++ b/apps/api/src/routes/logs.routes.ts @@ -1,7 +1,6 @@ import { Router, Request, Response, NextFunction } from 'express'; import { z } from 'zod'; import { WarmupStatus } from '../lib/prisma'; -import type { WarmupLogWithRelations } from '../models/types'; import { prisma } from '../lib/prisma'; import { stripPassword } from '../lib/inboxGuard'; @@ -10,17 +9,19 @@ const router = Router(); const querySchema = z.object({ status: z.nativeEnum(WarmupStatus).optional(), inboxId: z.string().optional(), + campaignId: z.string().optional(), page: z.coerce.number().int().positive().default(1), limit: z.coerce.number().int().positive().max(100).default(20), }); router.get('/', async (req: Request, res: Response, next: NextFunction) => { try { - const { status, inboxId, page, limit } = querySchema.parse(req.query); + const { status, inboxId, campaignId, page, limit } = querySchema.parse(req.query); const skip = (page - 1) * limit; const where = { ...(status ? { status } : {}), + ...(campaignId ? { campaignId } : {}), ...(inboxId ? { OR: [{ senderId: inboxId }, { receiverId: inboxId }] } : {}), @@ -38,7 +39,7 @@ router.get('/', async (req: Request, res: Response, next: NextFunction) => { ]); res.json({ - data: logs.map((log: WarmupLogWithRelations) => ({ + data: logs.map((log) => ({ ...log, sender: stripPassword(log.sender), receiver: stripPassword(log.receiver), diff --git a/apps/api/src/routes/recipes.routes.ts b/apps/api/src/routes/recipes.routes.ts new file mode 100644 index 0000000..41e76a6 --- /dev/null +++ b/apps/api/src/routes/recipes.routes.ts @@ -0,0 +1,60 @@ +import { Router, Request, Response, NextFunction } from 'express'; +import { CampaignRecipe } from '../lib/prisma'; +import { recipePreviewSchema } from '../models/campaign.schemas'; +import { + CUSTOM_RECIPE_TEMPLATE, + FLAT_RECIPE_TEMPLATE, + generateSchedule, + GROW_RECIPE_TEMPLATE, + RANDOMIZED_RECIPE_TEMPLATE, + RecipeValidationError, + validateRecipeParams, +} from '../services/recipe.service'; +import { AppError } from '../middleware/errorHandler'; + +const router = Router(); + +router.post('/preview', async (req: Request, res: Response, next: NextFunction) => { + try { + const params = recipePreviewSchema.parse(req.body); + const recipeParams = { + recipe: params.recipe, + durationDays: params.durationDays, + minEmailsPerDay: params.minEmailsPerDay, + maxEmailsPerDay: params.maxEmailsPerDay, + replyRatePercent: params.replyRatePercent, + }; + validateRecipeParams(recipeParams, { + customSchedule: + params.recipe === CampaignRecipe.custom ? params.customSchedule : undefined, + }); + const schedule = generateSchedule(recipeParams, { + seed: 'preview', + customSchedule: params.customSchedule, + }); + res.json({ schedule }); + } catch (err) { + if (err instanceof RecipeValidationError) { + next(new AppError(400, err.message)); + return; + } + next(err); + } +}); + +router.get('/templates', (_req: Request, res: Response) => { + res.json({ + grow: GROW_RECIPE_TEMPLATE, + flat: FLAT_RECIPE_TEMPLATE, + randomized: RANDOMIZED_RECIPE_TEMPLATE, + custom: CUSTOM_RECIPE_TEMPLATE, + supported: [ + CampaignRecipe.grow, + CampaignRecipe.flat, + CampaignRecipe.randomized, + CampaignRecipe.custom, + ], + }); +}); + +export default router; diff --git a/src/routes/settings.routes.ts b/apps/api/src/routes/settings.routes.ts similarity index 100% rename from src/routes/settings.routes.ts rename to apps/api/src/routes/settings.routes.ts diff --git a/apps/api/src/routes/stats.routes.ts b/apps/api/src/routes/stats.routes.ts new file mode 100644 index 0000000..edbc531 --- /dev/null +++ b/apps/api/src/routes/stats.routes.ts @@ -0,0 +1,78 @@ +import { Router, Request, Response, NextFunction } from 'express'; +import { z } from 'zod'; +import { CampaignError } from '../services/campaign.service'; +import { + getCampaignStatsDaily, + getCampaignStatsSummary, + getCampaignStatsToday, + getWorkspaceOverview, + StatsRange, +} from '../services/stats.service'; +import { AppError } from '../middleware/errorHandler'; + +const router = Router(); + +const rangeSchema = z.enum(['7d', '30d', 'all']); + +function handleError(err: unknown, next: NextFunction) { + if (err instanceof CampaignError) { + next(new AppError(err.statusCode, err.message)); + return; + } + next(err); +} + +router.get('/:id/stats/today', async (req: Request, res: Response, next: NextFunction) => { + try { + const stats = await getCampaignStatsToday(req.params.id); + res.json(stats); + } catch (err) { + handleError(err, next); + } +}); + +router.get('/:id/stats/daily', async (req: Request, res: Response, next: NextFunction) => { + try { + const from = typeof req.query.from === 'string' ? req.query.from : undefined; + const to = typeof req.query.to === 'string' ? req.query.to : undefined; + const stats = await getCampaignStatsDaily(req.params.id, from, to); + res.json({ data: stats }); + } catch (err) { + handleError(err, next); + } +}); + +router.get('/:id/stats', async (req: Request, res: Response, next: NextFunction) => { + try { + const range = rangeSchema.parse(req.query.range ?? '7d') as StatsRange; + const stats = await getCampaignStatsSummary(req.params.id, range); + res.json(stats); + } catch (err) { + handleError(err, next); + } +}); + +router.get('/:id/compliance', async (req: Request, res: Response, next: NextFunction) => { + try { + const { getCampaignCompliance } = await import('../services/compliance.service'); + const report = await getCampaignCompliance(req.params.id); + res.json(report); + } catch (err) { + handleError(err, next); + } +}); + +export default router; + +export const workspaceRouter = Router(); + +workspaceRouter.get('/overview', async (req: Request, res: Response, next: NextFunction) => { + try { + const workspaceId = + typeof req.query.workspaceId === 'string' ? req.query.workspaceId : undefined; + const overview = await getWorkspaceOverview(workspaceId); + res.json(overview); + } catch (err) { + next(err); + } +}); diff --git a/apps/api/src/services/ai.service.ts b/apps/api/src/services/ai.service.ts new file mode 100644 index 0000000..fc3cb66 --- /dev/null +++ b/apps/api/src/services/ai.service.ts @@ -0,0 +1,233 @@ +import OpenAI from 'openai'; +import { config } from '../config'; +import { prisma } from '../lib/prisma'; +import { GeneratedEmail } from '../models/types'; +import { logger } from '../lib/logger'; +import { + ContentValidationError, + validateEmailContent, + ValidationResult, +} from './content-validator.service'; + +const DEFAULT_MODEL = 'openrouter/free'; +const MAX_VALIDATION_ATTEMPTS = 3; + +export type MailboxProfile = { + senderName?: string | null; + companyName?: string | null; +}; + +async function getApiKey(): Promise { + if (config.AI_API_KEY) { + return config.AI_API_KEY; + } + + const setting = await prisma.setting.findUnique({ + where: { key: 'ai_api_key' }, + }); + + if (setting?.value) { + return setting.value; + } + + const legacy = await prisma.setting.findUnique({ + where: { key: 'openai_api_key' }, + }); + if (legacy?.value) { + return legacy.value; + } + + throw new Error( + 'AI API key not configured. Set AI_API_KEY in .env (get a free key at https://openrouter.ai/keys)', + ); +} + +async function getModel(): Promise { + if (config.AI_MODEL) { + return config.AI_MODEL; + } + + const setting = await prisma.setting.findUnique({ + where: { key: 'ai_model' }, + }); + + return setting?.value ?? DEFAULT_MODEL; +} + +async function createClient(): Promise { + const apiKey = await getApiKey(); + return new OpenAI({ + apiKey, + baseURL: config.AI_BASE_URL, + defaultHeaders: config.AI_BASE_URL.includes('openrouter') + ? { + 'HTTP-Referer': 'https://github.com/warmbox', + 'X-Title': 'Warmbox Email Warmup', + } + : undefined, + }); +} + +function extractJson(content: string): GeneratedEmail { + const trimmed = content.trim(); + + try { + return parseEmailResponse(trimmed); + } catch { + const match = trimmed.match(/\{[\s\S]*\}/); + if (match) { + return parseEmailResponse(match[0]); + } + throw new Error('AI response was not valid JSON'); + } +} + +function parseEmailResponse(content: string): GeneratedEmail { + const parsed = JSON.parse(content) as { subject?: string; body?: string }; + if (!parsed.subject || !parsed.body) { + throw new Error('AI response missing subject or body'); + } + return { subject: parsed.subject.trim(), body: parsed.body.trim() }; +} + +function profileContext(profile?: MailboxProfile): string { + const parts: string[] = []; + if (profile?.senderName) parts.push(`Sender name: ${profile.senderName}`); + if (profile?.companyName) parts.push(`Company: ${profile.companyName}`); + if (parts.length === 0) { + return 'Do not use placeholder names or bracket templates. Use generic professional language without [Name] style placeholders.'; + } + return `${parts.join('. ')}. Use these real values in the signature if needed. Never use bracket placeholders.`; +} + +async function chatOnce( + client: OpenAI, + model: string, + messages: OpenAI.Chat.ChatCompletionMessageParam[], +): Promise { + const request = async (useJsonMode: boolean) => { + const response = await client.chat.completions.create({ + model, + messages, + temperature: 0.8, + ...(useJsonMode ? { response_format: { type: 'json_object' as const } } : {}), + }); + + const content = response.choices[0]?.message?.content; + if (!content) { + throw new Error('Empty AI response'); + } + return extractJson(content); + }; + + try { + return await request(true); + } catch (err) { + logger.warn({ err, model }, 'AI request failed, retrying without json mode'); + await new Promise((r) => setTimeout(r, 1000)); + return request(false); + } +} + +async function generateValidated( + buildMessages: (validationFeedback?: string) => OpenAI.Chat.ChatCompletionMessageParam[], + options: { isReply?: boolean }, +): Promise { + const client = await createClient(); + const model = await getModel(); + let lastValidation: ValidationResult | null = null; + + for (let attempt = 1; attempt <= MAX_VALIDATION_ATTEMPTS; attempt++) { + const feedback = + lastValidation && !lastValidation.valid + ? `Previous attempt failed validation: ${[...lastValidation.errors, ...lastValidation.warnings].join('; ')}. Fix these issues.` + : undefined; + + const email = await chatOnce(client, model, buildMessages(feedback)); + const validation = validateEmailContent(email.subject, email.body, { + isReply: options.isReply, + failOnWarnings: attempt >= 2, + }); + + if (validation.valid) { + return { ...email, validationResult: validation }; + } + + lastValidation = validation; + logger.warn({ attempt, errors: validation.errors }, 'AI content validation failed'); + } + + throw new ContentValidationError( + 'AI content failed validation after maximum attempts', + lastValidation ?? { valid: false, errors: ['unknown'], warnings: [] }, + ); +} + +export async function generateInitialEmail( + industry: string, + profile?: MailboxProfile, +): Promise { + const result = await generateInitialEmailWithValidation(industry, profile); + const { validationResult: _, ...email } = result; + return email; +} + +export async function generateInitialEmailWithValidation( + industry: string, + profile?: MailboxProfile, +): Promise { + return generateValidated( + (feedback) => [ + { + role: 'system', + content: `You write realistic B2B professional emails for the ${industry} industry. +Respond with ONLY valid JSON: {"subject":"...","body":"..."} +The email should sound natural, business-appropriate, and avoid spam trigger words. +Body should be 80-150 words. Subject should be concise and realistic. +No URLs, no bracket placeholders, no mustache templates. +${profileContext(profile)} +${feedback ?? ''}`, + }, + { + role: 'user', + content: 'Generate a new outbound business email.', + }, + ], + { isReply: false }, + ); +} + +export async function generateReply( + industry: string, + incomingBody: string, + threadSubject: string, + profile?: MailboxProfile, +): Promise { + const result = await generateValidated( + (feedback) => [ + { + role: 'system', + content: `You write realistic B2B professional email replies for the ${industry} industry. +Respond with ONLY valid JSON: {"subject":"...","body":"..."} +Write a natural, short reply (40-100 words) that continues the conversation. +If subject does not start with "Re:", prefix it with "Re: ". +No URLs, no bracket placeholders. +${profileContext(profile)} +${feedback ?? ''}`, + }, + { + role: 'user', + content: `Thread subject: ${threadSubject}\n\nIncoming message:\n${incomingBody}`, + }, + ], + { isReply: true }, + ); + + let { subject, body } = result; + if (!subject.toLowerCase().startsWith('re:')) { + subject = `Re: ${threadSubject.replace(/^Re:\s*/i, '')}`; + } + + const { validationResult: _, ...email } = { subject, body, validationResult: result.validationResult }; + return email; +} diff --git a/apps/api/src/services/campaign.service.ts b/apps/api/src/services/campaign.service.ts new file mode 100644 index 0000000..bda08ce --- /dev/null +++ b/apps/api/src/services/campaign.service.ts @@ -0,0 +1,654 @@ +import { + CampaignRecipe, + CampaignStatus, + MailboxRole, + WarmupCampaign, +} from '../generated/prisma/client'; +import { DateTime } from 'luxon'; +import { prisma } from '../lib/prisma'; +import { + generateMaintenanceScheduleRow, + generateSchedule, + CustomDay, + RecipeParams, + validateRecipeParams, +} from './recipe.service'; +import { resolveWorkspaceId } from './workspace.service'; + +export const MIN_SATELLITES = 2; + +/** Statuses where a mailbox is still reserved as a campaign primary */ +const PRIMARY_RESERVED_STATUSES: CampaignStatus[] = [ + CampaignStatus.draft, + CampaignStatus.active, + CampaignStatus.paused, + CampaignStatus.maintenance, +]; + +export class CampaignError extends Error { + constructor( + message: string, + public readonly statusCode = 400, + ) { + super(message); + this.name = 'CampaignError'; + } +} + +function addDays(date: Date, days: number): Date { + const result = new Date(date); + result.setDate(result.getDate() + days); + return result; +} + +function campaignRecipeParams(campaign: WarmupCampaign): RecipeParams { + return { + recipe: campaign.recipe, + durationDays: campaign.durationDays, + minEmailsPerDay: campaign.minEmailsPerDay, + maxEmailsPerDay: campaign.maxEmailsPerDay, + replyRatePercent: campaign.replyRatePercent, + }; +} + +export function parseCustomSchedule(json: string | null | undefined): CustomDay[] { + if (!json) return []; + try { + const parsed = JSON.parse(json) as CustomDay[]; + if (!Array.isArray(parsed)) { + throw new CampaignError('customSchedule must be a JSON array'); + } + return parsed; + } catch (err) { + if (err instanceof CampaignError) throw err; + throw new CampaignError('Invalid customSchedule JSON'); + } +} + +function serializeCustomSchedule(customSchedule?: CustomDay[]): string | undefined { + if (!customSchedule?.length) return undefined; + return JSON.stringify(customSchedule); +} + +function validateCampaignRecipe(campaign: WarmupCampaign, customSchedule?: CustomDay[]) { + const schedule = + customSchedule ?? parseCustomSchedule(campaign.customSchedule); + validateRecipeParams(campaignRecipeParams(campaign), { + customSchedule: campaign.recipe === CampaignRecipe.custom ? schedule : undefined, + }); +} + +async function assertPrimaryAvailable(primaryMailboxId: string, excludeCampaignId?: string) { + const existing = await prisma.warmupCampaign.findFirst({ + where: { + primaryMailboxId, + status: { in: PRIMARY_RESERVED_STATUSES }, + ...(excludeCampaignId ? { id: { not: excludeCampaignId } } : {}), + }, + select: { id: true, name: true, status: true }, + }); + if (existing) { + throw new CampaignError( + `Primary mailbox is already used by campaign "${existing.name}" (${existing.status}). ` + + `Delete or complete that campaign first, or choose a different mailbox.`, + ); + } +} + +async function assertMailboxInWorkspace(mailboxId: string, workspaceId: string, role?: MailboxRole) { + const mailbox = await prisma.inbox.findUnique({ where: { id: mailboxId } }); + if (!mailbox) { + throw new CampaignError('Mailbox not found', 404); + } + if (mailbox.workspaceId !== workspaceId) { + throw new CampaignError('Mailbox must belong to the same workspace as the campaign'); + } + if (role && mailbox.role !== role && mailbox.role !== MailboxRole.unassigned) { + // Allow promoting unassigned to target role + if (!(role === MailboxRole.satellite && mailbox.role === MailboxRole.primary)) { + // primary can only be primary; satellite can be satellite or unassigned + } + } + return mailbox; +} + +export async function createCampaign(input: { + workspaceId?: string; + primaryMailboxId: string; + name: string; + recipe?: CampaignRecipe; + durationDays?: number; + minEmailsPerDay?: number; + maxEmailsPerDay?: number; + replyRatePercent?: number; + maintenanceVolumePercent?: number; + timezone?: string; + sendWindowStart?: string; + sendWindowEnd?: string; + customSchedule?: CustomDay[]; + satelliteMailboxIds?: string[]; +}) { + const workspaceId = await resolveWorkspaceId(input.workspaceId); + const primary = await assertMailboxInWorkspace(input.primaryMailboxId, workspaceId); + + await assertPrimaryAvailable(input.primaryMailboxId); + + await prisma.inbox.update({ + where: { id: primary.id }, + data: { role: MailboxRole.primary }, + }); + + const recipe = input.recipe ?? CampaignRecipe.grow; + const draftParams: RecipeParams = { + recipe, + durationDays: input.durationDays ?? 30, + minEmailsPerDay: input.minEmailsPerDay ?? 1, + maxEmailsPerDay: input.maxEmailsPerDay ?? 40, + replyRatePercent: input.replyRatePercent ?? 30, + }; + validateRecipeParams(draftParams, { + customSchedule: recipe === CampaignRecipe.custom ? input.customSchedule : undefined, + }); + + const campaign = await prisma.warmupCampaign.create({ + data: { + workspaceId, + primaryMailboxId: input.primaryMailboxId, + name: input.name, + recipe, + durationDays: draftParams.durationDays, + minEmailsPerDay: draftParams.minEmailsPerDay, + maxEmailsPerDay: draftParams.maxEmailsPerDay, + replyRatePercent: draftParams.replyRatePercent, + maintenanceVolumePercent: input.maintenanceVolumePercent ?? 20, + timezone: input.timezone ?? 'America/Vancouver', + sendWindowStart: input.sendWindowStart ?? '08:00', + sendWindowEnd: input.sendWindowEnd ?? '18:00', + customSchedule: serializeCustomSchedule(input.customSchedule), + status: CampaignStatus.draft, + }, + include: { + primaryMailbox: true, + satellites: { include: { satelliteMailbox: true } }, + }, + }); + + if (input.satelliteMailboxIds?.length) { + for (const mailboxId of input.satelliteMailboxIds) { + await assignSatellite(campaign.id, mailboxId); + } + } + + return getCampaignById(campaign.id); +} + +export async function listCampaigns(filters: { status?: CampaignStatus; workspaceId?: string }) { + const workspaceId = filters.workspaceId + ? await resolveWorkspaceId(filters.workspaceId) + : undefined; + + return prisma.warmupCampaign.findMany({ + where: { + ...(filters.status ? { status: filters.status } : {}), + ...(workspaceId ? { workspaceId } : {}), + }, + include: { + primaryMailbox: true, + satellites: { include: { satelliteMailbox: true }, where: { active: true } }, + _count: { select: { dailySchedules: true } }, + }, + orderBy: { createdAt: 'desc' }, + }); +} + +export async function getCampaignById(id: string) { + const campaign = await prisma.warmupCampaign.findUnique({ + where: { id }, + include: { + primaryMailbox: true, + satellites: { + include: { satelliteMailbox: true }, + where: { active: true }, + }, + dailySchedules: { orderBy: { dayIndex: 'asc' } }, + }, + }); + if (!campaign) { + throw new CampaignError('Campaign not found', 404); + } + + const todaySchedule = + campaign.currentDay > 0 + ? campaign.dailySchedules.find((s) => s.dayIndex === campaign.currentDay) + : null; + + return { ...campaign, todaySchedule }; +} + +export async function updateCampaign( + id: string, + data: Partial<{ + name: string; + recipe: CampaignRecipe; + durationDays: number; + minEmailsPerDay: number; + maxEmailsPerDay: number; + replyRatePercent: number; + maintenanceVolumePercent: number; + timezone: string; + sendWindowStart: string; + sendWindowEnd: string; + customSchedule?: CustomDay[]; + }>, +) { + const campaign = await prisma.warmupCampaign.findUnique({ where: { id } }); + if (!campaign) throw new CampaignError('Campaign not found', 404); + + const activeEditable = new Set([ + 'name', + 'timezone', + 'sendWindowStart', + 'sendWindowEnd', + 'replyRatePercent', + ]); + + if (campaign.status === CampaignStatus.draft) { + const merged = { ...campaign, ...data } as WarmupCampaign; + validateCampaignRecipe(merged, data.customSchedule); + + const { customSchedule, ...rest } = data; + const updateData = { + ...rest, + ...(customSchedule !== undefined + ? { customSchedule: serializeCustomSchedule(customSchedule) } + : {}), + }; + + const updated = await prisma.warmupCampaign.update({ + where: { id }, + data: updateData, + }); + return getCampaignById(updated.id); + } + + if (campaign.status === CampaignStatus.paused) { + const merged = { ...campaign, ...data } as WarmupCampaign; + validateCampaignRecipe(merged, data.customSchedule); + + const { customSchedule, ...rest } = data; + const updated = await prisma.warmupCampaign.update({ + where: { id }, + data: { + ...rest, + ...(customSchedule !== undefined + ? { customSchedule: serializeCustomSchedule(customSchedule) } + : {}), + }, + }); + + if (campaign.currentDay > 0) { + await rebuildFutureSchedule(updated); + } + + return getCampaignById(id); + } + + if ( + campaign.status === CampaignStatus.active || + campaign.status === CampaignStatus.maintenance + ) { + const keys = Object.keys(data) as Array; + const blocked = keys.filter((key) => !activeEditable.has(key)); + if (blocked.length > 0) { + throw new CampaignError( + `Running campaigns can only update: ${[...activeEditable].join(', ')}. Cannot change: ${blocked.join(', ')}`, + ); + } + + if (data.replyRatePercent !== undefined && data.replyRatePercent > 45) { + throw new CampaignError('Reply rate cannot exceed 45%'); + } + + const updated = await prisma.warmupCampaign.update({ + where: { id }, + data: { + ...(data.name !== undefined ? { name: data.name } : {}), + ...(data.timezone !== undefined ? { timezone: data.timezone } : {}), + ...(data.sendWindowStart !== undefined ? { sendWindowStart: data.sendWindowStart } : {}), + ...(data.sendWindowEnd !== undefined ? { sendWindowEnd: data.sendWindowEnd } : {}), + ...(data.replyRatePercent !== undefined ? { replyRatePercent: data.replyRatePercent } : {}), + }, + }); + return getCampaignById(updated.id); + } + + throw new CampaignError('Completed or failed campaigns cannot be updated'); +} + +export async function deleteCampaign(id: string) { + const campaign = await prisma.warmupCampaign.findUnique({ + where: { id }, + include: { satellites: true }, + }); + if (!campaign) throw new CampaignError('Campaign not found', 404); + if (campaign.status !== CampaignStatus.draft) { + throw new CampaignError('Only draft campaigns can be deleted'); + } + + const primaryId = campaign.primaryMailboxId; + const satelliteIds = campaign.satellites.map((s) => s.satelliteMailboxId); + + await prisma.campaignSatellite.deleteMany({ where: { campaignId: id } }); + await prisma.dailySchedule.deleteMany({ where: { campaignId: id } }); + await prisma.warmupCampaign.delete({ where: { id } }); + + const otherPrimaryCampaign = await prisma.warmupCampaign.findFirst({ + where: { + primaryMailboxId: primaryId, + status: { in: PRIMARY_RESERVED_STATUSES }, + }, + }); + if (!otherPrimaryCampaign) { + await prisma.inbox.update({ + where: { id: primaryId }, + data: { role: MailboxRole.unassigned }, + }); + } + + for (const satelliteId of satelliteIds) { + const remaining = await prisma.campaignSatellite.count({ + where: { satelliteMailboxId: satelliteId }, + }); + if (remaining === 0) { + await prisma.inbox.update({ + where: { id: satelliteId }, + data: { role: MailboxRole.unassigned }, + }); + } + } +} + +export async function assignSatellite(campaignId: string, mailboxId: string, weight = 1) { + const campaign = await prisma.warmupCampaign.findUnique({ where: { id: campaignId } }); + if (!campaign) throw new CampaignError('Campaign not found', 404); + + const mailbox = await assertMailboxInWorkspace(mailboxId, campaign.workspaceId); + if (mailbox.id === campaign.primaryMailboxId) { + throw new CampaignError('Primary mailbox cannot be assigned as a satellite'); + } + + await prisma.inbox.update({ + where: { id: mailboxId }, + data: { role: MailboxRole.satellite }, + }); + + return prisma.campaignSatellite.upsert({ + where: { + campaignId_satelliteMailboxId: { campaignId, satelliteMailboxId: mailboxId }, + }, + create: { campaignId, satelliteMailboxId: mailboxId, weight, active: true }, + update: { weight, active: true }, + include: { satelliteMailbox: true }, + }); +} + +export async function unassignSatellite(campaignId: string, mailboxId: string) { + const link = await prisma.campaignSatellite.findUnique({ + where: { + campaignId_satelliteMailboxId: { campaignId, satelliteMailboxId: mailboxId }, + }, + }); + if (!link) throw new CampaignError('Satellite assignment not found', 404); + await prisma.campaignSatellite.delete({ + where: { id: link.id }, + }); + + const remaining = await prisma.campaignSatellite.count({ + where: { satelliteMailboxId: mailboxId }, + }); + if (remaining === 0) { + await prisma.inbox.update({ + where: { id: mailboxId }, + data: { role: MailboxRole.unassigned }, + }); + } +} + +export async function listSatellites(campaignId: string) { + return prisma.campaignSatellite.findMany({ + where: { campaignId, active: true }, + include: { satelliteMailbox: true }, + }); +} + +async function rebuildFutureSchedule(campaign: WarmupCampaign): Promise { + if (campaign.currentDay <= 0) return; + + const customSchedule = parseCustomSchedule(campaign.customSchedule); + const rows = generateSchedule(campaignRecipeParams(campaign), { + seed: campaign.id, + customSchedule, + }); + + const startedAt = campaign.startedAt ?? new Date(); + + await prisma.dailySchedule.deleteMany({ + where: { + campaignId: campaign.id, + dayIndex: { gt: campaign.currentDay }, + }, + }); + + const futureRows = rows.filter((row) => row.dayIndex > campaign.currentDay); + if (futureRows.length > 0) { + await prisma.dailySchedule.createMany({ + data: futureRows.map((row) => ({ + campaignId: campaign.id, + dayIndex: row.dayIndex, + date: addDays(startedAt, row.dayIndex - 1), + plannedSends: row.plannedSends, + plannedReplies: row.plannedReplies, + })), + }); + } + + const currentRow = rows.find((row) => row.dayIndex === campaign.currentDay); + if (currentRow) { + await prisma.dailySchedule.updateMany({ + where: { campaignId: campaign.id, dayIndex: campaign.currentDay }, + data: { + plannedSends: currentRow.plannedSends, + plannedReplies: currentRow.plannedReplies, + }, + }); + } + + if (campaign.startedAt) { + await prisma.warmupCampaign.update({ + where: { id: campaign.id }, + data: { endsAt: addDays(campaign.startedAt, campaign.durationDays) }, + }); + } +} + +async function persistCampaignSchedule(campaign: WarmupCampaign, startedAt: Date) { + const customSchedule = parseCustomSchedule(campaign.customSchedule); + const rows = generateSchedule(campaignRecipeParams(campaign), { + seed: campaign.id, + customSchedule, + }); + await prisma.dailySchedule.createMany({ + data: rows.map((row) => ({ + campaignId: campaign.id, + dayIndex: row.dayIndex, + date: addDays(startedAt, row.dayIndex - 1), + plannedSends: row.plannedSends, + plannedReplies: row.plannedReplies, + })), + }); +} + +export async function startCampaign(id: string) { + const campaign = await prisma.warmupCampaign.findUnique({ + where: { id }, + include: { satellites: { where: { active: true } } }, + }); + if (!campaign) throw new CampaignError('Campaign not found', 404); + if (campaign.status !== CampaignStatus.draft) { + throw new CampaignError('Only draft campaigns can be started'); + } + + validateCampaignRecipe(campaign); + + if (campaign.satellites.length < MIN_SATELLITES) { + throw new CampaignError( + `At least ${MIN_SATELLITES} satellites required to start (have ${campaign.satellites.length})`, + ); + } + + await assertPrimaryAvailable(campaign.primaryMailboxId, id); + + const startedAt = new Date(); + const endsAt = addDays(startedAt, campaign.durationDays); + + await prisma.dailySchedule.deleteMany({ where: { campaignId: id } }); + await persistCampaignSchedule(campaign, startedAt); + + return prisma.warmupCampaign.update({ + where: { id }, + data: { + status: CampaignStatus.active, + startedAt, + endsAt, + currentDay: 1, + pausedFromStatus: null, + }, + include: { + primaryMailbox: true, + satellites: { include: { satelliteMailbox: true }, where: { active: true } }, + dailySchedules: { orderBy: { dayIndex: 'asc' } }, + }, + }); +} + +export async function pauseCampaign(id: string) { + const campaign = await prisma.warmupCampaign.findUnique({ where: { id } }); + if (!campaign) throw new CampaignError('Campaign not found', 404); + if (campaign.status !== CampaignStatus.active && campaign.status !== CampaignStatus.maintenance) { + throw new CampaignError('Only active or maintenance campaigns can be paused'); + } + + await prisma.warmupCampaign.update({ + where: { id }, + data: { + status: CampaignStatus.paused, + pausedFromStatus: campaign.status, + }, + }); + + return getCampaignById(id); +} + +export async function resumeCampaign(id: string) { + const campaign = await prisma.warmupCampaign.findUnique({ where: { id } }); + if (!campaign) throw new CampaignError('Campaign not found', 404); + if (campaign.status !== CampaignStatus.paused) { + throw new CampaignError('Only paused campaigns can be resumed'); + } + + const resumeTo = campaign.pausedFromStatus ?? CampaignStatus.active; + + await prisma.warmupCampaign.update({ + where: { id }, + data: { + status: resumeTo, + pausedFromStatus: null, + }, + }); + + return getCampaignById(id); +} + +export async function transitionToMaintenance(campaignId: string) { + const campaign = await prisma.warmupCampaign.findUnique({ + where: { id: campaignId }, + include: { dailySchedules: { orderBy: { dayIndex: 'desc' }, take: 1 } }, + }); + if (!campaign) throw new CampaignError('Campaign not found', 404); + if (campaign.status !== CampaignStatus.active) { + throw new CampaignError('Only active campaigns can transition to maintenance'); + } + + const nextDayIndex = (campaign.dailySchedules[0]?.dayIndex ?? campaign.durationDays) + 1; + const maintenanceRow = generateMaintenanceScheduleRow( + nextDayIndex, + campaign.maxEmailsPerDay, + campaign.replyRatePercent, + campaign.maintenanceVolumePercent, + ); + + const startedAt = campaign.startedAt ?? new Date(); + await prisma.dailySchedule.create({ + data: { + campaignId, + dayIndex: maintenanceRow.dayIndex, + date: addDays(startedAt, maintenanceRow.dayIndex - 1), + plannedSends: maintenanceRow.plannedSends, + plannedReplies: maintenanceRow.plannedReplies, + }, + }); + + return prisma.warmupCampaign.update({ + where: { id: campaignId }, + data: { + status: CampaignStatus.maintenance, + currentDay: nextDayIndex, + }, + include: { + dailySchedules: { orderBy: { dayIndex: 'desc' }, take: 1 }, + }, + }); +} + +export async function hasAnyCampaign(): Promise { + const count = await prisma.warmupCampaign.count(); + return count > 0; +} + +export async function syncCampaignDay(campaignId: string): Promise { + const campaign = await prisma.warmupCampaign.findUnique({ where: { id: campaignId } }); + if (!campaign || campaign.currentDay === 0) return; + if (campaign.status !== CampaignStatus.active && campaign.status !== CampaignStatus.maintenance) { + return; + } + + const schedule = await prisma.dailySchedule.findUnique({ + where: { + campaignId_dayIndex: { campaignId, dayIndex: campaign.currentDay }, + }, + }); + if (!schedule?.date) return; + + const today = DateTime.now().setZone(campaign.timezone).startOf('day'); + const scheduleDay = DateTime.fromJSDate(schedule.date).setZone(campaign.timezone).startOf('day'); + if (today <= scheduleDay) return; + + if (campaign.currentDay >= campaign.durationDays && campaign.status === CampaignStatus.active) { + await transitionToMaintenance(campaignId); + return; + } + + const nextDay = campaign.currentDay + 1; + const nextSchedule = await prisma.dailySchedule.findUnique({ + where: { + campaignId_dayIndex: { campaignId, dayIndex: nextDay }, + }, + }); + + if (nextSchedule) { + await prisma.warmupCampaign.update({ + where: { id: campaignId }, + data: { currentDay: nextDay }, + }); + } +} diff --git a/apps/api/src/services/compliance.service.ts b/apps/api/src/services/compliance.service.ts new file mode 100644 index 0000000..212408a --- /dev/null +++ b/apps/api/src/services/compliance.service.ts @@ -0,0 +1,169 @@ +import { CampaignRecipe, MailboxProvider } from '../lib/prisma'; +import { prisma } from '../lib/prisma'; +import { checkDomainAge } from '../lib/domain-age'; +import { MIN_SATELLITES, CampaignError } from './campaign.service'; +import { checkMailboxDns } from '../lib/dns-check'; +import { checkDomainInPostmaster } from './postmaster.service'; + +export type ComplianceStatus = 'pass' | 'warn' | 'fail' | 'na'; + +export type ComplianceCheckItem = { + id: string; + label: string; + status: ComplianceStatus; + message: string; + actionPath?: string; + actionLabel?: string; +}; + +export type ComplianceReport = { + campaignId: string; + overall: ComplianceStatus; + checks: ComplianceCheckItem[]; +}; + +function worstStatus(statuses: ComplianceStatus[]): ComplianceStatus { + if (statuses.includes('fail')) return 'fail'; + if (statuses.includes('warn')) return 'warn'; + return 'pass'; +} + +function extractDomain(email: string): string { + return email.split('@')[1]!.toLowerCase(); +} + +export async function getCampaignCompliance(campaignId: string): Promise { + const campaign = await prisma.warmupCampaign.findUnique({ + where: { id: campaignId }, + include: { + primaryMailbox: true, + satellites: { where: { active: true } }, + }, + }); + + if (!campaign) { + throw new CampaignError('Campaign not found', 404); + } + + const checks: ComplianceCheckItem[] = []; + + checks.push({ + id: 'reply_rate_cap', + label: 'Reply rate ≤ 45%', + status: campaign.replyRatePercent <= 45 ? 'pass' : 'fail', + message: `Configured at ${campaign.replyRatePercent}%`, + }); + + const satelliteCount = campaign.satellites.length; + checks.push({ + id: 'satellite_pool', + label: 'Satellite pool size', + status: + satelliteCount >= 4 ? 'pass' : satelliteCount >= MIN_SATELLITES ? 'warn' : 'fail', + message: + satelliteCount >= 4 + ? `${satelliteCount} satellites assigned` + : `${satelliteCount} satellites (recommend ≥4, minimum ${MIN_SATELLITES})`, + }); + + if (campaign.recipe === CampaignRecipe.grow) { + checks.push({ + id: 'grow_duration', + label: 'Grow recipe duration', + status: campaign.durationDays >= 30 ? 'pass' : 'warn', + message: + campaign.durationDays >= 30 + ? `${campaign.durationDays} days (recommended ≥30 for production)` + : `${campaign.durationDays} days — shorter ramps are fine for testing; use ≥30 days for production`, + }); + } + + checks.push({ + id: 'send_window', + label: 'Send window configured', + status: + campaign.sendWindowStart && campaign.sendWindowEnd ? 'pass' : 'warn', + message: `${campaign.sendWindowStart}–${campaign.sendWindowEnd} (${campaign.timezone})`, + }); + + checks.push({ + id: 'ai_validation', + label: 'AI content validation enabled', + status: 'pass', + message: 'Content validator active on all AI-generated emails', + }); + + try { + const dns = await checkMailboxDns(campaign.primaryMailbox.email); + const dnsStatus = worstStatus([dns.spf, dns.dkim, dns.dmarc]); + checks.push({ + id: 'dns_auth', + label: 'SPF / DKIM / DMARC', + status: dnsStatus, + message: dns.details.join('; '), + }); + } catch (err) { + checks.push({ + id: 'dns_auth', + label: 'SPF / DKIM / DMARC', + status: 'warn', + message: err instanceof Error ? err.message : 'DNS lookup failed', + }); + } + + const domain = extractDomain(campaign.primaryMailbox.email); + try { + const age = await checkDomainAge(domain); + if (age.ageDays === null) { + checks.push({ + id: 'domain_age', + label: 'Domain age ≥ 14 days', + status: 'warn', + message: `Could not determine registration date for ${domain} via RDAP`, + }); + } else { + checks.push({ + id: 'domain_age', + label: 'Domain age ≥ 14 days', + status: age.ageDays >= 14 ? 'pass' : 'warn', + message: `Registered ${age.ageDays} day(s) ago (${age.registeredAt?.slice(0, 10)})`, + }); + } + } catch (err) { + checks.push({ + id: 'domain_age', + label: 'Domain age ≥ 14 days', + status: 'warn', + message: err instanceof Error ? err.message : 'Domain age lookup failed', + }); + } + + const isGooglePrimary = + campaign.primaryMailbox.provider === MailboxProvider.google_workspace || + campaign.primaryMailbox.provider === MailboxProvider.gmail; + + if (isGooglePrimary) { + const postmaster = await checkDomainInPostmaster(domain); + checks.push({ + id: 'postmaster_tools', + label: 'Google Postmaster Tools', + status: postmaster.status, + message: postmaster.message, + actionPath: postmaster.actionPath, + actionLabel: postmaster.connected ? 'Review in Settings' : 'Connect in Settings', + }); + } else { + checks.push({ + id: 'postmaster_tools', + label: 'Google Postmaster Tools', + status: 'na', + message: 'Not required for non-Google primary mailboxes', + }); + } + + return { + campaignId, + overall: worstStatus(checks.map((c) => c.status).filter((s) => s !== 'na')), + checks, + }; +} diff --git a/apps/api/src/services/content-validator.service.test.ts b/apps/api/src/services/content-validator.service.test.ts new file mode 100644 index 0000000..7a1cc5d --- /dev/null +++ b/apps/api/src/services/content-validator.service.test.ts @@ -0,0 +1,37 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { validateEmailContent } from '../services/content-validator.service'; + +const VALID_BODY = + 'Hi there, I wanted to follow up on our conversation about streamlining operations for your team. ' + + 'We have been reviewing options that could reduce manual coordination and improve response times across departments. ' + + 'Would you be open to a short call next week to discuss priorities and next steps?'; + +describe('content-validator.service', () => { + it('accepts realistic B2B content', () => { + const result = validateEmailContent('Following up on our discussion', VALID_BODY); + assert.equal(result.valid, true); + assert.equal(result.errors.length, 0); + }); + + it('rejects bracket placeholders', () => { + const result = validateEmailContent('Hello', 'Dear [Director name], please review this.'); + assert.equal(result.valid, false); + assert.ok(result.errors.some((e) => e.includes('placeholder'))); + }); + + it('rejects mustache placeholders', () => { + const result = validateEmailContent('Hello', 'Dear {{company}}, thanks for your time.'); + assert.equal(result.valid, false); + }); + + it('rejects tier-1 spam phrases', () => { + const result = validateEmailContent('Act now', VALID_BODY); + assert.equal(result.valid, false); + }); + + it('rejects URLs in body', () => { + const result = validateEmailContent('Hello', 'Please see https://example.com for details.'); + assert.equal(result.valid, false); + }); +}); diff --git a/apps/api/src/services/content-validator.service.ts b/apps/api/src/services/content-validator.service.ts new file mode 100644 index 0000000..b17473b --- /dev/null +++ b/apps/api/src/services/content-validator.service.ts @@ -0,0 +1,108 @@ +export type ValidationResult = { + valid: boolean; + errors: string[]; + warnings: string[]; +}; + +export class ContentValidationError extends Error { + constructor( + message: string, + public readonly validation: ValidationResult, + ) { + super(message); + this.name = 'ContentValidationError'; + } +} + +const PLACEHOLDER_PATTERNS = [ + { pattern: /\[.*?\]/i, message: 'Bracket placeholder detected' }, + { pattern: /\{\{.*?\}\}/, message: 'Mustache placeholder detected' }, + { pattern: /\[Your .*?\]/i, message: 'Template placeholder detected' }, + { pattern: /\[Director .*?\]/i, message: 'Template placeholder detected' }, + { pattern: /\[Company .*?\]/i, message: 'Template placeholder detected' }, + { pattern: /INSERT .* HERE/i, message: 'Template placeholder detected' }, + { pattern: /\bTODO\b/, message: 'TODO placeholder detected' }, + { pattern: /lorem ipsum/i, message: 'Lorem ipsum placeholder detected' }, +]; + +const TIER1_SPAM = [ + 'act now', + '100% free', + 'risk-free', + 'free money', + 'click here', + 'limited time', + 'winner', + 'viagra', + 'casino', + 'lottery', + 'guarantee', +]; + +const TIER2_SPAM = [ + 'urgent', + 'limited', + 'offer', + 'discount', + 'sale', + 'promotion', + 'buy now', +]; + +const URL_PATTERN = /https?:\/\//i; + +function wordCount(text: string): number { + return text.trim().split(/\s+/).filter(Boolean).length; +} + +export type ValidateOptions = { + isReply?: boolean; + failOnWarnings?: boolean; +}; + +export function validateEmailContent( + subject: string, + body: string, + options: ValidateOptions = {}, +): ValidationResult { + const errors: string[] = []; + const warnings: string[] = []; + const combined = `${subject}\n${body}`; + + for (const { pattern, message } of PLACEHOLDER_PATTERNS) { + if (pattern.test(combined)) { + errors.push(message); + } + } + + if (URL_PATTERN.test(combined)) { + errors.push('URLs are not allowed in warmup content'); + } + + if (subject.length > 60) { + errors.push('Subject exceeds 60 characters'); + } + + const minWords = options.isReply ? 20 : 40; + const maxWords = options.isReply ? 120 : 200; + const words = wordCount(body); + if (words < minWords || words > maxWords) { + errors.push(`Body word count ${words} outside allowed range ${minWords}-${maxWords}`); + } + + const lower = combined.toLowerCase(); + for (const phrase of TIER1_SPAM) { + if (lower.includes(phrase)) { + errors.push(`Spam trigger phrase: "${phrase}"`); + } + } + for (const phrase of TIER2_SPAM) { + if (lower.includes(phrase)) { + warnings.push(`Soft spam phrase: "${phrase}"`); + } + } + + const valid = errors.length === 0 && (!options.failOnWarnings || warnings.length === 0); + + return { valid, errors, warnings }; +} diff --git a/src/services/crypto.service.ts b/apps/api/src/services/crypto.service.ts similarity index 100% rename from src/services/crypto.service.ts rename to apps/api/src/services/crypto.service.ts diff --git a/src/services/email.service.ts b/apps/api/src/services/email.service.ts similarity index 53% rename from src/services/email.service.ts rename to apps/api/src/services/email.service.ts index bebc6c8..334353c 100644 --- a/src/services/email.service.ts +++ b/apps/api/src/services/email.service.ts @@ -1,15 +1,46 @@ import nodemailer, { Transporter } from 'nodemailer'; import type { Inbox } from '../lib/prisma'; +import { MailboxAuthType } from '../lib/prisma'; import { decrypt } from './crypto.service'; import { EmailPayload } from '../models/types'; +import { resolveMailboxAccessToken } from './mailbox-auth.service'; +import { formatMicrosoftAuthError } from './microsoft-oauth.service'; function getPassword(inbox: Inbox): string { return decrypt(inbox.password); } -export function createSmtpTransport(inbox: Inbox): Transporter { +export async function createSmtpTransport(inbox: Inbox): Promise { const secure = inbox.smtpPort === 465; + if (inbox.authType === MailboxAuthType.microsoft_oauth) { + const accessToken = await resolveMailboxAccessToken(inbox); + const { clientId, clientSecret } = await import('./microsoft-oauth.service').then((m) => + m.getMicrosoftOAuthSettings(), + ); + if (!clientId || !clientSecret || !inbox.oauthRefreshToken) { + throw new Error('Microsoft OAuth is not configured for this mailbox'); + } + + return nodemailer.createTransport({ + host: inbox.smtpHost, + port: inbox.smtpPort, + secure, + requireTLS: !secure && inbox.smtpPort === 587, + auth: { + type: 'OAuth2', + user: inbox.username, + clientId, + clientSecret, + refreshToken: decrypt(inbox.oauthRefreshToken), + accessUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token', + accessToken, + }, + connectionTimeout: 30_000, + greetingTimeout: 30_000, + } as nodemailer.TransportOptions); + } + return nodemailer.createTransport({ host: inbox.smtpHost, port: inbox.smtpPort, @@ -27,14 +58,15 @@ export function createSmtpTransport(inbox: Inbox): Transporter { export async function testSmtp( inbox: Inbox, ): Promise<{ ok: boolean; error?: string }> { - const transporter = createSmtpTransport(inbox); + const transporter = await createSmtpTransport(inbox); try { await transporter.verify(); return { ok: true }; } catch (err) { + const message = err instanceof Error ? err.message : 'SMTP verification failed'; return { ok: false, - error: err instanceof Error ? err.message : 'SMTP verification failed', + error: formatMicrosoftAuthError(message), }; } finally { transporter.close(); @@ -45,7 +77,7 @@ export async function sendMail( inbox: Inbox, payload: EmailPayload, ): Promise<{ messageId: string }> { - const transporter = createSmtpTransport(inbox); + const transporter = await createSmtpTransport(inbox); try { const info = await transporter.sendMail({ diff --git a/src/services/imap.service.ts b/apps/api/src/services/imap.service.ts similarity index 54% rename from src/services/imap.service.ts rename to apps/api/src/services/imap.service.ts index 9fc95f7..6269fb6 100644 --- a/src/services/imap.service.ts +++ b/apps/api/src/services/imap.service.ts @@ -1,6 +1,9 @@ import { ImapFlow } from 'imapflow'; import type { Inbox } from '../lib/prisma'; +import { MailboxAuthType } from '../lib/prisma'; import { decrypt } from './crypto.service'; +import { resolveMailboxAccessToken } from './mailbox-auth.service'; +import { formatMicrosoftAuthError } from './microsoft-oauth.service'; const CONNECTION_TIMEOUT = 30_000; @@ -8,9 +11,28 @@ function getPassword(inbox: Inbox): string { return decrypt(inbox.password); } -export function createImapClient(inbox: Inbox): ImapFlow { +export async function createImapClient(inbox: Inbox): Promise { const secure = inbox.imapPort === 993; + if (inbox.authType === MailboxAuthType.microsoft_oauth) { + const accessToken = await resolveMailboxAccessToken(inbox); + if (!accessToken) { + throw new Error('Microsoft OAuth access token could not be resolved'); + } + return new ImapFlow({ + host: inbox.imapHost, + port: inbox.imapPort, + secure, + auth: { + user: inbox.username, + accessToken, + }, + logger: false, + connectionTimeout: CONNECTION_TIMEOUT, + greetingTimeout: CONNECTION_TIMEOUT, + }); + } + return new ImapFlow({ host: inbox.imapHost, port: inbox.imapPort, @@ -28,15 +50,16 @@ export function createImapClient(inbox: Inbox): ImapFlow { export async function testImap( inbox: Inbox, ): Promise<{ ok: boolean; error?: string }> { - const client = createImapClient(inbox); + const client = await createImapClient(inbox); try { await client.connect(); await client.mailboxOpen('INBOX'); return { ok: true }; } catch (err) { + const message = err instanceof Error ? err.message : 'IMAP connection failed'; return { ok: false, - error: err instanceof Error ? err.message : 'IMAP connection failed', + error: formatMicrosoftAuthError(message), }; } finally { try { @@ -51,7 +74,7 @@ export async function withImapClient( inbox: Inbox, fn: (client: ImapFlow) => Promise, ): Promise { - const client = createImapClient(inbox); + const client = await createImapClient(inbox); await client.connect(); try { return await fn(client); diff --git a/apps/api/src/services/job-queue.service.ts b/apps/api/src/services/job-queue.service.ts new file mode 100644 index 0000000..790d218 --- /dev/null +++ b/apps/api/src/services/job-queue.service.ts @@ -0,0 +1,111 @@ +import { SendJobStatus, prisma } from '../lib/prisma'; +import { logger } from '../lib/logger'; + +export async function logJobRun( + jobName: string, + success: boolean, + message?: string, +): Promise { + await prisma.jobRunLog.create({ + data: { jobName, success, message }, + }); +} + +export async function getJobStatuses(): Promise< + Array<{ jobName: string; lastRunAt: string | null; success: boolean | null; message: string | null }> +> { + const jobNames = [ + 'campaign-dispatcher', + 'rescuer', + 'campaign-rollover', + 'stats-rollup', + ]; + + const results = []; + for (const jobName of jobNames) { + const last = await prisma.jobRunLog.findFirst({ + where: { jobName }, + orderBy: { ranAt: 'desc' }, + }); + results.push({ + jobName, + lastRunAt: last?.ranAt.toISOString() ?? null, + success: last?.success ?? null, + message: last?.message ?? null, + }); + } + + const pendingSends = await prisma.sendJob.count({ + where: { status: SendJobStatus.pending }, + }); + + return results.map((row) => + row.jobName === 'campaign-dispatcher' + ? { ...row, message: row.message ?? `${pendingSends} queued sends` } + : row, + ); +} + +export async function enqueueSendJob( + campaignId: string, + scheduledAt: Date = new Date(), +): Promise { + const dayKey = scheduledAt.toISOString().slice(0, 10); + const idempotencyKey = `${campaignId}:${dayKey}:${scheduledAt.getTime()}`; + + const existing = await prisma.sendJob.findUnique({ + where: { idempotencyKey }, + }); + if (existing) { + return existing.id; + } + + const job = await prisma.sendJob.create({ + data: { + campaignId, + scheduledAt, + idempotencyKey, + status: SendJobStatus.pending, + }, + }); + + logger.debug({ campaignId, jobId: job.id }, 'Send job enqueued'); + return job.id; +} + +export async function claimDueSendJob(campaignId: string) { + const now = new Date(); + const job = await prisma.sendJob.findFirst({ + where: { + campaignId, + status: SendJobStatus.pending, + scheduledAt: { lte: now }, + }, + orderBy: { scheduledAt: 'asc' }, + }); + + if (!job) return null; + + const updated = await prisma.sendJob.updateMany({ + where: { id: job.id, status: SendJobStatus.pending }, + data: { status: SendJobStatus.processing }, + }); + + if (updated.count === 0) return null; + return job; +} + +export async function completeSendJob( + jobId: string, + success: boolean, + errorMessage?: string, +): Promise { + await prisma.sendJob.update({ + where: { id: jobId }, + data: { + status: success ? SendJobStatus.completed : SendJobStatus.failed, + processedAt: new Date(), + errorMessage, + }, + }); +} diff --git a/apps/api/src/services/mailbox-auth.service.ts b/apps/api/src/services/mailbox-auth.service.ts new file mode 100644 index 0000000..15d6c35 --- /dev/null +++ b/apps/api/src/services/mailbox-auth.service.ts @@ -0,0 +1,14 @@ +import type { Inbox } from '../lib/prisma'; +import { MailboxAuthType } from '../lib/prisma'; +import { decrypt } from './crypto.service'; +import { getMicrosoftAccessToken } from './microsoft-oauth.service'; + +export async function resolveMailboxAccessToken(inbox: Inbox): Promise { + if (inbox.authType !== MailboxAuthType.microsoft_oauth) { + return null; + } + if (!inbox.oauthRefreshToken) { + throw new Error('Microsoft OAuth refresh token is missing for this mailbox'); + } + return getMicrosoftAccessToken(decrypt(inbox.oauthRefreshToken)); +} diff --git a/apps/api/src/services/microsoft-oauth.service.ts b/apps/api/src/services/microsoft-oauth.service.ts new file mode 100644 index 0000000..08f1f93 --- /dev/null +++ b/apps/api/src/services/microsoft-oauth.service.ts @@ -0,0 +1,121 @@ +import { config } from '../config'; +import { getSetting } from './settings.service'; + +const MICROSOFT_SCOPES = [ + 'https://outlook.office.com/IMAP.AccessAsUser.All', + 'https://outlook.office.com/SMTP.Send', + 'offline_access', + 'openid', +].join(' '); + +const TOKEN_URL = 'https://login.microsoftonline.com/common/oauth2/v2.0/token'; + +export function isMicrosoftConsumerEmail(email: string): boolean { + const domain = email.split('@')[1]?.toLowerCase() ?? ''; + return ['live.com', 'outlook.com', 'hotmail.com', 'msn.com'].includes(domain); +} + +function getRedirectUri(): string { + const base = config.PUBLIC_BASE_URL ?? `http://localhost:${config.PORT}`; + return `${base.replace(/\/$/, '')}/api/integrations/microsoft/callback`; +} + +export async function getMicrosoftOAuthSettings(): Promise<{ + clientId: string | null; + clientSecret: string | null; +}> { + const [clientId, clientSecret] = await Promise.all([ + getSetting('microsoft_oauth_client_id'), + getSetting('microsoft_oauth_client_secret'), + ]); + return { clientId, clientSecret }; +} + +export function buildMicrosoftAuthUrl(clientId: string, state?: string): string { + const params = new URLSearchParams({ + client_id: clientId, + redirect_uri: getRedirectUri(), + response_type: 'code', + scope: MICROSOFT_SCOPES, + response_mode: 'query', + ...(state ? { state } : {}), + }); + return `https://login.microsoftonline.com/common/oauth2/v2.0/authorize?${params.toString()}`; +} + +export async function exchangeMicrosoftAuthCode(code: string): Promise<{ + refreshToken: string; + accessToken: string; +}> { + const { clientId, clientSecret } = await getMicrosoftOAuthSettings(); + if (!clientId || !clientSecret) { + throw new Error('Microsoft OAuth client ID and secret must be configured first'); + } + + const response = await fetch(TOKEN_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + client_id: clientId, + client_secret: clientSecret, + code, + redirect_uri: getRedirectUri(), + grant_type: 'authorization_code', + scope: MICROSOFT_SCOPES, + }), + }); + + const data = (await response.json()) as { + refresh_token?: string; + access_token?: string; + error_description?: string; + error?: string; + }; + + if (!response.ok || !data.refresh_token || !data.access_token) { + throw new Error(data.error_description ?? data.error ?? 'Microsoft token exchange failed'); + } + + return { refreshToken: data.refresh_token, accessToken: data.access_token }; +} + +export async function getMicrosoftAccessToken(refreshToken: string): Promise { + const { clientId, clientSecret } = await getMicrosoftOAuthSettings(); + if (!clientId || !clientSecret) { + throw new Error('Microsoft OAuth is not configured'); + } + + const response = await fetch(TOKEN_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + client_id: clientId, + client_secret: clientSecret, + refresh_token: refreshToken, + grant_type: 'refresh_token', + scope: MICROSOFT_SCOPES, + }), + }); + + const data = (await response.json()) as { + access_token?: string; + error_description?: string; + error?: string; + }; + + if (!response.ok || !data.access_token) { + throw new Error(data.error_description ?? data.error ?? 'Microsoft token refresh failed'); + } + + return data.access_token; +} + +export function formatMicrosoftAuthError(error: string): string { + if (error.includes('basic authentication is disabled') || error.includes('5.7.139')) { + return ( + 'Microsoft disabled basic password login for this account. Use "Connect Microsoft" in the mailbox ' + + 'form instead of an app password. Register OAuth credentials in Settings → Microsoft Outlook.' + ); + } + return error; +} diff --git a/apps/api/src/services/postmaster.service.ts b/apps/api/src/services/postmaster.service.ts new file mode 100644 index 0000000..d468878 --- /dev/null +++ b/apps/api/src/services/postmaster.service.ts @@ -0,0 +1,212 @@ +import { config } from '../config'; +import { getSetting } from './settings.service'; + +const POSTMASTER_SCOPE = 'https://www.googleapis.com/auth/postmaster.readonly'; +const TOKEN_URL = 'https://oauth2.googleapis.com/token'; +const DOMAINS_URL = 'https://gmailpostmastertools.googleapis.com/v1/domains'; + +export type PostmasterStatus = { + configured: boolean; + connected: boolean; + domains: string[]; + message: string; +}; + +export type PostmasterDomainCheck = { + applicable: boolean; + connected: boolean; + listed: boolean; + status: 'pass' | 'warn' | 'fail' | 'na'; + message: string; + actionPath?: string; +}; + +function getRedirectUri(): string { + const base = config.PUBLIC_BASE_URL ?? `http://localhost:${config.PORT}`; + return `${base.replace(/\/$/, '')}/api/integrations/google/callback`; +} + +export async function getGoogleOAuthSettings(): Promise<{ + clientId: string | null; + clientSecret: string | null; + refreshToken: string | null; +}> { + const [clientId, clientSecret, refreshToken] = await Promise.all([ + getSetting('google_oauth_client_id'), + getSetting('google_oauth_client_secret'), + getSetting('google_postmaster_refresh_token'), + ]); + + return { clientId, clientSecret, refreshToken }; +} + +export function buildGoogleAuthUrl(clientId: string): string { + const params = new URLSearchParams({ + client_id: clientId, + redirect_uri: getRedirectUri(), + response_type: 'code', + scope: POSTMASTER_SCOPE, + access_type: 'offline', + prompt: 'consent', + }); + + return `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`; +} + +export async function exchangeGoogleAuthCode(code: string): Promise { + const { clientId, clientSecret } = await getGoogleOAuthSettings(); + if (!clientId || !clientSecret) { + throw new Error('Google OAuth client ID and secret must be configured first'); + } + + const response = await fetch(TOKEN_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + code, + client_id: clientId, + client_secret: clientSecret, + redirect_uri: getRedirectUri(), + grant_type: 'authorization_code', + }), + }); + + const data = (await response.json()) as { + refresh_token?: string; + error?: string; + error_description?: string; + }; + + if (!response.ok || !data.refresh_token) { + throw new Error(data.error_description ?? data.error ?? 'Failed to exchange Google auth code'); + } + + return data.refresh_token; +} + +async function getAccessToken(): Promise { + const { clientId, clientSecret, refreshToken } = await getGoogleOAuthSettings(); + if (!clientId || !clientSecret || !refreshToken) { + return null; + } + + const response = await fetch(TOKEN_URL, { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + client_id: clientId, + client_secret: clientSecret, + refresh_token: refreshToken, + grant_type: 'refresh_token', + }), + }); + + const data = (await response.json()) as { access_token?: string; error?: string }; + if (!response.ok || !data.access_token) { + return null; + } + + return data.access_token; +} + +export async function getPostmasterStatus(): Promise { + const { clientId, clientSecret, refreshToken } = await getGoogleOAuthSettings(); + const configured = Boolean(clientId && clientSecret); + + if (!configured) { + return { + configured: false, + connected: false, + domains: [], + message: 'Add Google OAuth client ID and secret in Settings, then connect Postmaster Tools.', + }; + } + + if (!refreshToken) { + return { + configured: true, + connected: false, + domains: [], + message: 'OAuth credentials saved. Click Connect Google Postmaster to authorize.', + }; + } + + const accessToken = await getAccessToken(); + if (!accessToken) { + return { + configured: true, + connected: false, + domains: [], + message: 'Stored refresh token is invalid. Reconnect Google Postmaster.', + }; + } + + const response = await fetch(DOMAINS_URL, { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + + if (!response.ok) { + return { + configured: true, + connected: false, + domains: [], + message: 'Google Postmaster API call failed. Verify API is enabled and reconnect.', + }; + } + + const data = (await response.json()) as { domains?: Array<{ name?: string }> }; + const domains = + data.domains?.map((row) => row.name).filter((name): name is string => Boolean(name)) ?? []; + + return { + configured: true, + connected: true, + domains, + message: + domains.length > 0 + ? `Connected — ${domains.length} domain(s) visible in Postmaster Tools` + : 'Connected, but no domains found yet. Verify your domain in Postmaster Tools.', + }; +} + +export async function checkDomainInPostmaster(domain: string): Promise { + const status = await getPostmasterStatus(); + + if (!status.configured) { + return { + applicable: true, + connected: false, + listed: false, + status: 'warn', + message: status.message, + actionPath: '/settings#google-postmaster', + }; + } + + if (!status.connected) { + return { + applicable: true, + connected: false, + listed: false, + status: 'warn', + message: status.message, + actionPath: '/settings#google-postmaster', + }; + } + + const normalized = domain.toLowerCase(); + const listed = status.domains.some( + (name) => name.toLowerCase() === normalized || normalized.endsWith(`.${name.toLowerCase()}`), + ); + + return { + applicable: true, + connected: true, + listed, + status: listed ? 'pass' : 'fail', + message: listed + ? `Domain ${domain} is registered in Google Postmaster Tools` + : `Domain ${domain} not found in Postmaster Tools. Add and verify it at postmaster.google.com`, + actionPath: listed ? undefined : '/settings#google-postmaster', + }; +} diff --git a/apps/api/src/services/recipe.service.test.ts b/apps/api/src/services/recipe.service.test.ts new file mode 100644 index 0000000..ac2b21d --- /dev/null +++ b/apps/api/src/services/recipe.service.test.ts @@ -0,0 +1,144 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { CampaignRecipe } from '../lib/prisma'; +import { + apply48hCap, + generateCustomSchedule, + generateFlatSchedule, + generateGrowSchedule, + generateMaintenanceDailySends, + generateRandomizedSchedule, + generateSchedule, + GROW_RECIPE_TEMPLATE, + RecipeValidationError, + validateRecipeParams, +} from '../services/recipe.service'; + +describe('recipe.service', () => { + it('generates grow schedule starting at min and ending at or below max', () => { + const schedule = generateGrowSchedule(GROW_RECIPE_TEMPLATE); + assert.equal(schedule.length, 30); + assert.equal(schedule[0]!.plannedSends, 1); + assert.ok(schedule[29]!.plannedSends <= 40); + + for (let i = 1; i < schedule.length; i++) { + assert.ok(schedule[i]!.plannedSends >= schedule[i - 1]!.plannedSends); + } + }); + + it('applies 48h cap on grow schedule output', () => { + const schedule = generateGrowSchedule(GROW_RECIPE_TEMPLATE).map((r) => r.plannedSends); + for (let i = 2; i < schedule.length; i++) { + const avg = (schedule[i - 1]! + schedule[i - 2]!) / 2; + assert.ok(schedule[i]! <= Math.ceil(avg * 1.2) + 0.001); + } + }); + + it('generates flat schedule at max volume every day', () => { + const schedule = generateFlatSchedule({ + ...GROW_RECIPE_TEMPLATE, + recipe: CampaignRecipe.flat, + durationDays: 30, + maxEmailsPerDay: 40, + }); + assert.equal(schedule.length, 30); + for (const row of schedule) { + assert.equal(row.plannedSends, 40); + } + }); + + it('generates deterministic randomized schedule for same seed', () => { + const params = { + ...GROW_RECIPE_TEMPLATE, + recipe: CampaignRecipe.randomized, + }; + const a = generateRandomizedSchedule(params, 'campaign-abc'); + const b = generateRandomizedSchedule(params, 'campaign-abc'); + assert.deepEqual( + a.map((r) => r.plannedSends), + b.map((r) => r.plannedSends), + ); + + for (const row of a) { + assert.ok(row.plannedSends >= 1); + assert.ok(row.plannedSends <= 40); + } + }); + + it('generates custom schedule with hold-last for missing days', () => { + const params = { + ...GROW_RECIPE_TEMPLATE, + recipe: CampaignRecipe.custom, + durationDays: 14, + minEmailsPerDay: 1, + maxEmailsPerDay: 10, + }; + const schedule = generateCustomSchedule(params, [ + { day: 1, sends: 2 }, + { day: 2, sends: 5 }, + ]); + assert.equal(schedule.length, 14); + assert.equal(schedule[0]!.plannedSends, 2); + assert.equal(schedule[1]!.plannedSends, 5); + assert.equal(schedule[2]!.plannedSends, 5); + assert.equal(schedule[13]!.plannedSends, 5); + }); + + it('routes generateSchedule to each recipe type', () => { + const grow = generateSchedule({ ...GROW_RECIPE_TEMPLATE, recipe: CampaignRecipe.grow }); + const flat = generateSchedule({ ...GROW_RECIPE_TEMPLATE, recipe: CampaignRecipe.flat }); + const randomized = generateSchedule( + { ...GROW_RECIPE_TEMPLATE, recipe: CampaignRecipe.randomized }, + { seed: 'test-seed' }, + ); + const custom = generateSchedule( + { ...GROW_RECIPE_TEMPLATE, recipe: CampaignRecipe.custom, durationDays: 14 }, + { customSchedule: [{ day: 1, sends: 3 }] }, + ); + + assert.equal(grow.length, 30); + assert.equal(flat.length, 30); + assert.equal(randomized.length, 30); + assert.equal(custom.length, 14); + }); + + it('rejects invalid recipe params', () => { + assert.throws( + () => + validateRecipeParams({ + ...GROW_RECIPE_TEMPLATE, + maxEmailsPerDay: 51, + }), + RecipeValidationError, + ); + assert.throws( + () => + validateRecipeParams({ + ...GROW_RECIPE_TEMPLATE, + durationDays: 10, + }), + RecipeValidationError, + ); + assert.throws( + () => + validateRecipeParams({ + ...GROW_RECIPE_TEMPLATE, + replyRatePercent: 50, + }), + RecipeValidationError, + ); + assert.throws( + () => + validateRecipeParams({ + ...GROW_RECIPE_TEMPLATE, + recipe: CampaignRecipe.custom, + }), + RecipeValidationError, + ); + }); + + it('computes maintenance volume at 20% of max', () => { + assert.equal(generateMaintenanceDailySends(40, 20), 8); + assert.equal(generateMaintenanceDailySends(50, 20), 10); + }); +}); diff --git a/apps/api/src/services/recipe.service.ts b/apps/api/src/services/recipe.service.ts new file mode 100644 index 0000000..038d863 --- /dev/null +++ b/apps/api/src/services/recipe.service.ts @@ -0,0 +1,251 @@ +import { CampaignRecipe } from '../lib/prisma'; + +export type RecipeParams = { + recipe: CampaignRecipe; + durationDays: number; + minEmailsPerDay: number; + maxEmailsPerDay: number; + replyRatePercent: number; +}; + +export type CustomDay = { + day: number; + sends: number; +}; + +export type DailyScheduleRow = { + dayIndex: number; + plannedSends: number; + plannedReplies: number; +}; + +export class RecipeValidationError extends Error { + constructor(message: string) { + super(message); + this.name = 'RecipeValidationError'; + } +} + +function validateSharedBounds(params: RecipeParams): void { + if (params.durationDays < 14 || params.durationDays > 45) { + throw new RecipeValidationError('durationDays must be between 14 and 45'); + } + if (params.minEmailsPerDay < 1) { + throw new RecipeValidationError('minEmailsPerDay must be at least 1'); + } + if (params.maxEmailsPerDay > 50) { + throw new RecipeValidationError('maxEmailsPerDay cannot exceed 50'); + } + if (params.minEmailsPerDay > params.maxEmailsPerDay) { + throw new RecipeValidationError('minEmailsPerDay cannot exceed maxEmailsPerDay'); + } + if (params.replyRatePercent < 10 || params.replyRatePercent > 45) { + throw new RecipeValidationError('replyRatePercent must be between 10 and 45'); + } +} + +export function validateRecipeParams( + params: RecipeParams, + options?: { customSchedule?: CustomDay[] }, +): void { + validateSharedBounds(params); + + if (params.recipe === CampaignRecipe.custom) { + const customSchedule = options?.customSchedule; + if (!customSchedule?.length) { + throw new RecipeValidationError('customSchedule is required for custom recipe'); + } + for (const entry of customSchedule) { + if (entry.day < 1 || entry.day > params.durationDays) { + throw new RecipeValidationError( + `customSchedule day ${entry.day} must be between 1 and ${params.durationDays}`, + ); + } + if (entry.sends < params.minEmailsPerDay || entry.sends > params.maxEmailsPerDay) { + throw new RecipeValidationError( + `customSchedule day ${entry.day} sends must be between ${params.minEmailsPerDay} and ${params.maxEmailsPerDay}`, + ); + } + } + } +} + +export function apply48hCap(schedule: number[]): number[] { + const result: number[] = []; + for (let i = 0; i < schedule.length; i++) { + let value = schedule[i]!; + if (i >= 2) { + const avg48h = (result[i - 1]! + result[i - 2]!) / 2; + const cap = Math.ceil(avg48h * 1.2); + value = Math.min(value, cap); + } + if (i > 0) { + value = Math.max(result[i - 1]!, value); + } + result.push(value); + } + return result; +} + +function toScheduleRows(raw: number[], replyRatePercent: number): DailyScheduleRow[] { + return raw.map((plannedSends, index) => ({ + dayIndex: index + 1, + plannedSends, + plannedReplies: Math.ceil((plannedSends * replyRatePercent) / 100), + })); +} + +function hashSeed(seed: string): number { + let h = 2166136261; + for (let i = 0; i < seed.length; i++) { + h ^= seed.charCodeAt(i); + h = Math.imul(h, 16777619); + } + return h >>> 0; +} + +function createSeededRandom(seed: string): () => number { + let state = hashSeed(seed) || 1; + return () => { + state = (state + 0x6d2b79f5) | 0; + let t = Math.imul(state ^ (state >>> 15), state | 1); + t ^= t + Math.imul(t ^ (t >>> 7), t | 61); + return ((t ^ (t >>> 14)) >>> 0) / 4294967296; + }; +} + +export function generateGrowSchedule(params: RecipeParams): DailyScheduleRow[] { + validateRecipeParams(params); + + const { durationDays, minEmailsPerDay, maxEmailsPerDay, replyRatePercent } = params; + const totalGrowthDays = Math.max(1, Math.floor(durationDays * 0.7)); + const dailyIncrement = (maxEmailsPerDay - minEmailsPerDay) / totalGrowthDays; + + const raw: number[] = []; + for (let day = 1; day <= durationDays; day++) { + const growthDay = Math.min(day, totalGrowthDays); + const ramped = minEmailsPerDay + dailyIncrement * (growthDay - 1); + const target = Math.min( + maxEmailsPerDay, + Math.max(minEmailsPerDay, Math.floor(ramped)), + ); + raw.push(target); + } + + return toScheduleRows(apply48hCap(raw), replyRatePercent); +} + +export function generateFlatSchedule(params: RecipeParams): DailyScheduleRow[] { + validateRecipeParams(params); + + const { durationDays, maxEmailsPerDay, replyRatePercent } = params; + const raw = Array.from({ length: durationDays }, () => maxEmailsPerDay); + return toScheduleRows(raw, replyRatePercent); +} + +export function generateRandomizedSchedule( + params: RecipeParams, + seed: string, +): DailyScheduleRow[] { + validateRecipeParams(params); + + const { durationDays, minEmailsPerDay, maxEmailsPerDay, replyRatePercent } = params; + const rand = createSeededRandom(seed); + const raw = Array.from({ length: durationDays }, () => { + const range = maxEmailsPerDay - minEmailsPerDay + 1; + return Math.floor(rand() * range) + minEmailsPerDay; + }); + + return toScheduleRows(apply48hCap(raw), replyRatePercent); +} + +export function generateCustomSchedule( + params: RecipeParams, + customDays: CustomDay[], +): DailyScheduleRow[] { + validateRecipeParams(params, { customSchedule: customDays }); + + const dayMap = new Map(customDays.map((d) => [d.day, d.sends])); + let lastValue = params.minEmailsPerDay; + const raw: number[] = []; + + for (let day = 1; day <= params.durationDays; day++) { + if (dayMap.has(day)) { + lastValue = dayMap.get(day)!; + } + raw.push(lastValue); + } + + return toScheduleRows(raw, params.replyRatePercent); +} + +export function generateSchedule( + params: RecipeParams, + options?: { seed?: string; customSchedule?: CustomDay[] }, +): DailyScheduleRow[] { + switch (params.recipe) { + case CampaignRecipe.grow: + return generateGrowSchedule(params); + case CampaignRecipe.flat: + return generateFlatSchedule(params); + case CampaignRecipe.randomized: + return generateRandomizedSchedule(params, options?.seed ?? 'default'); + case CampaignRecipe.custom: + return generateCustomSchedule(params, options?.customSchedule ?? []); + default: + throw new RecipeValidationError(`Unknown recipe: ${params.recipe}`); + } +} + +export function generateMaintenanceDailySends( + maxEmailsPerDay: number, + maintenanceVolumePercent = 20, +): number { + return Math.max(1, Math.ceil((maxEmailsPerDay * maintenanceVolumePercent) / 100)); +} + +export function generateMaintenanceScheduleRow( + dayIndex: number, + maxEmailsPerDay: number, + replyRatePercent: number, + maintenanceVolumePercent = 20, +): DailyScheduleRow { + const plannedSends = generateMaintenanceDailySends(maxEmailsPerDay, maintenanceVolumePercent); + return { + dayIndex, + plannedSends, + plannedReplies: Math.ceil((plannedSends * replyRatePercent) / 100), + }; +} + +export const GROW_RECIPE_TEMPLATE: RecipeParams = { + recipe: CampaignRecipe.grow, + durationDays: 30, + minEmailsPerDay: 1, + maxEmailsPerDay: 40, + replyRatePercent: 30, +}; + +export const FLAT_RECIPE_TEMPLATE: RecipeParams = { + recipe: CampaignRecipe.flat, + durationDays: 30, + minEmailsPerDay: 1, + maxEmailsPerDay: 40, + replyRatePercent: 30, +}; + +export const RANDOMIZED_RECIPE_TEMPLATE: RecipeParams = { + recipe: CampaignRecipe.randomized, + durationDays: 30, + minEmailsPerDay: 1, + maxEmailsPerDay: 40, + replyRatePercent: 30, +}; + +export const CUSTOM_RECIPE_TEMPLATE: RecipeParams = { + recipe: CampaignRecipe.custom, + durationDays: 30, + minEmailsPerDay: 1, + maxEmailsPerDay: 40, + replyRatePercent: 30, +}; diff --git a/apps/api/src/services/reply-rate.service.test.ts b/apps/api/src/services/reply-rate.service.test.ts new file mode 100644 index 0000000..5cddf79 --- /dev/null +++ b/apps/api/src/services/reply-rate.service.test.ts @@ -0,0 +1,65 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { + MAX_REPLY_RATE_PERCENT, + shouldReply, + simulateReplyRateOverMessages, +} from '../services/reply-rate.service'; + +describe('reply-rate.service', () => { + it('returns false when daily reply quota is met', () => { + const result = shouldReply( + { replyRatePercent: 30, plannedReplies: 6, actualReplies: 6 }, + { received: 20, replies: 5 }, + 0, + ); + assert.equal(result, false); + }); + + it('returns false when actual rate already meets target', () => { + const result = shouldReply( + { replyRatePercent: 30, plannedReplies: 10, actualReplies: 3 }, + { received: 10, replies: 3 }, + 0, + ); + assert.equal(result, false); + }); + + it('clamps target at 45% even if configured higher', () => { + const atCap = shouldReply( + { replyRatePercent: 50, plannedReplies: 100, actualReplies: 0 }, + { received: 0, replies: 0 }, + 0.4, + ); + const belowCap = shouldReply( + { replyRatePercent: 50, plannedReplies: 100, actualReplies: 0 }, + { received: 0, replies: 0 }, + 0.5, + ); + assert.equal(atCap, true); + assert.equal(belowCap, false); + assert.equal(MAX_REPLY_RATE_PERCENT, 45); + }); + + it('biases toward target as day progresses', () => { + const belowTarget = shouldReply( + { replyRatePercent: 30, plannedReplies: 30, actualReplies: 1 }, + { received: 10, replies: 1 }, + 0.2, + ); + const atTarget = shouldReply( + { replyRatePercent: 30, plannedReplies: 30, actualReplies: 3 }, + { received: 10, replies: 3 }, + 0.2, + ); + assert.equal(belowTarget, true); + assert.equal(atTarget, false); + }); + + it('converges within ±5% of target over 100+ messages', () => { + const target = 30; + const { replyRate } = simulateReplyRateOverMessages(target, 120); + assert.ok(replyRate >= target - 5, `reply rate ${replyRate} below ${target - 5}%`); + assert.ok(replyRate <= target + 5, `reply rate ${replyRate} above ${target + 5}%`); + }); +}); diff --git a/apps/api/src/services/reply-rate.service.ts b/apps/api/src/services/reply-rate.service.ts new file mode 100644 index 0000000..f0ae87e --- /dev/null +++ b/apps/api/src/services/reply-rate.service.ts @@ -0,0 +1,122 @@ +import type { WarmupCampaign } from '../generated/prisma/client'; +import { WarmupStatus, prisma } from '../lib/prisma'; + +export const MAX_REPLY_RATE_PERCENT = 45; + +export type TodayReplyStats = { + received: number; + replies: number; +}; + +export type ShouldReplyInput = { + replyRatePercent: number; + plannedReplies: number; + actualReplies: number; +}; + +function clampTarget(replyRatePercent: number): number { + return Math.min(replyRatePercent, MAX_REPLY_RATE_PERCENT); +} + +export function shouldReply( + input: ShouldReplyInput, + statsToday: TodayReplyStats, + randomValue = Math.random(), +): boolean { + const target = clampTarget(input.replyRatePercent); + + if (input.actualReplies >= input.plannedReplies) { + return false; + } + + if (statsToday.received === 0) { + return randomValue < target / 100; + } + + const actualRate = (statsToday.replies / statsToday.received) * 100; + if (actualRate >= target) { + return false; + } + + return randomValue < target / 100; +} + +export async function getTodayReplyStats( + campaignId: string, + primaryMailboxId: string, +): Promise { + const receivedStatuses: WarmupStatus[] = [ + WarmupStatus.sent, + WarmupStatus.rescued_from_spam, + WarmupStatus.engaged_no_reply, + WarmupStatus.replied, + WarmupStatus.complete, + ]; + + const received = await prisma.warmupLog.count({ + where: { + campaignId, + receiverId: primaryMailboxId, + status: { in: receivedStatuses }, + }, + }); + + const replies = await prisma.warmupLog.count({ + where: { + campaignId, + senderId: primaryMailboxId, + status: WarmupStatus.replied, + }, + }); + + return { received, replies }; +} + +export function buildShouldReplyInput( + campaign: Pick, + schedule: { plannedReplies: number; actualReplies: number }, +): ShouldReplyInput { + return { + replyRatePercent: campaign.replyRatePercent, + plannedReplies: schedule.plannedReplies, + actualReplies: schedule.actualReplies, + }; +} + +/** + * Simulate reply sampling over N messages to verify convergence toward target. + */ +export function simulateReplyRateOverMessages( + replyRatePercent: number, + messageCount: number, + seed = 42, +): { replyRate: number; replies: number } { + let state = seed; + const random = () => { + state = (state * 1664525 + 1013904223) % 4294967296; + return state / 4294967296; + }; + + let replies = 0; + for (let i = 0; i < messageCount; i++) { + const stats: TodayReplyStats = { received: i + 1, replies }; + if ( + shouldReply( + { + replyRatePercent, + plannedReplies: Math.ceil((messageCount * replyRatePercent) / 100), + actualReplies: replies, + }, + stats, + random(), + ) + ) { + replies++; + } + } + + return { + replies, + replyRate: (replies / messageCount) * 100, + }; +} diff --git a/src/services/settings.service.ts b/apps/api/src/services/settings.service.ts similarity index 100% rename from src/services/settings.service.ts rename to apps/api/src/services/settings.service.ts diff --git a/apps/api/src/services/spam-folder.service.ts b/apps/api/src/services/spam-folder.service.ts new file mode 100644 index 0000000..a8f8994 --- /dev/null +++ b/apps/api/src/services/spam-folder.service.ts @@ -0,0 +1,46 @@ +import type { ImapFlow } from 'imapflow'; +import type { Inbox } from '../lib/prisma'; +import { prisma } from '../lib/prisma'; +import { SPAM_FOLDERS } from '../models/enums'; + +const SPAM_FOLDER_PATTERNS = [/spam/i, /junk/i, /bulk/i]; + +export async function resolveSpamFolder( + client: ImapFlow, + inbox: Inbox, +): Promise { + if (inbox.spamFolderPath) { + return inbox.spamFolderPath; + } + + for (const folder of SPAM_FOLDERS) { + try { + await client.mailboxOpen(folder); + await prisma.inbox.update({ + where: { id: inbox.id }, + data: { spamFolderPath: folder }, + }); + return folder; + } catch { + // try next + } + } + + try { + const mailboxes = await client.list(); + for (const mailbox of mailboxes) { + const path = mailbox.path; + if (SPAM_FOLDER_PATTERNS.some((pattern) => pattern.test(path))) { + await prisma.inbox.update({ + where: { id: inbox.id }, + data: { spamFolderPath: path }, + }); + return path; + } + } + } catch { + return null; + } + + return null; +} diff --git a/apps/api/src/services/spam-threshold.service.test.ts b/apps/api/src/services/spam-threshold.service.test.ts new file mode 100644 index 0000000..9669446 --- /dev/null +++ b/apps/api/src/services/spam-threshold.service.test.ts @@ -0,0 +1,17 @@ +import assert from 'node:assert/strict'; +import { describe, it } from 'node:test'; +import { computeInboxPlacementRate } from './stats.service'; + +describe('spam-threshold.service', () => { + it('treats inbox rate below 70% as high spam (30%+ spam)', () => { + const inboxRate = computeInboxPlacementRate(10, 4); + assert.equal(inboxRate, 60); + assert.ok(100 - inboxRate >= 30); + }); + + it('treats inbox rate above 70% as acceptable', () => { + const inboxRate = computeInboxPlacementRate(10, 2); + assert.equal(inboxRate, 80); + assert.ok(100 - inboxRate < 30); + }); +}); diff --git a/apps/api/src/services/spam-threshold.service.ts b/apps/api/src/services/spam-threshold.service.ts new file mode 100644 index 0000000..a5be611 --- /dev/null +++ b/apps/api/src/services/spam-threshold.service.ts @@ -0,0 +1,68 @@ +import { CampaignStatus, prisma } from '../lib/prisma'; +import { pauseCampaign } from './campaign.service'; +import { computeInboxPlacementRate } from './stats.service'; +import { logger } from '../lib/logger'; + +const SPAM_RATE_THRESHOLD = 30; +const MIN_DAYS_FOR_PAUSE = 3; + +export async function checkCampaignSpamThresholds(): Promise { + const campaigns = await prisma.warmupCampaign.findMany({ + where: { + status: { in: [CampaignStatus.active, CampaignStatus.maintenance] }, + }, + }); + + let paused = 0; + + for (const campaign of campaigns) { + try { + const shouldPause = await shouldPauseForSpam(campaign.id, campaign.timezone); + if (shouldPause) { + await pauseCampaign(campaign.id); + paused += 1; + logger.warn({ campaignId: campaign.id }, 'Campaign auto-paused due to high spam rate'); + } + } catch (err) { + logger.error({ err, campaignId: campaign.id }, 'Spam threshold check failed'); + } + } + + return paused; +} + +export async function shouldPauseForSpam( + campaignId: string, + _timezone: string, +): Promise { + const campaign = await prisma.warmupCampaign.findUnique({ + where: { id: campaignId }, + select: { currentDay: true }, + }); + if (!campaign || campaign.currentDay < MIN_DAYS_FOR_PAUSE) { + return false; + } + + const schedules = await prisma.dailySchedule.findMany({ + where: { + campaignId, + dayIndex: { + lte: campaign.currentDay, + gte: campaign.currentDay - MIN_DAYS_FOR_PAUSE + 1, + }, + actualSends: { gt: 0 }, + }, + orderBy: { dayIndex: 'asc' }, + }); + + if (schedules.length < MIN_DAYS_FOR_PAUSE) { + return false; + } + + const highSpamDays = schedules.filter((row) => { + const inboxRate = computeInboxPlacementRate(row.actualSends, row.spamDetected); + return 100 - inboxRate >= SPAM_RATE_THRESHOLD; + }); + + return highSpamDays.length >= MIN_DAYS_FOR_PAUSE; +} diff --git a/apps/api/src/services/stats.service.test.ts b/apps/api/src/services/stats.service.test.ts new file mode 100644 index 0000000..f19191b --- /dev/null +++ b/apps/api/src/services/stats.service.test.ts @@ -0,0 +1,102 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { + computeInboxPlacementRate, + computeOnTrack, + computeOnTrackReasons, +} from '../services/stats.service'; + +describe('stats.service', () => { + it('computes inbox placement rate', () => { + assert.equal(computeInboxPlacementRate(10, 2), 80); + assert.equal(computeInboxPlacementRate(0, 0), 100); + }); + + it('marks on track when sends and replies are within tolerance', () => { + assert.equal( + computeOnTrack( + { + plannedSends: 20, + plannedReplies: 6, + actualSends: 18, + actualReplies: 5, + }, + 30, + ), + true, + ); + }); + + it('stays on track during early-day warmup with few sends', () => { + assert.equal( + computeOnTrack( + { + plannedSends: 10, + plannedReplies: 3, + actualSends: 1, + actualReplies: 0, + }, + 30, + ), + true, + ); + }); + + it('marks off track when sends fall below 85% of plan', () => { + assert.equal( + computeOnTrack( + { + plannedSends: 20, + plannedReplies: 6, + actualSends: 10, + actualReplies: 3, + }, + 30, + ), + false, + ); + }); + + it('marks off track when reply rate deviates more than 10%', () => { + assert.equal( + computeOnTrack( + { + plannedSends: 10, + plannedReplies: 3, + actualSends: 10, + actualReplies: 9, + }, + 30, + ), + false, + ); + }); +}); + +describe('computeOnTrackReasons', () => { + it('returns no reasons during early warmup', () => { + const reasons = computeOnTrackReasons( + { + plannedSends: 10, + plannedReplies: 3, + actualSends: 1, + actualReplies: 0, + }, + 30, + ); + assert.equal(reasons.length, 0); + }); + + it('explains send shortfall', () => { + const reasons = computeOnTrackReasons( + { + plannedSends: 10, + plannedReplies: 3, + actualSends: 3, + actualReplies: 0, + }, + 30, + ); + assert.ok(reasons.some((r) => r.includes('Sends behind schedule'))); + }); +}); diff --git a/apps/api/src/services/stats.service.ts b/apps/api/src/services/stats.service.ts new file mode 100644 index 0000000..f0cf8a3 --- /dev/null +++ b/apps/api/src/services/stats.service.ts @@ -0,0 +1,384 @@ +import { DateTime } from 'luxon'; +import type { DailySchedule, WarmupCampaign } from '../generated/prisma/client'; +import { CampaignStatus, prisma } from '../lib/prisma'; +import { CampaignError } from './campaign.service'; + +export type StatsRange = '7d' | '30d' | 'all'; + +export type CampaignStatsPayload = { + campaignId: string; + date: string; + dayIndex: number; + planned: { sends: number; replies: number }; + actual: { + sends: number; + replies: number; + spamDetected: number; + spamRescued: number; + }; + rates: { reply: number; inbox: number }; + recipe: string; + onTrack: boolean; + onTrackReasons: string[]; +}; + +const MIN_SENDS_FOR_SEND_CHECK = 2; +const MIN_SENDS_FOR_REPLY_CHECK = 5; + +function scheduleToPayload( + campaign: Pick, + schedule: DailySchedule, + dateIso: string, +): CampaignStatsPayload { + const replyRate = + schedule.actualSends > 0 + ? (schedule.actualReplies / schedule.actualSends) * 100 + : 0; + const inboxRate = computeInboxPlacementRate( + schedule.actualSends, + schedule.spamDetected, + ); + + return { + campaignId: campaign.id, + date: dateIso, + dayIndex: schedule.dayIndex, + planned: { + sends: schedule.plannedSends, + replies: schedule.plannedReplies, + }, + actual: { + sends: schedule.actualSends, + replies: schedule.actualReplies, + spamDetected: schedule.spamDetected, + spamRescued: schedule.spamRescued, + }, + rates: { + reply: Math.round(replyRate * 10) / 10, + inbox: Math.round(inboxRate * 10) / 10, + }, + recipe: campaign.recipe, + onTrack: computeOnTrack(schedule, campaign.replyRatePercent), + onTrackReasons: computeOnTrackReasons(schedule, campaign.replyRatePercent), + }; +} + +export function computeInboxPlacementRate(received: number, spamDetected: number): number { + if (received <= 0) return 100; + return (1 - spamDetected / received) * 100; +} + +export function computeOnTrackReasons( + schedule: Pick< + DailySchedule, + 'plannedSends' | 'plannedReplies' | 'actualSends' | 'actualReplies' + >, + replyRatePercent: number, +): string[] { + const reasons: string[] = []; + + if (schedule.actualSends < MIN_SENDS_FOR_SEND_CHECK) { + return []; + } + + if ( + schedule.plannedSends > 0 && + schedule.actualSends < schedule.plannedSends * 0.85 + ) { + const needed = Math.ceil(schedule.plannedSends * 0.85); + reasons.push( + `Sends behind schedule: ${schedule.actualSends}/${schedule.plannedSends} today (need at least ${needed})`, + ); + } + + if (schedule.actualSends >= MIN_SENDS_FOR_REPLY_CHECK) { + const actualReplyRate = + schedule.actualSends > 0 + ? (schedule.actualReplies / schedule.actualSends) * 100 + : 0; + const delta = Math.abs(actualReplyRate - replyRatePercent); + if (delta > 10) { + reasons.push( + `Reply rate off target: ${actualReplyRate.toFixed(1)}% actual vs ${replyRatePercent}% target`, + ); + } + } + + return reasons; +} + +export function computeOnTrack( + schedule: Pick< + DailySchedule, + 'plannedSends' | 'plannedReplies' | 'actualSends' | 'actualReplies' + >, + replyRatePercent: number, +): boolean { + if (schedule.actualSends < MIN_SENDS_FOR_SEND_CHECK) { + return true; + } + + const sendOnTrack = + schedule.plannedSends === 0 || + schedule.actualSends >= schedule.plannedSends * 0.85; + + const actualReplyRate = + schedule.actualSends > 0 + ? (schedule.actualReplies / schedule.actualSends) * 100 + : 0; + const replyOnTrack = + schedule.actualSends < MIN_SENDS_FOR_REPLY_CHECK || + Math.abs(actualReplyRate - replyRatePercent) <= 10; + + return sendOnTrack && replyOnTrack; +} + +async function getCampaignOrThrow(campaignId: string) { + const campaign = await prisma.warmupCampaign.findUnique({ + where: { id: campaignId }, + }); + if (!campaign) { + throw new CampaignError('Campaign not found', 404); + } + return campaign; +} + +export async function getCampaignStatsToday(campaignId: string): Promise { + const campaign = await getCampaignOrThrow(campaignId); + const todayIso = DateTime.now().setZone(campaign.timezone).toISODate() ?? ''; + + const schedule = await prisma.dailySchedule.findUnique({ + where: { + campaignId_dayIndex: { campaignId, dayIndex: campaign.currentDay }, + }, + }); + + if (!schedule) { + throw new CampaignError('No schedule for current campaign day', 404); + } + + return scheduleToPayload(campaign, schedule, todayIso); +} + +export async function getCampaignStatsDaily( + campaignId: string, + from?: string, + to?: string, +): Promise { + const campaign = await getCampaignOrThrow(campaignId); + const zone = campaign.timezone; + + const fromDate = from + ? DateTime.fromISO(from, { zone }).startOf('day') + : DateTime.now().setZone(zone).minus({ days: 29 }).startOf('day'); + const toDate = to + ? DateTime.fromISO(to, { zone }).endOf('day') + : DateTime.now().setZone(zone).endOf('day'); + + const historical = await prisma.dailyStat.findMany({ + where: { + campaignId, + date: { + gte: fromDate.toJSDate(), + lte: toDate.toJSDate(), + }, + }, + orderBy: { date: 'asc' }, + }); + + const results: CampaignStatsPayload[] = historical.map((stat) => ({ + campaignId, + date: DateTime.fromJSDate(stat.date).setZone(zone).toISODate() ?? '', + dayIndex: stat.dayIndex, + planned: { sends: stat.plannedSends, replies: stat.plannedReplies }, + actual: { + sends: stat.actualSends, + replies: stat.actualReplies, + spamDetected: stat.spamDetected, + spamRescued: stat.spamRescued, + }, + rates: { + reply: stat.replyRateActual ?? 0, + inbox: stat.inboxPlacementRate ?? 100, + }, + recipe: campaign.recipe, + onTrack: computeOnTrack(stat, campaign.replyRatePercent), + onTrackReasons: computeOnTrackReasons(stat, campaign.replyRatePercent), + })); + + const todayIso = DateTime.now().setZone(zone).toISODate(); + const toIso = toDate.toISODate(); + if (todayIso && toIso && todayIso <= toIso) { + const today = await getCampaignStatsToday(campaignId); + const withoutToday = results.filter((r) => r.date !== todayIso); + return [...withoutToday, today]; + } + + return results; +} + +export async function getCampaignStatsSummary( + campaignId: string, + range: StatsRange = '7d', +): Promise<{ + campaignId: string; + range: StatsRange; + totals: CampaignStatsPayload['actual'] & { plannedSends: number; plannedReplies: number }; + rates: { reply: number; inbox: number }; + onTrack: boolean; + days: number; +}> { + const campaign = await getCampaignOrThrow(campaignId); + const zone = campaign.timezone; + const now = DateTime.now().setZone(zone); + + let fromDate: DateTime; + if (range === '7d') { + fromDate = now.minus({ days: 6 }).startOf('day'); + } else if (range === '30d') { + fromDate = now.minus({ days: 29 }).startOf('day'); + } else { + fromDate = DateTime.fromMillis(0); + } + + const daily = await getCampaignStatsDaily( + campaignId, + fromDate.toISODate() ?? undefined, + now.toISODate() ?? undefined, + ); + + const totals = daily.reduce( + (acc, day) => ({ + plannedSends: acc.plannedSends + day.planned.sends, + plannedReplies: acc.plannedReplies + day.planned.replies, + sends: acc.sends + day.actual.sends, + replies: acc.replies + day.actual.replies, + spamDetected: acc.spamDetected + day.actual.spamDetected, + spamRescued: acc.spamRescued + day.actual.spamRescued, + }), + { + plannedSends: 0, + plannedReplies: 0, + sends: 0, + replies: 0, + spamDetected: 0, + spamRescued: 0, + }, + ); + + const replyRate = totals.sends > 0 ? (totals.replies / totals.sends) * 100 : 0; + const inboxRate = computeInboxPlacementRate(totals.sends, totals.spamDetected); + + return { + campaignId, + range, + totals, + rates: { + reply: Math.round(replyRate * 10) / 10, + inbox: Math.round(inboxRate * 10) / 10, + }, + onTrack: daily.length > 0 ? daily.every((d) => d.onTrack) : true, + days: daily.length, + }; +} + +export async function getWorkspaceOverview(workspaceId?: string) { + const campaigns = await prisma.warmupCampaign.findMany({ + where: { + ...(workspaceId ? { workspaceId } : {}), + status: { + in: [CampaignStatus.active, CampaignStatus.maintenance, CampaignStatus.paused], + }, + }, + include: { + primaryMailbox: true, + dailySchedules: { + where: { dayIndex: { gt: 0 } }, + orderBy: { dayIndex: 'desc' }, + take: 1, + }, + }, + orderBy: { updatedAt: 'desc' }, + }); + + const items = await Promise.all( + campaigns.map(async (campaign) => { + let today: CampaignStatsPayload | null = null; + if (campaign.currentDay > 0) { + try { + today = await getCampaignStatsToday(campaign.id); + } catch { + today = null; + } + } + + return { + campaignId: campaign.id, + name: campaign.name, + status: campaign.status, + recipe: campaign.recipe, + primaryEmail: campaign.primaryMailbox.email, + currentDay: campaign.currentDay, + durationDays: campaign.durationDays, + today, + }; + }), + ); + + return { + workspaceId: workspaceId ?? null, + campaigns: items, + count: items.length, + }; +} + +export async function finalizeDailyStatForSchedule( + campaign: WarmupCampaign, + schedule: DailySchedule, +): Promise { + if (!schedule.date) return; + + const replyRateActual = + schedule.actualSends > 0 + ? (schedule.actualReplies / schedule.actualSends) * 100 + : 0; + const inboxPlacementRate = computeInboxPlacementRate( + schedule.actualSends, + schedule.spamDetected, + ); + + const dayStart = DateTime.fromJSDate(schedule.date) + .setZone(campaign.timezone) + .startOf('day') + .toJSDate(); + + await prisma.dailyStat.upsert({ + where: { + campaignId_date: { campaignId: campaign.id, date: dayStart }, + }, + create: { + campaignId: campaign.id, + date: dayStart, + dayIndex: schedule.dayIndex, + plannedSends: schedule.plannedSends, + plannedReplies: schedule.plannedReplies, + actualSends: schedule.actualSends, + actualReplies: schedule.actualReplies, + spamDetected: schedule.spamDetected, + spamRescued: schedule.spamRescued, + replyRateActual, + inboxPlacementRate, + }, + update: { + dayIndex: schedule.dayIndex, + plannedSends: schedule.plannedSends, + plannedReplies: schedule.plannedReplies, + actualSends: schedule.actualSends, + actualReplies: schedule.actualReplies, + spamDetected: schedule.spamDetected, + spamRescued: schedule.spamRescued, + replyRateActual, + inboxPlacementRate, + }, + }); +} diff --git a/apps/api/src/services/workspace.service.ts b/apps/api/src/services/workspace.service.ts new file mode 100644 index 0000000..0c7f71f --- /dev/null +++ b/apps/api/src/services/workspace.service.ts @@ -0,0 +1,34 @@ +import { prisma } from '../lib/prisma'; +import { DEFAULT_WORKSPACE_ID, DEFAULT_WORKSPACE_NAME } from '../models/workspace.constants'; + +export async function seedDefaultWorkspace(): Promise { + await prisma.workspace.upsert({ + where: { name: DEFAULT_WORKSPACE_NAME }, + create: { + id: DEFAULT_WORKSPACE_ID, + name: DEFAULT_WORKSPACE_NAME, + }, + update: {}, + }); +} + +export async function getDefaultWorkspace() { + await seedDefaultWorkspace(); + const workspace = await prisma.workspace.findUnique({ + where: { name: DEFAULT_WORKSPACE_NAME }, + }); + if (!workspace) { + throw new Error('Default workspace not found after seed'); + } + return workspace; +} + +export async function resolveWorkspaceId(workspaceId?: string): Promise { + if (workspaceId) { + const ws = await prisma.workspace.findUnique({ where: { id: workspaceId } }); + if (!ws) throw new Error(`Workspace not found: ${workspaceId}`); + return workspaceId; + } + const ws = await getDefaultWorkspace(); + return ws.id; +} diff --git a/apps/api/src/test/helpers/test-db.ts b/apps/api/src/test/helpers/test-db.ts new file mode 100644 index 0000000..c5dc0db --- /dev/null +++ b/apps/api/src/test/helpers/test-db.ts @@ -0,0 +1,60 @@ +import { randomUUID } from 'node:crypto'; +import { MailboxProvider, MailboxRole } from '../../lib/prisma'; +import { prisma } from '../../lib/prisma'; +import { DEFAULT_WORKSPACE_ID } from '../../models/workspace.constants'; + +let counter = 0; + +function nextId(prefix: string): string { + counter += 1; + return `${prefix}-${Date.now()}-${counter}`; +} + +export async function ensureTestWorkspace(): Promise { + await prisma.workspace.upsert({ + where: { id: DEFAULT_WORKSPACE_ID }, + create: { id: DEFAULT_WORKSPACE_ID, name: 'default' }, + update: {}, + }); + return DEFAULT_WORKSPACE_ID; +} + +export async function createTestInbox(overrides?: { + email?: string; + role?: MailboxRole; +}) { + const workspaceId = await ensureTestWorkspace(); + const email = overrides?.email ?? `inbox-${randomUUID()}@test.local`; + + return prisma.inbox.create({ + data: { + workspaceId, + email, + smtpHost: 'smtp.test.local', + smtpPort: 587, + imapHost: 'imap.test.local', + imapPort: 993, + username: email, + password: 'encrypted-test', + role: overrides?.role ?? MailboxRole.unassigned, + provider: MailboxProvider.custom, + }, + }); +} + +export async function cleanupCampaign(campaignId: string): Promise { + await prisma.warmupLog.deleteMany({ where: { campaignId } }); + await prisma.dailyStat.deleteMany({ where: { campaignId } }); + await prisma.dailySchedule.deleteMany({ where: { campaignId } }); + await prisma.campaignSatellite.deleteMany({ where: { campaignId } }); + await prisma.warmupCampaign.delete({ where: { id: campaignId } }); +} + +export async function cleanupInbox(inboxId: string): Promise { + await prisma.warmupLog.deleteMany({ + where: { OR: [{ senderId: inboxId }, { receiverId: inboxId }] }, + }); + await prisma.campaignSatellite.deleteMany({ where: { satelliteMailboxId: inboxId } }); + await prisma.warmupCampaign.deleteMany({ where: { primaryMailboxId: inboxId } }); + await prisma.inbox.delete({ where: { id: inboxId } }); +} diff --git a/apps/api/src/test/integration/campaign-flow.integration.test.ts b/apps/api/src/test/integration/campaign-flow.integration.test.ts new file mode 100644 index 0000000..dc80129 --- /dev/null +++ b/apps/api/src/test/integration/campaign-flow.integration.test.ts @@ -0,0 +1,42 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { shouldReply, simulateReplyRateOverMessages } from '../../services/reply-rate.service'; +import { WarmupStatus } from '../../lib/prisma'; + +describe('integration: campaign reply flow', () => { + it('samples replies instead of replying to every message', () => { + let replies = 0; + const messageCount = 50; + const target = 30; + + for (let i = 0; i < messageCount; i++) { + const stats = { received: i + 1, replies }; + if ( + shouldReply( + { + replyRatePercent: target, + plannedReplies: 15, + actualReplies: replies, + }, + stats, + (i * 17 + 3) / 100, + ) + ) { + replies++; + } + } + + assert.ok(replies < messageCount); + assert.ok(replies > 0); + }); + + it('simulation converges near 30% over 120 messages', () => { + const { replyRate } = simulateReplyRateOverMessages(30, 120); + assert.ok(replyRate >= 25); + assert.ok(replyRate <= 35); + }); + + it('engaged_no_reply is a valid terminal path status', () => { + assert.equal(WarmupStatus.engaged_no_reply, 'engaged_no_reply'); + }); +}); diff --git a/apps/api/src/test/integration/concurrent-primaries.integration.test.ts b/apps/api/src/test/integration/concurrent-primaries.integration.test.ts new file mode 100644 index 0000000..f9f0403 --- /dev/null +++ b/apps/api/src/test/integration/concurrent-primaries.integration.test.ts @@ -0,0 +1,111 @@ +import { after, describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { CampaignRecipe, MailboxRole } from '../../lib/prisma'; +import { assignSatellite, createCampaign, startCampaign } from '../../services/campaign.service'; +import { prisma } from '../../lib/prisma'; +import { getCampaignStatsToday } from '../../services/stats.service'; +import { + cleanupCampaign, + cleanupInbox, + createTestInbox, +} from '../helpers/test-db'; + +describe('integration: concurrent primaries', () => { + const campaignIds: string[] = []; + const inboxIds: string[] = []; + + after(async () => { + for (const id of campaignIds) await cleanupCampaign(id); + for (const id of inboxIds) await cleanupInbox(id); + }); + + it('runs two primaries with shared satellites independently', async () => { + const primaryA = await createTestInbox({ role: MailboxRole.primary }); + const primaryB = await createTestInbox({ role: MailboxRole.primary }); + const sat1 = await createTestInbox(); + const sat2 = await createTestInbox(); + inboxIds.push(primaryA.id, primaryB.id, sat1.id, sat2.id); + + const campaignA = await createCampaign({ + primaryMailboxId: primaryA.id, + name: 'Primary A', + recipe: CampaignRecipe.grow, + durationDays: 14, + }); + const campaignB = await createCampaign({ + primaryMailboxId: primaryB.id, + name: 'Primary B', + recipe: CampaignRecipe.flat, + durationDays: 14, + }); + campaignIds.push(campaignA.id, campaignB.id); + + for (const campaignId of [campaignA.id, campaignB.id]) { + await assignSatellite(campaignId, sat1.id); + await assignSatellite(campaignId, sat2.id); + } + + const startedA = await startCampaign(campaignA.id); + const startedB = await startCampaign(campaignB.id); + + assert.equal(startedA.status, 'active'); + assert.equal(startedB.status, 'active'); + assert.notEqual(startedA.id, startedB.id); + + const flatSchedule = await prisma.dailySchedule.findFirst({ + where: { campaignId: startedB.id, dayIndex: 1 }, + }); + const growSchedule = await prisma.dailySchedule.findFirst({ + where: { campaignId: startedA.id, dayIndex: 1 }, + }); + + assert.equal(flatSchedule!.plannedSends, 40); + assert.equal(growSchedule!.plannedSends, 1); + }); +}); + +describe('integration: campaign stats flow', () => { + const campaignIds: string[] = []; + const inboxIds: string[] = []; + + after(async () => { + for (const id of campaignIds) await cleanupCampaign(id); + for (const id of inboxIds) await cleanupInbox(id); + }); + + it('stats today matches DailySchedule counters', async () => { + const primary = await createTestInbox({ role: MailboxRole.primary }); + const sat1 = await createTestInbox(); + const sat2 = await createTestInbox(); + inboxIds.push(primary.id, sat1.id, sat2.id); + + const campaign = await createCampaign({ + primaryMailboxId: primary.id, + name: 'Stats test', + recipe: CampaignRecipe.flat, + durationDays: 14, + }); + campaignIds.push(campaign.id); + await assignSatellite(campaign.id, sat1.id); + await assignSatellite(campaign.id, sat2.id); + const started = await startCampaign(campaign.id); + + await prisma.dailySchedule.update({ + where: { + campaignId_dayIndex: { campaignId: started.id, dayIndex: 1 }, + }, + data: { + actualSends: 5, + actualReplies: 2, + spamDetected: 1, + spamRescued: 1, + }, + }); + + const stats = await getCampaignStatsToday(started.id); + assert.equal(stats.actual.sends, 5); + assert.equal(stats.actual.replies, 2); + assert.equal(stats.actual.spamDetected, 1); + assert.equal(stats.planned.sends, 40); + }); +}); diff --git a/apps/api/src/test/integration/primary-exclusivity.integration.test.ts b/apps/api/src/test/integration/primary-exclusivity.integration.test.ts new file mode 100644 index 0000000..d104eef --- /dev/null +++ b/apps/api/src/test/integration/primary-exclusivity.integration.test.ts @@ -0,0 +1,74 @@ +import { after, describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { CampaignRecipe, MailboxRole } from '../../lib/prisma'; +import { prisma } from '../../lib/prisma'; +import { + createCampaign, + deleteCampaign, + startCampaign, + CampaignError, +} from '../../services/campaign.service'; +import { + cleanupCampaign, + cleanupInbox, + createTestInbox, +} from '../helpers/test-db'; + +describe('integration: primary mailbox exclusivity', () => { + const campaignIds: string[] = []; + const inboxIds: string[] = []; + + after(async () => { + for (const id of campaignIds) await cleanupCampaign(id); + for (const id of inboxIds) await cleanupInbox(id); + }); + + it('rejects a second campaign for the same primary while one is active', async () => { + const primary = await createTestInbox({ role: MailboxRole.unassigned }); + const sat1 = await createTestInbox(); + const sat2 = await createTestInbox(); + inboxIds.push(primary.id, sat1.id, sat2.id); + + const running = await createCampaign({ + primaryMailboxId: primary.id, + name: 'Running', + recipe: CampaignRecipe.flat, + durationDays: 14, + satelliteMailboxIds: [sat1.id, sat2.id], + }); + campaignIds.push(running.id); + await startCampaign(running.id); + + await assert.rejects( + () => + createCampaign({ + primaryMailboxId: primary.id, + name: 'Duplicate', + recipe: CampaignRecipe.grow, + durationDays: 14, + }), + (err: unknown) => { + assert.ok(err instanceof CampaignError); + assert.match(err.message, /already used by campaign/i); + return true; + }, + ); + }); + + it('deleting a draft resets primary mailbox role when unused', async () => { + const primary = await createTestInbox({ role: MailboxRole.unassigned }); + inboxIds.push(primary.id); + + const draft = await createCampaign({ + primaryMailboxId: primary.id, + name: 'Draft to delete', + recipe: CampaignRecipe.grow, + durationDays: 14, + }); + + await deleteCampaign(draft.id); + + const refreshed = await prisma.inbox.findUnique({ where: { id: primary.id } }); + assert.equal(refreshed?.role, MailboxRole.unassigned); + }); +}); diff --git a/apps/api/src/test/integration/recipes.integration.test.ts b/apps/api/src/test/integration/recipes.integration.test.ts new file mode 100644 index 0000000..a2d757d --- /dev/null +++ b/apps/api/src/test/integration/recipes.integration.test.ts @@ -0,0 +1,118 @@ +import { after, describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { CampaignRecipe, MailboxRole } from '../../lib/prisma'; +import { assignSatellite, createCampaign, startCampaign } from '../../services/campaign.service'; +import { prisma } from '../../lib/prisma'; +import { + cleanupCampaign, + cleanupInbox, + createTestInbox, +} from '../helpers/test-db'; + +describe('integration: recipes', () => { + const campaignIds: string[] = []; + const inboxIds: string[] = []; + + after(async () => { + for (const id of campaignIds) await cleanupCampaign(id); + for (const id of inboxIds) await cleanupInbox(id); + }); + + async function setupCampaign(recipe: CampaignRecipe, extra?: Record) { + const primary = await createTestInbox({ role: MailboxRole.primary }); + const sat1 = await createTestInbox(); + const sat2 = await createTestInbox(); + inboxIds.push(primary.id, sat1.id, sat2.id); + + const campaign = await createCampaign({ + primaryMailboxId: primary.id, + name: `Test ${recipe}`, + recipe, + durationDays: 14, + minEmailsPerDay: 1, + maxEmailsPerDay: 40, + ...extra, + }); + campaignIds.push(campaign.id); + + await assignSatellite(campaign.id, sat1.id); + await assignSatellite(campaign.id, sat2.id); + + return startCampaign(campaign.id); + } + + it('flat recipe persists constant plannedSends', async () => { + const campaign = await setupCampaign(CampaignRecipe.flat); + const schedules = await prisma.dailySchedule.findMany({ + where: { campaignId: campaign.id }, + orderBy: { dayIndex: 'asc' }, + }); + assert.equal(schedules.length, 14); + for (const row of schedules) { + assert.equal(row.plannedSends, 40); + } + }); + + it('custom recipe persists hold-last schedule', async () => { + const primary = await createTestInbox({ role: MailboxRole.primary }); + const sat1 = await createTestInbox(); + const sat2 = await createTestInbox(); + inboxIds.push(primary.id, sat1.id, sat2.id); + + const campaign = await createCampaign({ + primaryMailboxId: primary.id, + name: 'Test custom', + recipe: CampaignRecipe.custom, + durationDays: 14, + customSchedule: [ + { day: 1, sends: 2 }, + { day: 2, sends: 5 }, + ], + }); + campaignIds.push(campaign.id); + await assignSatellite(campaign.id, sat1.id); + await assignSatellite(campaign.id, sat2.id); + const started = await startCampaign(campaign.id); + + const schedules = await prisma.dailySchedule.findMany({ + where: { campaignId: started.id }, + orderBy: { dayIndex: 'asc' }, + }); + assert.equal(schedules[0]!.plannedSends, 2); + assert.equal(schedules[1]!.plannedSends, 5); + assert.equal(schedules[13]!.plannedSends, 5); + }); + + it('randomized recipe is deterministic per campaign id seed', async () => { + const a = await setupCampaign(CampaignRecipe.randomized); + const bPrimary = await createTestInbox({ role: MailboxRole.primary }); + inboxIds.push(bPrimary.id); + const b = await createCampaign({ + primaryMailboxId: bPrimary.id, + name: 'Random B', + recipe: CampaignRecipe.randomized, + durationDays: 14, + }); + campaignIds.push(b.id); + const satB1 = await createTestInbox(); + const satB2 = await createTestInbox(); + inboxIds.push(satB1.id, satB2.id); + await assignSatellite(b.id, satB1.id); + await assignSatellite(b.id, satB2.id); + + const scheduleA = await prisma.dailySchedule.findMany({ + where: { campaignId: a.id }, + orderBy: { dayIndex: 'asc' }, + }); + const restarted = await startCampaign(b.id); + const scheduleB = await prisma.dailySchedule.findMany({ + where: { campaignId: restarted.id }, + orderBy: { dayIndex: 'asc' }, + }); + + assert.notDeepEqual( + scheduleA.map((r) => r.plannedSends), + scheduleB.map((r) => r.plannedSends), + ); + }); +}); diff --git a/apps/api/tsconfig.json b/apps/api/tsconfig.json new file mode 100644 index 0000000..0a6e9e4 --- /dev/null +++ b/apps/api/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "lib": ["ES2022"], + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "resolveJsonModule": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..6a14189 --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,13 @@ + + + + + + + Warmbox + + +
+ + + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..dbfe3dd --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,35 @@ +{ + "name": "@warmbox/web", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@tanstack/react-query": "^5.80.7", + "@warmbox/shared": "*", + "clsx": "^2.1.1", + "lucide-react": "^0.511.0", + "luxon": "^3.7.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.6.2", + "recharts": "^2.15.3", + "tailwind-merge": "^3.3.0", + "zod": "^3.25.28" + }, + "devDependencies": { + "@types/luxon": "^3.7.2", + "@types/react": "^19.1.6", + "@types/react-dom": "^19.1.5", + "@vitejs/plugin-react": "^4.5.2", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.4", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.3", + "vite": "^6.3.5" + } +} diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/apps/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/apps/web/public/vite.svg b/apps/web/public/vite.svg new file mode 100644 index 0000000..759a02d --- /dev/null +++ b/apps/web/public/vite.svg @@ -0,0 +1,4 @@ + + + + diff --git a/apps/web/src/App.tsx b/apps/web/src/App.tsx new file mode 100644 index 0000000..9caf369 --- /dev/null +++ b/apps/web/src/App.tsx @@ -0,0 +1,36 @@ +import { Routes, Route, Navigate } from 'react-router-dom'; +import { AppShell } from './components/layout/AppShell'; +import { LoginPage, RequireAuth } from './lib/auth'; +import { DashboardPage } from './pages/DashboardPage'; +import { CampaignsPage } from './pages/CampaignsPage'; +import { CampaignWizardPage } from './pages/CampaignWizardPage'; +import { CampaignDetailPage } from './pages/CampaignDetailPage'; +import { MailboxesPage } from './pages/MailboxesPage'; +import { MailboxDetailPage } from './pages/MailboxDetailPage'; +import { ActivityPage } from './pages/ActivityPage'; +import { SettingsPage } from './pages/SettingsPage'; + +export default function App() { + return ( + + } /> + + + + } + > + } /> + } /> + } /> + } /> + } /> + } /> + } /> + } /> + + } /> + + ); +} diff --git a/apps/web/src/components/ComplianceChecklist.tsx b/apps/web/src/components/ComplianceChecklist.tsx new file mode 100644 index 0000000..3d6510a --- /dev/null +++ b/apps/web/src/components/ComplianceChecklist.tsx @@ -0,0 +1,68 @@ +import type { ComplianceCheckItem, ComplianceReport } from '@warmbox/shared'; +import { Link } from 'react-router-dom'; +import { CheckCircle2, AlertTriangle, XCircle, MinusCircle } from 'lucide-react'; +import { cn } from '../lib/utils'; +import { Badge } from './ui/Badge'; + +const statusIcons = { + pass: CheckCircle2, + warn: AlertTriangle, + fail: XCircle, + na: MinusCircle, +}; + +const statusColors = { + pass: 'text-emerald-600', + warn: 'text-amber-600', + fail: 'text-red-600', + na: 'text-slate-400', +}; + +const overallVariants = { + pass: 'success' as const, + warn: 'warning' as const, + fail: 'danger' as const, + na: 'default' as const, +}; + +function CheckRow({ check }: { check: ComplianceCheckItem }) { + const Icon = statusIcons[check.status]; + return ( +
+ +
+

{check.label}

+

{check.message}

+ {check.actionPath && ( + + {check.actionLabel ?? 'Open settings'} + + )} +
+ + {check.status} + +
+ ); +} + +export function ComplianceChecklist({ report }: { report: ComplianceReport }) { + return ( +
+
+ Overall: + + {report.overall} + +
+
+ {report.checks.map((check) => ( + + ))} +
+
+ ); +} diff --git a/apps/web/src/components/EmptyState.tsx b/apps/web/src/components/EmptyState.tsx new file mode 100644 index 0000000..4c2bd90 --- /dev/null +++ b/apps/web/src/components/EmptyState.tsx @@ -0,0 +1,27 @@ +import type { LucideIcon } from 'lucide-react'; +import { Card, CardContent } from './ui/Card'; + +export function EmptyState({ + icon: Icon, + title, + description, + action, +}: { + icon: LucideIcon; + title: string; + description?: string; + action?: React.ReactNode; +}) { + return ( + + +
+ +
+

{title}

+ {description &&

{description}

} + {action &&
{action}
} +
+
+ ); +} diff --git a/apps/web/src/components/MetricCard.tsx b/apps/web/src/components/MetricCard.tsx new file mode 100644 index 0000000..b14d806 --- /dev/null +++ b/apps/web/src/components/MetricCard.tsx @@ -0,0 +1,48 @@ +import type { LucideIcon } from 'lucide-react'; +import { Card, CardContent } from './ui/Card'; +import { cn } from '../lib/utils'; + +export function MetricCard({ + title, + value, + subtitle, + icon: Icon, + trend, + className, +}: { + title: string; + value: string | number; + subtitle?: string; + icon?: LucideIcon; + trend?: { value: string; positive?: boolean }; + className?: string; +}) { + return ( + + +
+
+

{title}

+

{value}

+ {subtitle &&

{subtitle}

} + {trend && ( +

+ {trend.value} +

+ )} +
+ {Icon && ( +
+ +
+ )} +
+
+
+ ); +} diff --git a/apps/web/src/components/PageHeader.tsx b/apps/web/src/components/PageHeader.tsx new file mode 100644 index 0000000..bb7ea89 --- /dev/null +++ b/apps/web/src/components/PageHeader.tsx @@ -0,0 +1,24 @@ +import type { ReactNode } from 'react'; +import { cn } from '../lib/utils'; + +export function PageHeader({ + title, + description, + actions, + className, +}: { + title: string; + description?: string; + actions?: ReactNode; + className?: string; +}) { + return ( +
+
+

{title}

+ {description &&

{description}

} +
+ {actions &&
{actions}
} +
+ ); +} diff --git a/apps/web/src/components/StatusBadge.tsx b/apps/web/src/components/StatusBadge.tsx new file mode 100644 index 0000000..91b17e9 --- /dev/null +++ b/apps/web/src/components/StatusBadge.tsx @@ -0,0 +1,44 @@ +import type { CampaignStatus, InboxStatus } from '@warmbox/shared'; +import { Badge } from './ui/Badge'; + +const campaignVariants: Record< + CampaignStatus, + 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'outline' +> = { + draft: 'outline', + active: 'success', + paused: 'warning', + maintenance: 'primary', + completed: 'default', + failed: 'danger', +}; + +const inboxVariants: Record< + InboxStatus, + 'default' | 'primary' | 'success' | 'warning' | 'danger' | 'outline' +> = { + active: 'success', + paused: 'warning', + error: 'danger', +}; + +export function StatusBadge({ + status, + type = 'campaign', +}: { + status: CampaignStatus | InboxStatus | string; + type?: 'campaign' | 'inbox'; +}) { + const variant = + type === 'inbox' + ? inboxVariants[status as InboxStatus] ?? 'default' + : campaignVariants[status as CampaignStatus] ?? 'default'; + + const label = status.replace(/_/g, ' '); + + return ( + + {label} + + ); +} diff --git a/apps/web/src/components/layout/AppShell.tsx b/apps/web/src/components/layout/AppShell.tsx new file mode 100644 index 0000000..db7c446 --- /dev/null +++ b/apps/web/src/components/layout/AppShell.tsx @@ -0,0 +1,17 @@ +import { Outlet } from 'react-router-dom'; +import { Sidebar } from './Sidebar'; +import { BottomNav } from './BottomNav'; + +export function AppShell() { + return ( +
+ +
+
+ +
+
+ +
+ ); +} diff --git a/apps/web/src/components/layout/BottomNav.tsx b/apps/web/src/components/layout/BottomNav.tsx new file mode 100644 index 0000000..006fa94 --- /dev/null +++ b/apps/web/src/components/layout/BottomNav.tsx @@ -0,0 +1,36 @@ +import { NavLink } from 'react-router-dom'; +import { LayoutDashboard, Megaphone, Mail, Activity, Settings } from 'lucide-react'; +import { cn } from '../../lib/utils'; + +const navItems = [ + { to: '/', label: 'Home', icon: LayoutDashboard, end: true }, + { to: '/campaigns', label: 'Campaigns', icon: Megaphone }, + { to: '/mailboxes', label: 'Mail', icon: Mail }, + { to: '/activity', label: 'Activity', icon: Activity }, + { to: '/settings', label: 'Settings', icon: Settings }, +]; + +export function BottomNav() { + return ( + + ); +} diff --git a/apps/web/src/components/layout/Sidebar.tsx b/apps/web/src/components/layout/Sidebar.tsx new file mode 100644 index 0000000..ed97635 --- /dev/null +++ b/apps/web/src/components/layout/Sidebar.tsx @@ -0,0 +1,65 @@ +import { NavLink } from 'react-router-dom'; +import { + LayoutDashboard, + Megaphone, + Mail, + Activity, + Settings, + Flame, + LogOut, +} from 'lucide-react'; +import { cn } from '../../lib/utils'; +import { useAuth } from '../../lib/auth'; + +const navItems = [ + { to: '/', label: 'Dashboard', icon: LayoutDashboard, end: true }, + { to: '/campaigns', label: 'Campaigns', icon: Megaphone }, + { to: '/mailboxes', label: 'Mailboxes', icon: Mail }, + { to: '/activity', label: 'Activity', icon: Activity }, + { to: '/settings', label: 'Settings', icon: Settings }, +]; + +export function Sidebar() { + const { logout } = useAuth(); + + return ( + + ); +} diff --git a/apps/web/src/components/ui/Alert.tsx b/apps/web/src/components/ui/Alert.tsx new file mode 100644 index 0000000..e45ebb4 --- /dev/null +++ b/apps/web/src/components/ui/Alert.tsx @@ -0,0 +1,42 @@ +import { type ReactNode } from 'react'; +import { AlertCircle, CheckCircle2, Info } from 'lucide-react'; +import { cn } from '../../lib/utils'; + +const variants = { + default: 'border-slate-200 bg-slate-50 text-slate-800', + destructive: 'border-red-200 bg-red-50 text-red-800', + success: 'border-emerald-200 bg-emerald-50 text-emerald-800', + warning: 'border-amber-200 bg-amber-50 text-amber-800', +}; + +const icons = { + default: Info, + destructive: AlertCircle, + success: CheckCircle2, + warning: AlertCircle, +}; + +export function Alert({ + children, + variant = 'default', + className, +}: { + children: ReactNode; + variant?: keyof typeof variants; + className?: string; +}) { + const Icon = icons[variant]; + return ( +
+ +
{children}
+
+ ); +} diff --git a/apps/web/src/components/ui/Badge.tsx b/apps/web/src/components/ui/Badge.tsx new file mode 100644 index 0000000..9e4fd07 --- /dev/null +++ b/apps/web/src/components/ui/Badge.tsx @@ -0,0 +1,28 @@ +import { type HTMLAttributes } from 'react'; +import { cn } from '../../lib/utils'; + +const variants = { + default: 'bg-slate-100 text-slate-700', + primary: 'bg-primary-100 text-primary-800', + success: 'bg-emerald-100 text-emerald-800', + warning: 'bg-amber-100 text-amber-800', + danger: 'bg-red-100 text-red-800', + outline: 'border border-slate-200 bg-white text-slate-700', +}; + +export interface BadgeProps extends HTMLAttributes { + variant?: keyof typeof variants; +} + +export function Badge({ className, variant = 'default', ...props }: BadgeProps) { + return ( + + ); +} diff --git a/apps/web/src/components/ui/Button.tsx b/apps/web/src/components/ui/Button.tsx new file mode 100644 index 0000000..80a3652 --- /dev/null +++ b/apps/web/src/components/ui/Button.tsx @@ -0,0 +1,41 @@ +import { forwardRef, type ButtonHTMLAttributes } from 'react'; +import { cn } from '../../lib/utils'; + +const variants = { + default: 'bg-primary-600 text-white hover:bg-primary-700 focus-visible:ring-primary-500', + secondary: 'bg-slate-100 text-slate-900 hover:bg-slate-200 focus-visible:ring-slate-400', + outline: 'border border-slate-300 bg-white hover:bg-slate-50 focus-visible:ring-slate-400', + ghost: 'hover:bg-slate-100 focus-visible:ring-slate-400', + destructive: 'bg-red-600 text-white hover:bg-red-700 focus-visible:ring-red-500', +}; + +const sizes = { + default: 'h-10 px-4 py-2 text-sm', + sm: 'h-8 px-3 text-xs', + lg: 'h-11 px-6 text-base', + icon: 'h-10 w-10', +}; + +export interface ButtonProps extends ButtonHTMLAttributes { + variant?: keyof typeof variants; + size?: keyof typeof sizes; +} + +export const Button = forwardRef( + ({ className, variant = 'default', size = 'default', disabled, ...props }, ref) => ( + + ); +} + +export function TabsContent({ + value, + children, + className, +}: { + value: string; + children: ReactNode; + className?: string; +}) { + const ctx = useContext(TabsContext); + if (!ctx) throw new Error('TabsContent must be used within Tabs'); + if (ctx.value !== value) return null; + + return ( +
+ {children} +
+ ); +} diff --git a/apps/web/src/index.css b/apps/web/src/index.css new file mode 100644 index 0000000..cdaff9b --- /dev/null +++ b/apps/web/src/index.css @@ -0,0 +1,17 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap'); + +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + body { + @apply bg-slate-50 text-slate-900 font-sans antialiased; + } +} + +@layer utilities { + .safe-bottom { + padding-bottom: env(safe-area-inset-bottom, 0); + } +} diff --git a/apps/web/src/lib/api.ts b/apps/web/src/lib/api.ts new file mode 100644 index 0000000..c896870 --- /dev/null +++ b/apps/web/src/lib/api.ts @@ -0,0 +1,96 @@ +const API_KEY_STORAGE = 'warmbox_api_key'; +const BASE_URL_STORAGE = 'warmbox_base_url'; + +export class ApiError extends Error { + constructor( + public status: number, + message: string, + public body?: unknown, + ) { + super(message); + this.name = 'ApiError'; + } +} + +export function getApiKey(): string { + return sessionStorage.getItem(API_KEY_STORAGE) ?? ''; +} + +export function setApiKey(key: string): void { + if (key) { + sessionStorage.setItem(API_KEY_STORAGE, key); + } else { + sessionStorage.removeItem(API_KEY_STORAGE); + } +} + +export function getBaseUrl(): string { + return sessionStorage.getItem(BASE_URL_STORAGE) ?? ''; +} + +export function setBaseUrl(url: string): void { + if (url) { + sessionStorage.setItem(BASE_URL_STORAGE, url.replace(/\/$/, '')); + } else { + sessionStorage.removeItem(BASE_URL_STORAGE); + } +} + +function buildUrl(path: string): string { + const base = getBaseUrl(); + if (base) { + return `${base}${path.startsWith('/') ? path : `/${path}`}`; + } + return path; +} + +export async function api( + path: string, + options: RequestInit = {}, +): Promise { + const headers = new Headers(options.headers); + if (!headers.has('Content-Type') && options.body) { + headers.set('Content-Type', 'application/json'); + } + + const apiKey = getApiKey(); + if (apiKey) { + headers.set('X-API-Key', apiKey); + } + + const res = await fetch(buildUrl(path), { + ...options, + headers, + }); + + if (res.status === 204) { + return undefined as T; + } + + const text = await res.text(); + let body: unknown = null; + if (text) { + try { + body = JSON.parse(text); + } catch { + body = text; + } + } + + if (!res.ok) { + const message = + typeof body === 'object' && body !== null && 'message' in body + ? String((body as { message: string }).message) + : `Request failed (${res.status})`; + throw new ApiError(res.status, message, body); + } + + return body as T; +} + +export const apiGet = (path: string) => api(path); +export const apiPost = (path: string, data?: unknown) => + api(path, { method: 'POST', body: data ? JSON.stringify(data) : undefined }); +export const apiPatch = (path: string, data: unknown) => + api(path, { method: 'PATCH', body: JSON.stringify(data) }); +export const apiDelete = (path: string) => api(path, { method: 'DELETE' }); diff --git a/apps/web/src/lib/auth.tsx b/apps/web/src/lib/auth.tsx new file mode 100644 index 0000000..472a039 --- /dev/null +++ b/apps/web/src/lib/auth.tsx @@ -0,0 +1,154 @@ +import { + createContext, + useCallback, + useContext, + useMemo, + useState, + type ReactNode, +} from 'react'; +import { Navigate, useLocation } from 'react-router-dom'; +import { KeyRound } from 'lucide-react'; +import { ApiError, getApiKey, setApiKey, setBaseUrl, getBaseUrl } from './api'; +import { Button } from '../components/ui/Button'; +import { Input } from '../components/ui/Input'; +import { Label } from '../components/ui/Label'; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../components/ui/Card'; +import { Alert } from '../components/ui/Alert'; + +type AuthContextValue = { + apiKey: string; + baseUrl: string; + login: (apiKey: string, baseUrl?: string) => void; + logout: () => void; +}; + +const AuthContext = createContext(null); + +export function AuthProvider({ children }: { children: ReactNode }) { + const [apiKey, setApiKeyState] = useState(() => getApiKey()); + const [baseUrl, setBaseUrlState] = useState(() => getBaseUrl()); + + const login = useCallback((key: string, url = '') => { + setApiKey(key); + setBaseUrl(url); + setApiKeyState(key); + setBaseUrlState(url); + }, []); + + const logout = useCallback(() => { + setApiKey(''); + setBaseUrl(''); + setApiKeyState(''); + setBaseUrlState(''); + }, []); + + const value = useMemo( + () => ({ apiKey, baseUrl, login, logout }), + [apiKey, baseUrl, login, logout], + ); + + return {children}; +} + +export function useAuth() { + const ctx = useContext(AuthContext); + if (!ctx) throw new Error('useAuth must be used within AuthProvider'); + return ctx; +} + +export function RequireAuth({ children }: { children: ReactNode }) { + const { apiKey } = useAuth(); + const location = useLocation(); + + if (!apiKey) { + return ; + } + + return <>{children}; +} + +export function LoginPage() { + const { login } = useAuth(); + const location = useLocation(); + const from = (location.state as { from?: { pathname: string } })?.from?.pathname ?? '/'; + + const [key, setKey] = useState(''); + const [url, setUrl] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + + async function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + setError(''); + setLoading(true); + + try { + login(key.trim(), url.trim()); + const headers: HeadersInit = { 'X-API-Key': key.trim() }; + const testUrl = url.trim() + ? `${url.trim().replace(/\/$/, '')}/api/settings` + : '/api/settings'; + const res = await fetch(testUrl, { headers }); + if (!res.ok) { + throw new ApiError(res.status, 'Invalid API key or server unreachable'); + } + window.location.href = from; + } catch (err) { + if (err instanceof ApiError) { + setError(err.message); + } else { + setError('Could not connect to the API'); + } + login('', ''); + } finally { + setLoading(false); + } + } + + return ( +
+ + +
+ +
+ Warmbox + Enter your API key to access the dashboard +
+ +
+ {error && {error}} +
+ + setKey(e.target.value)} + placeholder="Your WARMBOX_API_KEY" + required + autoFocus + /> +
+
+ + setUrl(e.target.value)} + placeholder="Leave empty for local proxy" + /> +

+ In development, leave blank to use the Vite proxy at /api +

+
+ +
+
+
+
+ ); +} diff --git a/apps/web/src/lib/types.ts b/apps/web/src/lib/types.ts new file mode 100644 index 0000000..d4ddb27 --- /dev/null +++ b/apps/web/src/lib/types.ts @@ -0,0 +1,155 @@ +import type { + CampaignRecipe, + CampaignStatus, + CampaignStatsToday, + ComplianceReport, + DnsCheckResult, + InboxStatus, + JobStatus, + MailboxProvider, + MailboxRole, + PaginatedLogs, + ProviderPreset, + WarmupStatus, + WorkspaceOverview, +} from '@warmbox/shared'; + +export type { + CampaignRecipe, + CampaignStatus, + CampaignStatsToday, + ComplianceReport, + DnsCheckResult, + InboxStatus, + JobStatus, + MailboxProvider, + MailboxRole, + PaginatedLogs, + ProviderPreset, + WarmupStatus, + WorkspaceOverview, +}; + +export type Mailbox = { + id: string; + workspaceId: string; + email: string; + smtpHost: string; + smtpPort: number; + imapHost: string; + imapPort: number; + username: string; + authType?: 'password' | 'microsoft_oauth'; + role: MailboxRole; + provider: MailboxProvider; + status: InboxStatus; + dailyLimit: number; + currentRamp: number; + industry: string; + senderName: string | null; + companyName: string | null; + lastError: string | null; + errorAt: string | null; + spamFolderPath: string | null; + lastSpamRescuedAt: string | null; + createdAt: string; + updatedAt: string; +}; + +export type CampaignSatellite = { + id: string; + campaignId: string; + satelliteMailboxId: string; + weight: number; + satelliteMailbox: Mailbox; +}; + +export type Campaign = { + id: string; + workspaceId: string; + primaryMailboxId: string; + name: string; + recipe: CampaignRecipe; + status: CampaignStatus; + durationDays: number; + minEmailsPerDay: number; + maxEmailsPerDay: number; + replyRatePercent: number; + maintenanceVolumePercent: number; + timezone: string; + sendWindowStart: string; + sendWindowEnd: string; + customSchedule: string | null; + startedAt: string | null; + endsAt: string | null; + currentDay: number; + pausedFromStatus: CampaignStatus | null; + lastSendAt: string | null; + createdAt: string; + updatedAt: string; + primaryMailbox: Mailbox; + satellites: CampaignSatellite[]; +}; + +export type DailyStat = { + date: string; + dayIndex: number; + plannedSends: number; + plannedReplies: number; + actualSends: number; + actualReplies: number; + spamDetected: number; + spamRescued: number; +}; + +export type WarmupLog = { + id: string; + campaignId: string | null; + senderId: string; + receiverId: string; + status: WarmupStatus; + subject: string | null; + messageId: string | null; + threadId: string | null; + createdAt: string; + updatedAt: string; + sender: Mailbox; + receiver: Mailbox; +}; + +export type Settings = { + dispatcher_cron?: string; + rescuer_cron?: string; + ramp_increment?: string; + industries?: string; + ai_model?: string; +}; + +export type RecipePreviewDay = { + day: number; + sends: number; + replies: number; +}; + +export type CreateCampaignInput = { + primaryMailboxId: string; + name: string; + recipe: CampaignRecipe; + durationDays: number; + minEmailsPerDay: number; + maxEmailsPerDay: number; + replyRatePercent: number; + maintenanceVolumePercent?: number; + timezone?: string; + sendWindowStart?: string; + sendWindowEnd?: string; + satelliteMailboxIds?: string[]; +}; + +export type ProviderPresets = Record; + +export type ConnectionTestResult = { + ok: boolean; + smtp: { ok: boolean; error?: string }; + imap: { ok: boolean; error?: string }; +}; diff --git a/apps/web/src/lib/utils.ts b/apps/web/src/lib/utils.ts new file mode 100644 index 0000000..005e2f4 --- /dev/null +++ b/apps/web/src/lib/utils.ts @@ -0,0 +1,25 @@ +import { clsx, type ClassValue } from 'clsx'; +import { twMerge } from 'tailwind-merge'; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} + +export function formatPercent(value: number, decimals = 1): string { + return `${value.toFixed(decimals)}%`; +} + +export function formatDate(iso: string): string { + const d = new Date(iso); + return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric', year: 'numeric' }); +} + +export function formatDateTime(iso: string): string { + const d = new Date(iso); + return d.toLocaleString(undefined, { + month: 'short', + day: 'numeric', + hour: '2-digit', + minute: '2-digit', + }); +} diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx new file mode 100644 index 0000000..4921efe --- /dev/null +++ b/apps/web/src/main.tsx @@ -0,0 +1,28 @@ +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { BrowserRouter } from 'react-router-dom'; +import { AuthProvider } from './lib/auth'; +import App from './App'; +import './index.css'; + +const queryClient = new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30_000, + retry: 1, + }, + }, +}); + +createRoot(document.getElementById('root')!).render( + + + + + + + + + , +); diff --git a/apps/web/src/pages/ActivityPage.tsx b/apps/web/src/pages/ActivityPage.tsx new file mode 100644 index 0000000..a57e47e --- /dev/null +++ b/apps/web/src/pages/ActivityPage.tsx @@ -0,0 +1,151 @@ +import { useState } from 'react'; +import { useQuery } from '@tanstack/react-query'; +import { Activity } from 'lucide-react'; +import type { WarmupStatus } from '@warmbox/shared'; +import { apiGet } from '../lib/api'; +import type { PaginatedLogs, WarmupLog } from '../lib/types'; +import { PageHeader } from '../components/PageHeader'; +import { Button } from '../components/ui/Button'; +import { Card, CardContent } from '../components/ui/Card'; +import { Select } from '../components/ui/Select'; +import { Label } from '../components/ui/Label'; +import { EmptyState } from '../components/EmptyState'; +import { Skeleton } from '../components/ui/Skeleton'; +import { Alert } from '../components/ui/Alert'; +import { formatDateTime } from '../lib/utils'; + +const STATUSES: (WarmupStatus | '')[] = [ + '', + 'sent', + 'rescued_from_spam', + 'engaged_no_reply', + 'replied', + 'complete', +]; + +export function ActivityPage() { + const [page, setPage] = useState(1); + const [status, setStatus] = useState(''); + + const { data, isLoading, error } = useQuery({ + queryKey: ['logs', { page, status }], + queryFn: () => { + const params = new URLSearchParams({ page: String(page), limit: '20' }); + if (status) params.set('status', status); + return apiGet>(`/api/logs?${params}`); + }, + }); + + return ( +
+ + +
+
+ + +
+
+ + {error && Failed to load activity} + + {isLoading ? ( +
+ {Array.from({ length: 5 }).map((_, i) => ( + + ))} +
+ ) : !data?.data.length ? ( + + ) : ( + <> +
+ + + + + + + + + + + + {data.data.map((log) => ( + + + + + + + + ))} + +
TimeFromToStatusSubject
+ {formatDateTime(log.createdAt)} + {log.sender.email}{log.receiver.email}{log.status.replace(/_/g, ' ')} + {log.subject ?? '—'} +
+
+ +
+ {data.data.map((log) => ( + + +

{formatDateTime(log.createdAt)}

+

+ {log.sender.email} → {log.receiver.email} +

+

+ {log.status.replace(/_/g, ' ')} +

+ {log.subject &&

{log.subject}

} +
+
+ ))} +
+ +
+

+ Page {data.pagination.page} of {data.pagination.totalPages} ({data.pagination.total}{' '} + total) +

+
+ + +
+
+ + )} +
+ ); +} diff --git a/apps/web/src/pages/CampaignDetailPage.tsx b/apps/web/src/pages/CampaignDetailPage.tsx new file mode 100644 index 0000000..da20d18 --- /dev/null +++ b/apps/web/src/pages/CampaignDetailPage.tsx @@ -0,0 +1,738 @@ +import { useParams, useNavigate } from 'react-router-dom'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { useEffect, useState } from 'react'; +import { + LineChart, + Line, + BarChart, + Bar, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + ResponsiveContainer, + Legend, +} from 'recharts'; +import { apiGet, apiPatch, apiPost, apiDelete } from '../lib/api'; +import type { + Campaign, + CampaignSatellite, + CampaignStatsToday, + ComplianceReport, + DailyStat, + PaginatedLogs, + WarmupLog, +} from '../lib/types'; +import { PageHeader } from '../components/PageHeader'; +import { StatusBadge } from '../components/StatusBadge'; +import { MetricCard } from '../components/MetricCard'; +import { ComplianceChecklist } from '../components/ComplianceChecklist'; +import { Tabs, TabsList, TabsTrigger, TabsContent } from '../components/ui/Tabs'; +import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card'; +import { Skeleton } from '../components/ui/Skeleton'; +import { Alert } from '../components/ui/Alert'; +import { Button } from '../components/ui/Button'; +import { Input } from '../components/ui/Input'; +import { Label } from '../components/ui/Label'; +import { formatDateTime, formatPercent } from '../lib/utils'; +import { Badge } from '../components/ui/Badge'; +import { Send, MessageSquare, Shield, Inbox, Pause, Play, Loader2, Trash2 } from 'lucide-react'; + +function LogStatusBadge({ status }: { status: string }) { + if (status === 'rescued_from_spam') { + return Rescued from spam; + } + if (status === 'engaged_no_reply') { + return Engaged (no reply); + } + if (status === 'replied') { + return Replied; + } + return {status.replace(/_/g, ' ')}; +} + +export function CampaignDetailPage() { + const { id } = useParams<{ id: string }>(); + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const [settingsForm, setSettingsForm] = useState({ + name: '', + timezone: '', + sendWindowStart: '', + sendWindowEnd: '', + replyRatePercent: 30, + durationDays: 30, + minEmailsPerDay: 1, + maxEmailsPerDay: 40, + recipe: 'grow' as Campaign['recipe'], + }); + const [settingsMessage, setSettingsMessage] = useState(''); + const [settingsError, setSettingsError] = useState(''); + + const { data: campaign, isLoading, error } = useQuery({ + queryKey: ['campaign', id], + queryFn: () => apiGet(`/api/campaigns/${id}`), + enabled: !!id, + }); + + const { data: today } = useQuery({ + queryKey: ['campaign', id, 'stats', 'today'], + queryFn: () => apiGet(`/api/campaigns/${id}/stats/today`), + enabled: !!id, + }); + + const { data: dailyData } = useQuery({ + queryKey: ['campaign', id, 'stats', 'daily'], + queryFn: () => apiGet<{ data: DailyStat[] }>(`/api/campaigns/${id}/stats/daily`), + enabled: !!id, + }); + + const { data: logs } = useQuery({ + queryKey: ['logs', { campaignId: id }], + queryFn: () => + apiGet>(`/api/logs?campaignId=${id}&limit=50`), + enabled: !!id, + }); + + const { data: satellites } = useQuery({ + queryKey: ['campaign', id, 'satellites'], + queryFn: () => apiGet(`/api/campaigns/${id}/satellites`), + enabled: !!id, + }); + + const { data: compliance, refetch: refetchCompliance, isFetching: complianceLoading } = useQuery({ + queryKey: ['campaign', id, 'compliance'], + queryFn: () => apiGet(`/api/campaigns/${id}/compliance`), + enabled: !!id, + }); + + const { data: allMailboxes } = useQuery({ + queryKey: ['mailboxes'], + queryFn: () => apiGet('/api/mailboxes'), + enabled: !!id, + }); + + useEffect(() => { + if (campaign) { + setSettingsForm({ + name: campaign.name, + timezone: campaign.timezone, + sendWindowStart: campaign.sendWindowStart, + sendWindowEnd: campaign.sendWindowEnd, + replyRatePercent: campaign.replyRatePercent, + durationDays: campaign.durationDays, + minEmailsPerDay: campaign.minEmailsPerDay, + maxEmailsPerDay: campaign.maxEmailsPerDay, + recipe: campaign.recipe, + }); + } + }, [campaign]); + + const saveSettingsMutation = useMutation({ + mutationFn: (payload: typeof settingsForm) => apiPatch(`/api/campaigns/${id}`, payload), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['campaign', id] }); + setSettingsMessage('Campaign settings saved'); + setSettingsError(''); + }, + onError: (err: Error) => setSettingsError(err.message), + }); + + const pauseMutation = useMutation({ + mutationFn: () => apiPost(`/api/campaigns/${id}/pause`), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['campaign', id] }); + queryClient.invalidateQueries({ queryKey: ['workspace', 'overview'] }); + }, + }); + + const resumeMutation = useMutation({ + mutationFn: () => apiPost(`/api/campaigns/${id}/resume`), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['campaign', id] }); + queryClient.invalidateQueries({ queryKey: ['workspace', 'overview'] }); + }, + }); + + const assignSatelliteMutation = useMutation({ + mutationFn: (mailboxId: string) => + apiPost(`/api/campaigns/${id}/satellites`, { mailboxId }), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['campaign', id, 'satellites'] }); + queryClient.invalidateQueries({ queryKey: ['mailboxes'] }); + }, + }); + + const unassignSatelliteMutation = useMutation({ + mutationFn: (mailboxId: string) => apiDelete(`/api/campaigns/${id}/satellites/${mailboxId}`), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['campaign', id, 'satellites'] }); + queryClient.invalidateQueries({ queryKey: ['mailboxes'] }); + }, + }); + + const deleteMutation = useMutation({ + mutationFn: () => apiDelete(`/api/campaigns/${id}`), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['campaigns'] }); + queryClient.invalidateQueries({ queryKey: ['mailboxes'] }); + navigate('/campaigns'); + }, + }); + + function handleDeleteCampaign() { + if (!campaign) return; + if ( + !window.confirm( + `Delete draft campaign "${campaign.name}"? This cannot be undone.`, + ) + ) { + return; + } + deleteMutation.mutate(); + } + + const isPaused = campaign?.status === 'paused'; + const canEditSettings = + campaign && + ['draft', 'active', 'paused', 'maintenance'].includes(campaign.status); + + const availableSatellites = + allMailboxes?.filter( + (m) => + m.role !== 'primary' && + m.id !== campaign?.primaryMailboxId && + !satellites?.some((s) => s.satelliteMailboxId === m.id), + ) ?? []; + + const chartData = + dailyData?.data.map((d) => ({ + day: d.dayIndex, + sends: d.actualSends, + replies: d.actualReplies, + planned: d.plannedSends, + inbox: Math.max(0, d.actualSends - d.spamDetected), + spam: d.spamDetected, + })) ?? []; + + if (isLoading) { + return ( +
+ +
+ {Array.from({ length: 4 }).map((_, i) => ( + + ))} +
+
+ ); + } + + if (error || !campaign) { + return Campaign not found; + } + + return ( +
+ + + {campaign.status === 'active' || campaign.status === 'maintenance' ? ( + + ) : null} + {campaign.status === 'paused' ? ( + + ) : null} + {campaign.status === 'draft' ? ( + + ) : null} +
+ } + /> + + {campaign.status === 'paused' && ( + + This campaign is paused. If spam rate exceeded 30% over 3 days, it was auto-paused for + deliverability protection. + + )} + + + + Overview + Activity + Satellites + Settings + Compliance + + + +
+ {today && !today.onTrack && today.onTrackReasons.length > 0 && ( + + Behind schedule today: {today.onTrackReasons.join(' ')} + + )} +
+ + + + +
+ + + + Daily Performance + + + {chartData.length === 0 ? ( +

No daily stats yet

+ ) : ( +
+ + + + + + + + + + + + +
+ )} +
+
+ + + + Inbox Placement (7d) + + + {chartData.length === 0 ? ( +

No placement data yet

+ ) : ( +
+ + + + + + + + + + + +
+ )} +
+
+ + + + Campaign Details + + +
+
+
Recipe
+
{campaign.recipe}
+
+
+
Progress
+
+ Day {campaign.currentDay} of {campaign.durationDays} +
+
+
+
Reply Rate Target
+
{campaign.replyRatePercent}%
+
+
+
Send Window
+
+ {campaign.sendWindowStart} – {campaign.sendWindowEnd} ({campaign.timezone}) +
+
+
+
+
+
+
+ + + {!logs?.data.length ? ( +

No activity yet

+ ) : ( + <> +
+ + + + + + + + + + + {logs.data.map((log) => ( + + + + + + + ))} + +
TimeFromToStatus
+ {formatDateTime(log.createdAt)} + {log.sender.email}{log.receiver.email} + +
+
+
+ {logs.data.map((log) => ( + + +

{formatDateTime(log.createdAt)}

+

+ {log.sender.email} → {log.receiver.email} +

+

+ +

+
+
+ ))} +
+ + )} +
+ + +
+ {campaign.status === 'active' && ( + + Pause the campaign to change recipe or duration. You can add/remove satellites while + active. + + )} + {!satellites?.length ? ( +

No satellites assigned

+ ) : ( +
+ {satellites.map((s) => ( + + +
+

{s.satelliteMailbox.email}

+

Weight: {s.weight}

+ +
+ +
+
+ ))} +
+ )} + {availableSatellites.length > 0 && ( + + + Add satellite + + + {availableSatellites.map((mailbox) => ( +
+ {mailbox.email} + +
+ ))} +
+
+ )} +
+
+ + + {canEditSettings ? ( + + + Campaign Settings + + + {settingsMessage && {settingsMessage}} + {settingsError && {settingsError}} + {isPaused ? ( +

+ Campaign is paused — you can update duration, recipe, and volume. Future days + will be recalculated. +

+ ) : campaign.status !== 'draft' ? ( +

+ Active campaigns can update name, send window, timezone, and reply rate. Pause + to change duration or recipe. +

+ ) : null} +
{ + e.preventDefault(); + saveSettingsMutation.mutate(settingsForm); + }} + className="space-y-4 max-w-md" + > +
+ + setSettingsForm({ ...settingsForm, name: e.target.value })} + required + /> +
+
+ + setSettingsForm({ ...settingsForm, timezone: e.target.value })} + required + /> +
+
+
+ + + setSettingsForm({ ...settingsForm, sendWindowStart: e.target.value }) + } + required + /> +
+
+ + + setSettingsForm({ ...settingsForm, sendWindowEnd: e.target.value }) + } + required + /> +
+
+
+ + + setSettingsForm({ + ...settingsForm, + replyRatePercent: Number(e.target.value), + }) + } + required + /> +
+ {isPaused && ( + <> +
+ + + setSettingsForm({ + ...settingsForm, + durationDays: Number(e.target.value), + }) + } + /> +
+
+
+ + + setSettingsForm({ + ...settingsForm, + minEmailsPerDay: Number(e.target.value), + }) + } + /> +
+
+ + + setSettingsForm({ + ...settingsForm, + maxEmailsPerDay: Number(e.target.value), + }) + } + /> +
+
+ + )} + +
+
+
+ ) : ( +

+ This campaign can no longer be edited. +

+ )} +
+ + +
+

+ Compliance checks run live on each request (DNS, domain age via RDAP, Postmaster when + connected). Re-run after fixing DNS, connecting Postmaster, or changing mailboxes. +

+ + {compliance ? ( + + + + + + ) : ( +
+ + Loading compliance report… +
+ )} +
+
+
+ + ); +} diff --git a/apps/web/src/pages/CampaignWizardPage.tsx b/apps/web/src/pages/CampaignWizardPage.tsx new file mode 100644 index 0000000..70813ce --- /dev/null +++ b/apps/web/src/pages/CampaignWizardPage.tsx @@ -0,0 +1,400 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { ChevronLeft, ChevronRight, Loader2 } from 'lucide-react'; +import type { CampaignRecipe } from '@warmbox/shared'; +import { PROVIDER_LABELS } from '@warmbox/shared'; +import { apiDelete, apiGet, apiPost } from '../lib/api'; +import type { Campaign, CreateCampaignInput, Mailbox, RecipePreviewDay } from '../lib/types'; +import { PageHeader } from '../components/PageHeader'; +import { Button } from '../components/ui/Button'; +import { Input } from '../components/ui/Input'; +import { Label } from '../components/ui/Label'; +import { Select } from '../components/ui/Select'; +import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card'; +import { Alert } from '../components/ui/Alert'; +import { Progress } from '../components/ui/Progress'; + +const STEPS = ['Mailbox', 'Recipe', 'Satellites', 'Review']; + +const RECIPES: CampaignRecipe[] = ['grow', 'flat', 'randomized', 'custom']; + +export function CampaignWizardPage() { + const navigate = useNavigate(); + const queryClient = useQueryClient(); + const [step, setStep] = useState(0); + const [error, setError] = useState(''); + + const [primaryMailboxId, setPrimaryMailboxId] = useState(''); + const [name, setName] = useState(''); + const [recipe, setRecipe] = useState('grow'); + const [durationDays, setDurationDays] = useState(30); + const [minEmailsPerDay, setMinEmailsPerDay] = useState(1); + const [maxEmailsPerDay, setMaxEmailsPerDay] = useState(40); + const [replyRatePercent, setReplyRatePercent] = useState(30); + const [timezone, setTimezone] = useState('America/Vancouver'); + const [sendWindowStart, setSendWindowStart] = useState('08:00'); + const [sendWindowEnd, setSendWindowEnd] = useState('18:00'); + const [satelliteIds, setSatelliteIds] = useState([]); + const [preview, setPreview] = useState(null); + const [startAfterCreate, setStartAfterCreate] = useState(true); + + const { data: mailboxes } = useQuery({ + queryKey: ['mailboxes'], + queryFn: () => apiGet('/api/mailboxes'), + }); + + const { data: campaigns } = useQuery({ + queryKey: ['campaigns'], + queryFn: () => apiGet('/api/campaigns'), + }); + + const reservedPrimaryIds = new Set( + campaigns + ?.filter((c) => !['completed', 'failed'].includes(c.status)) + .map((c) => c.primaryMailboxId) ?? [], + ); + + const primaryCandidates = + mailboxes?.filter((m) => m.role !== 'satellite' && !reservedPrimaryIds.has(m.id)) ?? []; + const satelliteCandidates = + mailboxes?.filter((m) => m.id !== primaryMailboxId && m.role !== 'primary') ?? []; + + const previewMutation = useMutation({ + mutationFn: () => + apiPost<{ schedule: RecipePreviewDay[] }>('/api/recipes/preview', { + recipe, + durationDays, + minEmailsPerDay, + maxEmailsPerDay, + replyRatePercent, + }), + onSuccess: (data) => setPreview(data.schedule), + onError: (err: Error) => setError(err.message), + }); + + const createMutation = useMutation({ + mutationFn: async () => { + const payload: CreateCampaignInput = { + primaryMailboxId, + name, + recipe, + durationDays, + minEmailsPerDay, + maxEmailsPerDay, + replyRatePercent, + timezone, + sendWindowStart, + sendWindowEnd, + satelliteMailboxIds: satelliteIds.length ? satelliteIds : undefined, + }; + const campaign = await apiPost('/api/campaigns', payload); + if (startAfterCreate) { + try { + return await apiPost(`/api/campaigns/${campaign.id}/start`); + } catch (err) { + await apiDelete(`/api/campaigns/${campaign.id}`); + throw err; + } + } + return campaign; + }, + onSuccess: (campaign) => { + queryClient.invalidateQueries({ queryKey: ['campaigns'] }); + queryClient.invalidateQueries({ queryKey: ['mailboxes'] }); + navigate(`/campaigns/${campaign.id}`); + }, + onError: (err: Error) => setError(err.message), + }); + + function toggleSatellite(id: string) { + setSatelliteIds((prev) => + prev.includes(id) ? prev.filter((x) => x !== id) : [...prev, id], + ); + } + + async function handleNext() { + setError(''); + if (step === 0) { + if (!primaryMailboxId || !name.trim()) { + setError('Select a primary mailbox and enter a campaign name'); + return; + } + } + if (step === 1) { + await previewMutation.mutateAsync(); + } + setStep((s) => Math.min(s + 1, STEPS.length - 1)); + } + + function handleBack() { + setError(''); + setStep((s) => Math.max(s - 1, 0)); + } + + const progress = ((step + 1) / STEPS.length) * 100; + const isSubmitting = createMutation.isPending; + + return ( +
+ + +
+
+ {STEPS.map((label, i) => ( + + {label} + + ))} +
+ +
+ + {error && {error}} + + + + {STEPS[step]} + + + {step === 0 && ( + <> +
+ + setName(e.target.value)} + placeholder="Q1 Outreach Warmup" + /> +
+
+ + + {mailboxes?.some((m) => reservedPrimaryIds.has(m.id)) && ( +

+ Mailboxes already used as primary on another campaign are hidden. Pause or + delete that campaign to reuse the mailbox. +

+ )} + {primaryCandidates.length === 0 && ( +

+ No available primary mailboxes. Each mailbox can only warm up in one campaign at + a time. +

+ )} +
+ + )} + + {step === 1 && ( + <> +
+
+ + +
+
+ + setDurationDays(Number(e.target.value))} + /> +
+
+ + setMinEmailsPerDay(Number(e.target.value))} + /> +
+
+ + setMaxEmailsPerDay(Number(e.target.value))} + /> +
+
+ + setReplyRatePercent(Number(e.target.value))} + /> +
+
+ + setTimezone(e.target.value)} /> +
+
+ + setSendWindowStart(e.target.value)} + /> +
+
+ + setSendWindowEnd(e.target.value)} + /> +
+
+ {preview && ( +
+ + + + + + + + + + {preview.map((d) => ( + + + + + + ))} + +
DaySendsReplies
{d.day}{d.sends}{d.replies}
+
+ )} + + )} + + {step === 2 && ( +
+

Select satellite mailboxes (optional)

+ {satelliteCandidates.length === 0 ? ( +

No available satellite mailboxes

+ ) : ( +
+ {satelliteCandidates.map((m) => ( + + ))} +
+ )} +
+ )} + + {step === 3 && ( +
+
+
+ Name:{' '} + {name} +
+
+ Recipe:{' '} + {recipe} +
+
+ Duration:{' '} + {durationDays} days +
+
+ Reply rate:{' '} + {replyRatePercent}% +
+
+ Satellites:{' '} + {satelliteIds.length} +
+
+ +
+ )} +
+
+ +
+ + {step < STEPS.length - 1 ? ( + + ) : ( + + )} +
+
+ ); +} diff --git a/apps/web/src/pages/CampaignsPage.tsx b/apps/web/src/pages/CampaignsPage.tsx new file mode 100644 index 0000000..e16d3d5 --- /dev/null +++ b/apps/web/src/pages/CampaignsPage.tsx @@ -0,0 +1,179 @@ +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { Link } from 'react-router-dom'; +import { Plus, Megaphone, Trash2 } from 'lucide-react'; +import { apiDelete, apiGet } from '../lib/api'; +import type { Campaign } from '../lib/types'; +import { PageHeader } from '../components/PageHeader'; +import { Button } from '../components/ui/Button'; +import { Card, CardContent } from '../components/ui/Card'; +import { StatusBadge } from '../components/StatusBadge'; +import { EmptyState } from '../components/EmptyState'; +import { Skeleton } from '../components/ui/Skeleton'; +import { Alert } from '../components/ui/Alert'; +import { formatDate } from '../lib/utils'; + +export function CampaignsPage() { + const queryClient = useQueryClient(); + + const { data, isLoading, error } = useQuery({ + queryKey: ['campaigns'], + queryFn: () => apiGet('/api/campaigns'), + }); + + const deleteMutation = useMutation({ + mutationFn: (id: string) => apiDelete(`/api/campaigns/${id}`), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['campaigns'] }); + queryClient.invalidateQueries({ queryKey: ['mailboxes'] }); + }, + }); + + function handleDelete(campaign: Campaign) { + if ( + !window.confirm( + `Delete draft campaign "${campaign.name}"? This cannot be undone.`, + ) + ) { + return; + } + deleteMutation.mutate(campaign.id); + } + + return ( +
+ + + + } + /> + + {error && Failed to load campaigns} + {deleteMutation.isError && ( + + {deleteMutation.error instanceof Error + ? deleteMutation.error.message + : 'Failed to delete campaign'} + + )} + + {isLoading ? ( +
+ {Array.from({ length: 3 }).map((_, i) => ( + + ))} +
+ ) : !data?.length ? ( + + + + } + /> + ) : ( + <> + {/* Desktop table */} +
+ + + + + + + + + + + + + + {data.map((campaign) => ( + + + + + + + + + + ))} + +
NamePrimaryRecipeStatusProgressCreatedActions
+ + {campaign.name} + + {campaign.primaryMailbox.email}{campaign.recipe} + + + Day {campaign.currentDay}/{campaign.durationDays} + {formatDate(campaign.createdAt)} + {campaign.status === 'draft' && ( + + )} +
+
+ + {/* Mobile cards */} +
+ {data.map((campaign) => ( + + + +
+
+

{campaign.name}

+

{campaign.primaryMailbox.email}

+
+ +
+
+ {campaign.recipe} + + Day {campaign.currentDay}/{campaign.durationDays} + +
+ + {campaign.status === 'draft' && ( + + )} +
+
+ ))} +
+ + )} +
+ ); +} diff --git a/apps/web/src/pages/DashboardPage.tsx b/apps/web/src/pages/DashboardPage.tsx new file mode 100644 index 0000000..9692de4 --- /dev/null +++ b/apps/web/src/pages/DashboardPage.tsx @@ -0,0 +1,185 @@ +import { useQuery } from '@tanstack/react-query'; +import { Link } from 'react-router-dom'; +import { Megaphone, Mail, Send, MessageSquare, AlertTriangle } from 'lucide-react'; +import { apiGet } from '../lib/api'; +import type { WorkspaceOverview } from '../lib/types'; +import { PageHeader } from '../components/PageHeader'; +import { MetricCard } from '../components/MetricCard'; +import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card'; +import { StatusBadge } from '../components/StatusBadge'; +import { Progress } from '../components/ui/Progress'; +import { Skeleton } from '../components/ui/Skeleton'; +import { Alert } from '../components/ui/Alert'; +import { formatPercent } from '../lib/utils'; + +const SPAM_INBOX_THRESHOLD = 70; + +export function DashboardPage() { + const { data, isLoading, error } = useQuery({ + queryKey: ['workspace', 'overview'], + queryFn: () => apiGet('/api/workspace/overview'), + refetchInterval: 60_000, + }); + + const activeCampaigns = data?.campaigns.filter((c) => c.status === 'active') ?? []; + const pausedCampaigns = data?.campaigns.filter((c) => c.status === 'paused') ?? []; + const offTrack = data?.campaigns.filter((c) => c.today && !c.today.onTrack) ?? []; + const highSpam = data?.campaigns.filter( + (c) => + c.today && + c.today.actual.sends > 0 && + c.today.rates.inbox < SPAM_INBOX_THRESHOLD, + ) ?? []; + const totalSendsToday = + data?.campaigns.reduce((sum, c) => sum + (c.today?.actual.sends ?? 0), 0) ?? 0; + const avgInboxRate = + activeCampaigns.length > 0 + ? activeCampaigns.reduce((sum, c) => sum + (c.today?.rates.inbox ?? 100), 0) / + activeCampaigns.length + : null; + + return ( +
+ + + {error && Failed to load dashboard data} + +
+ {isLoading ? ( + Array.from({ length: 4 }).map((_, i) => ) + ) : ( + <> + + + + + + )} +
+ + {pausedCampaigns.length > 0 && ( + + {pausedCampaigns.length} campaign(s) paused — may include auto-pause due to + high spam rate. Review campaign details. + + )} + + {highSpam.length > 0 && ( + + {highSpam.length} campaign(s) have inbox placement below{' '} + {SPAM_INBOX_THRESHOLD}% today. + + )} + + {offTrack.length > 0 && ( + + {offTrack.length} campaign(s) are behind today's send or reply targets. +
    + {offTrack.map((campaign) => ( +
  • + + {campaign.name} + + {campaign.today?.onTrackReasons?.length ? ( + — {campaign.today.onTrackReasons.join('; ')} + ) : campaign.today ? ( + + {' '} + — {campaign.today.actual.sends}/{campaign.today.planned.sends} sends today + + ) : null} +
  • + ))} +
+

+ Sends are checked after the first few emails of the day. Jitter (45+ min between sends) + means ramping up can take hours. +

+
+ )} + +
+

Campaigns

+ {isLoading ? ( +
+ {Array.from({ length: 2 }).map((_, i) => ( + + ))} +
+ ) : data?.campaigns.length === 0 ? ( + + + No campaigns yet.{' '} + + Create one + + + + ) : ( +
+ {data?.campaigns.map((campaign) => { + const today = campaign.today; + const progress = (campaign.currentDay / campaign.durationDays) * 100; + return ( + + + +
+ {campaign.name} +

{campaign.primaryEmail}

+
+ +
+ +
+
+ + Day {campaign.currentDay} of {campaign.durationDays} + + {campaign.recipe} +
+ +
+ {today ? ( +
+
+ Sends: + + {today.actual.sends}/{today.planned.sends} + +
+
+ Reply: + {formatPercent(today.rates.reply)} +
+ {!today.onTrack && today.onTrackReasons?.length > 0 && ( +
+ + {today.onTrackReasons[0]} +
+ )} +
+ ) : ( +

No stats for today

+ )} +
+
+ + ); + })} +
+ )} +
+
+ ); +} diff --git a/apps/web/src/pages/MailboxDetailPage.tsx b/apps/web/src/pages/MailboxDetailPage.tsx new file mode 100644 index 0000000..b04d878 --- /dev/null +++ b/apps/web/src/pages/MailboxDetailPage.tsx @@ -0,0 +1,519 @@ +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { Loader2, Wifi, Globe, Pencil } from 'lucide-react'; +import { PROVIDER_LABELS, type MailboxProvider, type MailboxRole } from '@warmbox/shared'; +import { apiGet, apiPatch, apiPost } from '../lib/api'; +import type { ConnectionTestResult, DnsCheckResult, Mailbox, ProviderPresets } from '../lib/types'; +import { PageHeader } from '../components/PageHeader'; +import { StatusBadge } from '../components/StatusBadge'; +import { Button } from '../components/ui/Button'; +import { Input } from '../components/ui/Input'; +import { Label } from '../components/ui/Label'; +import { Select } from '../components/ui/Select'; +import { Card, CardContent, CardHeader, CardTitle } from '../components/ui/Card'; +import { Skeleton } from '../components/ui/Skeleton'; +import { Alert } from '../components/ui/Alert'; +import { Badge } from '../components/ui/Badge'; +import { formatDateTime } from '../lib/utils'; + +const dnsVariant = { + pass: 'success' as const, + warn: 'warning' as const, + fail: 'danger' as const, + na: 'default' as const, +}; + +type EditForm = { + email: string; + username: string; + password: string; + provider: MailboxProvider; + smtpHost: string; + smtpPort: number; + imapHost: string; + imapPort: number; + industry: string; + senderName: string; + companyName: string; + role: MailboxRole; + oauthRefreshToken: string; +}; + +export function MailboxDetailPage() { + const { id } = useParams<{ id: string }>(); + const queryClient = useQueryClient(); + const [testResult, setTestResult] = useState(null); + const [dnsResult, setDnsResult] = useState(null); + const [editing, setEditing] = useState(false); + const [form, setForm] = useState(null); + const [saveMessage, setSaveMessage] = useState(''); + const [saveError, setSaveError] = useState(''); + + const { data: mailbox, isLoading, error, refetch } = useQuery({ + queryKey: ['mailbox', id], + queryFn: () => apiGet(`/api/mailboxes/${id}`), + enabled: !!id, + }); + + const { data: presets } = useQuery({ + queryKey: ['provider-presets'], + queryFn: () => apiGet('/api/mailboxes/provider-presets'), + }); + + useEffect(() => { + if (mailbox) { + setForm({ + email: mailbox.email, + username: mailbox.username, + password: '', + provider: mailbox.provider, + smtpHost: mailbox.smtpHost, + smtpPort: mailbox.smtpPort, + imapHost: mailbox.imapHost, + imapPort: mailbox.imapPort, + industry: mailbox.industry, + senderName: mailbox.senderName ?? '', + companyName: mailbox.companyName ?? '', + role: mailbox.role, + oauthRefreshToken: '', + }); + } + }, [mailbox]); + + useEffect(() => { + function onMessage(event: MessageEvent) { + if (event.data?.type === 'warmbox-microsoft-oauth' && event.data.refreshToken) { + setForm((f) => (f ? { ...f, oauthRefreshToken: event.data.refreshToken } : f)); + setSaveMessage('Microsoft account connected. Click Save Changes to store the new token.'); + } + } + window.addEventListener('message', onMessage); + return () => window.removeEventListener('message', onMessage); + }, []); + + async function connectMicrosoft() { + setSaveError(''); + try { + const { url } = await apiGet<{ url: string }>('/api/integrations/microsoft/auth-url'); + window.open(url, '_blank', 'noopener,noreferrer'); + } catch (err) { + setSaveError(err instanceof Error ? err.message : 'Failed to start Microsoft OAuth'); + } + } + + const saveMutation = useMutation({ + mutationFn: (payload: Partial) => apiPatch(`/api/mailboxes/${id}`, payload), + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['mailbox', id] }); + queryClient.invalidateQueries({ queryKey: ['mailboxes'] }); + setEditing(false); + setSaveMessage('Mailbox updated. Run Test Connection to verify credentials.'); + setSaveError(''); + refetch(); + }, + onError: (err: Error) => setSaveError(err.message), + }); + + const testMutation = useMutation({ + mutationFn: () => apiPost(`/api/mailboxes/${id}/test`), + onSuccess: (result) => { + setTestResult(result); + refetch(); + queryClient.invalidateQueries({ queryKey: ['mailboxes'] }); + }, + }); + + const dnsMutation = useMutation({ + mutationFn: () => apiPost(`/api/mailboxes/${id}/dns-check`), + onSuccess: setDnsResult, + }); + + function handleProviderChange(provider: MailboxProvider) { + const preset = presets?.[provider]; + setForm((f) => + f + ? { + ...f, + provider, + smtpHost: preset?.smtpHost ?? f.smtpHost, + smtpPort: preset?.smtpPort ?? f.smtpPort, + imapHost: preset?.imapHost ?? f.imapHost, + imapPort: preset?.imapPort ?? f.imapPort, + } + : f, + ); + } + + function handleSave(e: React.FormEvent) { + e.preventDefault(); + if (!form) return; + + const payload: Partial & { authType?: string; oauthRefreshToken?: string } = { + email: form.email, + username: form.username, + provider: form.provider, + industry: form.industry, + senderName: form.senderName || undefined, + companyName: form.companyName || undefined, + role: form.role, + }; + + if (form.password) { + payload.password = form.password; + } + + if (form.oauthRefreshToken) { + payload.authType = 'microsoft_oauth'; + payload.oauthRefreshToken = form.oauthRefreshToken; + } + + if (form.provider === 'custom') { + payload.smtpHost = form.smtpHost; + payload.smtpPort = form.smtpPort; + payload.imapHost = form.imapHost; + payload.imapPort = form.imapPort; + } + + saveMutation.mutate(payload); + } + + if (isLoading) { + return ( +
+ + +
+ ); + } + + if (error || !mailbox || !form) { + return Mailbox not found; + } + + return ( +
+ + + +
+ } + /> + + {saveMessage && {saveMessage}} + {saveError && {saveError}} + + {mailbox.lastError && ( + + Last error: {mailbox.lastError} + {mailbox.errorAt && ( + at {formatDateTime(mailbox.errorAt)} + )} +
+ + )} + + {editing && ( + + + Edit Mailbox + + +
+
+ + +
+
+ + setForm({ ...form, email: e.target.value })} + /> +
+
+ + setForm({ ...form, username: e.target.value })} + /> +
+
+ + {form.provider === 'outlook' || mailbox.authType === 'microsoft_oauth' ? ( +
+

+ @live.com / @outlook.com use Microsoft OAuth. Configure credentials in + Settings, then reconnect. +

+ +
+ ) : ( + setForm({ ...form, password: e.target.value })} + /> + )} +
+ {form.provider === 'custom' && ( +
+
+ + setForm({ ...form, smtpHost: e.target.value })} + required + /> +
+
+ + setForm({ ...form, smtpPort: Number(e.target.value) })} + required + /> +
+
+ + setForm({ ...form, imapHost: e.target.value })} + required + /> +
+
+ + setForm({ ...form, imapPort: Number(e.target.value) })} + required + /> +
+
+ )} +
+ + +

+ Set to unassigned only after removing from all campaigns. Primary is assigned by + campaigns. +

+
+
+ + setForm({ ...form, industry: e.target.value })} + /> +
+ +
+
+
+ )} + +
+ + + Details + + +
+
+
Auth
+
+ {mailbox.authType === 'microsoft_oauth' ? 'Microsoft OAuth' : 'Password'} +
+
+
+
Role
+
{mailbox.role}
+
+
+
Username
+
{mailbox.username}
+
+
+
SMTP
+
+ {mailbox.smtpHost}:{mailbox.smtpPort} +
+
+
+
IMAP
+
+ {mailbox.imapHost}:{mailbox.imapPort} +
+
+
+
Daily Limit
+
{mailbox.dailyLimit}
+
+
+
Current Ramp
+
{mailbox.currentRamp}
+
+
+
Industry
+
{mailbox.industry}
+
+ {mailbox.spamFolderPath && ( +
+
Spam Folder
+
{mailbox.spamFolderPath}
+
+ )} + {mailbox.lastSpamRescuedAt && ( +
+
Last Spam Rescue
+
{formatDateTime(mailbox.lastSpamRescuedAt)}
+
+ )} +
+
+
+ + + + Connection Test + + + + {testResult && ( +
+
+ SMTP + + {testResult.smtp.ok ? 'OK' : 'Failed'} + +
+ {!testResult.smtp.ok && testResult.smtp.error && ( +

{testResult.smtp.error}

+ )} +
+ IMAP + + {testResult.imap.ok ? 'OK' : 'Failed'} + +
+ {!testResult.imap.ok && testResult.imap.error && ( +

{testResult.imap.error}

+ )} +
+ )} +
+
+ + + + DNS Check + + + + {dnsResult && ( +
+
+ SPF: {dnsResult.spf} + DKIM: {dnsResult.dkim} + DMARC: {dnsResult.dmarc} +
+ {dnsResult.details.length > 0 && ( +
    + {dnsResult.details.map((d, i) => ( +
  • • {d}
  • + ))} +
+ )} +
+ )} +
+
+
+ + ); +} diff --git a/apps/web/src/pages/MailboxesPage.tsx b/apps/web/src/pages/MailboxesPage.tsx new file mode 100644 index 0000000..5d7fb3c --- /dev/null +++ b/apps/web/src/pages/MailboxesPage.tsx @@ -0,0 +1,395 @@ +import { useEffect, useState } from 'react'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { Link } from 'react-router-dom'; +import { Plus, Mail, X } from 'lucide-react'; +import { PROVIDER_LABELS, type MailboxProvider } from '@warmbox/shared'; +import { apiDelete, apiGet, apiPost } from '../lib/api'; +import type { Mailbox, ProviderPresets } from '../lib/types'; +import { PageHeader } from '../components/PageHeader'; +import { Button } from '../components/ui/Button'; +import { Input } from '../components/ui/Input'; +import { Label } from '../components/ui/Label'; +import { Select } from '../components/ui/Select'; +import { Card, CardContent } from '../components/ui/Card'; +import { StatusBadge } from '../components/StatusBadge'; +import { EmptyState } from '../components/EmptyState'; +import { Skeleton } from '../components/ui/Skeleton'; +import { Alert } from '../components/ui/Alert'; + +type AddForm = { + email: string; + username: string; + password: string; + provider: MailboxProvider; + smtpHost: string; + smtpPort: number; + imapHost: string; + imapPort: number; + industry: string; + oauthRefreshToken: string; + useMicrosoftOAuth: boolean; +}; + +const defaultForm: AddForm = { + email: '', + username: '', + password: '', + provider: 'google_workspace', + smtpHost: '', + smtpPort: 587, + imapHost: '', + imapPort: 993, + industry: 'SaaS', + oauthRefreshToken: '', + useMicrosoftOAuth: false, +}; + +export function MailboxesPage() { + const queryClient = useQueryClient(); + const [showModal, setShowModal] = useState(false); + const [form, setForm] = useState(defaultForm); + const [error, setError] = useState(''); + + const { data, isLoading, error: loadError } = useQuery({ + queryKey: ['mailboxes'], + queryFn: () => apiGet('/api/mailboxes'), + }); + + const { data: presets } = useQuery({ + queryKey: ['provider-presets'], + queryFn: () => apiGet('/api/mailboxes/provider-presets'), + }); + + const createMutation = useMutation({ + mutationFn: (payload: AddForm) => { + const body: Record = { + email: payload.email, + username: payload.username, + provider: payload.provider, + industry: payload.industry, + smtpHost: payload.smtpHost, + smtpPort: payload.smtpPort, + imapHost: payload.imapHost, + imapPort: payload.imapPort, + }; + if (payload.useMicrosoftOAuth && payload.oauthRefreshToken) { + body.authType = 'microsoft_oauth'; + body.oauthRefreshToken = payload.oauthRefreshToken; + } else { + body.password = payload.password; + } + return apiPost('/api/mailboxes', body); + }, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ['mailboxes'] }); + setShowModal(false); + setForm(defaultForm); + setError(''); + }, + onError: (err: Error) => setError(err.message), + }); + + const deleteMutation = useMutation({ + mutationFn: (id: string) => apiDelete(`/api/mailboxes/${id}`), + onSuccess: () => queryClient.invalidateQueries({ queryKey: ['mailboxes'] }), + }); + + function handleProviderChange(provider: MailboxProvider) { + const preset = presets?.[provider]; + const useMicrosoftOAuth = provider === 'outlook'; + setForm((f) => ({ + ...f, + provider, + useMicrosoftOAuth, + oauthRefreshToken: useMicrosoftOAuth ? f.oauthRefreshToken : '', + smtpHost: preset?.smtpHost ?? '', + smtpPort: preset?.smtpPort ?? 587, + imapHost: preset?.imapHost ?? '', + imapPort: preset?.imapPort ?? 993, + })); + } + + useEffect(() => { + function onMessage(event: MessageEvent) { + if (event.data?.type === 'warmbox-microsoft-oauth' && event.data.refreshToken) { + setForm((f) => ({ ...f, oauthRefreshToken: event.data.refreshToken, useMicrosoftOAuth: true })); + setMessage('Microsoft account connected. Click Add Mailbox to save.'); + } + } + window.addEventListener('message', onMessage); + return () => window.removeEventListener('message', onMessage); + }, []); + + const [message, setMessage] = useState(''); + + async function connectMicrosoft() { + setError(''); + try { + const { url } = await apiGet<{ url: string }>('/api/integrations/microsoft/auth-url'); + window.open(url, '_blank', 'noopener,noreferrer'); + setMessage('Complete sign-in in the popup window, then add the mailbox.'); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to start Microsoft OAuth'); + } + } + + function handleSubmit(e: React.FormEvent) { + e.preventDefault(); + const payload = { ...form }; + if (form.provider !== 'custom') { + const preset = presets?.[form.provider]; + if (preset) { + payload.smtpHost = preset.smtpHost; + payload.smtpPort = preset.smtpPort; + payload.imapHost = preset.imapHost; + payload.imapPort = preset.imapPort; + } + } + if (form.provider === 'outlook' && !form.oauthRefreshToken) { + setError('Connect Microsoft before adding an Outlook mailbox'); + return; + } + createMutation.mutate(payload); + } + + return ( +
+ setShowModal(true)}> + + Add Mailbox + + } + /> + + {loadError && Failed to load mailboxes} + {message && {message}} + + {isLoading ? ( +
+ {Array.from({ length: 3 }).map((_, i) => ( + + ))} +
+ ) : !data?.length ? ( + setShowModal(true)}>Add Mailbox} + /> + ) : ( + <> +
+ + + + + + + + + + + + {data.map((mailbox) => ( + + + + + + + + ))} + +
EmailProviderRoleStatusActions
+ + {mailbox.email} + + + {PROVIDER_LABELS[mailbox.provider]} + {mailbox.role} + + + +
+
+ +
+ {data.map((mailbox) => ( + + + +
+
+

{mailbox.email}

+

+ {PROVIDER_LABELS[mailbox.provider]} +

+
+ +
+

{mailbox.role}

+
+
+ + ))} +
+ + )} + + {showModal && ( +
+ + +
+

Add Mailbox

+ +
+ + {error && ( + + {error} + + )} + +
+
+ + +
+
+ + setForm({ ...form, email: e.target.value })} + /> +
+
+ + setForm({ ...form, username: e.target.value })} + /> +
+
+ + {form.provider === 'outlook' ? ( +
+

+ @live.com / @outlook.com accounts require Microsoft OAuth (basic auth is + disabled). Configure OAuth in Settings first, then connect below. +

+ +
+ ) : ( + setForm({ ...form, password: e.target.value })} + /> + )} +
+ {form.provider === 'custom' && ( +
+
+ + setForm({ ...form, smtpHost: e.target.value })} + required + /> +
+
+ + setForm({ ...form, smtpPort: Number(e.target.value) })} + required + /> +
+
+ + setForm({ ...form, imapHost: e.target.value })} + required + /> +
+
+ + setForm({ ...form, imapPort: Number(e.target.value) })} + required + /> +
+
+ )} +
+ + setForm({ ...form, industry: e.target.value })} + /> +
+
+ + +
+
+
+
+
+ )} +
+ ); +} diff --git a/apps/web/src/pages/SettingsPage.tsx b/apps/web/src/pages/SettingsPage.tsx new file mode 100644 index 0000000..8ef4a96 --- /dev/null +++ b/apps/web/src/pages/SettingsPage.tsx @@ -0,0 +1,473 @@ +import { useEffect, useState } from 'react'; +import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; +import { Loader2, Play, KeyRound } from 'lucide-react'; +import { apiGet, apiPatch, apiPost } from '../lib/api'; +import { useAuth } from '../lib/auth'; +import type { Settings, JobStatus } from '../lib/types'; +import { PageHeader } from '../components/PageHeader'; +import { Button } from '../components/ui/Button'; +import { Input } from '../components/ui/Input'; +import { Label } from '../components/ui/Label'; +import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '../components/ui/Card'; +import { Alert } from '../components/ui/Alert'; +import { Skeleton } from '../components/ui/Skeleton'; + +const JOBS = [ + { id: 'dispatcher', label: 'Campaign Dispatcher', path: '/api/jobs/dispatcher/run' }, + { id: 'rollover', label: 'Campaign Rollover', path: '/api/jobs/rollover/run' }, + { id: 'rescuer', label: 'Spam Rescuer', path: '/api/jobs/rescuer/run' }, + { id: 'rollup', label: 'Stats Rollup', path: '/api/jobs/rollup/run' }, +] as const; + +type PostmasterIntegrationStatus = { + configured: boolean; + connected: boolean; + domains: string[]; + message: string; + hasClientId: boolean; +}; + +export function SettingsPage() { + const queryClient = useQueryClient(); + const { apiKey, baseUrl, login } = useAuth(); + const [localKey, setLocalKey] = useState(apiKey); + const [localUrl, setLocalUrl] = useState(baseUrl); + const [form, setForm] = useState({}); + const [message, setMessage] = useState(''); + const [error, setError] = useState(''); + const [runningJob, setRunningJob] = useState(null); + + const [googleClientId, setGoogleClientId] = useState(''); + const [googleClientSecret, setGoogleClientSecret] = useState(''); + const [microsoftClientId, setMicrosoftClientId] = useState(''); + const [microsoftClientSecret, setMicrosoftClientSecret] = useState(''); + + const { data: postmasterStatus, refetch: refetchPostmaster } = useQuery({ + queryKey: ['integrations', 'google', 'status'], + queryFn: () => apiGet('/api/integrations/google/status'), + }); + + const { data: microsoftStatus, refetch: refetchMicrosoft } = useQuery({ + queryKey: ['integrations', 'microsoft', 'status'], + queryFn: () => apiGet<{ configured: boolean; hasClientId: boolean }>( + '/api/integrations/microsoft/status', + ), + }); + + const saveMicrosoftCredsMutation = useMutation({ + mutationFn: () => + apiPost('/api/integrations/microsoft/credentials', { + microsoft_oauth_client_id: microsoftClientId, + microsoft_oauth_client_secret: microsoftClientSecret, + }), + onSuccess: () => { + setMessage('Microsoft OAuth credentials saved'); + refetchMicrosoft(); + }, + onError: (err: Error) => setError(err.message), + }); + + const saveGoogleCredsMutation = useMutation({ + mutationFn: () => + apiPost('/api/integrations/google/credentials', { + google_oauth_client_id: googleClientId, + google_oauth_client_secret: googleClientSecret, + }), + onSuccess: () => { + setMessage('Google OAuth credentials saved'); + refetchPostmaster(); + }, + onError: (err: Error) => setError(err.message), + }); + + async function connectPostmaster() { + setError(''); + try { + const { url } = await apiGet<{ url: string }>('/api/integrations/google/auth-url'); + window.open(url, '_blank', 'noopener,noreferrer'); + setMessage('Complete Google authorization in the new tab, then refresh this page.'); + } catch (err) { + setError(err instanceof Error ? err.message : 'Failed to start Google OAuth'); + } + } + + useEffect(() => { + if (window.location.hash === '#google-postmaster') { + document.getElementById('google-postmaster')?.scrollIntoView({ behavior: 'smooth' }); + } + }, []); + + const { data, isLoading } = useQuery({ + queryKey: ['settings'], + queryFn: () => apiGet('/api/settings'), + }); + + const { data: jobStatus } = useQuery({ + queryKey: ['jobs', 'status'], + queryFn: () => apiGet<{ jobs: JobStatus[] }>('/api/jobs/status'), + refetchInterval: 30_000, + }); + + useEffect(() => { + if (data) setForm(data); + }, [data]); + + const saveMutation = useMutation({ + mutationFn: (payload: Settings) => apiPatch('/api/settings', payload), + onSuccess: (updated) => { + setForm(updated); + queryClient.setQueryData(['settings'], updated); + setMessage('Settings saved'); + setError(''); + }, + onError: (err: Error) => setError(err.message), + }); + + function handleSaveSettings(e: React.FormEvent) { + e.preventDefault(); + saveMutation.mutate(form); + } + + function handleSaveApiConfig(e: React.FormEvent) { + e.preventDefault(); + login(localKey.trim(), localUrl.trim()); + setMessage('API configuration updated'); + } + + async function runJob(path: string, id: string) { + setRunningJob(id); + setError(''); + try { + await apiPost(path); + setMessage(`Job "${id}" completed`); + } catch (err) { + setError(err instanceof Error ? err.message : 'Job failed'); + } finally { + setRunningJob(null); + } + } + + return ( +
+ + + {message && {message}} + {error && {error}} + + + + + + API Configuration + + Connection settings for this dashboard session + + +
+
+ + setLocalKey(e.target.value)} + /> +
+
+ + setLocalUrl(e.target.value)} + placeholder="Leave empty for local proxy" + /> +
+ +
+
+
+ + + + Application Settings + Scheduler and warmup configuration + + + {isLoading ? ( + + ) : ( +
+
+ + setForm({ ...form, dispatcher_cron: e.target.value })} + placeholder="*/5 * * * *" + /> +
+
+ + setForm({ ...form, rescuer_cron: e.target.value })} + placeholder="*/10 * * * *" + /> +
+
+ + setForm({ ...form, ramp_increment: e.target.value })} + /> +
+
+ + setForm({ ...form, ai_model: e.target.value })} + /> +
+
+ + setForm({ ...form, industries: e.target.value })} + /> +
+ +
+ )} +
+
+ + + + Microsoft Outlook (@live.com) + + Required for consumer Outlook accounts — app passwords no longer work for SMTP/IMAP + + + + {microsoftStatus && ( + + {microsoftStatus.configured + ? 'Microsoft OAuth credentials are configured. Connect mailboxes from Mailboxes → Add.' + : 'Add Azure app credentials below before connecting @live.com mailboxes.'} + + )} + +
+

Setup (one-time)

+
    +
  1. + In{' '} + + Azure App registrations + + , create a Web app. Supported account types: personal Microsoft accounts. +
  2. +
  3. + Add redirect URI:{' '} + + http://localhost:3000/api/integrations/microsoft/callback + +
  4. +
  5. + Under API permissions, add delegated{' '} + IMAP.AccessAsUser.All and SMTP.Send for Office + 365 / Outlook. +
  6. +
  7. Create a client secret, paste ID + secret below, then add mailboxes via Connect Microsoft.
  8. +
+
+ +
+
+ + setMicrosoftClientId(e.target.value)} + /> +
+
+ + setMicrosoftClientSecret(e.target.value)} + /> +
+ +
+
+
+ + + + Google Postmaster Tools + + Connect once to auto-verify Gmail/Workspace domains in campaign compliance checks + + + + {postmasterStatus && ( + + {postmasterStatus.message} + {postmasterStatus.domains.length > 0 && ( + + Domains: {postmasterStatus.domains.join(', ')} + + )} + + )} + +
+

Setup (one-time)

+
    +
  1. + In{' '} + + Google Cloud Console + + , enable the Gmail Postmaster Tools API. +
  2. +
  3. + Create OAuth 2.0 credentials (Web application). Add redirect URI:{' '} + + http://localhost:3000/api/integrations/google/callback + +
  4. +
  5. + Add your sending domain at{' '} + + postmaster.google.com + {' '} + and complete DNS verification there. +
  6. +
  7. Paste Client ID and Secret below, save, then click Connect.
  8. +
+
+ +
+
+ + setGoogleClientId(e.target.value)} + placeholder="xxxx.apps.googleusercontent.com" + /> +
+
+ + setGoogleClientSecret(e.target.value)} + /> +
+
+ + +
+
+
+
+ + + + Jobs + Manually trigger background jobs + + + {jobStatus?.jobs && ( +
+ {jobStatus.jobs.map((job) => ( +
+ {job.jobName} + + {job.lastRunAt + ? `${new Date(job.lastRunAt).toLocaleString()} — ${ + job.success ? 'OK' : 'Failed' + }` + : 'Never run'} + {job.message ? ` (${job.message})` : ''} + +
+ ))} +
+ )} +
+ {JOBS.map((job) => ( + + ))} +
+
+
+
+ ); +} diff --git a/apps/web/src/vite-env.d.ts b/apps/web/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/apps/web/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/apps/web/tailwind.config.js b/apps/web/tailwind.config.js new file mode 100644 index 0000000..7e0968f --- /dev/null +++ b/apps/web/tailwind.config.js @@ -0,0 +1,27 @@ +/** @type {import('tailwindcss').Config} */ +export default { + content: ['./index.html', './src/**/*.{js,ts,jsx,tsx}'], + theme: { + extend: { + colors: { + primary: { + 50: '#eef2ff', + 100: '#e0e7ff', + 200: '#c7d2fe', + 300: '#a5b4fc', + 400: '#818cf8', + 500: '#6366f1', + 600: '#4f46e5', + 700: '#4338ca', + 800: '#3730a3', + 900: '#312e81', + 950: '#1e1b4b', + }, + }, + fontFamily: { + sans: ['Inter', 'system-ui', 'sans-serif'], + }, + }, + }, + plugins: [], +}; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..67f8a5e --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "useDefineForClassFields": true, + "lib": ["ES2022", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true, + "resolveJsonModule": true, + "forceConsistentCasingInFileNames": true + }, + "include": ["src"] +} diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json new file mode 100644 index 0000000..f315807 --- /dev/null +++ b/apps/web/tsconfig.node.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..0585f4c --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,25 @@ +import path from 'node:path'; +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + '@warmbox/shared': path.resolve(__dirname, '../../packages/shared/src/index.ts'), + }, + }, + server: { + port: 5173, + proxy: { + '/api': { + target: 'http://localhost:3000', + changeOrigin: true, + }, + }, + }, + build: { + outDir: 'dist', + emptyOutDir: true, + }, +}); diff --git a/docs/16-roadmap.md b/docs/16-roadmap.md index 5969ef1..c8cbdc8 100644 --- a/docs/16-roadmap.md +++ b/docs/16-roadmap.md @@ -11,94 +11,94 @@ --- -## Phase 1 — Production warmup core (8–10 weeks) +## Phase 1 — Production warmup core (8–10 weeks) — **Done** **Goal:** Reliable multi-primary warmup with recipes, roles, and stats. Self-hosted backend only. -### Sprint 1.1 — Domain model & roles (2 weeks) +### Sprint 1.1 — Domain model & roles (2 weeks) — **Done** -- Add `role` to mailbox (primary / satellite) -- Add `WarmupCampaign` entity -- Campaign ↔ satellite assignments -- Migrate existing inboxes -- Deprecate flat random pairing +- Workspace, `WarmupCampaign`, satellite assignments, Grow recipe, AI validator +- Campaign API; legacy dispatcher gated when campaigns exist -**Deliverable:** API to create campaign with satellites; no sends yet. +**Deliverable:** API to create campaign with satellites; Grow schedule + preview. -### Sprint 1.2 — Recipe engine (2 weeks) +### Sprint 1.2 + 1.3 — Recipes + live dispatcher (combined) — **Done** -- Implement Grow, Flat, Randomized, Custom -- `DailySchedule` generation -- Duration 30–45 days, min/max validation -- 48h volume cap -- `POST /recipes/preview` +- All 4 recipes: Grow, Flat, Randomized, Custom +- Campaign dispatcher (satellite → primary), send windows, jitter +- Day rollover, maintenance at 20%, campaign-aware rescuer -**Deliverable:** Unit-tested schedule for all 4 recipes. +**Deliverable:** End-to-end warmup with any recipe; emails actually send. -### Sprint 1.3 — Campaign dispatcher (2 weeks) +### Sprint 1.2 — Recipe engine (original scope, absorbed) -- Role-aware send: satellite → primary only -- Reply path: primary → satellite -- Jitter + send windows -- Campaign day rollover -- Idempotency keys +- ~~Implement Grow, Flat, Randomized, Custom~~ → shipped in 1.2+1.3 combined sprint +- Grow + preview delivered in 1.1; remainder completed in 1.2+1.3 -**Deliverable:** End-to-end warmup with one primary + 4 satellites. +### Sprint 1.3 — Campaign dispatcher (original scope, absorbed) -### Sprint 1.4 — Reply rate engine (1 week) +- ~~Role-aware send, jitter, rollover~~ → shipped in 1.2+1.3 combined sprint -- Probabilistic 30% default replies -- `engaged_no_reply` status -- Daily reply budget tracking +### Sprint 1.4 — Reply rate engine (1 week) — **Done** + +- Probabilistic 30% default replies, `engaged_no_reply` status +- Daily reply budget tracking, spam counter increments +- See [`docs/sprints/sprint-1.4.md`](sprints/sprint-1.4.md) **Deliverable:** Reply rate within ±5% of target over 7 days. -### Sprint 1.5 — Stats API (1 week) +### Sprint 1.5 — Stats API (1 week) — **Done** -- `DailyStat` rollups -- `/campaigns/:id/stats/today` and `/stats/daily` -- On-track calculation +- `DailyStat` rollups, stats endpoints, workspace overview +- On-track calculation, compliance API (warn-only DNS) +- See [`docs/sprints/sprint-1.5.md`](sprints/sprint-1.5.md) **Deliverable:** JSON stats matching manual log counts. -### Sprint 1.6 — AI content validator (1 week) +### Sprint 1.6 — AI content validator (1 week) — **Done (merged into 1.1)** -- Placeholder regex rejection -- Spam word tiers -- Regenerate loop (max 3) -- Mailbox profile (name, company) - -**Deliverable:** Zero placeholder emails in 100-test run. +- Placeholder regex rejection, spam word tiers, regenerate loop +- Delivered with Sprint 1.1 --- -## Phase 2 — Operator experience (6–8 weeks) +## Phase 2 — Operator experience (6–8 weeks) — **Done** **Goal:** Web UI + hardening for FillCare daily use. -### Sprint 2.1 — Web dashboard MVP (3 weeks) +### Sprint 2.0 — Monorepo foundation — **Done** -- Vite + React + shadcn -- Dashboard, campaign wizard, campaign detail -- Mailbox add/test flows +- npm workspaces (`apps/api`, `apps/web`, `packages/shared`) +- Dev CORS + production static SPA hosting +- See [`docs/sprints/sprint-2.0.md`](sprints/sprint-2.0.md) -### Sprint 2.2 — Spam rescue hardening (2 weeks) +### Sprint 2.1 — Web dashboard MVP (3 weeks) — **Done** + +- Vite + React + shadcn operator dashboard +- Dashboard, campaign wizard, campaign detail, mailboxes, settings +- See [`docs/sprints/sprint-2.1.md`](sprints/sprint-2.1.md) + +### Sprint 2.2 — Spam rescue hardening (2 weeks) — **Done** - Per-UID log matching - Placement tracking - Folder auto-discovery - Auto-pause on spam thresholds +- See [`docs/sprints/sprint-2.2.md`](sprints/sprint-2.2.md) -### Sprint 2.3 — DNS & health checks (1 week) +### Sprint 2.3 — DNS & health checks (1 week) — **Done** - SPF/DKIM/DMARC preflight - Provider presets (Gmail/Yahoo/Outlook) +- See [`docs/sprints/sprint-2.3.md`](sprints/sprint-2.3.md) -### Sprint 2.4 — Job queue (2 weeks) +### Sprint 2.4 — Job queue (2 weeks) — **Done** -- Replace batch cron with queued sends -- Manual job triggers for dev -- Better overlap protection +- SQLite-backed send queue with idempotency +- Job run log + manual triggers +- See [`docs/sprints/sprint-2.4.md`](sprints/sprint-2.4.md) + +**FillCare QA:** [`docs/fillcare-qa-playbook.md`](fillcare-qa-playbook.md) --- @@ -129,7 +129,7 @@ ## Milestone timeline (visual) ``` -2026 Q2 [Phase 1: Core backend ████████░░] +2026 Q2 [Phase 1: Core backend ██████████] 2026 Q3 [Phase 2: Web UI + hardening ░░░░████████] 2026 Q4 [Phase 3: SaaS beta ░░░░░░░░████] ``` @@ -152,18 +152,19 @@ ### Phase 1 done when -- [ ] 2+ primaries warming concurrently -- [ ] All 4 recipes pass integration tests -- [ ] Stats API accurate -- [ ] Reply rate configurable and honored -- [ ] AI validator blocks placeholders -- [ ] ESP compliance checklist passable +- [x] 2+ primaries warming concurrently +- [x] All 4 recipes pass integration tests +- [x] Stats API accurate +- [x] Reply rate configurable and honored +- [x] AI validator blocks placeholders +- [x] ESP compliance checklist passable (`GET /api/campaigns/:id/compliance`) ### Phase 2 done when -- [ ] Operator never needs curl for daily use -- [ ] Auto-pause tested on simulated spam spike -- [ ] FillCare running 30-day campaign via UI +- [x] Operator never needs curl for daily use +- [x] Auto-pause tested on simulated spam spike (unit threshold tests + rescuer integration path) +- [ ] FillCare running 30-day campaign via UI (operator acceptance — see FillCare QA playbook) +- [x] Dashboard usable on phone (375px) for monitoring; setup flows work on tablet+ ### Phase 3 done when diff --git a/docs/18-open-questions.md b/docs/18-open-questions.md index 55970d5..af6ebfc 100644 --- a/docs/18-open-questions.md +++ b/docs/18-open-questions.md @@ -11,16 +11,17 @@ Decisions needed before or during implementation. Update this doc as answers are | Warmup duration | 30–45 days recommended | 2026-06 | | Reply rate default | 30%, max 45% | 2026-06 | | Daily volume | Min 1, default max 40, cap 50 | 2026-06 | +| Q1 — Shared satellites | Yes, satellites may be shared across primaries in the same workspace/tenant | 2026-06 | +| Q3 — Post-campaign | Auto-continue at 20% maintenance; user can pause/stop | 2026-06 | +| Q5 — FillCare primary host | Google Workspace (use `google_workspace` preset; no hardcoded emails) | 2026-06 | +| Q15 — DNS before campaign start | Warn-only via `GET /api/campaigns/:id/compliance`; does not block start | 2026-06 | ## Product | # | Question | Options | Impact | |---|----------|---------|--------| -| Q1 | Can one satellite serve multiple primaries? | A) No (dedicated) B) Yes with toggle | Dispatcher pairing | | Q2 | Should primary ever send unsolicited to satellites (not replies)? | A) No B) Yes for variety | Traffic rules | -| Q3 | Post-campaign behavior default? | A) Stop B) Maintenance 20% C) Ask user | Campaign lifecycle | | Q4 | Weekend sending? | A) Same volume B) 50% C) Pause | Scheduling | -| Q5 | FillCare: is hello@ on Google Workspace? | Confirm SMTP/IMAP hosts | Onboarding docs | ## Technical @@ -54,8 +55,7 @@ Decisions needed before or during implementation. Update this doc as answers are 2. Update affected spec docs 3. Create implementation story in [17-agile-epics-backlog.md](./17-agile-epics-backlog.md) -## Questions for you (next conversation) +## Open for next conversation -1. **Q5** — Is `hello@fillcareapp.com` on Google Workspace, Microsoft 365, or cPanel? -2. **Q1** — Should satellites be dedicated per primary, or shared across your primaries? -3. **Q3** — After 30-day warmup completes, auto-switch to maintenance mode or stop? +1. **Q2** — Should primaries ever send unsolicited mail to satellites, or replies only? +2. **Q4** — Weekend send volume: same, reduced, or paused? diff --git a/docs/README.md b/docs/README.md index 7ceb213..2cd00e1 100644 --- a/docs/README.md +++ b/docs/README.md @@ -36,6 +36,10 @@ Planning artifacts for evolving Warmbox from a barebone MVP into a production-gr | 17 | [Agile Epics & Backlog](./17-agile-epics-backlog.md) | Epics, stories, integrations | | 18 | [Open Questions](./18-open-questions.md) | Unresolved decisions | | 19 | [Risk Register](./19-risk-register.md) | Risks & mitigations | +| — | [Sprint 1.1](./sprints/sprint-1.1.md) | Campaigns, Grow recipe, AI validator | +| — | [Sprint 1.2–1.3](./sprints/sprint-1.2-1.3.md) | All recipes, campaign dispatcher | +| — | [Sprint 1.4](./sprints/sprint-1.4.md) | Probabilistic reply rate engine | +| — | [Sprint 1.5](./sprints/sprint-1.5.md) | Stats API, DailyStat rollups | ## How to use these docs diff --git a/docs/fillcare-qa-playbook.md b/docs/fillcare-qa-playbook.md new file mode 100644 index 0000000..505889b --- /dev/null +++ b/docs/fillcare-qa-playbook.md @@ -0,0 +1,50 @@ +# FillCare QA Playbook — Phase 2 + +Operator acceptance checklist for FillCare warmup via the Warmbox dashboard. + +## Prerequisites + +- Warmbox running: `npm run dev` (or production `npm run build && npm run start`) +- API key configured in `.env` if required +- FillCare mailboxes and satellites provisioned + +## Week 4 — Dashboard live + +- [ ] Log in with API key at dashboard +- [ ] Dashboard shows workspace overview within 60s refresh +- [ ] Create primary mailbox (Google Workspace preset) +- [ ] Add 4+ satellite mailboxes +- [ ] Test connection on each mailbox (SMTP + IMAP green) +- [ ] Run DNS check on primary — SPF/DKIM/DMARC reviewed +- [ ] Create campaign via 4-step wizard (Grow recipe, 30 days) +- [ ] Start campaign from wizard review step +- [ ] Campaign detail shows ramp chart and today's stats +- [ ] Pause and resume campaign from UI + +## Week 6 — Spam monitoring + +- [ ] Activity log shows send/reply/rescue statuses +- [ ] Placement chart reflects inbox vs spam on campaign detail +- [ ] If spam spikes, dashboard shows alert strip +- [ ] Auto-pause triggers after sustained high spam (verify in staging) + +## Week 8 — Maintenance + +- [ ] Campaign reaches maintenance or completes +- [ ] Settings → Jobs manual triggers work (dispatcher, rescuer) +- [ ] Job status panel shows last run times + +## Mobile smoke (375px) + +- [ ] Bottom nav: Home, Campaigns, Mailboxes, More +- [ ] Dashboard campaign cards readable +- [ ] Campaign activity as card list (not table) +- [ ] Mailbox list as cards + +## Regression + +```bash +npm run build +npm test +npm run test:integration +``` diff --git a/docs/self-host-hardening.md b/docs/self-host-hardening.md new file mode 100644 index 0000000..20c3319 --- /dev/null +++ b/docs/self-host-hardening.md @@ -0,0 +1,97 @@ +# Self-Host Hardening Breakpoint + +Warmbox pauses new feature work here (before Phase 3 SaaS). Phases 1–2 are implemented for **local self-host** daily operator use. + +## What works today + +| Area | Location | Operator actions | +|------|----------|------------------| +| API + jobs | `apps/api/` | Cron dispatcher, rescuer, stats, send queue | +| Dashboard | `apps/web/` | Mailboxes, campaigns, stats, compliance, settings | +| Shared types | `packages/shared/` | API DTOs used by web + api | +| Database | `prisma/` | SQLite via `DATABASE_URL` in root `.env` | + +### Run locally + +```bash +npm install +npx prisma migrate dev +npm run dev # API :3000, dashboard :5173 +# or production: +npm run build && npm run start +``` + +## Operator workflows (no curl required) + +1. **Mailboxes** — add, edit credentials, test connection, DNS check +2. **Campaigns** — 4-step wizard, start/pause/resume, edit settings tab +3. **Compliance** — automatic domain age (RDAP), DNS, Postmaster when connected +4. **Monitoring** — dashboard overview, placement charts, job triggers in Settings + +## Google Postmaster Tools (compliance auto-check) + +1. Settings → **Google Postmaster Tools** +2. Enable [Gmail Postmaster Tools API](https://console.cloud.google.com/apis/library/gmailpostmastertools.googleapis.com) +3. Create OAuth Web credentials with redirect URI: + `http://localhost:3000/api/integrations/google/callback` +4. Verify your domain at [postmaster.google.com](https://postmaster.google.com) +5. Paste Client ID + Secret → Save → **Connect Google Postmaster** +6. Campaign compliance tab will show pass/fail for your primary domain + +Optional: set `PUBLIC_BASE_URL` in `.env` if not using `http://localhost:3000`. + +## Microsoft Outlook (@live.com / @outlook.com) + +Microsoft **disabled basic authentication** for consumer accounts. App passwords from +[account.live.com/proofs/AppPassword](https://account.live.com/proofs/AppPassword) will fail with +`535 5.7.139 basic authentication is disabled`. Use OAuth instead: + +1. Settings → **Microsoft Outlook** +2. Register an app in [Azure App registrations](https://portal.azure.com/#view/Microsoft_AAD_RegisteredApps/ApplicationsListBlade) (personal Microsoft accounts) +3. Redirect URI: `http://localhost:3000/api/integrations/microsoft/callback` +4. API permissions: delegated `IMAP.AccessAsUser.All`, `SMTP.Send` +5. Save Client ID + Secret in Settings +6. Mailboxes → Add → Outlook → **Connect Microsoft** → Add Mailbox + +To fix an existing Outlook mailbox: Mailbox detail → **Edit** → **Connect Microsoft** → Save. + +## Gmail connection errors (535 BadCredentials) + +Use a Google **App Password**, not your normal login password: + +1. Enable 2FA on the Google account +2. Create an app password at https://myaccount.google.com/apppasswords +3. Mailbox detail → **Edit** → paste app password → Save → **Test Connection** + +## “Off track” on dashboard + +Means today's sends or reply rate are behind the daily plan. Early in the day (first 1–2 sends), campaigns stay **on track** until enough volume accumulates. Sends are spaced by design (45+ min jitter + cron interval), so a 10-email day ramps over many hours. + +## Campaign editing limits + +| Status | Editable fields | +|--------|-----------------| +| Draft | All fields | +| Active / Maintenance | Name, timezone, send window, reply rate; satellites tab (add/remove) | +| Paused | Above + duration, min/max volume, recipe (future schedule rebuilt) | +| Completed / Failed | None | + +**One primary per mailbox** — a mailbox can only be the primary on one non-completed campaign at a time (draft, active, paused, or maintenance). Delete draft campaigns from the campaigns list to free a mailbox. + +**Compliance tab** — checks run live on each load. Use **Re-run compliance check** after DNS fixes, Postmaster connection, or mailbox changes. + +## Mailbox roles + +- New mailboxes start as **unassigned** +- Campaign creation assigns **primary** and **satellite** roles +- Edit role on mailbox detail: satellite ↔ unassigned (remove from campaigns first); primary is set via campaigns only +- Adding a mailbox runs a connection test first — failed connections are rejected with a clear error + +## Paused here (Phase 3+) + +- Multi-tenant JWT auth +- Postgres migration +- Stripe billing +- Webhooks / Postmaster push alerts + +See `docs/16-roadmap.md` for the full roadmap. diff --git a/docs/sprints/sprint-1.1.md b/docs/sprints/sprint-1.1.md new file mode 100644 index 0000000..432df47 --- /dev/null +++ b/docs/sprints/sprint-1.1.md @@ -0,0 +1,110 @@ +# Sprint 1.1 — Domain Model, Grow Recipe, AI Validator + +**Status:** Implemented +**Epics:** E1, E2 (Grow subset), E6 + +## Goal + +Campaign-centric warmup model with workspace scoping, Grow recipe schedules, and AI content validation — without campaign-aware sending yet. + +## Acceptance checklist + +- [x] Default workspace exists; all inboxes scoped to workspace +- [x] Create primary + satellites via API with `google_workspace` provider preset +- [x] Create campaign; assign shared satellite across primaries in same workspace +- [x] `POST /api/campaigns/:id/start` generates `durationDays` DailySchedule rows +- [x] `POST /api/recipes/preview` returns Grow ramp +- [x] `POST /api/campaigns/:id/pause` / `resume` lifecycle +- [x] `transitionToMaintenance()` adds 20% maintenance schedule row +- [x] AI content validator rejects placeholders, spam triggers, URLs +- [x] Legacy flat dispatcher skipped when any campaign exists +- [x] `npm run build` and `npm test` pass + +## New API endpoints + +| Method | Path | +|--------|------| +| POST/GET/PATCH/DELETE | `/api/campaigns` | +| POST | `/api/campaigns/:id/start` | +| POST | `/api/campaigns/:id/pause` | +| POST | `/api/campaigns/:id/resume` | +| GET/POST/DELETE | `/api/campaigns/:id/satellites` | +| POST | `/api/recipes/preview` | +| GET | `/api/recipes/templates` | +| POST | `/api/ai/test-generate` | +| * | `/api/mailboxes` (alias of `/api/inboxes`) | + +## Manual QA script + +```bash +# Start server +npm run dev + +# 1. Primary (Google Workspace preset — any email) +curl -s -X POST http://localhost:3000/api/inboxes \ + -H "Content-Type: application/json" \ + -d '{ + "email": "hello@yourdomain.com", + "provider": "google_workspace", + "username": "hello@yourdomain.com", + "password": "your-app-password", + "role": "primary", + "industry": "Childcare", + "senderName": "Jon", + "companyName": "FillCare" + }' + +# 2. Satellites (repeat for 2+ accounts) +curl -s -X POST http://localhost:3000/api/inboxes \ + -H "Content-Type: application/json" \ + -d '{ + "email": "satellite@gmail.com", + "provider": "gmail", + "username": "satellite@gmail.com", + "password": "app-password", + "role": "satellite" + }' + +# 3. Preview Grow ramp +curl -s -X POST http://localhost:3000/api/recipes/preview \ + -H "Content-Type: application/json" \ + -d '{"recipe":"grow","durationDays":30,"minEmailsPerDay":1,"maxEmailsPerDay":40,"replyRatePercent":30}' + +# 4. Create campaign (use IDs from step 1–2) +curl -s -X POST http://localhost:3000/api/campaigns \ + -H "Content-Type: application/json" \ + -d '{ + "name": "Primary warmup", + "primaryMailboxId": "", + "recipe": "grow", + "durationDays": 30, + "satelliteMailboxIds": ["", ""] + }' + +# 5. Start campaign → verify 30 schedule rows +curl -s -X POST http://localhost:3000/api/campaigns//start +curl -s http://localhost:3000/api/campaigns/ | jq '.dailySchedules | length' + +# 6. Pause +curl -s -X POST http://localhost:3000/api/campaigns//pause + +# 7. Test AI validator (requires AI_API_KEY) +curl -s -X POST http://localhost:3000/api/ai/test-generate \ + -H "Content-Type: application/json" \ + -d '{"industry":"Childcare","senderName":"Jon","companyName":"FillCare"}' +``` + +## Out of scope (Sprint 1.3+) + +- Campaign-aware dispatcher sends +- Reply rate sampling +- Statistics dashboard API +- Flat / Randomized / Custom recipes + +## Key files + +- [`prisma/schema.prisma`](../prisma/schema.prisma) +- [`src/services/campaign.service.ts`](../src/services/campaign.service.ts) +- [`src/services/recipe.service.ts`](../src/services/recipe.service.ts) +- [`src/services/content-validator.service.ts`](../src/services/content-validator.service.ts) +- [`src/routes/campaigns.routes.ts`](../src/routes/campaigns.routes.ts) diff --git a/docs/sprints/sprint-1.2-1.3.md b/docs/sprints/sprint-1.2-1.3.md new file mode 100644 index 0000000..b5b395c --- /dev/null +++ b/docs/sprints/sprint-1.2-1.3.md @@ -0,0 +1,81 @@ +# Sprint 1.2 + 1.3 — Recipes Complete + Campaign Dispatcher + +Combined sprint delivering all four warmup recipes and live campaign-aware sending. + +## What shipped + +### Sprint 1.2 — Recipe engine (complete) + +- **Grow**, **Flat**, **Randomized**, and **Custom** recipes via `generateSchedule()` in `recipe.service.ts` +- Randomized uses a seeded PRNG (`campaignId` at start) plus the 48h volume cap +- Custom validates per-day sends in `[min, max]` and fills gaps with hold-last +- `POST /api/recipes/preview` accepts all four recipes (optional `customSchedule` for custom) +- `GET /api/recipes/templates` returns templates for all four recipes +- `POST /api/campaigns/:id/start` persists the correct schedule per recipe + +### Sprint 1.3 — Campaign dispatcher (complete) + +- **`campaign-dispatcher.job.ts`** — satellite → primary sends for active/maintenance campaigns +- **`campaign-rollover.job.ts`** + **`syncCampaignDay()`** — day rollover and maintenance transition +- **`send-window.ts`** — timezone-aware send windows (luxon) +- **`lastSendAt`** on `WarmupCampaign` — 45 min minimum jitter between sends +- Campaign-aware **rescuer** — primary → satellite replies with `actualReplies` tracking +- Manual job endpoints: `POST /api/jobs/dispatcher/run`, `POST /api/jobs/rollover/run` +- Legacy flat dispatcher still runs when **zero** campaigns exist + +## Traffic rules + +| Direction | Job | +| ------------------- | --------- | +| Satellite → Primary | Dispatcher | +| Primary → Satellite | Rescuer (reply only) | + +## Scheduler + +| Job | Schedule | +| ---------------------- | --------------------- | +| Campaign dispatcher | `dispatcher_cron` setting | +| Legacy dispatcher | Same cron (gated) | +| Rescuer | `rescuer_cron` setting | +| Campaign rollover | Hourly (`0 * * * *`) | + +## Manual QA + +```bash +# 1. Create primary + 2+ satellites (see sprint-1.1.md) +# 2. Create campaign (recipe: grow), assign satellites, start +# 3. Trigger dispatch: +curl -X POST http://localhost:3000/api/jobs/dispatcher/run -H "x-api-key: $API_KEY" +# 4. Check logs: +curl http://localhost:3000/api/logs?inboxId= -H "x-api-key: $API_KEY" +# 5. Wait for rescuer cron or trigger rescuer manually via cron interval +# 6. Repeat with recipe:flat on a second primary to verify recipe router +``` + +## Preview examples + +```bash +# Flat 30-day preview +curl -X POST http://localhost:3000/api/recipes/preview \ + -H "Content-Type: application/json" -H "x-api-key: $API_KEY" \ + -d '{"recipe":"flat","durationDays":30,"maxEmailsPerDay":40}' + +# Custom schedule +curl -X POST http://localhost:3000/api/recipes/preview \ + -H "Content-Type: application/json" -H "x-api-key: $API_KEY" \ + -d '{"recipe":"custom","durationDays":14,"customSchedule":[{"day":1,"sends":2},{"day":2,"sends":5}]}' +``` + +## Deferred to Sprint 1.4 + +- Probabilistic reply rate (~30% sampling) +- `engaged_no_reply` status + +## Key files + +- `src/services/recipe.service.ts` +- `src/services/campaign.service.ts` +- `src/jobs/campaign-dispatcher.job.ts` +- `src/jobs/campaign-rollover.job.ts` +- `src/jobs/rescuer.job.ts` +- `src/lib/send-window.ts` diff --git a/docs/sprints/sprint-1.4.md b/docs/sprints/sprint-1.4.md new file mode 100644 index 0000000..7e0adf7 --- /dev/null +++ b/docs/sprints/sprint-1.4.md @@ -0,0 +1,39 @@ +# Sprint 1.4 — Reply Rate Engine + +Probabilistic reply sampling so warmup mimics real mailbox engagement (~30% default, max 45%). + +## What shipped + +- **`engaged_no_reply`** status on `WarmupStatus` enum +- **`reply-rate.service.ts`** — `shouldReply()`, `getTodayReplyStats()`, simulation helper +- **Campaign rescuer** samples replies; non-replied messages still get read/flagged/spam-rescue +- **Hard daily cap** — skips reply when `actualReplies >= plannedReplies` +- **Spam counters** — `spamDetected` / `spamRescued` increment on rescue +- **`WarmupLog.placement`** — `inbox` or `spam` +- **`POST /api/jobs/rescuer/run`** — manual rescuer trigger + +## Algorithm + +Per [`docs/07-reply-rate-engine.md`](../07-reply-rate-engine.md): + +1. If daily reply quota met → skip +2. If actual rate ≥ target → skip +3. Else probabilistic bias: `probability = needed / remaining` + +## Manual QA + +```bash +# Start campaign, dispatch sends, then run rescuer repeatedly +curl -X POST http://localhost:3000/api/jobs/dispatcher/run -H "x-api-key: $API_KEY" +curl -X POST http://localhost:3000/api/jobs/rescuer/run -H "x-api-key: $API_KEY" + +# Check mix of complete vs replied logs +curl "http://localhost:3000/api/logs?campaignId=" -H "x-api-key: $API_KEY" +``` + +Expect some logs with `engaged_no_reply` → `complete` and reply rate trending toward `replyRatePercent`. + +## Key files + +- `src/services/reply-rate.service.ts` +- `src/jobs/rescuer.job.ts` diff --git a/docs/sprints/sprint-1.5.md b/docs/sprints/sprint-1.5.md new file mode 100644 index 0000000..1789947 --- /dev/null +++ b/docs/sprints/sprint-1.5.md @@ -0,0 +1,44 @@ +# Sprint 1.5 — Stats API + +Operator-visible campaign metrics via REST, backed by real-time `DailySchedule` counters and nightly `DailyStat` rollups. + +## What shipped + +- **`DailyStat` model** — materialized daily rollups with `replyRateActual`, `inboxPlacementRate` +- **`stats.service.ts`** — today, daily time series, range summary, workspace overview +- **Stats routes:** + - `GET /api/campaigns/:id/stats/today` + - `GET /api/campaigns/:id/stats/daily?from=&to=` + - `GET /api/campaigns/:id/stats?range=7d|30d|all` + - `GET /api/workspace/overview` +- **`stats-rollup.job.ts`** — hourly cron, finalizes yesterday at 00:xx campaign TZ +- **`POST /api/jobs/rollup/run`** — manual rollup trigger +- **Logs filter** — `GET /api/logs?campaignId=` + +## Response example (`/stats/today`) + +```json +{ + "campaignId": "...", + "date": "2026-06-25", + "dayIndex": 12, + "planned": { "sends": 18, "replies": 6 }, + "actual": { "sends": 15, "replies": 5, "spamDetected": 2, "spamRescued": 2 }, + "rates": { "reply": 33.3, "inbox": 86.7 }, + "recipe": "grow", + "onTrack": true +} +``` + +## FillCare manual QA + +1. Active Grow campaign with sends in progress +2. `GET /api/campaigns/:id/stats/today` — verify planned vs actual +3. `GET /api/workspace/overview` — see hello@ primary at a glance +4. Compare stats counts to `GET /api/logs?campaignId=:id` + +## Key files + +- `src/services/stats.service.ts` +- `src/jobs/stats-rollup.job.ts` +- `src/routes/stats.routes.ts` diff --git a/docs/sprints/sprint-2.0.md b/docs/sprints/sprint-2.0.md new file mode 100644 index 0000000..dc1ea9b --- /dev/null +++ b/docs/sprints/sprint-2.0.md @@ -0,0 +1,29 @@ +# Sprint 2.0 — Monorepo foundation + +**Status:** Done + +## Goals + +- npm workspaces monorepo (`apps/api`, `apps/web`, `packages/shared`) +- Root dev/build/start scripts +- API CORS (dev) + static SPA hosting (prod) +- Expose `GET /api/mailboxes/provider-presets` + +## Deliverables + +| Item | Notes | +|------|-------| +| `apps/api` | Former `src/` moved here | +| `apps/web` | Vite + React dashboard scaffold | +| `packages/shared` | Shared enums and API DTO types | +| `npm run dev` | Concurrent API + Vite | +| `npm run build` | shared → api → web | +| `npm run start` | API serves `apps/web/dist` in production | + +## Verification + +```bash +npm install +npm run build +npm run dev # API :3000, web :5173 +``` diff --git a/docs/sprints/sprint-2.1.md b/docs/sprints/sprint-2.1.md new file mode 100644 index 0000000..84eae44 --- /dev/null +++ b/docs/sprints/sprint-2.1.md @@ -0,0 +1,40 @@ +# Sprint 2.1 — Web dashboard MVP + +**Status:** Done + +## Goals + +Warmbox-branded operator dashboard for daily warmup management without curl. + +## Screens + +| Route | Purpose | +|-------|---------| +| `/` | Workspace overview, metric cards, alerts | +| `/campaigns` | Campaign list with status filters | +| `/campaigns/new` | 4-step wizard with recipe preview | +| `/campaigns/:id` | Overview, activity, satellites, compliance tabs | +| `/mailboxes` | CRUD + connection test | +| `/mailboxes/:id` | Detail + DNS check | +| `/activity` | Global log feed | +| `/settings` | AI settings, API key gate, job triggers | + +## Stack + +- Vite 6 + React 19 + TypeScript +- Tailwind + shadcn-style components +- TanStack Query + React Router v7 +- Recharts for ramp and placement charts +- Mobile: bottom nav `< md`, sidebar `≥ md` + +## Auth (self-hosted) + +- API key stored in `sessionStorage` +- Skipped when server has no `API_KEY` set (dev mode) + +## Verification + +1. Open dashboard, enter API key if required +2. Create mailbox via provider preset +3. Launch campaign through wizard +4. Confirm stats on dashboard and campaign detail diff --git a/docs/sprints/sprint-2.2.md b/docs/sprints/sprint-2.2.md new file mode 100644 index 0000000..e099b15 --- /dev/null +++ b/docs/sprints/sprint-2.2.md @@ -0,0 +1,25 @@ +# Sprint 2.2 — Spam rescue hardening + +**Status:** Done + +## Backend + +- **Per-UID matching:** Rescuer fetches each spam UID, matches `message-id` / from+subject, moves only matched messages +- **Folder auto-discovery:** `resolveSpamFolder()` tries presets then IMAP LIST; persists `spamFolderPath` on inbox +- **Auto-pause:** `checkCampaignSpamThresholds()` pauses campaigns when 3 consecutive days exceed 30% spam rate (inbox < 70%) +- **Mailbox metadata:** `lastSpamRescuedAt` updated on successful rescue + +## Dashboard + +- Paused campaign alert on dashboard and campaign detail +- High spam rate alert strip (inbox < 70% today) +- Placement stacked bar chart on campaign overview +- Activity badges: `rescued_from_spam`, `engaged_no_reply` +- Mailbox detail shows spam folder path and last rescue time + +## Verification + +1. Simulate spam placement in test logs +2. Run rescuer job from Settings → Jobs +3. Confirm per-log rescue status (not bulk sender match) +4. After 3 high-spam days, campaign auto-pauses diff --git a/docs/sprints/sprint-2.3.md b/docs/sprints/sprint-2.3.md new file mode 100644 index 0000000..f1bf7d6 --- /dev/null +++ b/docs/sprints/sprint-2.3.md @@ -0,0 +1,23 @@ +# Sprint 2.3 — DNS and health checks + +**Status:** Done + +## Backend + +- `POST /api/mailboxes/:id/dns-check` — SPF/DKIM/DMARC via existing `dns-check` lib +- `GET /api/mailboxes/provider-presets` — SMTP/IMAP defaults for wizard and add-mailbox flow + +## Dashboard + +- Mailbox detail: DNS check card with pass/warn/fail badges +- Campaign wizard step 4: compliance checklist preview +- Mailbox add flow supports provider presets + +## Verification + +```bash +curl -X POST http://localhost:3000/api/mailboxes/{id}/dns-check \ + -H "x-api-key: $API_KEY" +``` + +Or use **Run DNS Check** on mailbox detail in the dashboard. diff --git a/docs/sprints/sprint-2.4.md b/docs/sprints/sprint-2.4.md new file mode 100644 index 0000000..e4ce2b3 --- /dev/null +++ b/docs/sprints/sprint-2.4.md @@ -0,0 +1,29 @@ +# Sprint 2.4 — Job queue + +**Status:** Done + +## Backend + +- **SQLite queue:** `SendJob` table with idempotency keys, `pending → processing → completed/failed` +- Campaign dispatcher enqueues and claims due jobs before sending +- **Job run log:** `JobRunLog` records last run per job name +- `GET /api/jobs/status` — last run timestamps for operators + +## Dashboard + +- Settings → Jobs panel shows last run status +- Manual trigger buttons: dispatcher, rollover, rescuer, rollup + +## Jobs tracked + +| Name | Schedule | +|------|----------| +| `campaign-dispatcher` | `dispatcher_cron` setting | +| `rescuer` | `rescuer_cron` setting | +| `campaign-rollover` | hourly | +| `stats-rollup` | hourly at :05 | + +## Verification + +1. Settings → Jobs — confirm last-run timestamps after manual trigger +2. Check `SendJob` rows created when dispatcher runs with pending sends diff --git a/package-lock.json b/package-lock.json index 8a29b67..cc1ee85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,13 +8,29 @@ "name": "warmbox", "version": "1.0.0", "license": "MIT", + "workspaces": [ + "apps/*", + "packages/*" + ], + "devDependencies": { + "concurrently": "^9.1.2", + "prisma": "^7.8.0", + "typescript": "^5.8.3" + } + }, + "apps/api": { + "name": "@warmbox/api", + "version": "1.0.0", "dependencies": { "@prisma/adapter-better-sqlite3": "^7.8.0", "@prisma/client": "^7.8.0", + "@warmbox/shared": "*", "better-sqlite3": "^11.10.0", + "cors": "^2.8.5", "dotenv": "^16.5.0", "express": "^4.21.2", "imapflow": "^1.0.189", + "luxon": "^3.7.2", "mailparser": "^3.7.3", "node-cron": "^3.0.3", "nodemailer": "^6.10.1", @@ -24,7 +40,9 @@ }, "devDependencies": { "@types/better-sqlite3": "^7.6.13", + "@types/cors": "^2.8.17", "@types/express": "^4.17.21", + "@types/luxon": "^3.7.2", "@types/mailparser": "^3.4.6", "@types/node": "^22.15.21", "@types/node-cron": "^3.0.11", @@ -34,6 +52,408 @@ "typescript": "^5.8.3" } }, + "apps/web": { + "name": "@warmbox/web", + "version": "1.0.0", + "dependencies": { + "@tanstack/react-query": "^5.80.7", + "@warmbox/shared": "*", + "clsx": "^2.1.1", + "lucide-react": "^0.511.0", + "luxon": "^3.7.2", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "react-router-dom": "^7.6.2", + "recharts": "^2.15.3", + "tailwind-merge": "^3.3.0", + "zod": "^3.25.28" + }, + "devDependencies": { + "@types/luxon": "^3.7.2", + "@types/react": "^19.1.6", + "@types/react-dom": "^19.1.5", + "@vitejs/plugin-react": "^4.5.2", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.4", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.3", + "vite": "^6.3.5" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/core/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/runtime": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", + "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@babel/traverse/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@electric-sql/pglite": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.4.1.tgz", @@ -77,6 +497,7 @@ "os": [ "aix" ], + "peer": true, "engines": { "node": ">=18" } @@ -94,6 +515,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -111,6 +533,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -128,6 +551,7 @@ "os": [ "android" ], + "peer": true, "engines": { "node": ">=18" } @@ -145,6 +569,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -162,6 +587,7 @@ "os": [ "darwin" ], + "peer": true, "engines": { "node": ">=18" } @@ -179,6 +605,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -196,6 +623,7 @@ "os": [ "freebsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -213,6 +641,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -230,6 +659,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -247,6 +677,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -264,6 +695,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -281,6 +713,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -298,6 +731,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -315,6 +749,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -332,6 +767,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -349,6 +785,7 @@ "os": [ "linux" ], + "peer": true, "engines": { "node": ">=18" } @@ -366,6 +803,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -383,6 +821,7 @@ "os": [ "netbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -400,6 +839,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -417,6 +857,7 @@ "os": [ "openbsd" ], + "peer": true, "engines": { "node": ">=18" } @@ -434,6 +875,7 @@ "os": [ "openharmony" ], + "peer": true, "engines": { "node": ">=18" } @@ -451,6 +893,7 @@ "os": [ "sunos" ], + "peer": true, "engines": { "node": ">=18" } @@ -468,6 +911,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -485,6 +929,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -502,6 +947,7 @@ "os": [ "win32" ], + "peer": true, "engines": { "node": ">=18" } @@ -519,6 +965,56 @@ "hono": "^4" } }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, "node_modules/@kurkle/color": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", @@ -526,6 +1022,44 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@pinojs/redact": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", @@ -901,6 +1435,363 @@ } } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@selderee/plugin-htmlparser2": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.12.0.tgz", @@ -924,6 +1815,77 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@tanstack/query-core": { + "version": "5.101.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.101.1.tgz", + "integrity": "sha512-Y6Y92dkXtNqx67m2pMSxUsA3zOCwv862JexZRP8/EPwvKXMPu9m8rv43spiXWzOUIggQ3SQApttALStzhA8B4g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.101.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.101.1.tgz", + "integrity": "sha512-ZnONUuQKJe1bJMStXUL1s5uKN9FcfC28j5cK+iDZcdSHtUv1wtin1cGc/Oewhf2Oc4eKY7lggtpvT/AbMmhHew==", + "license": "MIT", + "dependencies": { + "@tanstack/query-core": "5.101.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, "node_modules/@types/better-sqlite3": { "version": "7.6.13", "resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz", @@ -955,6 +1917,86 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/express": { "version": "4.17.25", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", @@ -988,6 +2030,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/@types/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-gW+Oib+vUtGJBtNC8V9Reww0oIpusw+4m81uncg9REGZAJfqOQHfo/nkabnc7w0QReXyPqjrbWMJk6NuAkiX3Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/mailparser": { "version": "3.4.6", "resolved": "https://registry.npmjs.org/@types/mailparser/-/mailparser-3.4.6.tgz", @@ -1062,11 +2111,20 @@ "integrity": "sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "csstype": "^3.2.2" } }, + "node_modules/@types/react-dom": { + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@types/react": "^19.2.0" + } + }, "node_modules/@types/send": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", @@ -1100,6 +2158,39 @@ "@types/node": "*" } }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@warmbox/api": { + "resolved": "apps/api", + "link": true + }, + "node_modules/@warmbox/shared": { + "resolved": "packages/shared", + "link": true + }, + "node_modules/@warmbox/web": { + "resolved": "apps/web", + "link": true + }, "node_modules/@zone-eu/mailsplit": { "version": "5.4.12", "resolved": "https://registry.npmjs.org/@zone-eu/mailsplit/-/mailsplit-5.4.12.tgz", @@ -1165,6 +2256,60 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true, + "license": "MIT" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true, + "license": "MIT" + }, "node_modules/array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", @@ -1186,6 +2331,43 @@ "node": ">=8.0.0" } }, + "node_modules/autoprefixer": { + "version": "10.5.2", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.2.tgz", + "integrity": "sha512-rD5t5DwOjJdmSORcTq64j8MawTC+tbQ+HHqjR4NDumamy/ambn1UJrlKL+KdwujWxMkFjPM3pPHOEA9tl4767Q==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.4", + "caniuse-lite": "^1.0.30001799", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, "node_modules/aws-ssl-profiles": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz", @@ -1216,6 +2398,19 @@ ], "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.40", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", + "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/better-result": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/better-result/-/better-result-2.9.2.tgz", @@ -1234,6 +2429,19 @@ "prebuild-install": "^7.1.1" } }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -1290,6 +2498,53 @@ "node": ">=0.10.0" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.4", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", + "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.38", + "caniuse-lite": "^1.0.30001799", + "electron-to-chromium": "^1.5.376", + "node-releases": "^2.0.48", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/buffer": { "version": "5.7.1", "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", @@ -1394,6 +2649,67 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001799", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", + "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/chart.js": { "version": "4.5.1", "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", @@ -1429,6 +2745,50 @@ "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", "license": "ISC" }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/clsx": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz", + "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -1441,6 +2801,41 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/concurrently": { + "version": "9.2.3", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.3.tgz", + "integrity": "sha512-ihjs0E2SxvDgq/MK418hX6YycQgKhsqxpbZuZbHo0yKfqDWdymWMjWYIpCIzqDDLLKClHlXev8whW/8WXmJ0BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "4.1.2", + "rxjs": "7.8.2", + "shell-quote": "1.8.4", + "supports-color": "8.1.1", + "tree-kill": "1.2.2", + "yargs": "17.7.2" + }, + "bin": { + "conc": "dist/bin/concurrently.js", + "concurrently": "dist/bin/concurrently.js" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/open-cli-tools/concurrently?sponsor=1" + } + }, "node_modules/confbox": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", @@ -1469,6 +2864,13 @@ "node": ">= 0.6" } }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -1484,6 +2886,23 @@ "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", "license": "MIT" }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1499,13 +2918,145 @@ "node": ">= 8" } }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "license": "MIT", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" + }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } }, "node_modules/debug": { "version": "2.6.9", @@ -1516,6 +3067,12 @@ "ms": "2.0.0" } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/decompress-response": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", @@ -1610,6 +3167,30 @@ "node": ">=8" } }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true, + "license": "MIT" + }, + "node_modules/dom-helpers": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", + "integrity": "sha512-nRCa7CK3VTrM2NmGkIy4cbK7IZlgBE/PYMn55rrXefr5xXDP0LdtfPnblFDoVdcAfslJ7or6iqAUnx0CCGIWQA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.8.7", + "csstype": "^3.0.2" + } + }, "node_modules/dom-serializer": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", @@ -1708,6 +3289,20 @@ "fast-check": "^3.23.1" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.378", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.378.tgz", + "integrity": "sha512-VinvOAuuPmdD1guEgGv5f2Qp7/vlfqOrUOMYNnOD4wj3pit8kRsQHzfIf6teyUGWo15Tg5+bOJaRunvyltpVWQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, "node_modules/empathic": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", @@ -1857,6 +3452,16 @@ "@esbuild/win32-x64": "0.28.1" } }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", @@ -1881,6 +3486,12 @@ "node": ">=6" } }, + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "license": "MIT" + }, "node_modules/expand-template": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", @@ -1973,6 +3584,45 @@ "devOptional": true, "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fast-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", @@ -1990,12 +3640,35 @@ ], "license": "BSD-3-Clause" }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", "license": "MIT" }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/finalhandler": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", @@ -2075,6 +3748,20 @@ "node": ">= 0.6" } }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, "node_modules/fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", @@ -2124,6 +3811,26 @@ "is-property": "^1.0.2" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -2184,6 +3891,19 @@ "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", "license": "MIT" }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -2217,6 +3937,16 @@ "devOptional": true, "license": "MIT" }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -2497,6 +4227,15 @@ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "license": "ISC" }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/ip-address": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", @@ -2515,6 +4254,78 @@ "node": ">= 0.10" } }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/is-property": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", @@ -2539,6 +4350,25 @@ "jiti": "lib/jiti-cli.mjs" } }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -2546,6 +4376,19 @@ "devOptional": true, "license": "MIT" }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/leac": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/leac/-/leac-0.7.0.tgz", @@ -2595,6 +4438,26 @@ "integrity": "sha512-0Wd+GPz1O134cP62YU2GTOPNA7Qgl09XwCqM5zpBv87ERCXdfDtyKXvV7c9U22yWJh44QZqBocFnXN11K96qow==", "license": "MIT" }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true, + "license": "MIT" + }, "node_modules/linkify-it": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.1.tgz", @@ -2614,6 +4477,12 @@ "uc.micro": "^2.0.0" } }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, "node_modules/long": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz", @@ -2621,6 +4490,28 @@ "devOptional": true, "license": "Apache-2.0" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, "node_modules/lru.min": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz", @@ -2637,6 +4528,24 @@ "url": "https://github.com/sponsors/wellwelwel" } }, + "node_modules/lucide-react": { + "version": "0.511.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.511.0.tgz", + "integrity": "sha512-VK5a2ydJ7xm8GvBeKLS9mu1pVK6ucef9780JVUjw6bAjJL/QXnd4Y0p7SPeOUMC27YhzNCZvm5d/QX0Tp3rc0w==", + "license": "ISC", + "peerDependencies": { + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/luxon": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-3.7.2.tgz", + "integrity": "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, "node_modules/mailparser": { "version": "3.9.11", "resolved": "https://registry.npmjs.org/mailparser/-/mailparser-3.9.11.tgz", @@ -2707,6 +4616,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -2716,6 +4635,20 @@ "node": ">= 0.6" } }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, "node_modules/mime": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", @@ -2820,6 +4753,18 @@ "url": "https://opencollective.com/express" } }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, "node_modules/named-placeholders": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz", @@ -2833,6 +4778,25 @@ "node": ">=8.0.0" } }, + "node_modules/nanoid": { + "version": "3.3.15", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", + "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/napi-build-utils": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", @@ -2912,6 +4876,16 @@ } } }, + "node_modules/node-releases": { + "version": "2.0.50", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", + "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/nodemailer": { "version": "6.10.1", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.10.1.tgz", @@ -2921,6 +4895,35 @@ "node": ">=6.0.0" } }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -3047,6 +5050,13 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true, + "license": "MIT" + }, "node_modules/path-to-regexp": { "version": "0.1.13", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.13.tgz", @@ -3076,6 +5086,36 @@ "devOptional": true, "license": "MIT" }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/pino": { "version": "9.14.0", "resolved": "https://registry.npmjs.org/pino/-/pino-9.14.0.tgz", @@ -3113,6 +5153,16 @@ "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", "license": "MIT" }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, "node_modules/pkg-types": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", @@ -3125,6 +5175,169 @@ "pathe": "^2.0.3" } }, + "node_modules/postcss": { + "version": "8.5.15", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", + "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "license": "MIT", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, "node_modules/postgres": { "version": "3.4.7", "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz", @@ -3216,6 +5429,23 @@ ], "license": "MIT" }, + "node_modules/prop-types": { + "version": "15.8.1", + "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", + "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==", + "license": "MIT", + "dependencies": { + "loose-envify": "^1.4.0", + "object-assign": "^4.1.1", + "react-is": "^16.13.1" + } + }, + "node_modules/prop-types/node_modules/react-is": { + "version": "16.13.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", + "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", + "license": "MIT" + }, "node_modules/proper-lockfile": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz", @@ -3299,6 +5529,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/quick-format-unescaped": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", @@ -3371,9 +5622,7 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/react/-/react-19.2.7.tgz", "integrity": "sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==", - "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3382,9 +5631,7 @@ "version": "19.2.7", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.7.tgz", "integrity": "sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==", - "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "scheduler": "^0.27.0" }, @@ -3392,6 +5639,114 @@ "react": "^19.2.7" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "license": "MIT" + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-router": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.0.tgz", + "integrity": "sha512-pTTGt8J+ji1NOmYnjzT+bAJy/1zD+Jp4ziO6cL7T3ZLvXKtusO7BpFqlRXitqpcPVqllsIXFHRMt+2/k3Xn6HQ==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.18.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.18.0.tgz", + "integrity": "sha512-Fi0yY6kgtKae/Th2xibdWK0KSdYZ4B53Gyf6wRtomOKWgpNm7H7+DyfDhncdz9FKbpS+1jmDhg3F4WoGJ+yFOA==", + "license": "MIT", + "dependencies": { + "react-router": "7.18.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, + "node_modules/react-router/node_modules/cookie": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.1.1.tgz", + "integrity": "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/react-smooth": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/react-smooth/-/react-smooth-4.0.4.tgz", + "integrity": "sha512-gnGKTpYwqL0Iii09gHobNolvX4Kiq4PKx6eWBCYYix+8cdw+cGo3do906l1NBPKkSWx1DghC1dlWG9L2uGd61Q==", + "license": "MIT", + "dependencies": { + "fast-equals": "^5.0.1", + "prop-types": "^15.8.1", + "react-transition-group": "^4.4.5" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/react-transition-group": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/react-transition-group/-/react-transition-group-4.4.5.tgz", + "integrity": "sha512-pZcd1MCJoiKiBR2NRxeCRg13uCXbydPnmB4EOeRrY7480qNWO8IIgQG6zlDkm6uRMsURXPuKq0GWtiM59a5Q6g==", + "license": "BSD-3-Clause", + "dependencies": { + "@babel/runtime": "^7.5.5", + "dom-helpers": "^5.0.1", + "loose-envify": "^1.4.0", + "prop-types": "^15.6.2" + }, + "peerDependencies": { + "react": ">=16.6.0", + "react-dom": ">=16.6.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -3429,6 +5784,39 @@ "node": ">= 12.13.0" } }, + "node_modules/recharts": { + "version": "2.15.4", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-2.15.4.tgz", + "integrity": "sha512-UT/q6fwS3c1dHbXv2uFgYJ9BMFHu3fwnd7AYZaEQhXuYQ4hgsxLvsUXzGdKeZrW5xopzDCvuA2N41WJ88I7zIw==", + "deprecated": "1.x and 2.x branches are no longer active. Bump to Recharts v3 to receive latest features and bugfixes. See https://github.com/recharts/recharts/wiki/3.0-migration-guide", + "license": "MIT", + "dependencies": { + "clsx": "^2.0.0", + "eventemitter3": "^4.0.1", + "lodash": "^4.17.21", + "react-is": "^18.3.1", + "react-smooth": "^4.0.4", + "recharts-scale": "^0.4.4", + "tiny-invariant": "^1.3.1", + "victory-vendor": "^36.6.8" + }, + "engines": { + "node": ">=14" + }, + "peerDependencies": { + "react": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/recharts-scale": { + "version": "0.4.5", + "resolved": "https://registry.npmjs.org/recharts-scale/-/recharts-scale-0.4.5.tgz", + "integrity": "sha512-kivNFO+0OcUNu7jQquLXAxz1FIwZj8nrj+YkOKc5694NbjCvcT6aSZiIzNzd2Kul4o4rTto8QVR9lMNtxD4G1w==", + "license": "MIT", + "dependencies": { + "decimal.js-light": "^2.4.1" + } + }, "node_modules/remeda": { "version": "2.33.4", "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz", @@ -3439,6 +5827,16 @@ "url": "https://github.com/sponsors/remeda" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-from-string": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", @@ -3449,6 +5847,28 @@ "node": ">=0.10.0" } }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", @@ -3459,6 +5879,96 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.62.2", + "@rollup/rollup-android-arm64": "4.62.2", + "@rollup/rollup-darwin-arm64": "4.62.2", + "@rollup/rollup-darwin-x64": "4.62.2", + "@rollup/rollup-freebsd-arm64": "4.62.2", + "@rollup/rollup-freebsd-x64": "4.62.2", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.2", + "@rollup/rollup-linux-arm-musleabihf": "4.62.2", + "@rollup/rollup-linux-arm64-gnu": "4.62.2", + "@rollup/rollup-linux-arm64-musl": "4.62.2", + "@rollup/rollup-linux-loong64-gnu": "4.62.2", + "@rollup/rollup-linux-loong64-musl": "4.62.2", + "@rollup/rollup-linux-ppc64-gnu": "4.62.2", + "@rollup/rollup-linux-ppc64-musl": "4.62.2", + "@rollup/rollup-linux-riscv64-gnu": "4.62.2", + "@rollup/rollup-linux-riscv64-musl": "4.62.2", + "@rollup/rollup-linux-s390x-gnu": "4.62.2", + "@rollup/rollup-linux-x64-gnu": "4.62.2", + "@rollup/rollup-linux-x64-musl": "4.62.2", + "@rollup/rollup-openbsd-x64": "4.62.2", + "@rollup/rollup-openharmony-arm64": "4.62.2", + "@rollup/rollup-win32-arm64-msvc": "4.62.2", + "@rollup/rollup-win32-ia32-msvc": "4.62.2", + "@rollup/rollup-win32-x64-gnu": "4.62.2", + "@rollup/rollup-win32-x64-msvc": "4.62.2", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -3498,9 +6008,7 @@ "version": "0.27.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", - "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/selderee": { "version": "0.12.0", @@ -3577,6 +6085,12 @@ "node": ">= 0.8.0" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", + "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", + "license": "MIT" + }, "node_modules/setprototypeof": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", @@ -3606,6 +6120,19 @@ "node": ">=8" } }, + "node_modules/shell-quote": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.4.tgz", + "integrity": "sha512-VsC6n6vz1ihYYyZZwX7YZSF5l5x36ca17OC+a69h94YqB7X6XLwf+5MOgynYir2SLFUbl8gIYvBo8K8RoNQ6bQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/side-channel": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.1.tgz", @@ -3769,6 +6296,16 @@ "atomic-sleep": "^1.0.0" } }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/split2": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", @@ -3813,6 +6350,34 @@ "safe-buffer": "~5.2.0" } }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/strip-json-comments": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", @@ -3822,6 +6387,167 @@ "node": ">=0.10.0" } }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwind-merge": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.6.0.tgz", + "integrity": "sha512-uxL7qAVQriqRQPAyK3pj66VqskWqoZ37PW94jwOTwNfq/z9oyu1V+eqrZqtR2+fCiXdYOZe/Modt8GtvqNzu+w==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tailwindcss/node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/tailwindcss/node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/tailwindcss/node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/tailwindcss/node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, "node_modules/tar-fs": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", @@ -3850,6 +6576,29 @@ "node": ">=6" } }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "license": "MIT", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, "node_modules/thread-stream": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-3.2.0.tgz", @@ -3859,6 +6608,60 @@ "real-require": "^0.2.0" } }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/tlds": { "version": "1.261.0", "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.261.0.tgz", @@ -3868,6 +6671,19 @@ "tlds": "bin.js" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -3883,6 +6699,30 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "license": "MIT" }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "license": "MIT", + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "dev": true, + "license": "0BSD" + }, "node_modules/tsx": { "version": "4.22.4", "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", @@ -3962,6 +6802,37 @@ "node": ">= 0.8" } }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -4011,6 +6882,618 @@ "node": ">= 0.8" } }, + "node_modules/victory-vendor": { + "version": "36.9.2", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-36.9.2.tgz", + "integrity": "sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/vite/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/web-streams-polyfill": { "version": "4.0.0-beta.3", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", @@ -4052,12 +7535,76 @@ "node": ">= 8" } }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/zeptomatch": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz", @@ -4077,6 +7624,13 @@ "funding": { "url": "https://github.com/sponsors/colinhacks" } + }, + "packages/shared": { + "name": "@warmbox/shared", + "version": "1.0.0", + "devDependencies": { + "typescript": "^5.8.3" + } } } } diff --git a/package.json b/package.json index 8b7fccd..a4ad6e5 100644 --- a/package.json +++ b/package.json @@ -1,48 +1,27 @@ { "name": "warmbox", "version": "1.0.0", + "private": true, "description": "Self-hosted email warmup and deliverability engine", - "main": "dist/index.js", + "workspaces": [ + "apps/*", + "packages/*" + ], "scripts": { - "dev": "tsx watch src/index.ts", - "build": "tsc", - "start": "node dist/index.js", + "dev": "concurrently -n api,web -c blue,green \"npm run dev -w @warmbox/api\" \"npm run dev -w @warmbox/web\"", + "build": "npm run build -w @warmbox/shared && npm run build -w @warmbox/api && npm run build -w @warmbox/web", + "start": "npm run start -w @warmbox/api", "db:migrate": "prisma migrate dev", "db:generate": "prisma generate", "db:push": "prisma db push", - "db:reset": "prisma migrate reset" - }, - "keywords": [ - "email", - "warmup", - "deliverability", - "imap", - "smtp" - ], - "license": "MIT", - "dependencies": { - "@prisma/adapter-better-sqlite3": "^7.8.0", - "@prisma/client": "^7.8.0", - "better-sqlite3": "^11.10.0", - "dotenv": "^16.5.0", - "express": "^4.21.2", - "imapflow": "^1.0.189", - "mailparser": "^3.7.3", - "node-cron": "^3.0.3", - "nodemailer": "^6.10.1", - "openai": "^4.104.0", - "pino": "^9.6.0", - "zod": "^3.25.28" + "db:reset": "prisma migrate reset", + "test": "npm run test -w @warmbox/api", + "test:integration": "npm run test:integration -w @warmbox/api" }, "devDependencies": { - "@types/better-sqlite3": "^7.6.13", - "@types/express": "^4.17.21", - "@types/mailparser": "^3.4.6", - "@types/node": "^22.15.21", - "@types/node-cron": "^3.0.11", - "@types/nodemailer": "^6.4.17", + "concurrently": "^9.1.2", "prisma": "^7.8.0", - "tsx": "^4.19.4", "typescript": "^5.8.3" - } + }, + "license": "MIT" } diff --git a/packages/shared/package.json b/packages/shared/package.json new file mode 100644 index 0000000..3e0d673 --- /dev/null +++ b/packages/shared/package.json @@ -0,0 +1,14 @@ +{ + "name": "@warmbox/shared", + "version": "1.0.0", + "private": true, + "main": "./dist/index.js", + "types": "./dist/index.d.ts", + "scripts": { + "build": "tsc", + "dev": "tsc --watch" + }, + "devDependencies": { + "typescript": "^5.8.3" + } +} diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts new file mode 100644 index 0000000..d68d77b --- /dev/null +++ b/packages/shared/src/index.ts @@ -0,0 +1,115 @@ +export type CampaignRecipe = 'grow' | 'flat' | 'randomized' | 'custom'; + +export type CampaignStatus = + | 'draft' + | 'active' + | 'paused' + | 'maintenance' + | 'completed' + | 'failed'; + +export type MailboxRole = 'unassigned' | 'primary' | 'satellite'; + +export type MailboxProvider = + | 'google_workspace' + | 'gmail' + | 'yahoo' + | 'outlook' + | 'custom'; + +export type InboxStatus = 'active' | 'paused' | 'error'; + +export type WarmupStatus = + | 'sent' + | 'rescued_from_spam' + | 'engaged_no_reply' + | 'replied' + | 'complete'; + +export type ComplianceStatus = 'pass' | 'warn' | 'fail' | 'na'; + +export type ProviderPreset = { + smtpHost: string; + smtpPort: number; + imapHost: string; + imapPort: number; +}; + +export const PROVIDER_LABELS: Record = { + google_workspace: 'Google Workspace', + gmail: 'Gmail', + yahoo: 'Yahoo', + outlook: 'Outlook / Microsoft 365', + custom: 'Custom', +}; + +export type CampaignStatsToday = { + campaignId: string; + date: string; + dayIndex: number; + planned: { sends: number; replies: number }; + actual: { + sends: number; + replies: number; + spamDetected: number; + spamRescued: number; + }; + rates: { reply: number; inbox: number }; + recipe: CampaignRecipe; + onTrack: boolean; + onTrackReasons: string[]; +}; + +export type WorkspaceOverview = { + workspaceId: string | null; + count: number; + campaigns: Array<{ + campaignId: string; + name: string; + status: CampaignStatus; + recipe: CampaignRecipe; + primaryEmail: string; + currentDay: number; + durationDays: number; + today: CampaignStatsToday | null; + }>; +}; + +export type ComplianceCheckItem = { + id: string; + label: string; + status: ComplianceStatus; + message: string; + actionPath?: string; + actionLabel?: string; +}; + +export type ComplianceReport = { + campaignId: string; + overall: ComplianceStatus; + checks: ComplianceCheckItem[]; +}; + +export type DnsCheckResult = { + spf: ComplianceStatus; + dkim: ComplianceStatus; + dmarc: ComplianceStatus; + details: string[]; +}; + +export type PaginatedLogs = { + data: T[]; + pagination: { + page: number; + limit: number; + total: number; + totalPages: number; + }; +}; + +export type JobStatus = { + jobName: string; + lastRunAt: string | null; + success: boolean | null; + message: string | null; +}; diff --git a/packages/shared/tsconfig.json b/packages/shared/tsconfig.json new file mode 100644 index 0000000..3cacd25 --- /dev/null +++ b/packages/shared/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "commonjs", + "declaration": true, + "outDir": "dist", + "rootDir": "src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true + }, + "include": ["src/**/*"] +} diff --git a/prisma/migrations/20260625054451_sprint_1_1_campaigns/migration.sql b/prisma/migrations/20260625054451_sprint_1_1_campaigns/migration.sql new file mode 100644 index 0000000..b08f1d3 --- /dev/null +++ b/prisma/migrations/20260625054451_sprint_1_1_campaigns/migration.sql @@ -0,0 +1,155 @@ +/* + Warnings: + + - Added the required column `workspaceId` to the `Inbox` table without a default value. This is not possible if the table is not empty. + +*/ +-- CreateTable +CREATE TABLE "Workspace" ( + "id" TEXT NOT NULL PRIMARY KEY, + "name" TEXT NOT NULL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL +); + +INSERT INTO "Workspace" ("id", "name", "createdAt", "updatedAt") +VALUES ('ws_default', 'default', CURRENT_TIMESTAMP, CURRENT_TIMESTAMP); + +-- CreateTable +CREATE TABLE "WarmupCampaign" ( + "id" TEXT NOT NULL PRIMARY KEY, + "workspaceId" TEXT NOT NULL, + "primaryMailboxId" TEXT NOT NULL, + "name" TEXT NOT NULL, + "recipe" TEXT NOT NULL DEFAULT 'grow', + "status" TEXT NOT NULL DEFAULT 'draft', + "durationDays" INTEGER NOT NULL DEFAULT 30, + "minEmailsPerDay" INTEGER NOT NULL DEFAULT 1, + "maxEmailsPerDay" INTEGER NOT NULL DEFAULT 40, + "replyRatePercent" INTEGER NOT NULL DEFAULT 30, + "maintenanceVolumePercent" INTEGER NOT NULL DEFAULT 20, + "timezone" TEXT NOT NULL DEFAULT 'America/Vancouver', + "sendWindowStart" TEXT NOT NULL DEFAULT '08:00', + "sendWindowEnd" TEXT NOT NULL DEFAULT '18:00', + "customSchedule" TEXT, + "startedAt" DATETIME, + "endsAt" DATETIME, + "currentDay" INTEGER NOT NULL DEFAULT 0, + "pausedFromStatus" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "WarmupCampaign_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "WarmupCampaign_primaryMailboxId_fkey" FOREIGN KEY ("primaryMailboxId") REFERENCES "Inbox" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "CampaignSatellite" ( + "id" TEXT NOT NULL PRIMARY KEY, + "campaignId" TEXT NOT NULL, + "satelliteMailboxId" TEXT NOT NULL, + "weight" INTEGER NOT NULL DEFAULT 1, + "active" BOOLEAN NOT NULL DEFAULT true, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "CampaignSatellite_campaignId_fkey" FOREIGN KEY ("campaignId") REFERENCES "WarmupCampaign" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "CampaignSatellite_satelliteMailboxId_fkey" FOREIGN KEY ("satelliteMailboxId") REFERENCES "Inbox" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "DailySchedule" ( + "id" TEXT NOT NULL PRIMARY KEY, + "campaignId" TEXT NOT NULL, + "dayIndex" INTEGER NOT NULL, + "date" DATETIME, + "plannedSends" INTEGER NOT NULL, + "plannedReplies" INTEGER NOT NULL, + "actualSends" INTEGER NOT NULL DEFAULT 0, + "actualReplies" INTEGER NOT NULL DEFAULT 0, + "spamDetected" INTEGER NOT NULL DEFAULT 0, + "spamRescued" INTEGER NOT NULL DEFAULT 0, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "DailySchedule_campaignId_fkey" FOREIGN KEY ("campaignId") REFERENCES "WarmupCampaign" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Inbox" ( + "id" TEXT NOT NULL PRIMARY KEY, + "workspaceId" TEXT NOT NULL, + "email" TEXT NOT NULL, + "smtpHost" TEXT NOT NULL, + "smtpPort" INTEGER NOT NULL, + "imapHost" TEXT NOT NULL, + "imapPort" INTEGER NOT NULL, + "username" TEXT NOT NULL, + "password" TEXT NOT NULL, + "role" TEXT NOT NULL DEFAULT 'unassigned', + "provider" TEXT NOT NULL DEFAULT 'custom', + "status" TEXT NOT NULL DEFAULT 'active', + "dailyLimit" INTEGER NOT NULL DEFAULT 40, + "currentRamp" INTEGER NOT NULL DEFAULT 5, + "industry" TEXT NOT NULL DEFAULT 'SaaS', + "senderName" TEXT, + "companyName" TEXT, + "lastError" TEXT, + "errorAt" DATETIME, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Inbox_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_Inbox" ("createdAt", "currentRamp", "dailyLimit", "email", "errorAt", "id", "imapHost", "imapPort", "industry", "lastError", "password", "smtpHost", "smtpPort", "status", "updatedAt", "username", "workspaceId") SELECT "createdAt", "currentRamp", "dailyLimit", "email", "errorAt", "id", "imapHost", "imapPort", "industry", "lastError", "password", "smtpHost", "smtpPort", "status", "updatedAt", "username", 'ws_default' FROM "Inbox"; +DROP TABLE "Inbox"; +ALTER TABLE "new_Inbox" RENAME TO "Inbox"; +CREATE INDEX "Inbox_status_idx" ON "Inbox"("status"); +CREATE INDEX "Inbox_workspaceId_role_idx" ON "Inbox"("workspaceId", "role"); +CREATE UNIQUE INDEX "Inbox_workspaceId_email_key" ON "Inbox"("workspaceId", "email"); +CREATE TABLE "new_WarmupLog" ( + "id" TEXT NOT NULL PRIMARY KEY, + "campaignId" TEXT, + "senderId" TEXT NOT NULL, + "receiverId" TEXT NOT NULL, + "messageId" TEXT, + "inReplyTo" TEXT, + "subject" TEXT NOT NULL, + "body" TEXT NOT NULL, + "status" TEXT NOT NULL DEFAULT 'sent', + "rescuedFromSpam" BOOLEAN NOT NULL DEFAULT false, + "validationResult" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "WarmupLog_campaignId_fkey" FOREIGN KEY ("campaignId") REFERENCES "WarmupCampaign" ("id") ON DELETE SET NULL ON UPDATE CASCADE, + CONSTRAINT "WarmupLog_senderId_fkey" FOREIGN KEY ("senderId") REFERENCES "Inbox" ("id") ON DELETE CASCADE ON UPDATE CASCADE, + CONSTRAINT "WarmupLog_receiverId_fkey" FOREIGN KEY ("receiverId") REFERENCES "Inbox" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_WarmupLog" ("body", "createdAt", "id", "inReplyTo", "messageId", "receiverId", "rescuedFromSpam", "senderId", "status", "subject", "updatedAt") SELECT "body", "createdAt", "id", "inReplyTo", "messageId", "receiverId", "rescuedFromSpam", "senderId", "status", "subject", "updatedAt" FROM "WarmupLog"; +DROP TABLE "WarmupLog"; +ALTER TABLE "new_WarmupLog" RENAME TO "WarmupLog"; +CREATE INDEX "WarmupLog_senderId_status_idx" ON "WarmupLog"("senderId", "status"); +CREATE INDEX "WarmupLog_receiverId_status_idx" ON "WarmupLog"("receiverId", "status"); +CREATE INDEX "WarmupLog_messageId_idx" ON "WarmupLog"("messageId"); +CREATE INDEX "WarmupLog_campaignId_idx" ON "WarmupLog"("campaignId"); +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; + +-- CreateIndex +CREATE UNIQUE INDEX "Workspace_name_key" ON "Workspace"("name"); + +-- CreateIndex +CREATE INDEX "WarmupCampaign_workspaceId_status_idx" ON "WarmupCampaign"("workspaceId", "status"); + +-- CreateIndex +CREATE INDEX "WarmupCampaign_primaryMailboxId_status_idx" ON "WarmupCampaign"("primaryMailboxId", "status"); + +-- CreateIndex +CREATE INDEX "CampaignSatellite_satelliteMailboxId_idx" ON "CampaignSatellite"("satelliteMailboxId"); + +-- CreateIndex +CREATE UNIQUE INDEX "CampaignSatellite_campaignId_satelliteMailboxId_key" ON "CampaignSatellite"("campaignId", "satelliteMailboxId"); + +-- CreateIndex +CREATE INDEX "DailySchedule_campaignId_idx" ON "DailySchedule"("campaignId"); + +-- CreateIndex +CREATE UNIQUE INDEX "DailySchedule_campaignId_dayIndex_key" ON "DailySchedule"("campaignId", "dayIndex"); diff --git a/prisma/migrations/20260625060207_sprint_1_2_1_3_dispatcher/migration.sql b/prisma/migrations/20260625060207_sprint_1_2_1_3_dispatcher/migration.sql new file mode 100644 index 0000000..716f760 --- /dev/null +++ b/prisma/migrations/20260625060207_sprint_1_2_1_3_dispatcher/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "WarmupCampaign" ADD COLUMN "lastSendAt" DATETIME; diff --git a/prisma/migrations/20260625173756_sprint_1_4_reply_rate/migration.sql b/prisma/migrations/20260625173756_sprint_1_4_reply_rate/migration.sql new file mode 100644 index 0000000..af5102c --- /dev/null +++ b/prisma/migrations/20260625173756_sprint_1_4_reply_rate/migration.sql @@ -0,0 +1 @@ +-- This is an empty migration. \ No newline at end of file diff --git a/prisma/migrations/20260625173848_sprint_1_5_stats/migration.sql b/prisma/migrations/20260625173848_sprint_1_5_stats/migration.sql new file mode 100644 index 0000000..0bc82a9 --- /dev/null +++ b/prisma/migrations/20260625173848_sprint_1_5_stats/migration.sql @@ -0,0 +1,27 @@ +-- AlterTable +ALTER TABLE "WarmupLog" ADD COLUMN "placement" TEXT; + +-- CreateTable +CREATE TABLE "DailyStat" ( + "id" TEXT NOT NULL PRIMARY KEY, + "campaignId" TEXT NOT NULL, + "date" DATETIME NOT NULL, + "dayIndex" INTEGER NOT NULL, + "plannedSends" INTEGER NOT NULL, + "plannedReplies" INTEGER NOT NULL, + "actualSends" INTEGER NOT NULL DEFAULT 0, + "actualReplies" INTEGER NOT NULL DEFAULT 0, + "spamDetected" INTEGER NOT NULL DEFAULT 0, + "spamRescued" INTEGER NOT NULL DEFAULT 0, + "replyRateActual" REAL, + "inboxPlacementRate" REAL, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "DailyStat_campaignId_fkey" FOREIGN KEY ("campaignId") REFERENCES "WarmupCampaign" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateIndex +CREATE INDEX "DailyStat_campaignId_idx" ON "DailyStat"("campaignId"); + +-- CreateIndex +CREATE UNIQUE INDEX "DailyStat_campaignId_date_key" ON "DailyStat"("campaignId", "date"); diff --git a/prisma/migrations/20260625181925_sprint_2_2_2_4/migration.sql b/prisma/migrations/20260625181925_sprint_2_2_2_4/migration.sql new file mode 100644 index 0000000..32bacd0 --- /dev/null +++ b/prisma/migrations/20260625181925_sprint_2_2_2_4/migration.sql @@ -0,0 +1,35 @@ +-- AlterTable +ALTER TABLE "Inbox" ADD COLUMN "lastSpamRescuedAt" DATETIME; +ALTER TABLE "Inbox" ADD COLUMN "spamFolderPath" TEXT; + +-- CreateTable +CREATE TABLE "SendJob" ( + "id" TEXT NOT NULL PRIMARY KEY, + "campaignId" TEXT NOT NULL, + "scheduledAt" DATETIME NOT NULL, + "status" TEXT NOT NULL DEFAULT 'pending', + "idempotencyKey" TEXT NOT NULL, + "processedAt" DATETIME, + "errorMessage" TEXT, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "SendJob_campaignId_fkey" FOREIGN KEY ("campaignId") REFERENCES "WarmupCampaign" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); + +-- CreateTable +CREATE TABLE "JobRunLog" ( + "id" TEXT NOT NULL PRIMARY KEY, + "jobName" TEXT NOT NULL, + "ranAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "success" BOOLEAN NOT NULL DEFAULT true, + "message" TEXT +); + +-- CreateIndex +CREATE UNIQUE INDEX "SendJob_idempotencyKey_key" ON "SendJob"("idempotencyKey"); + +-- CreateIndex +CREATE INDEX "SendJob_campaignId_status_scheduledAt_idx" ON "SendJob"("campaignId", "status", "scheduledAt"); + +-- CreateIndex +CREATE INDEX "JobRunLog_jobName_ranAt_idx" ON "JobRunLog"("jobName", "ranAt"); diff --git a/prisma/migrations/20260626035310_mailbox_auth_microsoft/migration.sql b/prisma/migrations/20260626035310_mailbox_auth_microsoft/migration.sql new file mode 100644 index 0000000..fe9f51b --- /dev/null +++ b/prisma/migrations/20260626035310_mailbox_auth_microsoft/migration.sql @@ -0,0 +1,39 @@ +-- RedefineTables +PRAGMA defer_foreign_keys=ON; +PRAGMA foreign_keys=OFF; +CREATE TABLE "new_Inbox" ( + "id" TEXT NOT NULL PRIMARY KEY, + "workspaceId" TEXT NOT NULL, + "email" TEXT NOT NULL, + "smtpHost" TEXT NOT NULL, + "smtpPort" INTEGER NOT NULL, + "imapHost" TEXT NOT NULL, + "imapPort" INTEGER NOT NULL, + "username" TEXT NOT NULL, + "password" TEXT NOT NULL, + "authType" TEXT NOT NULL DEFAULT 'password', + "oauthRefreshToken" TEXT, + "role" TEXT NOT NULL DEFAULT 'unassigned', + "provider" TEXT NOT NULL DEFAULT 'custom', + "status" TEXT NOT NULL DEFAULT 'active', + "dailyLimit" INTEGER NOT NULL DEFAULT 40, + "currentRamp" INTEGER NOT NULL DEFAULT 5, + "industry" TEXT NOT NULL DEFAULT 'SaaS', + "senderName" TEXT, + "companyName" TEXT, + "lastError" TEXT, + "errorAt" DATETIME, + "spamFolderPath" TEXT, + "lastSpamRescuedAt" DATETIME, + "createdAt" DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" DATETIME NOT NULL, + CONSTRAINT "Inbox_workspaceId_fkey" FOREIGN KEY ("workspaceId") REFERENCES "Workspace" ("id") ON DELETE CASCADE ON UPDATE CASCADE +); +INSERT INTO "new_Inbox" ("companyName", "createdAt", "currentRamp", "dailyLimit", "email", "errorAt", "id", "imapHost", "imapPort", "industry", "lastError", "lastSpamRescuedAt", "password", "provider", "role", "senderName", "smtpHost", "smtpPort", "spamFolderPath", "status", "updatedAt", "username", "workspaceId") SELECT "companyName", "createdAt", "currentRamp", "dailyLimit", "email", "errorAt", "id", "imapHost", "imapPort", "industry", "lastError", "lastSpamRescuedAt", "password", "provider", "role", "senderName", "smtpHost", "smtpPort", "spamFolderPath", "status", "updatedAt", "username", "workspaceId" FROM "Inbox"; +DROP TABLE "Inbox"; +ALTER TABLE "new_Inbox" RENAME TO "Inbox"; +CREATE INDEX "Inbox_status_idx" ON "Inbox"("status"); +CREATE INDEX "Inbox_workspaceId_role_idx" ON "Inbox"("workspaceId", "role"); +CREATE UNIQUE INDEX "Inbox_workspaceId_email_key" ON "Inbox"("workspaceId", "email"); +PRAGMA foreign_keys=ON; +PRAGMA defer_foreign_keys=OFF; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index cf3f3f7..a8f36a5 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,6 +1,6 @@ generator client { provider = "prisma-client" - output = "../src/generated/prisma" + output = "../apps/api/src/generated/prisma" } datasource db { @@ -16,36 +16,224 @@ enum InboxStatus { enum WarmupStatus { sent rescued_from_spam + engaged_no_reply replied complete } +enum MailboxRole { + unassigned + primary + satellite +} + +enum MailboxProvider { + google_workspace + gmail + yahoo + outlook + custom +} + +enum MailboxAuthType { + password + microsoft_oauth +} + +enum CampaignRecipe { + grow + flat + randomized + custom +} + +enum CampaignStatus { + draft + active + paused + maintenance + completed + failed +} + +model Workspace { + id String @id @default(cuid()) + name String @unique + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + inboxes Inbox[] + campaigns WarmupCampaign[] +} + +enum SendJobStatus { + pending + processing + completed + failed +} + +model SendJob { + id String @id @default(cuid()) + campaignId String + scheduledAt DateTime + status SendJobStatus @default(pending) + idempotencyKey String @unique + processedAt DateTime? + errorMessage String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + campaign WarmupCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) + + @@index([campaignId, status, scheduledAt]) +} + +model JobRunLog { + id String @id @default(cuid()) + jobName String + ranAt DateTime @default(now()) + success Boolean @default(true) + message String? + + @@index([jobName, ranAt]) +} + model Inbox { - id String @id @default(cuid()) - email String @unique + id String @id @default(cuid()) + workspaceId String + email String smtpHost String smtpPort Int imapHost String imapPort Int username String password String - status InboxStatus @default(active) - dailyLimit Int @default(40) - currentRamp Int @default(5) - industry String @default("SaaS") + authType MailboxAuthType @default(password) + oauthRefreshToken String? + role MailboxRole @default(unassigned) + provider MailboxProvider @default(custom) + status InboxStatus @default(active) + dailyLimit Int @default(40) + currentRamp Int @default(5) + industry String @default("SaaS") + senderName String? + companyName String? lastError String? errorAt DateTime? - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt + spamFolderPath String? + lastSpamRescuedAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt - sentLogs WarmupLog[] @relation("Sender") - receivedLogs WarmupLog[] @relation("Receiver") + workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) + sentLogs WarmupLog[] @relation("Sender") + receivedLogs WarmupLog[] @relation("Receiver") + primaryCampaigns WarmupCampaign[] @relation("PrimaryMailbox") + satelliteCampaigns CampaignSatellite[] + @@unique([workspaceId, email]) @@index([status]) + @@index([workspaceId, role]) +} + +model WarmupCampaign { + id String @id @default(cuid()) + workspaceId String + primaryMailboxId String + name String + recipe CampaignRecipe @default(grow) + status CampaignStatus @default(draft) + durationDays Int @default(30) + minEmailsPerDay Int @default(1) + maxEmailsPerDay Int @default(40) + replyRatePercent Int @default(30) + maintenanceVolumePercent Int @default(20) + timezone String @default("America/Vancouver") + sendWindowStart String @default("08:00") + sendWindowEnd String @default("18:00") + customSchedule String? + startedAt DateTime? + endsAt DateTime? + currentDay Int @default(0) + pausedFromStatus CampaignStatus? + lastSendAt DateTime? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + workspace Workspace @relation(fields: [workspaceId], references: [id], onDelete: Cascade) + primaryMailbox Inbox @relation("PrimaryMailbox", fields: [primaryMailboxId], references: [id], onDelete: Cascade) + satellites CampaignSatellite[] + dailySchedules DailySchedule[] + dailyStats DailyStat[] + sendJobs SendJob[] + warmupLogs WarmupLog[] + + @@index([workspaceId, status]) + @@index([primaryMailboxId, status]) +} + +model CampaignSatellite { + id String @id @default(cuid()) + campaignId String + satelliteMailboxId String + weight Int @default(1) + active Boolean @default(true) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + campaign WarmupCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) + satelliteMailbox Inbox @relation(fields: [satelliteMailboxId], references: [id], onDelete: Cascade) + + @@unique([campaignId, satelliteMailboxId]) + @@index([satelliteMailboxId]) +} + +model DailySchedule { + id String @id @default(cuid()) + campaignId String + dayIndex Int + date DateTime? + plannedSends Int + plannedReplies Int + actualSends Int @default(0) + actualReplies Int @default(0) + spamDetected Int @default(0) + spamRescued Int @default(0) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + campaign WarmupCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) + + @@unique([campaignId, dayIndex]) + @@index([campaignId]) +} + +model DailyStat { + id String @id @default(cuid()) + campaignId String + date DateTime + dayIndex Int + plannedSends Int + plannedReplies Int + actualSends Int @default(0) + actualReplies Int @default(0) + spamDetected Int @default(0) + spamRescued Int @default(0) + replyRateActual Float? + inboxPlacementRate Float? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + campaign WarmupCampaign @relation(fields: [campaignId], references: [id], onDelete: Cascade) + + @@unique([campaignId, date]) + @@index([campaignId]) } model WarmupLog { id String @id @default(cuid()) + campaignId String? senderId String receiverId String messageId String? @@ -53,16 +241,20 @@ model WarmupLog { subject String body String status WarmupStatus @default(sent) + placement String? rescuedFromSpam Boolean @default(false) + validationResult String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - sender Inbox @relation("Sender", fields: [senderId], references: [id], onDelete: Cascade) - receiver Inbox @relation("Receiver", fields: [receiverId], references: [id], onDelete: Cascade) + campaign WarmupCampaign? @relation(fields: [campaignId], references: [id], onDelete: SetNull) + sender Inbox @relation("Sender", fields: [senderId], references: [id], onDelete: Cascade) + receiver Inbox @relation("Receiver", fields: [receiverId], references: [id], onDelete: Cascade) @@index([senderId, status]) @@index([receiverId, status]) @@index([messageId]) + @@index([campaignId]) } model Setting { diff --git a/src/index.ts b/src/index.ts deleted file mode 100644 index 018aee1..0000000 --- a/src/index.ts +++ /dev/null @@ -1,37 +0,0 @@ -import express from 'express'; -import { config } from './config'; -import { errorHandler } from './middleware/errorHandler'; -import { apiKeyAuth } from './middleware/apiKeyAuth'; -import inboxesRouter from './routes/inboxes.routes'; -import logsRouter from './routes/logs.routes'; -import settingsRouter from './routes/settings.routes'; -import { startScheduler } from './jobs/scheduler'; -import { logger } from './lib/logger'; - -const app = express(); - -app.use(express.json()); -app.use('/api', apiKeyAuth); - -app.get('/health', (_req, res) => { - res.json({ status: 'ok' }); -}); - -app.use('/api/inboxes', inboxesRouter); -app.use('/api/logs', logsRouter); -app.use('/api/settings', settingsRouter); - -app.use(errorHandler); - -async function main(): Promise { - await startScheduler(); - - app.listen(config.PORT, () => { - logger.info({ port: config.PORT }, 'Warmbox server started'); - }); -} - -main().catch((err) => { - logger.error({ err }, 'Failed to start server'); - process.exit(1); -}); diff --git a/src/jobs/rescuer.job.ts b/src/jobs/rescuer.job.ts deleted file mode 100644 index 5a2eac4..0000000 --- a/src/jobs/rescuer.job.ts +++ /dev/null @@ -1,216 +0,0 @@ -import { simpleParser } from 'mailparser'; -import { ImapFlow } from 'imapflow'; -import type { Inbox, WarmupLog } from '../generated/prisma/client'; -import { InboxStatus, WarmupStatus } from '../lib/prisma'; -import { prisma } from '../lib/prisma'; -import { withInboxGuard } from '../lib/inboxGuard'; -import { logger } from '../lib/logger'; -import { SPAM_FOLDERS } from '../models/enums'; -import * as aiService from '../services/ai.service'; -import * as emailService from '../services/email.service'; -import { withImapClient } from '../services/imap.service'; - -type PendingLog = WarmupLog & { sender: Inbox }; - -function normalizeMessageId(id: string | undefined | null): string | null { - if (!id) return null; - return id.replace(/^<|>$/g, '').trim(); -} - -function extractFromAddress(fromHeader: string | undefined): string | null { - if (!fromHeader) return null; - const match = fromHeader.match(/<([^>]+)>/) ?? fromHeader.match(/([\w.+-]+@[\w.-]+)/); - return match?.[1]?.toLowerCase() ?? null; -} - -async function findMatchingLog( - logs: PendingLog[], - messageId: string | null, - fromEmail: string | null, - subject: string | undefined, -): Promise { - if (messageId) { - const normalized = normalizeMessageId(messageId); - const byId = logs.find( - (l) => l.messageId && normalizeMessageId(l.messageId) === normalized, - ); - if (byId) return byId; - } - - if (fromEmail && subject) { - return logs.find( - (l) => - l.sender.email.toLowerCase() === fromEmail && - l.subject.trim() === subject.replace(/^Re:\s*/i, '').trim(), - ); - } - - return undefined; -} - -async function processMessage( - receiver: Inbox, - uid: number, - client: ImapFlow, - pendingLogs: PendingLog[], -): Promise { - const msg = await client.fetchOne(String(uid), { - envelope: true, - source: true, - headers: ['message-id', 'from', 'subject'], - uid: true, - }, { uid: true }); - - if (!msg || !msg.source) return; - - const parsed = await simpleParser(msg.source); - const fromEmail = extractFromAddress(parsed.from?.text ?? msg.envelope?.from?.[0]?.address); - const messageId = parsed.messageId ?? msg.envelope?.messageId ?? null; - const subject = parsed.subject ?? msg.envelope?.subject ?? ''; - - const log = await findMatchingLog(pendingLogs, messageId, fromEmail, subject); - if (!log) return; - - await client.messageFlagsAdd(String(uid), ['\\Seen', '\\Flagged'], { uid: true }); - - try { - await client.messageFlagsAdd(String(uid), ['\\Important'], { uid: true, useLabels: true }); - } catch { - logger.debug({ inboxId: receiver.id }, 'Important flag not supported'); - } - - const incomingBody = parsed.text ?? parsed.html?.toString() ?? ''; - const { subject: replySubject, body: replyBody } = await aiService.generateReply( - receiver.industry, - incomingBody, - log.subject, - ); - - const originalMessageId = log.messageId ?? messageId ?? undefined; - - await withInboxGuard(receiver, async () => { - const { messageId: replyMessageId } = await emailService.sendMail(receiver, { - to: log.sender.email, - subject: replySubject, - body: replyBody, - inReplyTo: originalMessageId, - references: originalMessageId, - }); - - await prisma.warmupLog.update({ - where: { id: log.id }, - data: { status: WarmupStatus.replied, inReplyTo: replyMessageId }, - }); - - await prisma.warmupLog.update({ - where: { id: log.id }, - data: { status: WarmupStatus.complete }, - }); - - logger.info( - { receiver: receiver.email, sender: log.sender.email, logId: log.id }, - 'Warmup reply sent', - ); - }); - - // Remove from pending so we don't process twice - const idx = pendingLogs.findIndex((l) => l.id === log.id); - if (idx >= 0) pendingLogs.splice(idx, 1); -} - -async function rescueFromSpam( - receiver: Inbox, - client: ImapFlow, - pendingLogs: PendingLog[], -): Promise { - const senderEmails = [...new Set(pendingLogs.map((l) => l.sender.email))]; - - for (const folder of SPAM_FOLDERS) { - try { - await client.mailboxOpen(folder); - } catch { - continue; - } - - for (const senderEmail of senderEmails) { - const uids = await client.search({ seen: false, from: senderEmail }, { uid: true }); - if (!uids || uids.length === 0) continue; - - await client.messageMove(uids, 'INBOX', { uid: true }); - - const matchingLogs = pendingLogs.filter( - (l) => l.sender.email.toLowerCase() === senderEmail.toLowerCase(), - ); - - for (const log of matchingLogs) { - await prisma.warmupLog.update({ - where: { id: log.id }, - data: { - status: WarmupStatus.rescued_from_spam, - rescuedFromSpam: true, - }, - }); - log.status = WarmupStatus.rescued_from_spam; - } - - logger.info( - { receiver: receiver.email, folder, count: uids.length, senderEmail }, - 'Rescued emails from spam', - ); - } - } -} - -async function processInbox(receiver: Inbox): Promise { - const pendingLogs = (await prisma.warmupLog.findMany({ - where: { - receiverId: receiver.id, - status: { in: [WarmupStatus.sent, WarmupStatus.rescued_from_spam] }, - }, - include: { sender: true }, - })) as PendingLog[]; - - if (pendingLogs.length === 0) return; - - await withInboxGuard(receiver, async () => { - await withImapClient(receiver, async (client) => { - await rescueFromSpam(receiver, client, pendingLogs); - - await client.mailboxOpen('INBOX'); - const senderEmails: string[] = [ - ...new Set(pendingLogs.map((l: PendingLog) => l.sender.email)), - ]; - - for (const senderEmail of senderEmails) { - const uids = await client.search({ seen: false, from: senderEmail }, { uid: true }); - if (!uids || uids.length === 0) continue; - - for (const uid of uids) { - try { - await processMessage(receiver, uid, client, pendingLogs); - } catch (err) { - logger.error({ err, receiverId: receiver.id, uid }, 'Failed to process message'); - } - } - } - }); - }); -} - -export async function runRescuerJob(): Promise { - logger.info('Rescuer job started'); - - const inboxes = await prisma.inbox.findMany({ - where: { status: InboxStatus.active }, - }); - - for (const inbox of inboxes) { - try { - await processInbox(inbox); - } catch (err) { - logger.error({ err, inboxId: inbox.id }, 'Rescuer failed for inbox'); - } - } - - logger.info('Rescuer job finished'); -} diff --git a/src/routes/inboxes.routes.ts b/src/routes/inboxes.routes.ts deleted file mode 100644 index 64b8f7b..0000000 --- a/src/routes/inboxes.routes.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { Router, Request, Response, NextFunction } from 'express'; -import { z } from 'zod'; -import { InboxStatus } from '../lib/prisma'; -import { prisma } from '../lib/prisma'; -import { encrypt } from '../services/crypto.service'; -import { testConnection } from '../services/email.service'; -import { stripPassword } from '../lib/inboxGuard'; -import { AppError } from '../middleware/errorHandler'; - -const router = Router(); - -const createInboxSchema = z.object({ - email: z.string().email(), - smtpHost: z.string().min(1), - smtpPort: z.coerce.number().int().positive(), - imapHost: z.string().min(1), - imapPort: z.coerce.number().int().positive(), - username: z.string().min(1), - password: z.string().min(1), - dailyLimit: z.coerce.number().int().positive().optional(), - currentRamp: z.coerce.number().int().positive().optional(), - industry: z.string().min(1).optional(), - status: z.nativeEnum(InboxStatus).optional(), -}); - -const updateInboxSchema = z.object({ - email: z.string().email().optional(), - smtpHost: z.string().min(1).optional(), - smtpPort: z.coerce.number().int().positive().optional(), - imapHost: z.string().min(1).optional(), - imapPort: z.coerce.number().int().positive().optional(), - username: z.string().min(1).optional(), - password: z.string().min(1).optional(), - dailyLimit: z.coerce.number().int().positive().optional(), - currentRamp: z.coerce.number().int().positive().optional(), - industry: z.string().min(1).optional(), - status: z.nativeEnum(InboxStatus).optional(), -}); - -router.post('/', async (req: Request, res: Response, next: NextFunction) => { - try { - const data = createInboxSchema.parse(req.body); - const inbox = await prisma.inbox.create({ - data: { - ...data, - password: encrypt(data.password), - dailyLimit: data.dailyLimit ?? 40, - currentRamp: data.currentRamp ?? 5, - industry: data.industry ?? 'SaaS', - status: data.status ?? InboxStatus.active, - }, - }); - res.status(201).json(stripPassword(inbox)); - } catch (err) { - next(err); - } -}); - -router.get('/', async (_req: Request, res: Response, next: NextFunction) => { - try { - const inboxes = await prisma.inbox.findMany({ orderBy: { createdAt: 'desc' } }); - res.json(inboxes.map(stripPassword)); - } catch (err) { - next(err); - } -}); - -router.get('/:id', async (req: Request, res: Response, next: NextFunction) => { - try { - const inbox = await prisma.inbox.findUnique({ where: { id: req.params.id } }); - if (!inbox) { - throw new AppError(404, 'Inbox not found'); - } - res.json(stripPassword(inbox)); - } catch (err) { - next(err); - } -}); - -router.patch('/:id', async (req: Request, res: Response, next: NextFunction) => { - try { - const data = updateInboxSchema.parse(req.body); - const { password, ...rest } = data; - - const inbox = await prisma.inbox.update({ - where: { id: req.params.id }, - data: { - ...rest, - ...(password ? { password: encrypt(password) } : {}), - ...(rest.status === InboxStatus.active - ? { lastError: null, errorAt: null } - : {}), - }, - }); - res.json(stripPassword(inbox)); - } catch (err) { - next(err); - } -}); - -router.delete('/:id', async (req: Request, res: Response, next: NextFunction) => { - try { - await prisma.inbox.delete({ where: { id: req.params.id } }); - res.status(204).send(); - } catch (err) { - next(err); - } -}); - -router.post('/:id/test', async (req: Request, res: Response, next: NextFunction) => { - try { - const inbox = await prisma.inbox.findUnique({ where: { id: req.params.id } }); - if (!inbox) { - throw new AppError(404, 'Inbox not found'); - } - - const result = await testConnection(inbox); - const ok = result.smtp.ok && result.imap.ok; - - if (!ok) { - await prisma.inbox.update({ - where: { id: inbox.id }, - data: { - status: InboxStatus.error, - lastError: result.smtp.error ?? result.imap.error ?? 'Connection test failed', - errorAt: new Date(), - }, - }); - } else if (inbox.status === InboxStatus.error) { - await prisma.inbox.update({ - where: { id: inbox.id }, - data: { status: InboxStatus.active, lastError: null, errorAt: null }, - }); - } - - res.json({ ok, ...result }); - } catch (err) { - next(err); - } -}); - -export default router; diff --git a/src/services/ai.service.ts b/src/services/ai.service.ts deleted file mode 100644 index 08e536c..0000000 --- a/src/services/ai.service.ts +++ /dev/null @@ -1,165 +0,0 @@ -import OpenAI from 'openai'; -import { config } from '../config'; -import { prisma } from '../lib/prisma'; -import { GeneratedEmail } from '../models/types'; -import { logger } from '../lib/logger'; - -const DEFAULT_MODEL = 'openrouter/free'; - -async function getApiKey(): Promise { - if (config.AI_API_KEY) { - return config.AI_API_KEY; - } - - const setting = await prisma.setting.findUnique({ - where: { key: 'ai_api_key' }, - }); - - if (setting?.value) { - return setting.value; - } - - // Backward compatibility - const legacy = await prisma.setting.findUnique({ - where: { key: 'openai_api_key' }, - }); - if (legacy?.value) { - return legacy.value; - } - - throw new Error( - 'AI API key not configured. Set AI_API_KEY in .env (get a free key at https://openrouter.ai/keys)', - ); -} - -async function getModel(): Promise { - if (config.AI_MODEL) { - return config.AI_MODEL; - } - - const setting = await prisma.setting.findUnique({ - where: { key: 'ai_model' }, - }); - - return setting?.value ?? DEFAULT_MODEL; -} - -async function createClient(): Promise { - const apiKey = await getApiKey(); - return new OpenAI({ - apiKey, - baseURL: config.AI_BASE_URL, - defaultHeaders: config.AI_BASE_URL.includes('openrouter') - ? { - 'HTTP-Referer': 'https://github.com/warmbox', - 'X-Title': 'Warmbox Email Warmup', - } - : undefined, - }); -} - -function extractJson(content: string): GeneratedEmail { - const trimmed = content.trim(); - - try { - return parseEmailResponse(trimmed); - } catch { - const match = trimmed.match(/\{[\s\S]*\}/); - if (match) { - return parseEmailResponse(match[0]); - } - throw new Error('AI response was not valid JSON'); - } -} - -function parseEmailResponse(content: string): GeneratedEmail { - const parsed = JSON.parse(content) as { subject?: string; body?: string }; - if (!parsed.subject || !parsed.body) { - throw new Error('AI response missing subject or body'); - } - return { subject: parsed.subject.trim(), body: parsed.body.trim() }; -} - -async function chatWithRetry( - client: OpenAI, - model: string, - messages: OpenAI.Chat.ChatCompletionMessageParam[], -): Promise { - const request = async (useJsonMode: boolean) => { - const response = await client.chat.completions.create({ - model, - messages, - temperature: 0.8, - ...(useJsonMode ? { response_format: { type: 'json_object' as const } } : {}), - }); - - const content = response.choices[0]?.message?.content; - if (!content) { - throw new Error('Empty AI response'); - } - return extractJson(content); - }; - - try { - return await request(true); - } catch (err) { - logger.warn({ err, model }, 'AI request failed, retrying without json mode'); - await new Promise((r) => setTimeout(r, 2000)); - - try { - return await request(false); - } catch (retryErr) { - logger.warn({ err: retryErr, model }, 'AI retry failed'); - await new Promise((r) => setTimeout(r, 2000)); - return request(false); - } - } -} - -export async function generateInitialEmail(industry: string): Promise { - const client = await createClient(); - const model = await getModel(); - - return chatWithRetry(client, model, [ - { - role: 'system', - content: `You write realistic B2B professional emails for the ${industry} industry. -Respond with ONLY valid JSON: {"subject":"...","body":"..."} -The email should sound natural, business-appropriate, and avoid spam trigger words. -Body should be 80-150 words. Subject should be concise and realistic.`, - }, - { - role: 'user', - content: 'Generate a new outbound business email.', - }, - ]); -} - -export async function generateReply( - industry: string, - incomingBody: string, - threadSubject: string, -): Promise { - const client = await createClient(); - const model = await getModel(); - - const result = await chatWithRetry(client, model, [ - { - role: 'system', - content: `You write realistic B2B professional email replies for the ${industry} industry. -Respond with ONLY valid JSON: {"subject":"...","body":"..."} -Write a natural, short reply (40-100 words) that continues the conversation. -If subject does not start with "Re:", prefix it with "Re: ".`, - }, - { - role: 'user', - content: `Thread subject: ${threadSubject}\n\nIncoming message:\n${incomingBody}`, - }, - ]); - - if (!result.subject.toLowerCase().startsWith('re:')) { - result.subject = `Re: ${threadSubject.replace(/^Re:\s*/i, '')}`; - } - - return result; -}

+ {mailbox.provider === 'outlook' || mailbox.authType === 'microsoft_oauth' ? ( + <> + Microsoft consumer accounts require OAuth — use Edit → Connect Microsoft (configure + OAuth in Settings first). App passwords from account.live.com no longer work. + + ) : ( + <> + For Google Workspace/Gmail, use an{' '} + + App Password + {' '} + (not your regular account password). Click Edit to update credentials. + + )} +