diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index c2d7a33..67e7bb6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -4,7 +4,6 @@ about: Create a report to help us improve title: '' labels: bug assignees: '' - --- **Describe the bug** @@ -12,9 +11,10 @@ A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: + 1. Go to '...' 2. Click on '....' -5. See error +3. See error **Expected behavior** A clear and concise description of what you expected to happen. @@ -23,7 +23,8 @@ A clear and concise description of what you expected to happen. If applicable, add screenshots to help explain your problem. **System:** - - Open Archiver Version: + +- Open Archiver Version: **Relevant logs:** Any relevant logs (Redact sensitive information) diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md index 361075c..0871c30 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.md +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -4,11 +4,10 @@ about: Suggest an idea for this project title: '' labels: enhancement assignees: '' - --- **Is your feature request related to a problem? Please describe.** -A clear and concise description of what the problem is. +A clear and concise description of what the problem is. **Describe the solution you'd like** A clear and concise description of what you want to happen. diff --git a/apps/open-archiver/index.ts b/apps/open-archiver/index.ts index 4b89743..b9a3009 100644 --- a/apps/open-archiver/index.ts +++ b/apps/open-archiver/index.ts @@ -4,23 +4,21 @@ import * as dotenv from 'dotenv'; dotenv.config(); async function start() { - // --- Environment Variable Validation --- - const { PORT_BACKEND } = process.env; + // --- Environment Variable Validation --- + const { PORT_BACKEND } = process.env; - if (!PORT_BACKEND) { - throw new Error( - 'Missing required environment variables for the backend: PORT_BACKEND.' - ); - } - // Create the server instance (passing no modules for the default OSS version) - const app = await createServer([]); + if (!PORT_BACKEND) { + throw new Error('Missing required environment variables for the backend: PORT_BACKEND.'); + } + // Create the server instance (passing no modules for the default OSS version) + const app = await createServer([]); - app.listen(PORT_BACKEND, () => { - logger.info({}, `✅ Open Archiver (OSS) running on port ${PORT_BACKEND}`); - }); + app.listen(PORT_BACKEND, () => { + logger.info({}, `✅ Open Archiver (OSS) running on port ${PORT_BACKEND}`); + }); } -start().catch(error => { - logger.error({ error }, 'Failed to start the server:', error); - process.exit(1); +start().catch((error) => { + logger.error({ error }, 'Failed to start the server:', error); + process.exit(1); }); diff --git a/packages/backend/src/api/controllers/ingestion.controller.ts b/packages/backend/src/api/controllers/ingestion.controller.ts index b4c9b93..f0d05d3 100644 --- a/packages/backend/src/api/controllers/ingestion.controller.ts +++ b/packages/backend/src/api/controllers/ingestion.controller.ts @@ -24,7 +24,6 @@ export class IngestionController { } public create = async (req: Request, res: Response): Promise => { - try { const dto: CreateIngestionSourceDto = req.body; const userId = req.user?.sub; @@ -35,7 +34,12 @@ export class IngestionController { if (!actor) { return res.status(401).json({ message: req.t('errors.unauthorized') }); } - const newSource = await IngestionService.create(dto, userId, actor, req.ip || 'unknown'); + const newSource = await IngestionService.create( + dto, + userId, + actor, + req.ip || 'unknown' + ); const safeSource = this.toSafeIngestionSource(newSource); return res.status(201).json(safeSource); } catch (error: any) { @@ -78,7 +82,6 @@ export class IngestionController { }; public update = async (req: Request, res: Response): Promise => { - try { const { id } = req.params; const dto: UpdateIngestionSourceDto = req.body; @@ -90,7 +93,12 @@ export class IngestionController { if (!actor) { return res.status(401).json({ message: req.t('errors.unauthorized') }); } - const updatedSource = await IngestionService.update(id, dto, actor, req.ip || 'unknown'); + const updatedSource = await IngestionService.update( + id, + dto, + actor, + req.ip || 'unknown' + ); const safeSource = this.toSafeIngestionSource(updatedSource); return res.status(200).json(safeSource); } catch (error) { @@ -103,7 +111,6 @@ export class IngestionController { }; public delete = async (req: Request, res: Response): Promise => { - try { checkDeletionEnabled(); const { id } = req.params; @@ -129,7 +136,6 @@ export class IngestionController { }; public triggerInitialImport = async (req: Request, res: Response): Promise => { - try { const { id } = req.params; await IngestionService.triggerInitialImport(id); @@ -144,7 +150,6 @@ export class IngestionController { }; public pause = async (req: Request, res: Response): Promise => { - try { const { id } = req.params; const userId = req.user?.sub; @@ -173,7 +178,6 @@ export class IngestionController { }; public triggerForceSync = async (req: Request, res: Response): Promise => { - try { const { id } = req.params; const userId = req.user?.sub; diff --git a/packages/backend/src/api/controllers/integrity.controller.ts b/packages/backend/src/api/controllers/integrity.controller.ts index 94442d5..d585b59 100644 --- a/packages/backend/src/api/controllers/integrity.controller.ts +++ b/packages/backend/src/api/controllers/integrity.controller.ts @@ -3,27 +3,27 @@ import { IntegrityService } from '../../services/IntegrityService'; import { z } from 'zod'; const checkIntegritySchema = z.object({ - id: z.string().uuid(), + id: z.string().uuid(), }); export class IntegrityController { - private integrityService = new IntegrityService(); + private integrityService = new IntegrityService(); - public checkIntegrity = async (req: Request, res: Response) => { - try { - const { id } = checkIntegritySchema.parse(req.params); - const results = await this.integrityService.checkEmailIntegrity(id); - res.status(200).json(results); - } catch (error) { - if (error instanceof z.ZodError) { - return res - .status(400) - .json({ message: req.t('api.requestBodyInvalid'), errors: error.message }); - } - if (error instanceof Error && error.message === 'Archived email not found') { - return res.status(404).json({ message: req.t('errors.notFound') }); - } - res.status(500).json({ message: req.t('errors.internalServerError') }); - } - }; + public checkIntegrity = async (req: Request, res: Response) => { + try { + const { id } = checkIntegritySchema.parse(req.params); + const results = await this.integrityService.checkEmailIntegrity(id); + res.status(200).json(results); + } catch (error) { + if (error instanceof z.ZodError) { + return res + .status(400) + .json({ message: req.t('api.requestBodyInvalid'), errors: error.message }); + } + if (error instanceof Error && error.message === 'Archived email not found') { + return res.status(404).json({ message: req.t('errors.notFound') }); + } + res.status(500).json({ message: req.t('errors.internalServerError') }); + } + }; } diff --git a/packages/backend/src/api/controllers/jobs.controller.ts b/packages/backend/src/api/controllers/jobs.controller.ts index a713a5e..3e45eba 100644 --- a/packages/backend/src/api/controllers/jobs.controller.ts +++ b/packages/backend/src/api/controllers/jobs.controller.ts @@ -1,42 +1,42 @@ import { Request, Response } from 'express'; import { JobsService } from '../../services/JobsService'; import { - IGetQueueJobsRequestParams, - IGetQueueJobsRequestQuery, - JobStatus, + IGetQueueJobsRequestParams, + IGetQueueJobsRequestQuery, + JobStatus, } from '@open-archiver/types'; export class JobsController { - private jobsService: JobsService; + private jobsService: JobsService; - constructor() { - this.jobsService = new JobsService(); - } + constructor() { + this.jobsService = new JobsService(); + } - public getQueues = async (req: Request, res: Response) => { - try { - const queues = await this.jobsService.getQueues(); - res.status(200).json({ queues }); - } catch (error) { - res.status(500).json({ message: 'Error fetching queues', error }); - } - }; + public getQueues = async (req: Request, res: Response) => { + try { + const queues = await this.jobsService.getQueues(); + res.status(200).json({ queues }); + } catch (error) { + res.status(500).json({ message: 'Error fetching queues', error }); + } + }; - public getQueueJobs = async (req: Request, res: Response) => { - try { - const { queueName } = req.params as unknown as IGetQueueJobsRequestParams; - const { status, page, limit } = req.query as unknown as IGetQueueJobsRequestQuery; - const pageNumber = parseInt(page, 10) || 1; - const limitNumber = parseInt(limit, 10) || 10; - const queueDetails = await this.jobsService.getQueueDetails( - queueName, - status, - pageNumber, - limitNumber - ); - res.status(200).json(queueDetails); - } catch (error) { - res.status(500).json({ message: 'Error fetching queue jobs', error }); - } - }; + public getQueueJobs = async (req: Request, res: Response) => { + try { + const { queueName } = req.params as unknown as IGetQueueJobsRequestParams; + const { status, page, limit } = req.query as unknown as IGetQueueJobsRequestQuery; + const pageNumber = parseInt(page, 10) || 1; + const limitNumber = parseInt(limit, 10) || 10; + const queueDetails = await this.jobsService.getQueueDetails( + queueName, + status, + pageNumber, + limitNumber + ); + res.status(200).json(queueDetails); + } catch (error) { + res.status(500).json({ message: 'Error fetching queue jobs', error }); + } + }; } diff --git a/packages/backend/src/api/controllers/user.controller.ts b/packages/backend/src/api/controllers/user.controller.ts index 6c67b0b..418c1de 100644 --- a/packages/backend/src/api/controllers/user.controller.ts +++ b/packages/backend/src/api/controllers/user.controller.ts @@ -4,7 +4,6 @@ import * as schema from '../../database/schema'; import { sql } from 'drizzle-orm'; import { db } from '../../database'; - const userService = new UserService(); export const getUsers = async (req: Request, res: Response) => { diff --git a/packages/backend/src/api/routes/integrity.routes.ts b/packages/backend/src/api/routes/integrity.routes.ts index 52efbcd..bcd5d74 100644 --- a/packages/backend/src/api/routes/integrity.routes.ts +++ b/packages/backend/src/api/routes/integrity.routes.ts @@ -5,12 +5,12 @@ import { requirePermission } from '../middleware/requirePermission'; import { AuthService } from '../../services/AuthService'; export const integrityRoutes = (authService: AuthService): Router => { - const router = Router(); - const controller = new IntegrityController(); + const router = Router(); + const controller = new IntegrityController(); - router.use(requireAuth(authService)); + router.use(requireAuth(authService)); - router.get('/:id', requirePermission('read', 'archive'), controller.checkIntegrity); + router.get('/:id', requirePermission('read', 'archive'), controller.checkIntegrity); - return router; + return router; }; diff --git a/packages/backend/src/api/routes/jobs.routes.ts b/packages/backend/src/api/routes/jobs.routes.ts index 0e55e79..9387c14 100644 --- a/packages/backend/src/api/routes/jobs.routes.ts +++ b/packages/backend/src/api/routes/jobs.routes.ts @@ -5,21 +5,21 @@ import { requirePermission } from '../middleware/requirePermission'; import { AuthService } from '../../services/AuthService'; export const createJobsRouter = (authService: AuthService): Router => { - const router = Router(); - const jobsController = new JobsController(); + const router = Router(); + const jobsController = new JobsController(); - router.use(requireAuth(authService)); + router.use(requireAuth(authService)); - router.get( - '/queues', - requirePermission('manage', 'all', 'user.requiresSuperAdminRole'), - jobsController.getQueues - ); - router.get( - '/queues/:queueName', - requirePermission('manage', 'all', 'user.requiresSuperAdminRole'), - jobsController.getQueueJobs - ); + router.get( + '/queues', + requirePermission('manage', 'all', 'user.requiresSuperAdminRole'), + jobsController.getQueues + ); + router.get( + '/queues/:queueName', + requirePermission('manage', 'all', 'user.requiresSuperAdminRole'), + jobsController.getQueueJobs + ); - return router; + return router; }; diff --git a/packages/backend/src/api/server.ts b/packages/backend/src/api/server.ts index 350e10a..f3f74a5 100644 --- a/packages/backend/src/api/server.ts +++ b/packages/backend/src/api/server.ts @@ -37,134 +37,134 @@ import { config } from '../config'; import { OpenArchiverFeature } from '@open-archiver/types'; // Define the "plugin" interface export interface ArchiverModule { - initialize: (app: Express, authService: AuthService) => Promise; - name: OpenArchiverFeature; + initialize: (app: Express, authService: AuthService) => Promise; + name: OpenArchiverFeature; } export let authService: AuthService; export async function createServer(modules: ArchiverModule[] = []): Promise { - // Load environment variables - dotenv.config(); + // Load environment variables + dotenv.config(); - // --- Environment Variable Validation --- - const { JWT_SECRET, JWT_EXPIRES_IN } = process.env; + // --- Environment Variable Validation --- + const { JWT_SECRET, JWT_EXPIRES_IN } = process.env; - if (!JWT_SECRET || !JWT_EXPIRES_IN) { - throw new Error( - 'Missing required environment variables for the backend: JWT_SECRET, JWT_EXPIRES_IN.' - ); - } + if (!JWT_SECRET || !JWT_EXPIRES_IN) { + throw new Error( + 'Missing required environment variables for the backend: JWT_SECRET, JWT_EXPIRES_IN.' + ); + } - // --- Dependency Injection Setup --- - const auditService = new AuditService(); - const userService = new UserService(); - authService = new AuthService(userService, auditService, JWT_SECRET, JWT_EXPIRES_IN); - const authController = new AuthController(authService, userService); - const ingestionController = new IngestionController(); - const archivedEmailController = new ArchivedEmailController(); - const storageService = new StorageService(); - const storageController = new StorageController(storageService); - const searchService = new SearchService(); - const searchController = new SearchController(); - const iamService = new IamService(); - const iamController = new IamController(iamService); - const settingsService = new SettingsService(); + // --- Dependency Injection Setup --- + const auditService = new AuditService(); + const userService = new UserService(); + authService = new AuthService(userService, auditService, JWT_SECRET, JWT_EXPIRES_IN); + const authController = new AuthController(authService, userService); + const ingestionController = new IngestionController(); + const archivedEmailController = new ArchivedEmailController(); + const storageService = new StorageService(); + const storageController = new StorageController(storageService); + const searchService = new SearchService(); + const searchController = new SearchController(); + const iamService = new IamService(); + const iamController = new IamController(iamService); + const settingsService = new SettingsService(); - // --- i18next Initialization --- - const initializeI18next = async () => { - const systemSettings = await settingsService.getSystemSettings(); - const defaultLanguage = systemSettings?.language || 'en'; - logger.info({ language: defaultLanguage }, 'Default language'); - await i18next.use(FsBackend).init({ - lng: defaultLanguage, - fallbackLng: defaultLanguage, - ns: ['translation'], - defaultNS: 'translation', - backend: { - loadPath: path.resolve(__dirname, '../locales/{{lng}}/{{ns}}.json'), - }, - }); - }; + // --- i18next Initialization --- + const initializeI18next = async () => { + const systemSettings = await settingsService.getSystemSettings(); + const defaultLanguage = systemSettings?.language || 'en'; + logger.info({ language: defaultLanguage }, 'Default language'); + await i18next.use(FsBackend).init({ + lng: defaultLanguage, + fallbackLng: defaultLanguage, + ns: ['translation'], + defaultNS: 'translation', + backend: { + loadPath: path.resolve(__dirname, '../locales/{{lng}}/{{ns}}.json'), + }, + }); + }; - // Initialize i18next - await initializeI18next(); - logger.info({}, 'i18next initialized'); + // Initialize i18next + await initializeI18next(); + logger.info({}, 'i18next initialized'); - // Configure the Meilisearch index on startup - logger.info({}, 'Configuring email index...'); - await searchService.configureEmailIndex(); + // Configure the Meilisearch index on startup + logger.info({}, 'Configuring email index...'); + await searchService.configureEmailIndex(); - const app = express(); + const app = express(); - // --- CORS --- - app.use( - cors({ - origin: process.env.APP_URL || 'http://localhost:3000', - credentials: true, - }) - ); + // --- CORS --- + app.use( + cors({ + origin: process.env.APP_URL || 'http://localhost:3000', + credentials: true, + }) + ); - // Trust the proxy to get the real IP address of the client. - // This is important for audit logging and security. - app.set('trust proxy', true); + // Trust the proxy to get the real IP address of the client. + // This is important for audit logging and security. + app.set('trust proxy', true); - // --- Routes --- - const authRouter = createAuthRouter(authController); - const ingestionRouter = createIngestionRouter(ingestionController, authService); - const archivedEmailRouter = createArchivedEmailRouter(archivedEmailController, authService); - const storageRouter = createStorageRouter(storageController, authService); - const searchRouter = createSearchRouter(searchController, authService); - const dashboardRouter = createDashboardRouter(authService); - const iamRouter = createIamRouter(iamController, authService); - const uploadRouter = createUploadRouter(authService); - const userRouter = createUserRouter(authService); - const settingsRouter = createSettingsRouter(authService); - const apiKeyRouter = apiKeyRoutes(authService); - const integrityRouter = integrityRoutes(authService); - const jobsRouter = createJobsRouter(authService); + // --- Routes --- + const authRouter = createAuthRouter(authController); + const ingestionRouter = createIngestionRouter(ingestionController, authService); + const archivedEmailRouter = createArchivedEmailRouter(archivedEmailController, authService); + const storageRouter = createStorageRouter(storageController, authService); + const searchRouter = createSearchRouter(searchController, authService); + const dashboardRouter = createDashboardRouter(authService); + const iamRouter = createIamRouter(iamController, authService); + const uploadRouter = createUploadRouter(authService); + const userRouter = createUserRouter(authService); + const settingsRouter = createSettingsRouter(authService); + const apiKeyRouter = apiKeyRoutes(authService); + const integrityRouter = integrityRoutes(authService); + const jobsRouter = createJobsRouter(authService); - // Middleware for all other routes - app.use((req, res, next) => { - // exclude certain API endpoints from the rate limiter, for example status, system settings - const excludedPatterns = [/^\/v\d+\/auth\/status$/, /^\/v\d+\/settings\/system$/]; - for (const pattern of excludedPatterns) { - if (pattern.test(req.path)) { - return next(); - } - } - rateLimiter(req, res, next); - }); - app.use(express.json()); - app.use(express.urlencoded({ extended: true })); + // Middleware for all other routes + app.use((req, res, next) => { + // exclude certain API endpoints from the rate limiter, for example status, system settings + const excludedPatterns = [/^\/v\d+\/auth\/status$/, /^\/v\d+\/settings\/system$/]; + for (const pattern of excludedPatterns) { + if (pattern.test(req.path)) { + return next(); + } + } + rateLimiter(req, res, next); + }); + app.use(express.json()); + app.use(express.urlencoded({ extended: true })); - // i18n middleware - app.use(i18nextMiddleware.handle(i18next)); + // i18n middleware + app.use(i18nextMiddleware.handle(i18next)); - app.use(`/${config.api.version}/auth`, authRouter); - app.use(`/${config.api.version}/iam`, iamRouter); - app.use(`/${config.api.version}/upload`, uploadRouter); - app.use(`/${config.api.version}/ingestion-sources`, ingestionRouter); - app.use(`/${config.api.version}/archived-emails`, archivedEmailRouter); - app.use(`/${config.api.version}/storage`, storageRouter); - app.use(`/${config.api.version}/search`, searchRouter); - app.use(`/${config.api.version}/dashboard`, dashboardRouter); - app.use(`/${config.api.version}/users`, userRouter); - app.use(`/${config.api.version}/settings`, settingsRouter); - app.use(`/${config.api.version}/api-keys`, apiKeyRouter); - app.use(`/${config.api.version}/integrity`, integrityRouter); - app.use(`/${config.api.version}/jobs`, jobsRouter); + app.use(`/${config.api.version}/auth`, authRouter); + app.use(`/${config.api.version}/iam`, iamRouter); + app.use(`/${config.api.version}/upload`, uploadRouter); + app.use(`/${config.api.version}/ingestion-sources`, ingestionRouter); + app.use(`/${config.api.version}/archived-emails`, archivedEmailRouter); + app.use(`/${config.api.version}/storage`, storageRouter); + app.use(`/${config.api.version}/search`, searchRouter); + app.use(`/${config.api.version}/dashboard`, dashboardRouter); + app.use(`/${config.api.version}/users`, userRouter); + app.use(`/${config.api.version}/settings`, settingsRouter); + app.use(`/${config.api.version}/api-keys`, apiKeyRouter); + app.use(`/${config.api.version}/integrity`, integrityRouter); + app.use(`/${config.api.version}/jobs`, jobsRouter); - // Load all provided extension modules - for (const module of modules) { - await module.initialize(app, authService); - console.log(`🏢 Enterprise module loaded: ${module.name}`); - } - app.get('/', (req, res) => { - res.send('Backend is running!!'); - }); + // Load all provided extension modules + for (const module of modules) { + await module.initialize(app, authService); + console.log(`🏢 Enterprise module loaded: ${module.name}`); + } + app.get('/', (req, res) => { + res.send('Backend is running!!'); + }); - console.log('✅ Core OSS modules loaded.'); + console.log('✅ Core OSS modules loaded.'); - return app; + return app; } diff --git a/packages/backend/src/config/api.ts b/packages/backend/src/config/api.ts index e102f69..0175c21 100644 --- a/packages/backend/src/config/api.ts +++ b/packages/backend/src/config/api.ts @@ -9,5 +9,5 @@ export const apiConfig = { ? parseInt(process.env.RATE_LIMIT_MAX_REQUESTS, 10) : 100, // limit each IP to 100 requests per windowMs }, - version: 'v1' + version: 'v1', }; diff --git a/packages/backend/src/database/migrations/meta/0020_snapshot.json b/packages/backend/src/database/migrations/meta/0020_snapshot.json index 57eeb9e..6d22496 100644 --- a/packages/backend/src/database/migrations/meta/0020_snapshot.json +++ b/packages/backend/src/database/migrations/meta/0020_snapshot.json @@ -1,1245 +1,1178 @@ { - "id": "ed44e48c-b43b-402b-bf9d-5b5312edf42e", - "prevId": "a83bff5b-47ef-43e9-8e7d-667af35a206f", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.archived_emails": { - "name": "archived_emails", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "thread_id": { - "name": "thread_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "ingestion_source_id": { - "name": "ingestion_source_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "user_email": { - "name": "user_email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "message_id_header": { - "name": "message_id_header", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sent_at": { - "name": "sent_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "subject": { - "name": "subject", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sender_name": { - "name": "sender_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sender_email": { - "name": "sender_email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "recipients": { - "name": "recipients", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "storage_path": { - "name": "storage_path", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "storage_hash_sha256": { - "name": "storage_hash_sha256", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "size_bytes": { - "name": "size_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "is_indexed": { - "name": "is_indexed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "has_attachments": { - "name": "has_attachments", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "is_on_legal_hold": { - "name": "is_on_legal_hold", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "thread_id_idx": { - "name": "thread_id_idx", - "columns": [ - { - "expression": "thread_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "archived_emails_ingestion_source_id_ingestion_sources_id_fk": { - "name": "archived_emails_ingestion_source_id_ingestion_sources_id_fk", - "tableFrom": "archived_emails", - "tableTo": "ingestion_sources", - "columnsFrom": [ - "ingestion_source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.attachments": { - "name": "attachments", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "filename": { - "name": "filename", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "mime_type": { - "name": "mime_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "size_bytes": { - "name": "size_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "content_hash_sha256": { - "name": "content_hash_sha256", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "storage_path": { - "name": "storage_path", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "attachments_content_hash_sha256_unique": { - "name": "attachments_content_hash_sha256_unique", - "nullsNotDistinct": false, - "columns": [ - "content_hash_sha256" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.email_attachments": { - "name": "email_attachments", - "schema": "", - "columns": { - "email_id": { - "name": "email_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "attachment_id": { - "name": "attachment_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "email_attachments_email_id_archived_emails_id_fk": { - "name": "email_attachments_email_id_archived_emails_id_fk", - "tableFrom": "email_attachments", - "tableTo": "archived_emails", - "columnsFrom": [ - "email_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "email_attachments_attachment_id_attachments_id_fk": { - "name": "email_attachments_attachment_id_attachments_id_fk", - "tableFrom": "email_attachments", - "tableTo": "attachments", - "columnsFrom": [ - "attachment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "email_attachments_email_id_attachment_id_pk": { - "name": "email_attachments_email_id_attachment_id_pk", - "columns": [ - "email_id", - "attachment_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.audit_logs": { - "name": "audit_logs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "bigserial", - "primaryKey": true, - "notNull": true - }, - "timestamp": { - "name": "timestamp", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "actor_identifier": { - "name": "actor_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "action": { - "name": "action", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "target_type": { - "name": "target_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "target_id": { - "name": "target_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "details": { - "name": "details", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "is_tamper_evident": { - "name": "is_tamper_evident", - "type": "boolean", - "primaryKey": false, - "notNull": false, - "default": false - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ediscovery_cases": { - "name": "ediscovery_cases", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'open'" - }, - "created_by_identifier": { - "name": "created_by_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "ediscovery_cases_name_unique": { - "name": "ediscovery_cases_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.export_jobs": { - "name": "export_jobs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "case_id": { - "name": "case_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "format": { - "name": "format", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "query": { - "name": "query", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "file_path": { - "name": "file_path", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_by_identifier": { - "name": "created_by_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "export_jobs_case_id_ediscovery_cases_id_fk": { - "name": "export_jobs_case_id_ediscovery_cases_id_fk", - "tableFrom": "export_jobs", - "tableTo": "ediscovery_cases", - "columnsFrom": [ - "case_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.legal_holds": { - "name": "legal_holds", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "case_id": { - "name": "case_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "custodian_id": { - "name": "custodian_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "hold_criteria": { - "name": "hold_criteria", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "reason": { - "name": "reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "applied_by_identifier": { - "name": "applied_by_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "applied_at": { - "name": "applied_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "removed_at": { - "name": "removed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "legal_holds_case_id_ediscovery_cases_id_fk": { - "name": "legal_holds_case_id_ediscovery_cases_id_fk", - "tableFrom": "legal_holds", - "tableTo": "ediscovery_cases", - "columnsFrom": [ - "case_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "legal_holds_custodian_id_custodians_id_fk": { - "name": "legal_holds_custodian_id_custodians_id_fk", - "tableFrom": "legal_holds", - "tableTo": "custodians", - "columnsFrom": [ - "custodian_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.retention_policies": { - "name": "retention_policies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "priority": { - "name": "priority", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "retention_period_days": { - "name": "retention_period_days", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "action_on_expiry": { - "name": "action_on_expiry", - "type": "retention_action", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "is_enabled": { - "name": "is_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "conditions": { - "name": "conditions", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "retention_policies_name_unique": { - "name": "retention_policies_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.custodians": { - "name": "custodians", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "source_type": { - "name": "source_type", - "type": "ingestion_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "custodians_email_unique": { - "name": "custodians_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ingestion_sources": { - "name": "ingestion_sources", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "ingestion_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "credentials": { - "name": "credentials", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "ingestion_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending_auth'" - }, - "last_sync_started_at": { - "name": "last_sync_started_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_sync_finished_at": { - "name": "last_sync_finished_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_sync_status_message": { - "name": "last_sync_status_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sync_state": { - "name": "sync_state", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "ingestion_sources_user_id_users_id_fk": { - "name": "ingestion_sources_user_id_users_id_fk", - "tableFrom": "ingestion_sources", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.roles": { - "name": "roles", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "policies": { - "name": "policies", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "roles_name_unique": { - "name": "roles_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - }, - "roles_slug_unique": { - "name": "roles_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.sessions": { - "name": "sessions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "sessions_user_id_users_id_fk": { - "name": "sessions_user_id_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_roles": { - "name": "user_roles", - "schema": "", - "columns": { - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "role_id": { - "name": "role_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "user_roles_user_id_users_id_fk": { - "name": "user_roles_user_id_users_id_fk", - "tableFrom": "user_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_roles_role_id_roles_id_fk": { - "name": "user_roles_role_id_roles_id_fk", - "tableFrom": "user_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "user_roles_user_id_role_id_pk": { - "name": "user_roles_user_id_role_id_pk", - "columns": [ - "user_id", - "role_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.users": { - "name": "users", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "first_name": { - "name": "first_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_name": { - "name": "last_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'local'" - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.system_settings": { - "name": "system_settings", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": true, - "notNull": true - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_keys": { - "name": "api_keys", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_keys_user_id_users_id_fk": { - "name": "api_keys_user_id_users_id_fk", - "tableFrom": "api_keys", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.retention_action": { - "name": "retention_action", - "schema": "public", - "values": [ - "delete_permanently", - "notify_admin" - ] - }, - "public.ingestion_provider": { - "name": "ingestion_provider", - "schema": "public", - "values": [ - "google_workspace", - "microsoft_365", - "generic_imap", - "pst_import", - "eml_import", - "mbox_import" - ] - }, - "public.ingestion_status": { - "name": "ingestion_status", - "schema": "public", - "values": [ - "active", - "paused", - "error", - "pending_auth", - "syncing", - "importing", - "auth_success", - "imported" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "ed44e48c-b43b-402b-bf9d-5b5312edf42e", + "prevId": "a83bff5b-47ef-43e9-8e7d-667af35a206f", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.archived_emails": { + "name": "archived_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ingestion_source_id": { + "name": "ingestion_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_email": { + "name": "user_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id_header": { + "name": "message_id_header", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_name": { + "name": "sender_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_email": { + "name": "sender_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipients": { + "name": "recipients", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_hash_sha256": { + "name": "storage_hash_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "is_indexed": { + "name": "is_indexed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_on_legal_hold": { + "name": "is_on_legal_hold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "thread_id_idx": { + "name": "thread_id_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "archived_emails_ingestion_source_id_ingestion_sources_id_fk": { + "name": "archived_emails_ingestion_source_id_ingestion_sources_id_fk", + "tableFrom": "archived_emails", + "tableTo": "ingestion_sources", + "columnsFrom": ["ingestion_source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.attachments": { + "name": "attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "content_hash_sha256": { + "name": "content_hash_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "attachments_content_hash_sha256_unique": { + "name": "attachments_content_hash_sha256_unique", + "nullsNotDistinct": false, + "columns": ["content_hash_sha256"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_attachments": { + "name": "email_attachments", + "schema": "", + "columns": { + "email_id": { + "name": "email_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "email_attachments_email_id_archived_emails_id_fk": { + "name": "email_attachments_email_id_archived_emails_id_fk", + "tableFrom": "email_attachments", + "tableTo": "archived_emails", + "columnsFrom": ["email_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_attachments_attachment_id_attachments_id_fk": { + "name": "email_attachments_attachment_id_attachments_id_fk", + "tableFrom": "email_attachments", + "tableTo": "attachments", + "columnsFrom": ["attachment_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "email_attachments_email_id_attachment_id_pk": { + "name": "email_attachments_email_id_attachment_id_pk", + "columns": ["email_id", "attachment_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actor_identifier": { + "name": "actor_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "action": { + "name": "action", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "is_tamper_evident": { + "name": "is_tamper_evident", + "type": "boolean", + "primaryKey": false, + "notNull": false, + "default": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ediscovery_cases": { + "name": "ediscovery_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "created_by_identifier": { + "name": "created_by_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ediscovery_cases_name_unique": { + "name": "ediscovery_cases_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.export_jobs": { + "name": "export_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "query": { + "name": "query", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_identifier": { + "name": "created_by_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "export_jobs_case_id_ediscovery_cases_id_fk": { + "name": "export_jobs_case_id_ediscovery_cases_id_fk", + "tableFrom": "export_jobs", + "tableTo": "ediscovery_cases", + "columnsFrom": ["case_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.legal_holds": { + "name": "legal_holds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custodian_id": { + "name": "custodian_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "hold_criteria": { + "name": "hold_criteria", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied_by_identifier": { + "name": "applied_by_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "removed_at": { + "name": "removed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "legal_holds_case_id_ediscovery_cases_id_fk": { + "name": "legal_holds_case_id_ediscovery_cases_id_fk", + "tableFrom": "legal_holds", + "tableTo": "ediscovery_cases", + "columnsFrom": ["case_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "legal_holds_custodian_id_custodians_id_fk": { + "name": "legal_holds_custodian_id_custodians_id_fk", + "tableFrom": "legal_holds", + "tableTo": "custodians", + "columnsFrom": ["custodian_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.retention_policies": { + "name": "retention_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "retention_period_days": { + "name": "retention_period_days", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action_on_expiry": { + "name": "action_on_expiry", + "type": "retention_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "retention_policies_name_unique": { + "name": "retention_policies_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custodians": { + "name": "custodians", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "ingestion_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custodians_email_unique": { + "name": "custodians_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ingestion_sources": { + "name": "ingestion_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "ingestion_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "credentials": { + "name": "credentials", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "ingestion_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending_auth'" + }, + "last_sync_started_at": { + "name": "last_sync_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_finished_at": { + "name": "last_sync_finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_status_message": { + "name": "last_sync_status_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_state": { + "name": "sync_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ingestion_sources_user_id_users_id_fk": { + "name": "ingestion_sources_user_id_users_id_fk", + "tableFrom": "ingestion_sources", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policies": { + "name": "policies", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + }, + "roles_slug_unique": { + "name": "roles_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_roles_user_id_role_id_pk": { + "name": "user_roles_user_id_role_id_pk", + "columns": ["user_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'local'" + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.retention_action": { + "name": "retention_action", + "schema": "public", + "values": ["delete_permanently", "notify_admin"] + }, + "public.ingestion_provider": { + "name": "ingestion_provider", + "schema": "public", + "values": [ + "google_workspace", + "microsoft_365", + "generic_imap", + "pst_import", + "eml_import", + "mbox_import" + ] + }, + "public.ingestion_status": { + "name": "ingestion_status", + "schema": "public", + "values": [ + "active", + "paused", + "error", + "pending_auth", + "syncing", + "importing", + "auth_success", + "imported" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/backend/src/database/migrations/meta/0021_snapshot.json b/packages/backend/src/database/migrations/meta/0021_snapshot.json index d2786c3..58083ac 100644 --- a/packages/backend/src/database/migrations/meta/0021_snapshot.json +++ b/packages/backend/src/database/migrations/meta/0021_snapshot.json @@ -1,1292 +1,1225 @@ { - "id": "93820787-6893-434f-8b7b-99f6b84f5123", - "prevId": "ed44e48c-b43b-402b-bf9d-5b5312edf42e", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.archived_emails": { - "name": "archived_emails", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "thread_id": { - "name": "thread_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "ingestion_source_id": { - "name": "ingestion_source_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "user_email": { - "name": "user_email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "message_id_header": { - "name": "message_id_header", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sent_at": { - "name": "sent_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "subject": { - "name": "subject", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sender_name": { - "name": "sender_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sender_email": { - "name": "sender_email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "recipients": { - "name": "recipients", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "storage_path": { - "name": "storage_path", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "storage_hash_sha256": { - "name": "storage_hash_sha256", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "size_bytes": { - "name": "size_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "is_indexed": { - "name": "is_indexed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "has_attachments": { - "name": "has_attachments", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "is_on_legal_hold": { - "name": "is_on_legal_hold", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "thread_id_idx": { - "name": "thread_id_idx", - "columns": [ - { - "expression": "thread_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "archived_emails_ingestion_source_id_ingestion_sources_id_fk": { - "name": "archived_emails_ingestion_source_id_ingestion_sources_id_fk", - "tableFrom": "archived_emails", - "tableTo": "ingestion_sources", - "columnsFrom": [ - "ingestion_source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.attachments": { - "name": "attachments", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "filename": { - "name": "filename", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "mime_type": { - "name": "mime_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "size_bytes": { - "name": "size_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "content_hash_sha256": { - "name": "content_hash_sha256", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "storage_path": { - "name": "storage_path", - "type": "text", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "attachments_content_hash_sha256_unique": { - "name": "attachments_content_hash_sha256_unique", - "nullsNotDistinct": false, - "columns": [ - "content_hash_sha256" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.email_attachments": { - "name": "email_attachments", - "schema": "", - "columns": { - "email_id": { - "name": "email_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "attachment_id": { - "name": "attachment_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "email_attachments_email_id_archived_emails_id_fk": { - "name": "email_attachments_email_id_archived_emails_id_fk", - "tableFrom": "email_attachments", - "tableTo": "archived_emails", - "columnsFrom": [ - "email_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "email_attachments_attachment_id_attachments_id_fk": { - "name": "email_attachments_attachment_id_attachments_id_fk", - "tableFrom": "email_attachments", - "tableTo": "attachments", - "columnsFrom": [ - "attachment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "email_attachments_email_id_attachment_id_pk": { - "name": "email_attachments_email_id_attachment_id_pk", - "columns": [ - "email_id", - "attachment_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.audit_logs": { - "name": "audit_logs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "bigserial", - "primaryKey": true, - "notNull": true - }, - "previous_hash": { - "name": "previous_hash", - "type": "varchar(64)", - "primaryKey": false, - "notNull": false - }, - "timestamp": { - "name": "timestamp", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "actor_identifier": { - "name": "actor_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "actor_ip": { - "name": "actor_ip", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "action_type": { - "name": "action_type", - "type": "audit_log_action", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "target_type": { - "name": "target_type", - "type": "audit_log_target_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": false - }, - "target_id": { - "name": "target_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "details": { - "name": "details", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "current_hash": { - "name": "current_hash", - "type": "varchar(64)", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ediscovery_cases": { - "name": "ediscovery_cases", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'open'" - }, - "created_by_identifier": { - "name": "created_by_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "ediscovery_cases_name_unique": { - "name": "ediscovery_cases_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.export_jobs": { - "name": "export_jobs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "case_id": { - "name": "case_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "format": { - "name": "format", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "query": { - "name": "query", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "file_path": { - "name": "file_path", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_by_identifier": { - "name": "created_by_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "export_jobs_case_id_ediscovery_cases_id_fk": { - "name": "export_jobs_case_id_ediscovery_cases_id_fk", - "tableFrom": "export_jobs", - "tableTo": "ediscovery_cases", - "columnsFrom": [ - "case_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.legal_holds": { - "name": "legal_holds", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "case_id": { - "name": "case_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "custodian_id": { - "name": "custodian_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "hold_criteria": { - "name": "hold_criteria", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "reason": { - "name": "reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "applied_by_identifier": { - "name": "applied_by_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "applied_at": { - "name": "applied_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "removed_at": { - "name": "removed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "legal_holds_case_id_ediscovery_cases_id_fk": { - "name": "legal_holds_case_id_ediscovery_cases_id_fk", - "tableFrom": "legal_holds", - "tableTo": "ediscovery_cases", - "columnsFrom": [ - "case_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "legal_holds_custodian_id_custodians_id_fk": { - "name": "legal_holds_custodian_id_custodians_id_fk", - "tableFrom": "legal_holds", - "tableTo": "custodians", - "columnsFrom": [ - "custodian_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.retention_policies": { - "name": "retention_policies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "priority": { - "name": "priority", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "retention_period_days": { - "name": "retention_period_days", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "action_on_expiry": { - "name": "action_on_expiry", - "type": "retention_action", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "is_enabled": { - "name": "is_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "conditions": { - "name": "conditions", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "retention_policies_name_unique": { - "name": "retention_policies_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.custodians": { - "name": "custodians", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "source_type": { - "name": "source_type", - "type": "ingestion_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "custodians_email_unique": { - "name": "custodians_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ingestion_sources": { - "name": "ingestion_sources", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "ingestion_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "credentials": { - "name": "credentials", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "ingestion_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending_auth'" - }, - "last_sync_started_at": { - "name": "last_sync_started_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_sync_finished_at": { - "name": "last_sync_finished_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_sync_status_message": { - "name": "last_sync_status_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sync_state": { - "name": "sync_state", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "ingestion_sources_user_id_users_id_fk": { - "name": "ingestion_sources_user_id_users_id_fk", - "tableFrom": "ingestion_sources", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.roles": { - "name": "roles", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "policies": { - "name": "policies", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "roles_name_unique": { - "name": "roles_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - }, - "roles_slug_unique": { - "name": "roles_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.sessions": { - "name": "sessions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "sessions_user_id_users_id_fk": { - "name": "sessions_user_id_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_roles": { - "name": "user_roles", - "schema": "", - "columns": { - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "role_id": { - "name": "role_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "user_roles_user_id_users_id_fk": { - "name": "user_roles_user_id_users_id_fk", - "tableFrom": "user_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_roles_role_id_roles_id_fk": { - "name": "user_roles_role_id_roles_id_fk", - "tableFrom": "user_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "user_roles_user_id_role_id_pk": { - "name": "user_roles_user_id_role_id_pk", - "columns": [ - "user_id", - "role_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.users": { - "name": "users", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "first_name": { - "name": "first_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_name": { - "name": "last_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'local'" - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.system_settings": { - "name": "system_settings", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": true, - "notNull": true - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_keys": { - "name": "api_keys", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_keys_user_id_users_id_fk": { - "name": "api_keys_user_id_users_id_fk", - "tableFrom": "api_keys", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.retention_action": { - "name": "retention_action", - "schema": "public", - "values": [ - "delete_permanently", - "notify_admin" - ] - }, - "public.ingestion_provider": { - "name": "ingestion_provider", - "schema": "public", - "values": [ - "google_workspace", - "microsoft_365", - "generic_imap", - "pst_import", - "eml_import", - "mbox_import" - ] - }, - "public.ingestion_status": { - "name": "ingestion_status", - "schema": "public", - "values": [ - "active", - "paused", - "error", - "pending_auth", - "syncing", - "importing", - "auth_success", - "imported" - ] - }, - "public.audit_log_action": { - "name": "audit_log_action", - "schema": "public", - "values": [ - "CREATE", - "READ", - "UPDATE", - "DELETE", - "LOGIN", - "LOGOUT", - "SETUP", - "IMPORT", - "PAUSE", - "SYNC", - "UPLOAD", - "SEARCH", - "DOWNLOAD", - "GENERATE" - ] - }, - "public.audit_log_target_type": { - "name": "audit_log_target_type", - "schema": "public", - "values": [ - "ApiKey", - "ArchivedEmail", - "Dashboard", - "IngestionSource", - "Role", - "SystemSettings", - "User", - "File" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "93820787-6893-434f-8b7b-99f6b84f5123", + "prevId": "ed44e48c-b43b-402b-bf9d-5b5312edf42e", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.archived_emails": { + "name": "archived_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ingestion_source_id": { + "name": "ingestion_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_email": { + "name": "user_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id_header": { + "name": "message_id_header", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_name": { + "name": "sender_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_email": { + "name": "sender_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipients": { + "name": "recipients", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_hash_sha256": { + "name": "storage_hash_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "is_indexed": { + "name": "is_indexed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_on_legal_hold": { + "name": "is_on_legal_hold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "thread_id_idx": { + "name": "thread_id_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "archived_emails_ingestion_source_id_ingestion_sources_id_fk": { + "name": "archived_emails_ingestion_source_id_ingestion_sources_id_fk", + "tableFrom": "archived_emails", + "tableTo": "ingestion_sources", + "columnsFrom": ["ingestion_source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.attachments": { + "name": "attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "content_hash_sha256": { + "name": "content_hash_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "attachments_content_hash_sha256_unique": { + "name": "attachments_content_hash_sha256_unique", + "nullsNotDistinct": false, + "columns": ["content_hash_sha256"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_attachments": { + "name": "email_attachments", + "schema": "", + "columns": { + "email_id": { + "name": "email_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "email_attachments_email_id_archived_emails_id_fk": { + "name": "email_attachments_email_id_archived_emails_id_fk", + "tableFrom": "email_attachments", + "tableTo": "archived_emails", + "columnsFrom": ["email_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_attachments_attachment_id_attachments_id_fk": { + "name": "email_attachments_attachment_id_attachments_id_fk", + "tableFrom": "email_attachments", + "tableTo": "attachments", + "columnsFrom": ["attachment_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "email_attachments_email_id_attachment_id_pk": { + "name": "email_attachments_email_id_attachment_id_pk", + "columns": ["email_id", "attachment_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "previous_hash": { + "name": "previous_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actor_identifier": { + "name": "actor_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_ip": { + "name": "actor_ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_type": { + "name": "action_type", + "type": "audit_log_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "audit_log_target_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "current_hash": { + "name": "current_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ediscovery_cases": { + "name": "ediscovery_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "created_by_identifier": { + "name": "created_by_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ediscovery_cases_name_unique": { + "name": "ediscovery_cases_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.export_jobs": { + "name": "export_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "query": { + "name": "query", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_identifier": { + "name": "created_by_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "export_jobs_case_id_ediscovery_cases_id_fk": { + "name": "export_jobs_case_id_ediscovery_cases_id_fk", + "tableFrom": "export_jobs", + "tableTo": "ediscovery_cases", + "columnsFrom": ["case_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.legal_holds": { + "name": "legal_holds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custodian_id": { + "name": "custodian_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "hold_criteria": { + "name": "hold_criteria", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied_by_identifier": { + "name": "applied_by_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "removed_at": { + "name": "removed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "legal_holds_case_id_ediscovery_cases_id_fk": { + "name": "legal_holds_case_id_ediscovery_cases_id_fk", + "tableFrom": "legal_holds", + "tableTo": "ediscovery_cases", + "columnsFrom": ["case_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "legal_holds_custodian_id_custodians_id_fk": { + "name": "legal_holds_custodian_id_custodians_id_fk", + "tableFrom": "legal_holds", + "tableTo": "custodians", + "columnsFrom": ["custodian_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.retention_policies": { + "name": "retention_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "retention_period_days": { + "name": "retention_period_days", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action_on_expiry": { + "name": "action_on_expiry", + "type": "retention_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "retention_policies_name_unique": { + "name": "retention_policies_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custodians": { + "name": "custodians", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "ingestion_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custodians_email_unique": { + "name": "custodians_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ingestion_sources": { + "name": "ingestion_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "ingestion_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "credentials": { + "name": "credentials", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "ingestion_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending_auth'" + }, + "last_sync_started_at": { + "name": "last_sync_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_finished_at": { + "name": "last_sync_finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_status_message": { + "name": "last_sync_status_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_state": { + "name": "sync_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ingestion_sources_user_id_users_id_fk": { + "name": "ingestion_sources_user_id_users_id_fk", + "tableFrom": "ingestion_sources", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policies": { + "name": "policies", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + }, + "roles_slug_unique": { + "name": "roles_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_roles_user_id_role_id_pk": { + "name": "user_roles_user_id_role_id_pk", + "columns": ["user_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'local'" + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.retention_action": { + "name": "retention_action", + "schema": "public", + "values": ["delete_permanently", "notify_admin"] + }, + "public.ingestion_provider": { + "name": "ingestion_provider", + "schema": "public", + "values": [ + "google_workspace", + "microsoft_365", + "generic_imap", + "pst_import", + "eml_import", + "mbox_import" + ] + }, + "public.ingestion_status": { + "name": "ingestion_status", + "schema": "public", + "values": [ + "active", + "paused", + "error", + "pending_auth", + "syncing", + "importing", + "auth_success", + "imported" + ] + }, + "public.audit_log_action": { + "name": "audit_log_action", + "schema": "public", + "values": [ + "CREATE", + "READ", + "UPDATE", + "DELETE", + "LOGIN", + "LOGOUT", + "SETUP", + "IMPORT", + "PAUSE", + "SYNC", + "UPLOAD", + "SEARCH", + "DOWNLOAD", + "GENERATE" + ] + }, + "public.audit_log_target_type": { + "name": "audit_log_target_type", + "schema": "public", + "values": [ + "ApiKey", + "ArchivedEmail", + "Dashboard", + "IngestionSource", + "Role", + "SystemSettings", + "User", + "File" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/backend/src/database/migrations/meta/0022_snapshot.json b/packages/backend/src/database/migrations/meta/0022_snapshot.json index 7e5636a..440d70f 100644 --- a/packages/backend/src/database/migrations/meta/0022_snapshot.json +++ b/packages/backend/src/database/migrations/meta/0022_snapshot.json @@ -1,1326 +1,1257 @@ { - "id": "d61a882f-72c4-4fbb-8a6e-6c9dc8372679", - "prevId": "93820787-6893-434f-8b7b-99f6b84f5123", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.archived_emails": { - "name": "archived_emails", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "thread_id": { - "name": "thread_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "ingestion_source_id": { - "name": "ingestion_source_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "user_email": { - "name": "user_email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "message_id_header": { - "name": "message_id_header", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sent_at": { - "name": "sent_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "subject": { - "name": "subject", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sender_name": { - "name": "sender_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sender_email": { - "name": "sender_email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "recipients": { - "name": "recipients", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "storage_path": { - "name": "storage_path", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "storage_hash_sha256": { - "name": "storage_hash_sha256", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "size_bytes": { - "name": "size_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "is_indexed": { - "name": "is_indexed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "has_attachments": { - "name": "has_attachments", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "is_on_legal_hold": { - "name": "is_on_legal_hold", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "thread_id_idx": { - "name": "thread_id_idx", - "columns": [ - { - "expression": "thread_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "archived_emails_ingestion_source_id_ingestion_sources_id_fk": { - "name": "archived_emails_ingestion_source_id_ingestion_sources_id_fk", - "tableFrom": "archived_emails", - "tableTo": "ingestion_sources", - "columnsFrom": [ - "ingestion_source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.attachments": { - "name": "attachments", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "filename": { - "name": "filename", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "mime_type": { - "name": "mime_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "size_bytes": { - "name": "size_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "content_hash_sha256": { - "name": "content_hash_sha256", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "storage_path": { - "name": "storage_path", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "ingestion_source_id": { - "name": "ingestion_source_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "source_hash_unique": { - "name": "source_hash_unique", - "columns": [ - { - "expression": "ingestion_source_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "content_hash_sha256", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": true, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "attachments_ingestion_source_id_ingestion_sources_id_fk": { - "name": "attachments_ingestion_source_id_ingestion_sources_id_fk", - "tableFrom": "attachments", - "tableTo": "ingestion_sources", - "columnsFrom": [ - "ingestion_source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.email_attachments": { - "name": "email_attachments", - "schema": "", - "columns": { - "email_id": { - "name": "email_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "attachment_id": { - "name": "attachment_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "email_attachments_email_id_archived_emails_id_fk": { - "name": "email_attachments_email_id_archived_emails_id_fk", - "tableFrom": "email_attachments", - "tableTo": "archived_emails", - "columnsFrom": [ - "email_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "email_attachments_attachment_id_attachments_id_fk": { - "name": "email_attachments_attachment_id_attachments_id_fk", - "tableFrom": "email_attachments", - "tableTo": "attachments", - "columnsFrom": [ - "attachment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "email_attachments_email_id_attachment_id_pk": { - "name": "email_attachments_email_id_attachment_id_pk", - "columns": [ - "email_id", - "attachment_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.audit_logs": { - "name": "audit_logs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "bigserial", - "primaryKey": true, - "notNull": true - }, - "previous_hash": { - "name": "previous_hash", - "type": "varchar(64)", - "primaryKey": false, - "notNull": false - }, - "timestamp": { - "name": "timestamp", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "actor_identifier": { - "name": "actor_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "actor_ip": { - "name": "actor_ip", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "action_type": { - "name": "action_type", - "type": "audit_log_action", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "target_type": { - "name": "target_type", - "type": "audit_log_target_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": false - }, - "target_id": { - "name": "target_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "details": { - "name": "details", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "current_hash": { - "name": "current_hash", - "type": "varchar(64)", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ediscovery_cases": { - "name": "ediscovery_cases", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'open'" - }, - "created_by_identifier": { - "name": "created_by_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "ediscovery_cases_name_unique": { - "name": "ediscovery_cases_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.export_jobs": { - "name": "export_jobs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "case_id": { - "name": "case_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "format": { - "name": "format", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "query": { - "name": "query", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "file_path": { - "name": "file_path", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_by_identifier": { - "name": "created_by_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "export_jobs_case_id_ediscovery_cases_id_fk": { - "name": "export_jobs_case_id_ediscovery_cases_id_fk", - "tableFrom": "export_jobs", - "tableTo": "ediscovery_cases", - "columnsFrom": [ - "case_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.legal_holds": { - "name": "legal_holds", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "case_id": { - "name": "case_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "custodian_id": { - "name": "custodian_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "hold_criteria": { - "name": "hold_criteria", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "reason": { - "name": "reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "applied_by_identifier": { - "name": "applied_by_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "applied_at": { - "name": "applied_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "removed_at": { - "name": "removed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "legal_holds_case_id_ediscovery_cases_id_fk": { - "name": "legal_holds_case_id_ediscovery_cases_id_fk", - "tableFrom": "legal_holds", - "tableTo": "ediscovery_cases", - "columnsFrom": [ - "case_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "legal_holds_custodian_id_custodians_id_fk": { - "name": "legal_holds_custodian_id_custodians_id_fk", - "tableFrom": "legal_holds", - "tableTo": "custodians", - "columnsFrom": [ - "custodian_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.retention_policies": { - "name": "retention_policies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "priority": { - "name": "priority", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "retention_period_days": { - "name": "retention_period_days", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "action_on_expiry": { - "name": "action_on_expiry", - "type": "retention_action", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "is_enabled": { - "name": "is_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "conditions": { - "name": "conditions", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "retention_policies_name_unique": { - "name": "retention_policies_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.custodians": { - "name": "custodians", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "source_type": { - "name": "source_type", - "type": "ingestion_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "custodians_email_unique": { - "name": "custodians_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ingestion_sources": { - "name": "ingestion_sources", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "ingestion_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "credentials": { - "name": "credentials", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "ingestion_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending_auth'" - }, - "last_sync_started_at": { - "name": "last_sync_started_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_sync_finished_at": { - "name": "last_sync_finished_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_sync_status_message": { - "name": "last_sync_status_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sync_state": { - "name": "sync_state", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "ingestion_sources_user_id_users_id_fk": { - "name": "ingestion_sources_user_id_users_id_fk", - "tableFrom": "ingestion_sources", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.roles": { - "name": "roles", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "policies": { - "name": "policies", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "roles_name_unique": { - "name": "roles_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - }, - "roles_slug_unique": { - "name": "roles_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.sessions": { - "name": "sessions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "sessions_user_id_users_id_fk": { - "name": "sessions_user_id_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_roles": { - "name": "user_roles", - "schema": "", - "columns": { - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "role_id": { - "name": "role_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "user_roles_user_id_users_id_fk": { - "name": "user_roles_user_id_users_id_fk", - "tableFrom": "user_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_roles_role_id_roles_id_fk": { - "name": "user_roles_role_id_roles_id_fk", - "tableFrom": "user_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "user_roles_user_id_role_id_pk": { - "name": "user_roles_user_id_role_id_pk", - "columns": [ - "user_id", - "role_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.users": { - "name": "users", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "first_name": { - "name": "first_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_name": { - "name": "last_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'local'" - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.system_settings": { - "name": "system_settings", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": true, - "notNull": true - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_keys": { - "name": "api_keys", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_keys_user_id_users_id_fk": { - "name": "api_keys_user_id_users_id_fk", - "tableFrom": "api_keys", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.retention_action": { - "name": "retention_action", - "schema": "public", - "values": [ - "delete_permanently", - "notify_admin" - ] - }, - "public.ingestion_provider": { - "name": "ingestion_provider", - "schema": "public", - "values": [ - "google_workspace", - "microsoft_365", - "generic_imap", - "pst_import", - "eml_import", - "mbox_import" - ] - }, - "public.ingestion_status": { - "name": "ingestion_status", - "schema": "public", - "values": [ - "active", - "paused", - "error", - "pending_auth", - "syncing", - "importing", - "auth_success", - "imported" - ] - }, - "public.audit_log_action": { - "name": "audit_log_action", - "schema": "public", - "values": [ - "CREATE", - "READ", - "UPDATE", - "DELETE", - "LOGIN", - "LOGOUT", - "SETUP", - "IMPORT", - "PAUSE", - "SYNC", - "UPLOAD", - "SEARCH", - "DOWNLOAD", - "GENERATE" - ] - }, - "public.audit_log_target_type": { - "name": "audit_log_target_type", - "schema": "public", - "values": [ - "ApiKey", - "ArchivedEmail", - "Dashboard", - "IngestionSource", - "Role", - "SystemSettings", - "User", - "File" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "d61a882f-72c4-4fbb-8a6e-6c9dc8372679", + "prevId": "93820787-6893-434f-8b7b-99f6b84f5123", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.archived_emails": { + "name": "archived_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ingestion_source_id": { + "name": "ingestion_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_email": { + "name": "user_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id_header": { + "name": "message_id_header", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_name": { + "name": "sender_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_email": { + "name": "sender_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipients": { + "name": "recipients", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_hash_sha256": { + "name": "storage_hash_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "is_indexed": { + "name": "is_indexed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_on_legal_hold": { + "name": "is_on_legal_hold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "thread_id_idx": { + "name": "thread_id_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "archived_emails_ingestion_source_id_ingestion_sources_id_fk": { + "name": "archived_emails_ingestion_source_id_ingestion_sources_id_fk", + "tableFrom": "archived_emails", + "tableTo": "ingestion_sources", + "columnsFrom": ["ingestion_source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.attachments": { + "name": "attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "content_hash_sha256": { + "name": "content_hash_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ingestion_source_id": { + "name": "ingestion_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "source_hash_unique": { + "name": "source_hash_unique", + "columns": [ + { + "expression": "ingestion_source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "content_hash_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": true, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "attachments_ingestion_source_id_ingestion_sources_id_fk": { + "name": "attachments_ingestion_source_id_ingestion_sources_id_fk", + "tableFrom": "attachments", + "tableTo": "ingestion_sources", + "columnsFrom": ["ingestion_source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_attachments": { + "name": "email_attachments", + "schema": "", + "columns": { + "email_id": { + "name": "email_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "email_attachments_email_id_archived_emails_id_fk": { + "name": "email_attachments_email_id_archived_emails_id_fk", + "tableFrom": "email_attachments", + "tableTo": "archived_emails", + "columnsFrom": ["email_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_attachments_attachment_id_attachments_id_fk": { + "name": "email_attachments_attachment_id_attachments_id_fk", + "tableFrom": "email_attachments", + "tableTo": "attachments", + "columnsFrom": ["attachment_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "email_attachments_email_id_attachment_id_pk": { + "name": "email_attachments_email_id_attachment_id_pk", + "columns": ["email_id", "attachment_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "previous_hash": { + "name": "previous_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actor_identifier": { + "name": "actor_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_ip": { + "name": "actor_ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_type": { + "name": "action_type", + "type": "audit_log_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "audit_log_target_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "current_hash": { + "name": "current_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ediscovery_cases": { + "name": "ediscovery_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "created_by_identifier": { + "name": "created_by_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ediscovery_cases_name_unique": { + "name": "ediscovery_cases_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.export_jobs": { + "name": "export_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "query": { + "name": "query", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_identifier": { + "name": "created_by_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "export_jobs_case_id_ediscovery_cases_id_fk": { + "name": "export_jobs_case_id_ediscovery_cases_id_fk", + "tableFrom": "export_jobs", + "tableTo": "ediscovery_cases", + "columnsFrom": ["case_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.legal_holds": { + "name": "legal_holds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custodian_id": { + "name": "custodian_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "hold_criteria": { + "name": "hold_criteria", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied_by_identifier": { + "name": "applied_by_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "removed_at": { + "name": "removed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "legal_holds_case_id_ediscovery_cases_id_fk": { + "name": "legal_holds_case_id_ediscovery_cases_id_fk", + "tableFrom": "legal_holds", + "tableTo": "ediscovery_cases", + "columnsFrom": ["case_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "legal_holds_custodian_id_custodians_id_fk": { + "name": "legal_holds_custodian_id_custodians_id_fk", + "tableFrom": "legal_holds", + "tableTo": "custodians", + "columnsFrom": ["custodian_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.retention_policies": { + "name": "retention_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "retention_period_days": { + "name": "retention_period_days", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action_on_expiry": { + "name": "action_on_expiry", + "type": "retention_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "retention_policies_name_unique": { + "name": "retention_policies_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custodians": { + "name": "custodians", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "ingestion_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custodians_email_unique": { + "name": "custodians_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ingestion_sources": { + "name": "ingestion_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "ingestion_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "credentials": { + "name": "credentials", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "ingestion_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending_auth'" + }, + "last_sync_started_at": { + "name": "last_sync_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_finished_at": { + "name": "last_sync_finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_status_message": { + "name": "last_sync_status_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_state": { + "name": "sync_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ingestion_sources_user_id_users_id_fk": { + "name": "ingestion_sources_user_id_users_id_fk", + "tableFrom": "ingestion_sources", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policies": { + "name": "policies", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + }, + "roles_slug_unique": { + "name": "roles_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_roles_user_id_role_id_pk": { + "name": "user_roles_user_id_role_id_pk", + "columns": ["user_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'local'" + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.retention_action": { + "name": "retention_action", + "schema": "public", + "values": ["delete_permanently", "notify_admin"] + }, + "public.ingestion_provider": { + "name": "ingestion_provider", + "schema": "public", + "values": [ + "google_workspace", + "microsoft_365", + "generic_imap", + "pst_import", + "eml_import", + "mbox_import" + ] + }, + "public.ingestion_status": { + "name": "ingestion_status", + "schema": "public", + "values": [ + "active", + "paused", + "error", + "pending_auth", + "syncing", + "importing", + "auth_success", + "imported" + ] + }, + "public.audit_log_action": { + "name": "audit_log_action", + "schema": "public", + "values": [ + "CREATE", + "READ", + "UPDATE", + "DELETE", + "LOGIN", + "LOGOUT", + "SETUP", + "IMPORT", + "PAUSE", + "SYNC", + "UPLOAD", + "SEARCH", + "DOWNLOAD", + "GENERATE" + ] + }, + "public.audit_log_target_type": { + "name": "audit_log_target_type", + "schema": "public", + "values": [ + "ApiKey", + "ArchivedEmail", + "Dashboard", + "IngestionSource", + "Role", + "SystemSettings", + "User", + "File" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/backend/src/database/migrations/meta/0023_snapshot.json b/packages/backend/src/database/migrations/meta/0023_snapshot.json index 724edd9..2b760be 100644 --- a/packages/backend/src/database/migrations/meta/0023_snapshot.json +++ b/packages/backend/src/database/migrations/meta/0023_snapshot.json @@ -1,1326 +1,1257 @@ { - "id": "2747b009-4502-4e19-a725-1c5e9807c52b", - "prevId": "d61a882f-72c4-4fbb-8a6e-6c9dc8372679", - "version": "7", - "dialect": "postgresql", - "tables": { - "public.archived_emails": { - "name": "archived_emails", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "thread_id": { - "name": "thread_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "ingestion_source_id": { - "name": "ingestion_source_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "user_email": { - "name": "user_email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "message_id_header": { - "name": "message_id_header", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sent_at": { - "name": "sent_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "subject": { - "name": "subject", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sender_name": { - "name": "sender_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sender_email": { - "name": "sender_email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "recipients": { - "name": "recipients", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "storage_path": { - "name": "storage_path", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "storage_hash_sha256": { - "name": "storage_hash_sha256", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "size_bytes": { - "name": "size_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "is_indexed": { - "name": "is_indexed", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "has_attachments": { - "name": "has_attachments", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "is_on_legal_hold": { - "name": "is_on_legal_hold", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": false - }, - "archived_at": { - "name": "archived_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "path": { - "name": "path", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "tags": { - "name": "tags", - "type": "jsonb", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "thread_id_idx": { - "name": "thread_id_idx", - "columns": [ - { - "expression": "thread_id", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "archived_emails_ingestion_source_id_ingestion_sources_id_fk": { - "name": "archived_emails_ingestion_source_id_ingestion_sources_id_fk", - "tableFrom": "archived_emails", - "tableTo": "ingestion_sources", - "columnsFrom": [ - "ingestion_source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.attachments": { - "name": "attachments", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "filename": { - "name": "filename", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "mime_type": { - "name": "mime_type", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "size_bytes": { - "name": "size_bytes", - "type": "bigint", - "primaryKey": false, - "notNull": true - }, - "content_hash_sha256": { - "name": "content_hash_sha256", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "storage_path": { - "name": "storage_path", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "ingestion_source_id": { - "name": "ingestion_source_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - } - }, - "indexes": { - "source_hash_idx": { - "name": "source_hash_idx", - "columns": [ - { - "expression": "ingestion_source_id", - "isExpression": false, - "asc": true, - "nulls": "last" - }, - { - "expression": "content_hash_sha256", - "isExpression": false, - "asc": true, - "nulls": "last" - } - ], - "isUnique": false, - "concurrently": false, - "method": "btree", - "with": {} - } - }, - "foreignKeys": { - "attachments_ingestion_source_id_ingestion_sources_id_fk": { - "name": "attachments_ingestion_source_id_ingestion_sources_id_fk", - "tableFrom": "attachments", - "tableTo": "ingestion_sources", - "columnsFrom": [ - "ingestion_source_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.email_attachments": { - "name": "email_attachments", - "schema": "", - "columns": { - "email_id": { - "name": "email_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "attachment_id": { - "name": "attachment_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "email_attachments_email_id_archived_emails_id_fk": { - "name": "email_attachments_email_id_archived_emails_id_fk", - "tableFrom": "email_attachments", - "tableTo": "archived_emails", - "columnsFrom": [ - "email_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "email_attachments_attachment_id_attachments_id_fk": { - "name": "email_attachments_attachment_id_attachments_id_fk", - "tableFrom": "email_attachments", - "tableTo": "attachments", - "columnsFrom": [ - "attachment_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "restrict", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "email_attachments_email_id_attachment_id_pk": { - "name": "email_attachments_email_id_attachment_id_pk", - "columns": [ - "email_id", - "attachment_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.audit_logs": { - "name": "audit_logs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "bigserial", - "primaryKey": true, - "notNull": true - }, - "previous_hash": { - "name": "previous_hash", - "type": "varchar(64)", - "primaryKey": false, - "notNull": false - }, - "timestamp": { - "name": "timestamp", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "actor_identifier": { - "name": "actor_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "actor_ip": { - "name": "actor_ip", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "action_type": { - "name": "action_type", - "type": "audit_log_action", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "target_type": { - "name": "target_type", - "type": "audit_log_target_type", - "typeSchema": "public", - "primaryKey": false, - "notNull": false - }, - "target_id": { - "name": "target_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "details": { - "name": "details", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "current_hash": { - "name": "current_hash", - "type": "varchar(64)", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ediscovery_cases": { - "name": "ediscovery_cases", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'open'" - }, - "created_by_identifier": { - "name": "created_by_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "ediscovery_cases_name_unique": { - "name": "ediscovery_cases_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.export_jobs": { - "name": "export_jobs", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "case_id": { - "name": "case_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "format": { - "name": "format", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "status": { - "name": "status", - "type": "text", - "primaryKey": false, - "notNull": true, - "default": "'pending'" - }, - "query": { - "name": "query", - "type": "jsonb", - "primaryKey": false, - "notNull": true - }, - "file_path": { - "name": "file_path", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_by_identifier": { - "name": "created_by_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "completed_at": { - "name": "completed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "export_jobs_case_id_ediscovery_cases_id_fk": { - "name": "export_jobs_case_id_ediscovery_cases_id_fk", - "tableFrom": "export_jobs", - "tableTo": "ediscovery_cases", - "columnsFrom": [ - "case_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "set null", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.legal_holds": { - "name": "legal_holds", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "case_id": { - "name": "case_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "custodian_id": { - "name": "custodian_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "hold_criteria": { - "name": "hold_criteria", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "reason": { - "name": "reason", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "applied_by_identifier": { - "name": "applied_by_identifier", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "applied_at": { - "name": "applied_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "removed_at": { - "name": "removed_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - } - }, - "indexes": {}, - "foreignKeys": { - "legal_holds_case_id_ediscovery_cases_id_fk": { - "name": "legal_holds_case_id_ediscovery_cases_id_fk", - "tableFrom": "legal_holds", - "tableTo": "ediscovery_cases", - "columnsFrom": [ - "case_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "legal_holds_custodian_id_custodians_id_fk": { - "name": "legal_holds_custodian_id_custodians_id_fk", - "tableFrom": "legal_holds", - "tableTo": "custodians", - "columnsFrom": [ - "custodian_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.retention_policies": { - "name": "retention_policies", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "description": { - "name": "description", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "priority": { - "name": "priority", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "retention_period_days": { - "name": "retention_period_days", - "type": "integer", - "primaryKey": false, - "notNull": true - }, - "action_on_expiry": { - "name": "action_on_expiry", - "type": "retention_action", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "is_enabled": { - "name": "is_enabled", - "type": "boolean", - "primaryKey": false, - "notNull": true, - "default": true - }, - "conditions": { - "name": "conditions", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "retention_policies_name_unique": { - "name": "retention_policies_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.custodians": { - "name": "custodians", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "display_name": { - "name": "display_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "source_type": { - "name": "source_type", - "type": "ingestion_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "custodians_email_unique": { - "name": "custodians_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.ingestion_sources": { - "name": "ingestion_sources", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": false - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "provider": { - "name": "provider", - "type": "ingestion_provider", - "typeSchema": "public", - "primaryKey": false, - "notNull": true - }, - "credentials": { - "name": "credentials", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "status": { - "name": "status", - "type": "ingestion_status", - "typeSchema": "public", - "primaryKey": false, - "notNull": true, - "default": "'pending_auth'" - }, - "last_sync_started_at": { - "name": "last_sync_started_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_sync_finished_at": { - "name": "last_sync_finished_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": false - }, - "last_sync_status_message": { - "name": "last_sync_status_message", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "sync_state": { - "name": "sync_state", - "type": "jsonb", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "ingestion_sources_user_id_users_id_fk": { - "name": "ingestion_sources_user_id_users_id_fk", - "tableFrom": "ingestion_sources", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.roles": { - "name": "roles", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "policies": { - "name": "policies", - "type": "jsonb", - "primaryKey": false, - "notNull": true, - "default": "'[]'::jsonb" - }, - "slug": { - "name": "slug", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "roles_name_unique": { - "name": "roles_name_unique", - "nullsNotDistinct": false, - "columns": [ - "name" - ] - }, - "roles_slug_unique": { - "name": "roles_slug_unique", - "nullsNotDistinct": false, - "columns": [ - "slug" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.sessions": { - "name": "sessions", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "text", - "primaryKey": true, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "sessions_user_id_users_id_fk": { - "name": "sessions_user_id_users_id_fk", - "tableFrom": "sessions", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.user_roles": { - "name": "user_roles", - "schema": "", - "columns": { - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "role_id": { - "name": "role_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": { - "user_roles_user_id_users_id_fk": { - "name": "user_roles_user_id_users_id_fk", - "tableFrom": "user_roles", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - }, - "user_roles_role_id_roles_id_fk": { - "name": "user_roles_role_id_roles_id_fk", - "tableFrom": "user_roles", - "tableTo": "roles", - "columnsFrom": [ - "role_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": { - "user_roles_user_id_role_id_pk": { - "name": "user_roles_user_id_role_id_pk", - "columns": [ - "user_id", - "role_id" - ] - } - }, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.users": { - "name": "users", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "email": { - "name": "email", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "first_name": { - "name": "first_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "last_name": { - "name": "last_name", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "password": { - "name": "password", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "provider": { - "name": "provider", - "type": "text", - "primaryKey": false, - "notNull": false, - "default": "'local'" - }, - "provider_id": { - "name": "provider_id", - "type": "text", - "primaryKey": false, - "notNull": false - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": { - "users_email_unique": { - "name": "users_email_unique", - "nullsNotDistinct": false, - "columns": [ - "email" - ] - } - }, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.system_settings": { - "name": "system_settings", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "serial", - "primaryKey": true, - "notNull": true - }, - "config": { - "name": "config", - "type": "jsonb", - "primaryKey": false, - "notNull": true - } - }, - "indexes": {}, - "foreignKeys": {}, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - }, - "public.api_keys": { - "name": "api_keys", - "schema": "", - "columns": { - "id": { - "name": "id", - "type": "uuid", - "primaryKey": true, - "notNull": true, - "default": "gen_random_uuid()" - }, - "name": { - "name": "name", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "user_id": { - "name": "user_id", - "type": "uuid", - "primaryKey": false, - "notNull": true - }, - "key": { - "name": "key", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "key_hash": { - "name": "key_hash", - "type": "text", - "primaryKey": false, - "notNull": true - }, - "expires_at": { - "name": "expires_at", - "type": "timestamp with time zone", - "primaryKey": false, - "notNull": true - }, - "created_at": { - "name": "created_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - }, - "updated_at": { - "name": "updated_at", - "type": "timestamp", - "primaryKey": false, - "notNull": true, - "default": "now()" - } - }, - "indexes": {}, - "foreignKeys": { - "api_keys_user_id_users_id_fk": { - "name": "api_keys_user_id_users_id_fk", - "tableFrom": "api_keys", - "tableTo": "users", - "columnsFrom": [ - "user_id" - ], - "columnsTo": [ - "id" - ], - "onDelete": "cascade", - "onUpdate": "no action" - } - }, - "compositePrimaryKeys": {}, - "uniqueConstraints": {}, - "policies": {}, - "checkConstraints": {}, - "isRLSEnabled": false - } - }, - "enums": { - "public.retention_action": { - "name": "retention_action", - "schema": "public", - "values": [ - "delete_permanently", - "notify_admin" - ] - }, - "public.ingestion_provider": { - "name": "ingestion_provider", - "schema": "public", - "values": [ - "google_workspace", - "microsoft_365", - "generic_imap", - "pst_import", - "eml_import", - "mbox_import" - ] - }, - "public.ingestion_status": { - "name": "ingestion_status", - "schema": "public", - "values": [ - "active", - "paused", - "error", - "pending_auth", - "syncing", - "importing", - "auth_success", - "imported" - ] - }, - "public.audit_log_action": { - "name": "audit_log_action", - "schema": "public", - "values": [ - "CREATE", - "READ", - "UPDATE", - "DELETE", - "LOGIN", - "LOGOUT", - "SETUP", - "IMPORT", - "PAUSE", - "SYNC", - "UPLOAD", - "SEARCH", - "DOWNLOAD", - "GENERATE" - ] - }, - "public.audit_log_target_type": { - "name": "audit_log_target_type", - "schema": "public", - "values": [ - "ApiKey", - "ArchivedEmail", - "Dashboard", - "IngestionSource", - "Role", - "SystemSettings", - "User", - "File" - ] - } - }, - "schemas": {}, - "sequences": {}, - "roles": {}, - "policies": {}, - "views": {}, - "_meta": { - "columns": {}, - "schemas": {}, - "tables": {} - } -} \ No newline at end of file + "id": "2747b009-4502-4e19-a725-1c5e9807c52b", + "prevId": "d61a882f-72c4-4fbb-8a6e-6c9dc8372679", + "version": "7", + "dialect": "postgresql", + "tables": { + "public.archived_emails": { + "name": "archived_emails", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "thread_id": { + "name": "thread_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "ingestion_source_id": { + "name": "ingestion_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "user_email": { + "name": "user_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "message_id_header": { + "name": "message_id_header", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sent_at": { + "name": "sent_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "subject": { + "name": "subject", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_name": { + "name": "sender_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sender_email": { + "name": "sender_email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "recipients": { + "name": "recipients", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_hash_sha256": { + "name": "storage_hash_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "is_indexed": { + "name": "is_indexed", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "has_attachments": { + "name": "has_attachments", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "is_on_legal_hold": { + "name": "is_on_legal_hold", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": false + }, + "archived_at": { + "name": "archived_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "tags": { + "name": "tags", + "type": "jsonb", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "thread_id_idx": { + "name": "thread_id_idx", + "columns": [ + { + "expression": "thread_id", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "archived_emails_ingestion_source_id_ingestion_sources_id_fk": { + "name": "archived_emails_ingestion_source_id_ingestion_sources_id_fk", + "tableFrom": "archived_emails", + "tableTo": "ingestion_sources", + "columnsFrom": ["ingestion_source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.attachments": { + "name": "attachments", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "filename": { + "name": "filename", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "mime_type": { + "name": "mime_type", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "size_bytes": { + "name": "size_bytes", + "type": "bigint", + "primaryKey": false, + "notNull": true + }, + "content_hash_sha256": { + "name": "content_hash_sha256", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "storage_path": { + "name": "storage_path", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "ingestion_source_id": { + "name": "ingestion_source_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + } + }, + "indexes": { + "source_hash_idx": { + "name": "source_hash_idx", + "columns": [ + { + "expression": "ingestion_source_id", + "isExpression": false, + "asc": true, + "nulls": "last" + }, + { + "expression": "content_hash_sha256", + "isExpression": false, + "asc": true, + "nulls": "last" + } + ], + "isUnique": false, + "concurrently": false, + "method": "btree", + "with": {} + } + }, + "foreignKeys": { + "attachments_ingestion_source_id_ingestion_sources_id_fk": { + "name": "attachments_ingestion_source_id_ingestion_sources_id_fk", + "tableFrom": "attachments", + "tableTo": "ingestion_sources", + "columnsFrom": ["ingestion_source_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.email_attachments": { + "name": "email_attachments", + "schema": "", + "columns": { + "email_id": { + "name": "email_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "attachment_id": { + "name": "attachment_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "email_attachments_email_id_archived_emails_id_fk": { + "name": "email_attachments_email_id_archived_emails_id_fk", + "tableFrom": "email_attachments", + "tableTo": "archived_emails", + "columnsFrom": ["email_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "email_attachments_attachment_id_attachments_id_fk": { + "name": "email_attachments_attachment_id_attachments_id_fk", + "tableFrom": "email_attachments", + "tableTo": "attachments", + "columnsFrom": ["attachment_id"], + "columnsTo": ["id"], + "onDelete": "restrict", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "email_attachments_email_id_attachment_id_pk": { + "name": "email_attachments_email_id_attachment_id_pk", + "columns": ["email_id", "attachment_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.audit_logs": { + "name": "audit_logs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "bigserial", + "primaryKey": true, + "notNull": true + }, + "previous_hash": { + "name": "previous_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": false + }, + "timestamp": { + "name": "timestamp", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "actor_identifier": { + "name": "actor_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "actor_ip": { + "name": "actor_ip", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "action_type": { + "name": "action_type", + "type": "audit_log_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "target_type": { + "name": "target_type", + "type": "audit_log_target_type", + "typeSchema": "public", + "primaryKey": false, + "notNull": false + }, + "target_id": { + "name": "target_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "details": { + "name": "details", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "current_hash": { + "name": "current_hash", + "type": "varchar(64)", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ediscovery_cases": { + "name": "ediscovery_cases", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'open'" + }, + "created_by_identifier": { + "name": "created_by_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "ediscovery_cases_name_unique": { + "name": "ediscovery_cases_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.export_jobs": { + "name": "export_jobs", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "format": { + "name": "format", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "default": "'pending'" + }, + "query": { + "name": "query", + "type": "jsonb", + "primaryKey": false, + "notNull": true + }, + "file_path": { + "name": "file_path", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_by_identifier": { + "name": "created_by_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "completed_at": { + "name": "completed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "export_jobs_case_id_ediscovery_cases_id_fk": { + "name": "export_jobs_case_id_ediscovery_cases_id_fk", + "tableFrom": "export_jobs", + "tableTo": "ediscovery_cases", + "columnsFrom": ["case_id"], + "columnsTo": ["id"], + "onDelete": "set null", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.legal_holds": { + "name": "legal_holds", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "case_id": { + "name": "case_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "custodian_id": { + "name": "custodian_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "hold_criteria": { + "name": "hold_criteria", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "reason": { + "name": "reason", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "applied_by_identifier": { + "name": "applied_by_identifier", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "applied_at": { + "name": "applied_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "removed_at": { + "name": "removed_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + } + }, + "indexes": {}, + "foreignKeys": { + "legal_holds_case_id_ediscovery_cases_id_fk": { + "name": "legal_holds_case_id_ediscovery_cases_id_fk", + "tableFrom": "legal_holds", + "tableTo": "ediscovery_cases", + "columnsFrom": ["case_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "legal_holds_custodian_id_custodians_id_fk": { + "name": "legal_holds_custodian_id_custodians_id_fk", + "tableFrom": "legal_holds", + "tableTo": "custodians", + "columnsFrom": ["custodian_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.retention_policies": { + "name": "retention_policies", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "priority": { + "name": "priority", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "retention_period_days": { + "name": "retention_period_days", + "type": "integer", + "primaryKey": false, + "notNull": true + }, + "action_on_expiry": { + "name": "action_on_expiry", + "type": "retention_action", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "is_enabled": { + "name": "is_enabled", + "type": "boolean", + "primaryKey": false, + "notNull": true, + "default": true + }, + "conditions": { + "name": "conditions", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "retention_policies_name_unique": { + "name": "retention_policies_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.custodians": { + "name": "custodians", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "display_name": { + "name": "display_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "source_type": { + "name": "source_type", + "type": "ingestion_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "custodians_email_unique": { + "name": "custodians_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.ingestion_sources": { + "name": "ingestion_sources", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "provider": { + "name": "provider", + "type": "ingestion_provider", + "typeSchema": "public", + "primaryKey": false, + "notNull": true + }, + "credentials": { + "name": "credentials", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "status": { + "name": "status", + "type": "ingestion_status", + "typeSchema": "public", + "primaryKey": false, + "notNull": true, + "default": "'pending_auth'" + }, + "last_sync_started_at": { + "name": "last_sync_started_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_finished_at": { + "name": "last_sync_finished_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": false + }, + "last_sync_status_message": { + "name": "last_sync_status_message", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "sync_state": { + "name": "sync_state", + "type": "jsonb", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "ingestion_sources_user_id_users_id_fk": { + "name": "ingestion_sources_user_id_users_id_fk", + "tableFrom": "ingestion_sources", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.roles": { + "name": "roles", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "policies": { + "name": "policies", + "type": "jsonb", + "primaryKey": false, + "notNull": true, + "default": "'[]'::jsonb" + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "roles_name_unique": { + "name": "roles_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + }, + "roles_slug_unique": { + "name": "roles_slug_unique", + "nullsNotDistinct": false, + "columns": ["slug"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.sessions": { + "name": "sessions", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "sessions_user_id_users_id_fk": { + "name": "sessions_user_id_users_id_fk", + "tableFrom": "sessions", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.user_roles": { + "name": "user_roles", + "schema": "", + "columns": { + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "role_id": { + "name": "role_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": { + "user_roles_user_id_users_id_fk": { + "name": "user_roles_user_id_users_id_fk", + "tableFrom": "user_roles", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "user_roles_role_id_roles_id_fk": { + "name": "user_roles_role_id_roles_id_fk", + "tableFrom": "user_roles", + "tableTo": "roles", + "columnsFrom": ["role_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": { + "user_roles_user_id_role_id_pk": { + "name": "user_roles_user_id_role_id_pk", + "columns": ["user_id", "role_id"] + } + }, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.users": { + "name": "users", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "first_name": { + "name": "first_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "last_name": { + "name": "last_name", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "password": { + "name": "password", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "provider": { + "name": "provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "default": "'local'" + }, + "provider_id": { + "name": "provider_id", + "type": "text", + "primaryKey": false, + "notNull": false + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": { + "users_email_unique": { + "name": "users_email_unique", + "nullsNotDistinct": false, + "columns": ["email"] + } + }, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.system_settings": { + "name": "system_settings", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "serial", + "primaryKey": true, + "notNull": true + }, + "config": { + "name": "config", + "type": "jsonb", + "primaryKey": false, + "notNull": true + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + }, + "public.api_keys": { + "name": "api_keys", + "schema": "", + "columns": { + "id": { + "name": "id", + "type": "uuid", + "primaryKey": true, + "notNull": true, + "default": "gen_random_uuid()" + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "user_id": { + "name": "user_id", + "type": "uuid", + "primaryKey": false, + "notNull": true + }, + "key": { + "name": "key", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "key_hash": { + "name": "key_hash", + "type": "text", + "primaryKey": false, + "notNull": true + }, + "expires_at": { + "name": "expires_at", + "type": "timestamp with time zone", + "primaryKey": false, + "notNull": true + }, + "created_at": { + "name": "created_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + }, + "updated_at": { + "name": "updated_at", + "type": "timestamp", + "primaryKey": false, + "notNull": true, + "default": "now()" + } + }, + "indexes": {}, + "foreignKeys": { + "api_keys_user_id_users_id_fk": { + "name": "api_keys_user_id_users_id_fk", + "tableFrom": "api_keys", + "tableTo": "users", + "columnsFrom": ["user_id"], + "columnsTo": ["id"], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "policies": {}, + "checkConstraints": {}, + "isRLSEnabled": false + } + }, + "enums": { + "public.retention_action": { + "name": "retention_action", + "schema": "public", + "values": ["delete_permanently", "notify_admin"] + }, + "public.ingestion_provider": { + "name": "ingestion_provider", + "schema": "public", + "values": [ + "google_workspace", + "microsoft_365", + "generic_imap", + "pst_import", + "eml_import", + "mbox_import" + ] + }, + "public.ingestion_status": { + "name": "ingestion_status", + "schema": "public", + "values": [ + "active", + "paused", + "error", + "pending_auth", + "syncing", + "importing", + "auth_success", + "imported" + ] + }, + "public.audit_log_action": { + "name": "audit_log_action", + "schema": "public", + "values": [ + "CREATE", + "READ", + "UPDATE", + "DELETE", + "LOGIN", + "LOGOUT", + "SETUP", + "IMPORT", + "PAUSE", + "SYNC", + "UPLOAD", + "SEARCH", + "DOWNLOAD", + "GENERATE" + ] + }, + "public.audit_log_target_type": { + "name": "audit_log_target_type", + "schema": "public", + "values": [ + "ApiKey", + "ArchivedEmail", + "Dashboard", + "IngestionSource", + "Role", + "SystemSettings", + "User", + "File" + ] + } + }, + "schemas": {}, + "sequences": {}, + "roles": {}, + "policies": {}, + "views": {}, + "_meta": { + "columns": {}, + "schemas": {}, + "tables": {} + } +} diff --git a/packages/backend/src/database/migrations/meta/_journal.json b/packages/backend/src/database/migrations/meta/_journal.json index 921d559..de1ed47 100644 --- a/packages/backend/src/database/migrations/meta/_journal.json +++ b/packages/backend/src/database/migrations/meta/_journal.json @@ -1,174 +1,174 @@ { - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1752225352591, - "tag": "0000_amusing_namora", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1752326803882, - "tag": "0001_odd_night_thrasher", - "breakpoints": true - }, - { - "idx": 2, - "version": "7", - "when": 1752332648392, - "tag": "0002_lethal_quentin_quire", - "breakpoints": true - }, - { - "idx": 3, - "version": "7", - "when": 1752332967084, - "tag": "0003_petite_wrecker", - "breakpoints": true - }, - { - "idx": 4, - "version": "7", - "when": 1752606108876, - "tag": "0004_sleepy_paper_doll", - "breakpoints": true - }, - { - "idx": 5, - "version": "7", - "when": 1752606327253, - "tag": "0005_chunky_sue_storm", - "breakpoints": true - }, - { - "idx": 6, - "version": "7", - "when": 1753112018514, - "tag": "0006_majestic_caretaker", - "breakpoints": true - }, - { - "idx": 7, - "version": "7", - "when": 1753190159356, - "tag": "0007_handy_archangel", - "breakpoints": true - }, - { - "idx": 8, - "version": "7", - "when": 1753370737317, - "tag": "0008_eminent_the_spike", - "breakpoints": true - }, - { - "idx": 9, - "version": "7", - "when": 1754337938241, - "tag": "0009_late_lenny_balinger", - "breakpoints": true - }, - { - "idx": 10, - "version": "7", - "when": 1754420780849, - "tag": "0010_perpetual_lightspeed", - "breakpoints": true - }, - { - "idx": 11, - "version": "7", - "when": 1754422064158, - "tag": "0011_tan_blackheart", - "breakpoints": true - }, - { - "idx": 12, - "version": "7", - "when": 1754476962901, - "tag": "0012_warm_the_stranger", - "breakpoints": true - }, - { - "idx": 13, - "version": "7", - "when": 1754659373517, - "tag": "0013_classy_talkback", - "breakpoints": true - }, - { - "idx": 14, - "version": "7", - "when": 1754831765718, - "tag": "0014_foamy_vapor", - "breakpoints": true - }, - { - "idx": 15, - "version": "7", - "when": 1755443936046, - "tag": "0015_wakeful_norman_osborn", - "breakpoints": true - }, - { - "idx": 16, - "version": "7", - "when": 1755780572342, - "tag": "0016_lonely_mariko_yashida", - "breakpoints": true - }, - { - "idx": 17, - "version": "7", - "when": 1755961566627, - "tag": "0017_tranquil_shooting_star", - "breakpoints": true - }, - { - "idx": 18, - "version": "7", - "when": 1756911118035, - "tag": "0018_flawless_owl", - "breakpoints": true - }, - { - "idx": 19, - "version": "7", - "when": 1756937533843, - "tag": "0019_confused_scream", - "breakpoints": true - }, - { - "idx": 20, - "version": "7", - "when": 1757860242528, - "tag": "0020_panoramic_wolverine", - "breakpoints": true - }, - { - "idx": 21, - "version": "7", - "when": 1759412986134, - "tag": "0021_nosy_veda", - "breakpoints": true - }, - { - "idx": 22, - "version": "7", - "when": 1759701622932, - "tag": "0022_complete_triton", - "breakpoints": true - }, - { - "idx": 23, - "version": "7", - "when": 1760354094610, - "tag": "0023_swift_swordsman", - "breakpoints": true - } - ] -} \ No newline at end of file + "version": "7", + "dialect": "postgresql", + "entries": [ + { + "idx": 0, + "version": "7", + "when": 1752225352591, + "tag": "0000_amusing_namora", + "breakpoints": true + }, + { + "idx": 1, + "version": "7", + "when": 1752326803882, + "tag": "0001_odd_night_thrasher", + "breakpoints": true + }, + { + "idx": 2, + "version": "7", + "when": 1752332648392, + "tag": "0002_lethal_quentin_quire", + "breakpoints": true + }, + { + "idx": 3, + "version": "7", + "when": 1752332967084, + "tag": "0003_petite_wrecker", + "breakpoints": true + }, + { + "idx": 4, + "version": "7", + "when": 1752606108876, + "tag": "0004_sleepy_paper_doll", + "breakpoints": true + }, + { + "idx": 5, + "version": "7", + "when": 1752606327253, + "tag": "0005_chunky_sue_storm", + "breakpoints": true + }, + { + "idx": 6, + "version": "7", + "when": 1753112018514, + "tag": "0006_majestic_caretaker", + "breakpoints": true + }, + { + "idx": 7, + "version": "7", + "when": 1753190159356, + "tag": "0007_handy_archangel", + "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1753370737317, + "tag": "0008_eminent_the_spike", + "breakpoints": true + }, + { + "idx": 9, + "version": "7", + "when": 1754337938241, + "tag": "0009_late_lenny_balinger", + "breakpoints": true + }, + { + "idx": 10, + "version": "7", + "when": 1754420780849, + "tag": "0010_perpetual_lightspeed", + "breakpoints": true + }, + { + "idx": 11, + "version": "7", + "when": 1754422064158, + "tag": "0011_tan_blackheart", + "breakpoints": true + }, + { + "idx": 12, + "version": "7", + "when": 1754476962901, + "tag": "0012_warm_the_stranger", + "breakpoints": true + }, + { + "idx": 13, + "version": "7", + "when": 1754659373517, + "tag": "0013_classy_talkback", + "breakpoints": true + }, + { + "idx": 14, + "version": "7", + "when": 1754831765718, + "tag": "0014_foamy_vapor", + "breakpoints": true + }, + { + "idx": 15, + "version": "7", + "when": 1755443936046, + "tag": "0015_wakeful_norman_osborn", + "breakpoints": true + }, + { + "idx": 16, + "version": "7", + "when": 1755780572342, + "tag": "0016_lonely_mariko_yashida", + "breakpoints": true + }, + { + "idx": 17, + "version": "7", + "when": 1755961566627, + "tag": "0017_tranquil_shooting_star", + "breakpoints": true + }, + { + "idx": 18, + "version": "7", + "when": 1756911118035, + "tag": "0018_flawless_owl", + "breakpoints": true + }, + { + "idx": 19, + "version": "7", + "when": 1756937533843, + "tag": "0019_confused_scream", + "breakpoints": true + }, + { + "idx": 20, + "version": "7", + "when": 1757860242528, + "tag": "0020_panoramic_wolverine", + "breakpoints": true + }, + { + "idx": 21, + "version": "7", + "when": 1759412986134, + "tag": "0021_nosy_veda", + "breakpoints": true + }, + { + "idx": 22, + "version": "7", + "when": 1759701622932, + "tag": "0022_complete_triton", + "breakpoints": true + }, + { + "idx": 23, + "version": "7", + "when": 1760354094610, + "tag": "0023_swift_swordsman", + "breakpoints": true + } + ] +} diff --git a/packages/backend/src/database/schema/attachments.ts b/packages/backend/src/database/schema/attachments.ts index 8d10fbe..a1e938b 100644 --- a/packages/backend/src/database/schema/attachments.ts +++ b/packages/backend/src/database/schema/attachments.ts @@ -16,9 +16,7 @@ export const attachments = pgTable( onDelete: 'cascade', }), }, - (table) => [ - index('source_hash_idx').on(table.ingestionSourceId, table.contentHashSha256), - ] + (table) => [index('source_hash_idx').on(table.ingestionSourceId, table.contentHashSha256)] ); export const emailAttachments = pgTable( diff --git a/packages/backend/src/helpers/deletionGuard.ts b/packages/backend/src/helpers/deletionGuard.ts index 4acec41..996b274 100644 --- a/packages/backend/src/helpers/deletionGuard.ts +++ b/packages/backend/src/helpers/deletionGuard.ts @@ -2,8 +2,8 @@ import { config } from '../config'; import i18next from 'i18next'; export function checkDeletionEnabled() { - if (!config.app.enableDeletion) { - const errorMessage = i18next.t('Deletion is disabled for this instance.'); - throw new Error(errorMessage); - } + if (!config.app.enableDeletion) { + const errorMessage = i18next.t('Deletion is disabled for this instance.'); + throw new Error(errorMessage); + } } diff --git a/packages/backend/src/helpers/textExtractor.ts b/packages/backend/src/helpers/textExtractor.ts index f82c301..07335ae 100644 --- a/packages/backend/src/helpers/textExtractor.ts +++ b/packages/backend/src/helpers/textExtractor.ts @@ -59,14 +59,17 @@ async function extractTextLegacy(buffer: Buffer, mimeType: string): Promise 50 * 1024 * 1024) { // 50MB Limit + if (buffer.length > 50 * 1024 * 1024) { + // 50MB Limit logger.warn('PDF too large for legacy extraction, skipping'); return ''; } return await extractTextFromPdf(buffer); } - if (mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document') { + if ( + mimeType === 'application/vnd.openxmlformats-officedocument.wordprocessingml.document' + ) { const { value } = await mammoth.extractRawText({ buffer }); return value; } @@ -118,7 +121,9 @@ export async function extractText(buffer: Buffer, mimeType: string): Promise maxSize) { - logger.warn(`File too large for text extraction: ${buffer.length} bytes (limit: ${maxSize})`); + logger.warn( + `File too large for text extraction: ${buffer.length} bytes (limit: ${maxSize})` + ); return ''; } @@ -128,12 +133,12 @@ export async function extractText(buffer: Buffer, mimeType: string): Promise) { - const { emails } = job.data; - console.log(`Indexing email batch with ${emails.length} emails`); - await indexingService.indexEmailBatch(emails); + const { emails } = job.data; + console.log(`Indexing email batch with ${emails.length} emails`); + await indexingService.indexEmailBatch(emails); } diff --git a/packages/backend/src/jobs/processors/process-mailbox.processor.ts b/packages/backend/src/jobs/processors/process-mailbox.processor.ts index 9db1598..0d95914 100644 --- a/packages/backend/src/jobs/processors/process-mailbox.processor.ts +++ b/packages/backend/src/jobs/processors/process-mailbox.processor.ts @@ -15,7 +15,6 @@ import { DatabaseService } from '../../services/DatabaseService'; import { config } from '../../config'; import { indexingQueue } from '../queues'; - /** * This processor handles the ingestion of emails for a single user's mailbox. * If an error occurs during processing (e.g., an API failure), diff --git a/packages/backend/src/services/AuditService.ts b/packages/backend/src/services/AuditService.ts index 533f174..b6fc776 100644 --- a/packages/backend/src/services/AuditService.ts +++ b/packages/backend/src/services/AuditService.ts @@ -1,193 +1,199 @@ import { db, Database } from '../database'; import * as schema from '../database/schema'; -import { AuditLogEntry, CreateAuditLogEntry, GetAuditLogsOptions, GetAuditLogsResponse } from '@open-archiver/types'; +import { + AuditLogEntry, + CreateAuditLogEntry, + GetAuditLogsOptions, + GetAuditLogsResponse, +} from '@open-archiver/types'; import { desc, sql, asc, and, gte, lte, eq } from 'drizzle-orm'; import { createHash } from 'crypto'; export class AuditService { - private sanitizeObject(obj: any): any { - if (obj === null || typeof obj !== 'object') { - return obj; - } - if (Array.isArray(obj)) { - return obj.map((item) => this.sanitizeObject(item)); - } - const sanitizedObj: { [key: string]: any } = {}; - for (const key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) { - const value = obj[key]; - sanitizedObj[key] = value === undefined ? null : this.sanitizeObject(value); - } - } - return sanitizedObj; - } + private sanitizeObject(obj: any): any { + if (obj === null || typeof obj !== 'object') { + return obj; + } + if (Array.isArray(obj)) { + return obj.map((item) => this.sanitizeObject(item)); + } + const sanitizedObj: { [key: string]: any } = {}; + for (const key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + const value = obj[key]; + sanitizedObj[key] = value === undefined ? null : this.sanitizeObject(value); + } + } + return sanitizedObj; + } - public async createAuditLog(entry: CreateAuditLogEntry) { - return db.transaction(async (tx) => { - // Lock the table to prevent race conditions - await tx.execute(sql`LOCK TABLE audit_logs IN EXCLUSIVE MODE`); + public async createAuditLog(entry: CreateAuditLogEntry) { + return db.transaction(async (tx) => { + // Lock the table to prevent race conditions + await tx.execute(sql`LOCK TABLE audit_logs IN EXCLUSIVE MODE`); - const sanitizedEntry = this.sanitizeObject(entry); + const sanitizedEntry = this.sanitizeObject(entry); - const previousHash = await this.getLatestHash(tx); - const newEntry = { - ...sanitizedEntry, - previousHash, - timestamp: new Date() - }; - const currentHash = this.calculateHash(newEntry); + const previousHash = await this.getLatestHash(tx); + const newEntry = { + ...sanitizedEntry, + previousHash, + timestamp: new Date(), + }; + const currentHash = this.calculateHash(newEntry); - const finalEntry = { - ...newEntry, - currentHash - }; + const finalEntry = { + ...newEntry, + currentHash, + }; - await tx.insert(schema.auditLogs).values(finalEntry); + await tx.insert(schema.auditLogs).values(finalEntry); - return finalEntry; - }); - } + return finalEntry; + }); + } - private async getLatestHash(tx: Database): Promise { - const [latest] = await tx - .select({ - currentHash: schema.auditLogs.currentHash - }) - .from(schema.auditLogs) - .orderBy(desc(schema.auditLogs.id)) - .limit(1); + private async getLatestHash(tx: Database): Promise { + const [latest] = await tx + .select({ + currentHash: schema.auditLogs.currentHash, + }) + .from(schema.auditLogs) + .orderBy(desc(schema.auditLogs.id)) + .limit(1); - return latest?.currentHash ?? null; - } + return latest?.currentHash ?? null; + } - private calculateHash(entry: any): string { - // Create a canonical object for hashing to ensure consistency in property order and types. - const objectToHash = { - actorIdentifier: entry.actorIdentifier, - actorIp: entry.actorIp ?? null, - actionType: entry.actionType, - targetType: entry.targetType ?? null, - targetId: entry.targetId ?? null, - details: entry.details ?? null, - previousHash: entry.previousHash ?? null, - // Normalize timestamp to milliseconds since epoch to avoid precision issues. - timestamp: new Date(entry.timestamp).getTime() - }; + private calculateHash(entry: any): string { + // Create a canonical object for hashing to ensure consistency in property order and types. + const objectToHash = { + actorIdentifier: entry.actorIdentifier, + actorIp: entry.actorIp ?? null, + actionType: entry.actionType, + targetType: entry.targetType ?? null, + targetId: entry.targetId ?? null, + details: entry.details ?? null, + previousHash: entry.previousHash ?? null, + // Normalize timestamp to milliseconds since epoch to avoid precision issues. + timestamp: new Date(entry.timestamp).getTime(), + }; - const data = this.canonicalStringify(objectToHash); - return createHash('sha256').update(data).digest('hex'); - } + const data = this.canonicalStringify(objectToHash); + return createHash('sha256').update(data).digest('hex'); + } - private canonicalStringify(obj: any): string { - if (obj === undefined) { - return 'null'; - } - if (obj === null || typeof obj !== 'object') { - return JSON.stringify(obj); - } + private canonicalStringify(obj: any): string { + if (obj === undefined) { + return 'null'; + } + if (obj === null || typeof obj !== 'object') { + return JSON.stringify(obj); + } - if (Array.isArray(obj)) { - return `[${obj.map((item) => this.canonicalStringify(item)).join(',')}]`; - } + if (Array.isArray(obj)) { + return `[${obj.map((item) => this.canonicalStringify(item)).join(',')}]`; + } - const keys = Object.keys(obj).sort(); - const pairs = keys.map((key) => { - const value = obj[key]; - return `${JSON.stringify(key)}:${this.canonicalStringify(value)}`; - }); - return `{${pairs.join(',')}}`; - } + const keys = Object.keys(obj).sort(); + const pairs = keys.map((key) => { + const value = obj[key]; + return `${JSON.stringify(key)}:${this.canonicalStringify(value)}`; + }); + return `{${pairs.join(',')}}`; + } - public async getAuditLogs(options: GetAuditLogsOptions = {}): Promise { - const { - page = 1, - limit = 20, - startDate, - endDate, - actor, - actionType, - sort = 'desc' - } = options; + public async getAuditLogs(options: GetAuditLogsOptions = {}): Promise { + const { + page = 1, + limit = 20, + startDate, + endDate, + actor, + actionType, + sort = 'desc', + } = options; - const whereClauses = []; - if (startDate) whereClauses.push(gte(schema.auditLogs.timestamp, startDate)); - if (endDate) whereClauses.push(lte(schema.auditLogs.timestamp, endDate)); - if (actor) whereClauses.push(eq(schema.auditLogs.actorIdentifier, actor)); - if (actionType) whereClauses.push(eq(schema.auditLogs.actionType, actionType)); + const whereClauses = []; + if (startDate) whereClauses.push(gte(schema.auditLogs.timestamp, startDate)); + if (endDate) whereClauses.push(lte(schema.auditLogs.timestamp, endDate)); + if (actor) whereClauses.push(eq(schema.auditLogs.actorIdentifier, actor)); + if (actionType) whereClauses.push(eq(schema.auditLogs.actionType, actionType)); - const where = and(...whereClauses); + const where = and(...whereClauses); - const logs = await db.query.auditLogs.findMany({ - where, - orderBy: [sort === 'asc' ? asc(schema.auditLogs.id) : desc(schema.auditLogs.id)], - limit, - offset: (page - 1) * limit - }); + const logs = await db.query.auditLogs.findMany({ + where, + orderBy: [sort === 'asc' ? asc(schema.auditLogs.id) : desc(schema.auditLogs.id)], + limit, + offset: (page - 1) * limit, + }); - const totalResult = await db - .select({ - count: sql`count(*)` - }) - .from(schema.auditLogs) - .where(where); + const totalResult = await db + .select({ + count: sql`count(*)`, + }) + .from(schema.auditLogs) + .where(where); - const total = totalResult[0].count; + const total = totalResult[0].count; - return { - data: logs as AuditLogEntry[], - meta: { - total, - page, - limit - } - }; - } + return { + data: logs as AuditLogEntry[], + meta: { + total, + page, + limit, + }, + }; + } - public async verifyAuditLog(): Promise<{ ok: boolean; message: string; logId?: number }> { - const chunkSize = 1000; - let offset = 0; - let previousHash: string | null = null; - /** - * TODO: create job for audit log verification, generate audit report (new DB table) - */ - while (true) { - const logs = await db.query.auditLogs.findMany({ - orderBy: [asc(schema.auditLogs.id)], - limit: chunkSize, - offset - }); + public async verifyAuditLog(): Promise<{ ok: boolean; message: string; logId?: number }> { + const chunkSize = 1000; + let offset = 0; + let previousHash: string | null = null; + /** + * TODO: create job for audit log verification, generate audit report (new DB table) + */ + while (true) { + const logs = await db.query.auditLogs.findMany({ + orderBy: [asc(schema.auditLogs.id)], + limit: chunkSize, + offset, + }); - if (logs.length === 0) { - break; - } + if (logs.length === 0) { + break; + } - for (const log of logs) { - if (log.previousHash !== previousHash) { - return { - ok: false, - message: 'Audit log chain is broken!', - logId: log.id - }; - } + for (const log of logs) { + if (log.previousHash !== previousHash) { + return { + ok: false, + message: 'Audit log chain is broken!', + logId: log.id, + }; + } - const calculatedHash = this.calculateHash(log); + const calculatedHash = this.calculateHash(log); - if (log.currentHash !== calculatedHash) { - return { - ok: false, - message: 'Audit log entry is tampered!', - logId: log.id - }; - } - previousHash = log.currentHash; - } + if (log.currentHash !== calculatedHash) { + return { + ok: false, + message: 'Audit log entry is tampered!', + logId: log.id, + }; + } + previousHash = log.currentHash; + } - offset += chunkSize; - } + offset += chunkSize; + } - return { - ok: true, - message: 'Audit log integrity verified successfully. The logs are not tempered with and the log chain is complete.' - }; - } + return { + ok: true, + message: + 'Audit log integrity verified successfully. The logs are not tempered with and the log chain is complete.', + }; + } } diff --git a/packages/backend/src/services/AuthService.ts b/packages/backend/src/services/AuthService.ts index 6e8f327..8a5a04b 100644 --- a/packages/backend/src/services/AuthService.ts +++ b/packages/backend/src/services/AuthService.ts @@ -41,11 +41,7 @@ export class AuthService { .sign(this.#jwtSecret); } - public async login( - email: string, - password: string, - ip: string - ): Promise { + public async login(email: string, password: string, ip: string): Promise { const user = await this.#userService.findByEmail(email); if (!user || !user.password) { diff --git a/packages/backend/src/services/IndexingService.ts b/packages/backend/src/services/IndexingService.ts index 1b4bba8..f5b0bc2 100644 --- a/packages/backend/src/services/IndexingService.ts +++ b/packages/backend/src/services/IndexingService.ts @@ -60,7 +60,6 @@ function sanitizeObject(obj: T): T { return obj; } - export class IndexingService { private dbService: DatabaseService; private searchService: SearchService; @@ -235,9 +234,7 @@ export class IndexingService { /** * @deprecated */ - private async indexByEmail( - pendingEmail: PendingEmail - ): Promise { + private async indexByEmail(pendingEmail: PendingEmail): Promise { const attachments: AttachmentsType = []; if (pendingEmail.email.attachments && pendingEmail.email.attachments.length > 0) { for (const attachment of pendingEmail.email.attachments) { @@ -259,7 +256,6 @@ export class IndexingService { await this.searchService.addDocuments('emails', [document], 'id'); } - /** * Creates a search document from a raw email object and its attachments. */ @@ -478,14 +474,12 @@ export class IndexingService { 'image/heif', ]; - - return extractableTypes.some((type) => mimeType.toLowerCase().includes(type)); } /** - * Ensures all required fields are present in EmailDocument - */ + * Ensures all required fields are present in EmailDocument + */ private ensureEmailDocumentFields(doc: Partial): EmailDocument { return { id: doc.id || 'missing-id', @@ -510,7 +504,10 @@ export class IndexingService { JSON.stringify(doc); return true; } catch (error) { - logger.error({ doc, error: (error as Error).message }, 'Invalid EmailDocument detected'); + logger.error( + { doc, error: (error as Error).message }, + 'Invalid EmailDocument detected' + ); return false; } } diff --git a/packages/backend/src/services/IngestionService.ts b/packages/backend/src/services/IngestionService.ts index 6f01233..e59708a 100644 --- a/packages/backend/src/services/IngestionService.ts +++ b/packages/backend/src/services/IngestionService.ts @@ -186,7 +186,7 @@ export class IngestionService { (key) => key !== 'providerConfig' && originalSource[key as keyof IngestionSource] !== - decryptedSource[key as keyof IngestionSource] + decryptedSource[key as keyof IngestionSource] ); if (changedFields.length > 0) { await this.auditService.createAuditLog({ diff --git a/packages/backend/src/services/IntegrityService.ts b/packages/backend/src/services/IntegrityService.ts index 607e86b..5e8fd30 100644 --- a/packages/backend/src/services/IntegrityService.ts +++ b/packages/backend/src/services/IntegrityService.ts @@ -8,86 +8,86 @@ import type { IntegrityCheckResult } from '@open-archiver/types'; import { streamToBuffer } from '../helpers/streamToBuffer'; export class IntegrityService { - private storageService = new StorageService(); + private storageService = new StorageService(); - public async checkEmailIntegrity(emailId: string): Promise { - const results: IntegrityCheckResult[] = []; + public async checkEmailIntegrity(emailId: string): Promise { + const results: IntegrityCheckResult[] = []; - // 1. Fetch the archived email - const email = await db.query.archivedEmails.findFirst({ - where: eq(archivedEmails.id, emailId), - }); + // 1. Fetch the archived email + const email = await db.query.archivedEmails.findFirst({ + where: eq(archivedEmails.id, emailId), + }); - if (!email) { - throw new Error('Archived email not found'); - } + if (!email) { + throw new Error('Archived email not found'); + } - // 2. Check the email's integrity - const emailStream = await this.storageService.get(email.storagePath); - const emailBuffer = await streamToBuffer(emailStream); - const currentEmailHash = createHash('sha256').update(emailBuffer).digest('hex'); + // 2. Check the email's integrity + const emailStream = await this.storageService.get(email.storagePath); + const emailBuffer = await streamToBuffer(emailStream); + const currentEmailHash = createHash('sha256').update(emailBuffer).digest('hex'); - if (currentEmailHash === email.storageHashSha256) { - results.push({ type: 'email', id: email.id, isValid: true }); - } else { - results.push({ - type: 'email', - id: email.id, - isValid: false, - reason: 'Stored hash does not match current hash.', - }); - } + if (currentEmailHash === email.storageHashSha256) { + results.push({ type: 'email', id: email.id, isValid: true }); + } else { + results.push({ + type: 'email', + id: email.id, + isValid: false, + reason: 'Stored hash does not match current hash.', + }); + } - // 3. If the email has attachments, check them - if (email.hasAttachments) { - const emailAttachmentsRelations = await db.query.emailAttachments.findMany({ - where: eq(emailAttachments.emailId, emailId), - with: { - attachment: true, - }, - }); + // 3. If the email has attachments, check them + if (email.hasAttachments) { + const emailAttachmentsRelations = await db.query.emailAttachments.findMany({ + where: eq(emailAttachments.emailId, emailId), + with: { + attachment: true, + }, + }); - for (const relation of emailAttachmentsRelations) { - const attachment = relation.attachment; - try { - const attachmentStream = await this.storageService.get(attachment.storagePath); - const attachmentBuffer = await streamToBuffer(attachmentStream); - const currentAttachmentHash = createHash('sha256') - .update(attachmentBuffer) - .digest('hex'); + for (const relation of emailAttachmentsRelations) { + const attachment = relation.attachment; + try { + const attachmentStream = await this.storageService.get(attachment.storagePath); + const attachmentBuffer = await streamToBuffer(attachmentStream); + const currentAttachmentHash = createHash('sha256') + .update(attachmentBuffer) + .digest('hex'); - if (currentAttachmentHash === attachment.contentHashSha256) { - results.push({ - type: 'attachment', - id: attachment.id, - filename: attachment.filename, - isValid: true, - }); - } else { - results.push({ - type: 'attachment', - id: attachment.id, - filename: attachment.filename, - isValid: false, - reason: 'Stored hash does not match current hash.', - }); - } - } catch (error) { - logger.error( - { attachmentId: attachment.id, error }, - 'Failed to read attachment from storage for integrity check.' - ); - results.push({ - type: 'attachment', - id: attachment.id, - filename: attachment.filename, - isValid: false, - reason: 'Could not read attachment file from storage.', - }); - } - } - } + if (currentAttachmentHash === attachment.contentHashSha256) { + results.push({ + type: 'attachment', + id: attachment.id, + filename: attachment.filename, + isValid: true, + }); + } else { + results.push({ + type: 'attachment', + id: attachment.id, + filename: attachment.filename, + isValid: false, + reason: 'Stored hash does not match current hash.', + }); + } + } catch (error) { + logger.error( + { attachmentId: attachment.id, error }, + 'Failed to read attachment from storage for integrity check.' + ); + results.push({ + type: 'attachment', + id: attachment.id, + filename: attachment.filename, + isValid: false, + reason: 'Could not read attachment file from storage.', + }); + } + } + } - return results; - } + return results; + } } diff --git a/packages/backend/src/services/JobsService.ts b/packages/backend/src/services/JobsService.ts index 19d8eab..93cdd84 100644 --- a/packages/backend/src/services/JobsService.ts +++ b/packages/backend/src/services/JobsService.ts @@ -1,107 +1,101 @@ import { Job, Queue } from 'bullmq'; import { ingestionQueue, indexingQueue } from '../jobs/queues'; -import { - IJob, - IQueueCounts, - IQueueDetails, - IQueueOverview, - JobStatus, -} from '@open-archiver/types'; +import { IJob, IQueueCounts, IQueueDetails, IQueueOverview, JobStatus } from '@open-archiver/types'; export class JobsService { - private queues: Queue[]; + private queues: Queue[]; - constructor() { - this.queues = [ingestionQueue, indexingQueue]; - } + constructor() { + this.queues = [ingestionQueue, indexingQueue]; + } - public async getQueues(): Promise { - const queueOverviews: IQueueOverview[] = []; - for (const queue of this.queues) { - const counts = await queue.getJobCounts( - 'active', - 'completed', - 'failed', - 'delayed', - 'waiting', - 'paused' - ); - queueOverviews.push({ - name: queue.name, - counts: { - active: counts.active || 0, - completed: counts.completed || 0, - failed: counts.failed || 0, - delayed: counts.delayed || 0, - waiting: counts.waiting || 0, - paused: counts.paused || 0, - }, - }); - } - return queueOverviews; - } + public async getQueues(): Promise { + const queueOverviews: IQueueOverview[] = []; + for (const queue of this.queues) { + const counts = await queue.getJobCounts( + 'active', + 'completed', + 'failed', + 'delayed', + 'waiting', + 'paused' + ); + queueOverviews.push({ + name: queue.name, + counts: { + active: counts.active || 0, + completed: counts.completed || 0, + failed: counts.failed || 0, + delayed: counts.delayed || 0, + waiting: counts.waiting || 0, + paused: counts.paused || 0, + }, + }); + } + return queueOverviews; + } - public async getQueueDetails( - queueName: string, - status: JobStatus, - page: number, - limit: number - ): Promise { - const queue = this.queues.find((q) => q.name === queueName); - if (!queue) { - throw new Error(`Queue ${queueName} not found`); - } + public async getQueueDetails( + queueName: string, + status: JobStatus, + page: number, + limit: number + ): Promise { + const queue = this.queues.find((q) => q.name === queueName); + if (!queue) { + throw new Error(`Queue ${queueName} not found`); + } - const counts = await queue.getJobCounts( - 'active', - 'completed', - 'failed', - 'delayed', - 'waiting', - 'paused' - ); - const start = (page - 1) * limit; - const end = start + limit - 1; - const jobStatus = status === 'waiting' ? 'wait' : status; - const jobs = await queue.getJobs([jobStatus], start, end, true); - const totalJobs = await queue.getJobCountByTypes(jobStatus); + const counts = await queue.getJobCounts( + 'active', + 'completed', + 'failed', + 'delayed', + 'waiting', + 'paused' + ); + const start = (page - 1) * limit; + const end = start + limit - 1; + const jobStatus = status === 'waiting' ? 'wait' : status; + const jobs = await queue.getJobs([jobStatus], start, end, true); + const totalJobs = await queue.getJobCountByTypes(jobStatus); - return { - name: queue.name, - counts: { - active: counts.active || 0, - completed: counts.completed || 0, - failed: counts.failed || 0, - delayed: counts.delayed || 0, - waiting: counts.waiting || 0, - paused: counts.paused || 0, - }, - jobs: await Promise.all(jobs.map((job) => this.formatJob(job))), - pagination: { - currentPage: page, - totalPages: Math.ceil(totalJobs / limit), - totalJobs, - limit, - }, - }; - } + return { + name: queue.name, + counts: { + active: counts.active || 0, + completed: counts.completed || 0, + failed: counts.failed || 0, + delayed: counts.delayed || 0, + waiting: counts.waiting || 0, + paused: counts.paused || 0, + }, + jobs: await Promise.all(jobs.map((job) => this.formatJob(job))), + pagination: { + currentPage: page, + totalPages: Math.ceil(totalJobs / limit), + totalJobs, + limit, + }, + }; + } - private async formatJob(job: Job): Promise { - const state = await job.getState(); - return { - id: job.id, - name: job.name, - data: job.data, - state: state, - failedReason: job.failedReason, - timestamp: job.timestamp, - processedOn: job.processedOn, - finishedOn: job.finishedOn, - attemptsMade: job.attemptsMade, - stacktrace: job.stacktrace, - returnValue: job.returnvalue, - ingestionSourceId: job.data.ingestionSourceId, - error: state === 'failed' ? job.stacktrace : undefined, - }; - } + private async formatJob(job: Job): Promise { + const state = await job.getState(); + return { + id: job.id, + name: job.name, + data: job.data, + state: state, + failedReason: job.failedReason, + timestamp: job.timestamp, + processedOn: job.processedOn, + finishedOn: job.finishedOn, + attemptsMade: job.attemptsMade, + stacktrace: job.stacktrace, + returnValue: job.returnvalue, + ingestionSourceId: job.data.ingestionSourceId, + error: state === 'failed' ? job.stacktrace : undefined, + }; + } } diff --git a/packages/backend/src/services/OcrService.ts b/packages/backend/src/services/OcrService.ts index 1aceca7..4945e2b 100644 --- a/packages/backend/src/services/OcrService.ts +++ b/packages/backend/src/services/OcrService.ts @@ -3,269 +3,270 @@ import { logger } from '../config/logger'; // Simple LRU cache for Tika results with statistics class TikaCache { - private cache = new Map(); - private maxSize = 50; - private hits = 0; - private misses = 0; + private cache = new Map(); + private maxSize = 50; + private hits = 0; + private misses = 0; - get(key: string): string | undefined { - const value = this.cache.get(key); - if (value !== undefined) { - this.hits++; - // LRU: Move element to the end - this.cache.delete(key); - this.cache.set(key, value); - } else { - this.misses++; - } - return value; - } + get(key: string): string | undefined { + const value = this.cache.get(key); + if (value !== undefined) { + this.hits++; + // LRU: Move element to the end + this.cache.delete(key); + this.cache.set(key, value); + } else { + this.misses++; + } + return value; + } - set(key: string, value: string): void { - // If already exists, delete first - if (this.cache.has(key)) { - this.cache.delete(key); - } - // If cache is full, remove oldest element - else if (this.cache.size >= this.maxSize) { - const firstKey = this.cache.keys().next().value; - if (firstKey !== undefined) { - this.cache.delete(firstKey); - } - } + set(key: string, value: string): void { + // If already exists, delete first + if (this.cache.has(key)) { + this.cache.delete(key); + } + // If cache is full, remove oldest element + else if (this.cache.size >= this.maxSize) { + const firstKey = this.cache.keys().next().value; + if (firstKey !== undefined) { + this.cache.delete(firstKey); + } + } - this.cache.set(key, value); - } + this.cache.set(key, value); + } - getStats(): { size: number; maxSize: number; hits: number; misses: number; hitRate: number } { - const total = this.hits + this.misses; - const hitRate = total > 0 ? (this.hits / total) * 100 : 0; - return { - size: this.cache.size, - maxSize: this.maxSize, - hits: this.hits, - misses: this.misses, - hitRate: Math.round(hitRate * 100) / 100 // 2 decimal places - }; - } + getStats(): { size: number; maxSize: number; hits: number; misses: number; hitRate: number } { + const total = this.hits + this.misses; + const hitRate = total > 0 ? (this.hits / total) * 100 : 0; + return { + size: this.cache.size, + maxSize: this.maxSize, + hits: this.hits, + misses: this.misses, + hitRate: Math.round(hitRate * 100) / 100, // 2 decimal places + }; + } - reset(): void { - this.cache.clear(); - this.hits = 0; - this.misses = 0; - } + reset(): void { + this.cache.clear(); + this.hits = 0; + this.misses = 0; + } } // Semaphore for running Tika requests class TikaSemaphore { - private inProgress = new Map>(); - private waitCount = 0; + private inProgress = new Map>(); + private waitCount = 0; - async acquire(key: string, operation: () => Promise): Promise { - // Check if a request for this key is already running - const existingPromise = this.inProgress.get(key); - if (existingPromise) { - this.waitCount++; - logger.debug(`Waiting for in-progress Tika request (${key.slice(0, 8)}...)`); - try { - return await existingPromise; - } finally { - this.waitCount--; - } - } + async acquire(key: string, operation: () => Promise): Promise { + // Check if a request for this key is already running + const existingPromise = this.inProgress.get(key); + if (existingPromise) { + this.waitCount++; + logger.debug(`Waiting for in-progress Tika request (${key.slice(0, 8)}...)`); + try { + return await existingPromise; + } finally { + this.waitCount--; + } + } - // Start new request - const promise = this.executeOperation(key, operation); - this.inProgress.set(key, promise); + // Start new request + const promise = this.executeOperation(key, operation); + this.inProgress.set(key, promise); - try { - return await promise; - } finally { - // Remove promise from map when finished - this.inProgress.delete(key); - } - } + try { + return await promise; + } finally { + // Remove promise from map when finished + this.inProgress.delete(key); + } + } - private async executeOperation(key: string, operation: () => Promise): Promise { - try { - return await operation(); - } catch (error) { - // Remove promise from map even on errors - logger.error(`Tika operation failed for key ${key.slice(0, 8)}...`, error); - throw error; - } - } + private async executeOperation(key: string, operation: () => Promise): Promise { + try { + return await operation(); + } catch (error) { + // Remove promise from map even on errors + logger.error(`Tika operation failed for key ${key.slice(0, 8)}...`, error); + throw error; + } + } - getStats(): { inProgress: number; waitCount: number } { - return { - inProgress: this.inProgress.size, - waitCount: this.waitCount - }; - } + getStats(): { inProgress: number; waitCount: number } { + return { + inProgress: this.inProgress.size, + waitCount: this.waitCount, + }; + } - clear(): void { - this.inProgress.clear(); - this.waitCount = 0; - } + clear(): void { + this.inProgress.clear(); + this.waitCount = 0; + } } export class OcrService { - private tikaCache = new TikaCache(); - private tikaSemaphore = new TikaSemaphore(); + private tikaCache = new TikaCache(); + private tikaSemaphore = new TikaSemaphore(); - // Tika-based text extraction with cache and semaphore - async extractTextWithTika(buffer: Buffer, mimeType: string): Promise { - const tikaUrl = process.env.TIKA_URL; - if (!tikaUrl) { - throw new Error('TIKA_URL environment variable not set'); - } + // Tika-based text extraction with cache and semaphore + async extractTextWithTika(buffer: Buffer, mimeType: string): Promise { + const tikaUrl = process.env.TIKA_URL; + if (!tikaUrl) { + throw new Error('TIKA_URL environment variable not set'); + } - // Cache key: SHA-256 hash of the buffer - const hash = crypto.createHash('sha256').update(buffer).digest('hex'); + // Cache key: SHA-256 hash of the buffer + const hash = crypto.createHash('sha256').update(buffer).digest('hex'); - // Cache lookup (before semaphore!) - const cachedResult = this.tikaCache.get(hash); - if (cachedResult !== undefined) { - logger.debug(`Tika cache hit for ${mimeType} (${buffer.length} bytes)`); - return cachedResult; - } + // Cache lookup (before semaphore!) + const cachedResult = this.tikaCache.get(hash); + if (cachedResult !== undefined) { + logger.debug(`Tika cache hit for ${mimeType} (${buffer.length} bytes)`); + return cachedResult; + } - // Use semaphore to deduplicate parallel requests - return await this.tikaSemaphore.acquire(hash, async () => { - // Check cache again (might have been filled by parallel request) - const cachedAfterWait = this.tikaCache.get(hash); - if (cachedAfterWait !== undefined) { - logger.debug(`Tika cache hit after wait for ${mimeType} (${buffer.length} bytes)`); - return cachedAfterWait; - } + // Use semaphore to deduplicate parallel requests + return await this.tikaSemaphore.acquire(hash, async () => { + // Check cache again (might have been filled by parallel request) + const cachedAfterWait = this.tikaCache.get(hash); + if (cachedAfterWait !== undefined) { + logger.debug(`Tika cache hit after wait for ${mimeType} (${buffer.length} bytes)`); + return cachedAfterWait; + } - logger.debug(`Executing Tika request for ${mimeType} (${buffer.length} bytes)`); + logger.debug(`Executing Tika request for ${mimeType} (${buffer.length} bytes)`); - // DNS fallback: If "tika" hostname, also try localhost - const urlsToTry = [ - `${tikaUrl}/tika`, - // Fallback falls DNS-Problem mit "tika" hostname - ...(tikaUrl.includes('://tika:') - ? [`${tikaUrl.replace('://tika:', '://localhost:')}/tika`] - : []) - ]; + // DNS fallback: If "tika" hostname, also try localhost + const urlsToTry = [ + `${tikaUrl}/tika`, + // Fallback falls DNS-Problem mit "tika" hostname + ...(tikaUrl.includes('://tika:') + ? [`${tikaUrl.replace('://tika:', '://localhost:')}/tika`] + : []), + ]; - for (const url of urlsToTry) { - try { - logger.debug(`Trying Tika URL: ${url}`); - const response = await fetch(url, { - method: 'PUT', - headers: { - 'Content-Type': mimeType || 'application/octet-stream', - Accept: 'text/plain', - Connection: 'close' - }, - body: buffer, - signal: AbortSignal.timeout(180000) - }); + for (const url of urlsToTry) { + try { + logger.debug(`Trying Tika URL: ${url}`); + const response = await fetch(url, { + method: 'PUT', + headers: { + 'Content-Type': mimeType || 'application/octet-stream', + Accept: 'text/plain', + Connection: 'close', + }, + body: buffer, + signal: AbortSignal.timeout(180000), + }); - if (!response.ok) { - logger.warn( - `Tika extraction failed at ${url}: ${response.status} ${response.statusText}` - ); - continue; // Try next URL - } + if (!response.ok) { + logger.warn( + `Tika extraction failed at ${url}: ${response.status} ${response.statusText}` + ); + continue; // Try next URL + } - const text = await response.text(); - const result = text.trim(); + const text = await response.text(); + const result = text.trim(); - // Cache result (also empty strings to avoid repeated attempts) - this.tikaCache.set(hash, result); + // Cache result (also empty strings to avoid repeated attempts) + this.tikaCache.set(hash, result); - const cacheStats = this.tikaCache.getStats(); - const semaphoreStats = this.tikaSemaphore.getStats(); - logger.debug( - `Tika extraction successful - Cache: ${cacheStats.hits}H/${cacheStats.misses}M (${cacheStats.hitRate}%) - Semaphore: ${semaphoreStats.inProgress} active, ${semaphoreStats.waitCount} waiting` - ); + const cacheStats = this.tikaCache.getStats(); + const semaphoreStats = this.tikaSemaphore.getStats(); + logger.debug( + `Tika extraction successful - Cache: ${cacheStats.hits}H/${cacheStats.misses}M (${cacheStats.hitRate}%) - Semaphore: ${semaphoreStats.inProgress} active, ${semaphoreStats.waitCount} waiting` + ); - return result; - } catch (error) { - logger.warn( - `Tika extraction error at ${url}:`, - error instanceof Error ? error.message : 'Unknown error' - ); - // Continue to next URL - } - } + return result; + } catch (error) { + logger.warn( + `Tika extraction error at ${url}:`, + error instanceof Error ? error.message : 'Unknown error' + ); + // Continue to next URL + } + } - // All URLs failed - cache this too (as empty string) - logger.error('All Tika URLs failed'); - this.tikaCache.set(hash, ''); - return ''; - }); - } + // All URLs failed - cache this too (as empty string) + logger.error('All Tika URLs failed'); + this.tikaCache.set(hash, ''); + return ''; + }); + } - // Helper function to check Tika availability - async checkTikaAvailability(): Promise { - const tikaUrl = process.env.TIKA_URL; - if (!tikaUrl) { - return false; - } + // Helper function to check Tika availability + async checkTikaAvailability(): Promise { + const tikaUrl = process.env.TIKA_URL; + if (!tikaUrl) { + return false; + } - try { - const response = await fetch(`${tikaUrl}/version`, { - method: 'GET', - signal: AbortSignal.timeout(5000) // 5 seconds timeout - }); + try { + const response = await fetch(`${tikaUrl}/version`, { + method: 'GET', + signal: AbortSignal.timeout(5000), // 5 seconds timeout + }); - if (response.ok) { - const version = await response.text(); - logger.info(`Tika server available, version: ${version.trim()}`); - return true; - } + if (response.ok) { + const version = await response.text(); + logger.info(`Tika server available, version: ${version.trim()}`); + return true; + } - return false; - } catch (error) { - logger.warn( - 'Tika server not available:', - error instanceof Error ? error.message : 'Unknown error' - ); - return false; - } - } + return false; + } catch (error) { + logger.warn( + 'Tika server not available:', + error instanceof Error ? error.message : 'Unknown error' + ); + return false; + } + } - // Optional: Tika health check on startup - async initializeTextExtractor(): Promise { - const tikaUrl = process.env.TIKA_URL; + // Optional: Tika health check on startup + async initializeTextExtractor(): Promise { + const tikaUrl = process.env.TIKA_URL; - if (tikaUrl) { - const isAvailable = await this.checkTikaAvailability(); - if (!isAvailable) { - logger.error(`Tika server configured but not available at: ${tikaUrl}`); - logger.error('Text extraction will fall back to legacy methods or fail'); - } - } else { - logger.info('Using legacy text extraction methods (pdf2json, mammoth, xlsx)'); - logger.info('Set TIKA_URL environment variable to use Apache Tika for better extraction'); - } - } + if (tikaUrl) { + const isAvailable = await this.checkTikaAvailability(); + if (!isAvailable) { + logger.error(`Tika server configured but not available at: ${tikaUrl}`); + logger.error('Text extraction will fall back to legacy methods or fail'); + } + } else { + logger.info('Using legacy text extraction methods (pdf2json, mammoth, xlsx)'); + logger.info( + 'Set TIKA_URL environment variable to use Apache Tika for better extraction' + ); + } + } - // Get cache statistics - getTikaCacheStats(): { - size: number; - maxSize: number; - hits: number; - misses: number; - hitRate: number; - } { - return this.tikaCache.getStats(); - } + // Get cache statistics + getTikaCacheStats(): { + size: number; + maxSize: number; + hits: number; + misses: number; + hitRate: number; + } { + return this.tikaCache.getStats(); + } - // Get semaphore statistics - getTikaSemaphoreStats(): { inProgress: number; waitCount: number } { - return this.tikaSemaphore.getStats(); - } + // Get semaphore statistics + getTikaSemaphoreStats(): { inProgress: number; waitCount: number } { + return this.tikaSemaphore.getStats(); + } - // Clear cache (e.g. for tests or manual reset) - clearTikaCache(): void { - this.tikaCache.reset(); - this.tikaSemaphore.clear(); - logger.info('Tika cache and semaphore cleared'); - } + // Clear cache (e.g. for tests or manual reset) + clearTikaCache(): void { + this.tikaCache.reset(); + this.tikaSemaphore.clear(); + logger.info('Tika cache and semaphore cleared'); + } } - diff --git a/packages/backend/src/services/SettingsService.ts b/packages/backend/src/services/SettingsService.ts index 4f6b4d7..6682ff6 100644 --- a/packages/backend/src/services/SettingsService.ts +++ b/packages/backend/src/services/SettingsService.ts @@ -44,7 +44,8 @@ export class SettingsService { const changedFields = Object.keys(newConfig).filter( (key) => - currentConfig[key as keyof SystemSettings] !== newConfig[key as keyof SystemSettings] + currentConfig[key as keyof SystemSettings] !== + newConfig[key as keyof SystemSettings] ); if (changedFields.length > 0) { diff --git a/packages/backend/src/services/StorageService.ts b/packages/backend/src/services/StorageService.ts index fe02184..565a41a 100644 --- a/packages/backend/src/services/StorageService.ts +++ b/packages/backend/src/services/StorageService.ts @@ -67,7 +67,9 @@ export class StorageService implements IStorageProvider { async put(path: string, content: Buffer | NodeJS.ReadableStream): Promise { const buffer = - content instanceof Buffer ? content : await streamToBuffer(content as NodeJS.ReadableStream); + content instanceof Buffer + ? content + : await streamToBuffer(content as NodeJS.ReadableStream); const encryptedContent = await this.encrypt(buffer); return this.provider.put(path, encryptedContent); } diff --git a/packages/backend/src/services/ingestion-connectors/ImapConnector.ts b/packages/backend/src/services/ingestion-connectors/ImapConnector.ts index c75fb67..56e8106 100644 --- a/packages/backend/src/services/ingestion-connectors/ImapConnector.ts +++ b/packages/backend/src/services/ingestion-connectors/ImapConnector.ts @@ -8,7 +8,7 @@ import type { import type { IEmailConnector } from '../EmailProviderFactory'; import { ImapFlow } from 'imapflow'; import { simpleParser, ParsedMail, Attachment, AddressObject, Headers } from 'mailparser'; -import { config } from '../../config' +import { config } from '../../config'; import { logger } from '../../config/logger'; import { getThreadId } from './helpers/utils'; @@ -161,18 +161,12 @@ export class ImapConnector implements IEmailConnector { // filter out junk/spam mail emails if (mailbox.specialUse) { const specialUse = mailbox.specialUse.toLowerCase(); - if ( - specialUse === '\\junk' || - specialUse === '\\trash' - ) { + if (specialUse === '\\junk' || specialUse === '\\trash') { return false; } } // Fallback to checking flags - if ( - mailbox.flags.has('\\Trash') || - mailbox.flags.has('\\Junk') - ) { + if (mailbox.flags.has('\\Trash') || mailbox.flags.has('\\Junk')) { return false; } diff --git a/packages/backend/src/services/ingestion-connectors/MboxConnector.ts b/packages/backend/src/services/ingestion-connectors/MboxConnector.ts index b193d03..1af2596 100644 --- a/packages/backend/src/services/ingestion-connectors/MboxConnector.ts +++ b/packages/backend/src/services/ingestion-connectors/MboxConnector.ts @@ -1,9 +1,9 @@ import type { - MboxImportCredentials, - EmailObject, - EmailAddress, - SyncState, - MailboxUser, + MboxImportCredentials, + EmailObject, + EmailAddress, + SyncState, + MailboxUser, } from '@open-archiver/types'; import type { IEmailConnector } from '../EmailProviderFactory'; import { simpleParser, ParsedMail, Attachment, AddressObject } from 'mailparser'; @@ -15,160 +15,160 @@ import { createHash } from 'crypto'; import { streamToBuffer } from '../../helpers/streamToBuffer'; export class MboxConnector implements IEmailConnector { - private storage: StorageService; + private storage: StorageService; - constructor(private credentials: MboxImportCredentials) { - this.storage = new StorageService(); - } + constructor(private credentials: MboxImportCredentials) { + this.storage = new StorageService(); + } - public async testConnection(): Promise { - try { - if (!this.credentials.uploadedFilePath) { - throw Error('Mbox file path not provided.'); - } - if (!this.credentials.uploadedFilePath.includes('.mbox')) { - throw Error('Provided file is not in the MBOX format.'); - } - const fileExist = await this.storage.exists(this.credentials.uploadedFilePath); - if (!fileExist) { - throw Error('Mbox file upload not finished yet, please wait.'); - } + public async testConnection(): Promise { + try { + if (!this.credentials.uploadedFilePath) { + throw Error('Mbox file path not provided.'); + } + if (!this.credentials.uploadedFilePath.includes('.mbox')) { + throw Error('Provided file is not in the MBOX format.'); + } + const fileExist = await this.storage.exists(this.credentials.uploadedFilePath); + if (!fileExist) { + throw Error('Mbox file upload not finished yet, please wait.'); + } - return true; - } catch (error) { - logger.error({ error, credentials: this.credentials }, 'Mbox file validation failed.'); - throw error; - } - } + return true; + } catch (error) { + logger.error({ error, credentials: this.credentials }, 'Mbox file validation failed.'); + throw error; + } + } - public async *listAllUsers(): AsyncGenerator { - const displayName = - this.credentials.uploadedFileName || `mbox-import-${new Date().getTime()}`; - logger.info(`Found potential mailbox: ${displayName}`); - const constructedPrimaryEmail = `${displayName.replace(/ /g, '.').toLowerCase()}@mbox.local`; - yield { - id: constructedPrimaryEmail, - primaryEmail: constructedPrimaryEmail, - displayName: displayName, - }; - } + public async *listAllUsers(): AsyncGenerator { + const displayName = + this.credentials.uploadedFileName || `mbox-import-${new Date().getTime()}`; + logger.info(`Found potential mailbox: ${displayName}`); + const constructedPrimaryEmail = `${displayName.replace(/ /g, '.').toLowerCase()}@mbox.local`; + yield { + id: constructedPrimaryEmail, + primaryEmail: constructedPrimaryEmail, + displayName: displayName, + }; + } - public async *fetchEmails( - userEmail: string, - syncState?: SyncState | null - ): AsyncGenerator { - try { - const fileStream = await this.storage.get(this.credentials.uploadedFilePath); - const fileBuffer = await streamToBuffer(fileStream as Readable); - const mboxContent = fileBuffer.toString('utf-8'); - const emailDelimiter = '\nFrom '; - const emails = mboxContent.split(emailDelimiter); + public async *fetchEmails( + userEmail: string, + syncState?: SyncState | null + ): AsyncGenerator { + try { + const fileStream = await this.storage.get(this.credentials.uploadedFilePath); + const fileBuffer = await streamToBuffer(fileStream as Readable); + const mboxContent = fileBuffer.toString('utf-8'); + const emailDelimiter = '\nFrom '; + const emails = mboxContent.split(emailDelimiter); - // The first split part might be empty or part of the first email's header, so we adjust. - if (emails.length > 0 && !mboxContent.startsWith('From ')) { - emails.shift(); // Adjust if the file doesn't start with "From " - } + // The first split part might be empty or part of the first email's header, so we adjust. + if (emails.length > 0 && !mboxContent.startsWith('From ')) { + emails.shift(); // Adjust if the file doesn't start with "From " + } - logger.info(`Found ${emails.length} potential emails in the mbox file.`); - let emailCount = 0; + logger.info(`Found ${emails.length} potential emails in the mbox file.`); + let emailCount = 0; - for (const email of emails) { - try { - // Re-add the "From " delimiter for the parser, except for the very first email - const emailWithDelimiter = - emailCount > 0 || mboxContent.startsWith('From ') ? `From ${email}` : email; - const emailBuffer = Buffer.from(emailWithDelimiter, 'utf-8'); - const emailObject = await this.parseMessage(emailBuffer, ''); - yield emailObject; - emailCount++; - } catch (error) { - logger.error( - { error, file: this.credentials.uploadedFilePath }, - 'Failed to process a single message from mbox file. Skipping.' - ); - } - } - logger.info(`Finished processing mbox file. Total emails processed: ${emailCount}`); - } finally { - try { - await this.storage.delete(this.credentials.uploadedFilePath); - } catch (error) { - logger.error( - { error, file: this.credentials.uploadedFilePath }, - 'Failed to delete mbox file after processing.' - ); - } - } - } + for (const email of emails) { + try { + // Re-add the "From " delimiter for the parser, except for the very first email + const emailWithDelimiter = + emailCount > 0 || mboxContent.startsWith('From ') ? `From ${email}` : email; + const emailBuffer = Buffer.from(emailWithDelimiter, 'utf-8'); + const emailObject = await this.parseMessage(emailBuffer, ''); + yield emailObject; + emailCount++; + } catch (error) { + logger.error( + { error, file: this.credentials.uploadedFilePath }, + 'Failed to process a single message from mbox file. Skipping.' + ); + } + } + logger.info(`Finished processing mbox file. Total emails processed: ${emailCount}`); + } finally { + try { + await this.storage.delete(this.credentials.uploadedFilePath); + } catch (error) { + logger.error( + { error, file: this.credentials.uploadedFilePath }, + 'Failed to delete mbox file after processing.' + ); + } + } + } - private async parseMessage(emlBuffer: Buffer, path: string): Promise { - const parsedEmail: ParsedMail = await simpleParser(emlBuffer); + private async parseMessage(emlBuffer: Buffer, path: string): Promise { + const parsedEmail: ParsedMail = await simpleParser(emlBuffer); - const attachments = parsedEmail.attachments.map((attachment: Attachment) => ({ - filename: attachment.filename || 'untitled', - contentType: attachment.contentType, - size: attachment.size, - content: attachment.content as Buffer, - })); + const attachments = parsedEmail.attachments.map((attachment: Attachment) => ({ + filename: attachment.filename || 'untitled', + contentType: attachment.contentType, + size: attachment.size, + content: attachment.content as Buffer, + })); - const mapAddresses = ( - addresses: AddressObject | AddressObject[] | undefined - ): EmailAddress[] => { - if (!addresses) return []; - const addressArray = Array.isArray(addresses) ? addresses : [addresses]; - return addressArray.flatMap((a) => - a.value.map((v) => ({ - name: v.name, - address: v.address?.replaceAll(`'`, '') || '', - })) - ); - }; + const mapAddresses = ( + addresses: AddressObject | AddressObject[] | undefined + ): EmailAddress[] => { + if (!addresses) return []; + const addressArray = Array.isArray(addresses) ? addresses : [addresses]; + return addressArray.flatMap((a) => + a.value.map((v) => ({ + name: v.name, + address: v.address?.replaceAll(`'`, '') || '', + })) + ); + }; - const threadId = getThreadId(parsedEmail.headers); - let messageId = parsedEmail.messageId; + const threadId = getThreadId(parsedEmail.headers); + let messageId = parsedEmail.messageId; - if (!messageId) { - messageId = `generated-${createHash('sha256').update(emlBuffer).digest('hex')}`; - } + if (!messageId) { + messageId = `generated-${createHash('sha256').update(emlBuffer).digest('hex')}`; + } - const from = mapAddresses(parsedEmail.from); - if (from.length === 0) { - from.push({ name: 'No Sender', address: 'No Sender' }); - } + const from = mapAddresses(parsedEmail.from); + if (from.length === 0) { + from.push({ name: 'No Sender', address: 'No Sender' }); + } - // Extract folder path from headers. Mbox files don't have a standard folder structure, so we rely on custom headers added by email clients. - // Gmail uses 'X-Gmail-Labels', and other clients like Thunderbird may use 'X-Folder'. - const gmailLabels = parsedEmail.headers.get('x-gmail-labels'); - const folderHeader = parsedEmail.headers.get('x-folder'); - let finalPath = ''; + // Extract folder path from headers. Mbox files don't have a standard folder structure, so we rely on custom headers added by email clients. + // Gmail uses 'X-Gmail-Labels', and other clients like Thunderbird may use 'X-Folder'. + const gmailLabels = parsedEmail.headers.get('x-gmail-labels'); + const folderHeader = parsedEmail.headers.get('x-folder'); + let finalPath = ''; - if (gmailLabels && typeof gmailLabels === 'string') { - // We take the first label as the primary folder. - // Gmail labels can be hierarchical, but we'll simplify to the first label. - finalPath = gmailLabels.split(',')[0]; - } else if (folderHeader && typeof folderHeader === 'string') { - finalPath = folderHeader; - } + if (gmailLabels && typeof gmailLabels === 'string') { + // We take the first label as the primary folder. + // Gmail labels can be hierarchical, but we'll simplify to the first label. + finalPath = gmailLabels.split(',')[0]; + } else if (folderHeader && typeof folderHeader === 'string') { + finalPath = folderHeader; + } - return { - id: messageId, - threadId: threadId, - from, - to: mapAddresses(parsedEmail.to), - cc: mapAddresses(parsedEmail.cc), - bcc: mapAddresses(parsedEmail.bcc), - subject: parsedEmail.subject || '', - body: parsedEmail.text || '', - html: parsedEmail.html || '', - headers: parsedEmail.headers, - attachments, - receivedAt: parsedEmail.date || new Date(), - eml: emlBuffer, - path: finalPath, - }; - } + return { + id: messageId, + threadId: threadId, + from, + to: mapAddresses(parsedEmail.to), + cc: mapAddresses(parsedEmail.cc), + bcc: mapAddresses(parsedEmail.bcc), + subject: parsedEmail.subject || '', + body: parsedEmail.text || '', + html: parsedEmail.html || '', + headers: parsedEmail.headers, + attachments, + receivedAt: parsedEmail.date || new Date(), + eml: emlBuffer, + path: finalPath, + }; + } - public getUpdatedSyncState(): SyncState { - return {}; - } + public getUpdatedSyncState(): SyncState { + return {}; + } } diff --git a/packages/backend/src/services/ingestion-connectors/PSTConnector.ts b/packages/backend/src/services/ingestion-connectors/PSTConnector.ts index 1199b32..405d1b3 100644 --- a/packages/backend/src/services/ingestion-connectors/PSTConnector.ts +++ b/packages/backend/src/services/ingestion-connectors/PSTConnector.ts @@ -281,8 +281,8 @@ export class PSTConnector implements IEmailConnector { emlBuffer ?? Buffer.from(parsedEmail.text || parsedEmail.html || '', 'utf-8') ) .digest('hex')}-${createHash('sha256') - .update(emlBuffer ?? Buffer.from(msg.subject || '', 'utf-8')) - .digest('hex')}-${msg.clientSubmitTime?.getTime()}`; + .update(emlBuffer ?? Buffer.from(msg.subject || '', 'utf-8')) + .digest('hex')}-${msg.clientSubmitTime?.getTime()}`; } return { id: messageId, diff --git a/packages/enterprise/src/index.ts b/packages/enterprise/src/index.ts index 9070921..89c4e35 100644 --- a/packages/enterprise/src/index.ts +++ b/packages/enterprise/src/index.ts @@ -4,7 +4,7 @@ import { auditLogModule } from './modules/audit-log/audit-log.module'; import { licenseModule } from './modules/license/license.module'; export const enterpriseModules: ArchiverModule[] = [ - licenseModule, - retentionPolicyModule, - auditLogModule + licenseModule, + retentionPolicyModule, + auditLogModule, ]; diff --git a/packages/enterprise/src/middleware/featureEnabled.ts b/packages/enterprise/src/middleware/featureEnabled.ts index 40dd498..5ccbf36 100644 --- a/packages/enterprise/src/middleware/featureEnabled.ts +++ b/packages/enterprise/src/middleware/featureEnabled.ts @@ -11,21 +11,24 @@ import { logger } from '@open-archiver/backend'; * @returns An Express middleware function. */ export const featureEnabled = (feature: OpenArchiverFeature) => { - return (req: Request, res: Response, next: NextFunction) => { - try { - if (licenseService.isFeatureEnabled(feature)) { - return next(); - } + return (req: Request, res: Response, next: NextFunction) => { + try { + if (licenseService.isFeatureEnabled(feature)) { + return next(); + } - res.status(403).json({ - error: 'Forbidden', - message: `This feature (${feature}) is not enabled for your current license. Please upgrade your plan to access this feature.`, - }); - } catch (error) { - // In case of an unexpected error during license verification, - // log the error but allow the request to proceed. - logger.error(`🚨 CRITICAL: License check failed for feature "${feature}". Allowing access by default. Error:`, error); - return next(); - } - }; + res.status(403).json({ + error: 'Forbidden', + message: `This feature (${feature}) is not enabled for your current license. Please upgrade your plan to access this feature.`, + }); + } catch (error) { + // In case of an unexpected error during license verification, + // log the error but allow the request to proceed. + logger.error( + `🚨 CRITICAL: License check failed for feature "${feature}". Allowing access by default. Error:`, + error + ); + return next(); + } + }; }; diff --git a/packages/enterprise/src/modules/audit-log/audit-log.controller.ts b/packages/enterprise/src/modules/audit-log/audit-log.controller.ts index b9ae996..f6bbc7a 100644 --- a/packages/enterprise/src/modules/audit-log/audit-log.controller.ts +++ b/packages/enterprise/src/modules/audit-log/audit-log.controller.ts @@ -4,38 +4,40 @@ import { AuditLogActions, AuditLogTargetTypes } from '@open-archiver/types'; import { z } from 'zod'; const getAuditLogsSchema = z.object({ - page: z.coerce.number().min(1).optional(), - limit: z.coerce.number().min(1).max(100).optional(), - startDate: z.coerce.date().optional(), - endDate: z.coerce.date().optional(), - actor: z.string().optional(), - action: z.enum(AuditLogActions).optional(), - targetType: z.enum(AuditLogTargetTypes).optional(), - sort: z.enum(['asc', 'desc']).optional() + page: z.coerce.number().min(1).optional(), + limit: z.coerce.number().min(1).max(100).optional(), + startDate: z.coerce.date().optional(), + endDate: z.coerce.date().optional(), + actor: z.string().optional(), + action: z.enum(AuditLogActions).optional(), + targetType: z.enum(AuditLogTargetTypes).optional(), + sort: z.enum(['asc', 'desc']).optional(), }); export class AuditLogController { - private auditService = new AuditService(); + private auditService = new AuditService(); - public getAuditLogs = async (req: Request, res: Response) => { - try { - const query = getAuditLogsSchema.parse(req.query); - const result = await this.auditService.getAuditLogs(query); - res.status(200).json(result); - } catch (error) { - if (error instanceof z.ZodError) { - return res.status(400).json({ message: 'Invalid query parameters', errors: error.issues }); - } - res.status(500).json({ message: 'Internal server error.' }); - } - }; + public getAuditLogs = async (req: Request, res: Response) => { + try { + const query = getAuditLogsSchema.parse(req.query); + const result = await this.auditService.getAuditLogs(query); + res.status(200).json(result); + } catch (error) { + if (error instanceof z.ZodError) { + return res + .status(400) + .json({ message: 'Invalid query parameters', errors: error.issues }); + } + res.status(500).json({ message: 'Internal server error.' }); + } + }; - public verifyAuditLog = async (req: Request, res: Response) => { - const result = await this.auditService.verifyAuditLog(); - if (result.ok) { - res.status(200).json(result); - } else { - res.status(500).json(result); - } - }; + public verifyAuditLog = async (req: Request, res: Response) => { + const result = await this.auditService.verifyAuditLog(); + if (result.ok) { + res.status(200).json(result); + } else { + res.status(500).json(result); + } + }; } diff --git a/packages/enterprise/src/modules/audit-log/audit-log.module.ts b/packages/enterprise/src/modules/audit-log/audit-log.module.ts index 5197534..4b1f94f 100644 --- a/packages/enterprise/src/modules/audit-log/audit-log.module.ts +++ b/packages/enterprise/src/modules/audit-log/audit-log.module.ts @@ -6,11 +6,11 @@ import { config } from '@open-archiver/backend'; import { OpenArchiverFeature } from '@open-archiver/types'; class AuditLogModule implements ArchiverModule { - name: OpenArchiverFeature = OpenArchiverFeature.AUDIT_LOG; + name: OpenArchiverFeature = OpenArchiverFeature.AUDIT_LOG; - async initialize(app: Express, authService: AuthService): Promise { - app.use(`/${config.api.version}/enterprise/audit-logs`, auditLogRoutes(authService)); - } + async initialize(app: Express, authService: AuthService): Promise { + app.use(`/${config.api.version}/enterprise/audit-logs`, auditLogRoutes(authService)); + } } export const auditLogModule = new AuditLogModule(); diff --git a/packages/enterprise/src/modules/audit-log/audit-log.routes.ts b/packages/enterprise/src/modules/audit-log/audit-log.routes.ts index cc05cf8..d344e6e 100644 --- a/packages/enterprise/src/modules/audit-log/audit-log.routes.ts +++ b/packages/enterprise/src/modules/audit-log/audit-log.routes.ts @@ -5,20 +5,14 @@ import { AuthService } from '@open-archiver/backend'; import { featureEnabled } from '../../middleware/featureEnabled'; import { OpenArchiverFeature } from '@open-archiver/types'; - - export const auditLogRoutes = (authService: AuthService): Router => { - const router = Router(); - const controller = new AuditLogController(); - router.use(requireAuth(authService), featureEnabled(OpenArchiverFeature.AUDIT_LOG)); + const router = Router(); + const controller = new AuditLogController(); + router.use(requireAuth(authService), featureEnabled(OpenArchiverFeature.AUDIT_LOG)); - router.get('/', - requirePermission('manage', 'all'), - controller.getAuditLogs); + router.get('/', requirePermission('manage', 'all'), controller.getAuditLogs); - router.post('/verify', - requirePermission('manage', 'all'), - controller.verifyAuditLog); + router.post('/verify', requirePermission('manage', 'all'), controller.verifyAuditLog); - return router; + return router; }; diff --git a/packages/enterprise/src/modules/license/LicenseReportingService.ts b/packages/enterprise/src/modules/license/LicenseReportingService.ts index a5d689d..fcd7536 100644 --- a/packages/enterprise/src/modules/license/LicenseReportingService.ts +++ b/packages/enterprise/src/modules/license/LicenseReportingService.ts @@ -2,80 +2,76 @@ import * as cron from 'node-cron'; import * as fs from 'fs/promises'; import * as path from 'path'; import { LicenseFilePayload, LicenseStatusPayload } from '@open-archiver/types'; -import { - logger, - db, - drizzleOrm, - archivedEmails -} from '@open-archiver/backend'; +import { logger, db, drizzleOrm, archivedEmails } from '@open-archiver/backend'; // license server is yet to be implemented. const LICENSE_SERVER_URL = 'https://licensing.openarchiver.com/api/v1/ping'; export const CACHE_FILE_PATH = path.join(__dirname, 'license-status.json'); class LicenseReportingService { - private licensePayload: LicenseFilePayload | null = null; + private licensePayload: LicenseFilePayload | null = null; - public start(payload: LicenseFilePayload) { - this.licensePayload = payload; - // Schedule to run once every 24 hours, with a random minute/hour to distribute load. - const cronExpression = `${Math.floor(Math.random() * 60)} ${Math.floor(Math.random() * 5)} * * *`; + public start(payload: LicenseFilePayload) { + this.licensePayload = payload; + // Schedule to run once every 24 hours, with a random minute/hour to distribute load. + const cronExpression = `${Math.floor(Math.random() * 60)} ${Math.floor(Math.random() * 5)} * * *`; - cron.schedule(cronExpression, () => { - this.phoneHome(); - }); + cron.schedule(cronExpression, () => { + this.phoneHome(); + }); - logger.info(`📞 License reporting service scheduled with expression: ${cronExpression}`); - } + logger.info(`📞 License reporting service scheduled with expression: ${cronExpression}`); + } - public async phoneHome() { - if (!this.licensePayload) { - logger.warn('📞 Phone home skipped: License payload not loaded.'); - return; - } + public async phoneHome() { + if (!this.licensePayload) { + logger.warn('📞 Phone home skipped: License payload not loaded.'); + return; + } - try { - // 1. Count Active Seats, the result will be used to send to license server. - const activeSeats = await this.countActiveSeats(); + try { + // 1. Count Active Seats, the result will be used to send to license server. + const activeSeats = await this.countActiveSeats(); - logger.info(`Performing daily license check for ${this.licensePayload.customerName}. Active seats: ${activeSeats}`); + logger.info( + `Performing daily license check for ${this.licensePayload.customerName}. Active seats: ${activeSeats}` + ); - // 2. Phone Home (mocked for now) - // will be replaced by a fetch call to the license server. - const mockedResponse: LicenseStatusPayload = { - status: 'VALID' - }; + // 2. Phone Home (mocked for now) + // will be replaced by a fetch call to the license server. + const mockedResponse: LicenseStatusPayload = { + status: 'VALID', + }; - // 3. Cache Response - await this.cacheLicenseStatus(mockedResponse); + // 3. Cache Response + await this.cacheLicenseStatus(mockedResponse); + } catch (error) { + logger.error('Phone home failed:', error); + // If the request fails, we do nothing and the app continues with the last known status. + } + } - } catch (error) { - logger.error('Phone home failed:', error); - // If the request fails, we do nothing and the app continues with the last known status. - } - } + public async countActiveSeats(): Promise { + try { + const result = await db + .select({ count: drizzleOrm.countDistinct(archivedEmails.userEmail) }) + .from(archivedEmails); - public async countActiveSeats(): Promise { - try { - const result = await db - .select({ count: drizzleOrm.countDistinct(archivedEmails.userEmail) }) - .from(archivedEmails); + return result[0]?.count || 0; + } catch (error) { + logger.error('Failed to count active seats from database:', error); + return 0; // Return 0 if the query fails to avoid breaking the process. + } + } - return result[0]?.count || 0; - } catch (error) { - logger.error('Failed to count active seats from database:', error); - return 0; // Return 0 if the query fails to avoid breaking the process. - } - } - - private async cacheLicenseStatus(status: LicenseStatusPayload) { - try { - await fs.writeFile(CACHE_FILE_PATH, JSON.stringify(status, null, 2)); - logger.info(`License status successfully cached to ${CACHE_FILE_PATH}`); - } catch (error) { - logger.error(`Failed to cache license status to ${CACHE_FILE_PATH}:`, error); - } - } + private async cacheLicenseStatus(status: LicenseStatusPayload) { + try { + await fs.writeFile(CACHE_FILE_PATH, JSON.stringify(status, null, 2)); + logger.info(`License status successfully cached to ${CACHE_FILE_PATH}`); + } catch (error) { + logger.error(`Failed to cache license status to ${CACHE_FILE_PATH}:`, error); + } + } } export const licenseReportingService = new LicenseReportingService(); diff --git a/packages/enterprise/src/modules/license/LicenseService.ts b/packages/enterprise/src/modules/license/LicenseService.ts index 7b9a742..7692067 100644 --- a/packages/enterprise/src/modules/license/LicenseService.ts +++ b/packages/enterprise/src/modules/license/LicenseService.ts @@ -1,7 +1,11 @@ import * as fs from 'fs'; import * as path from 'path'; import * as jwt from 'jsonwebtoken'; -import { LicenseFilePayload, LicenseStatusPayload, OpenArchiverFeature } from '@open-archiver/types'; +import { + LicenseFilePayload, + LicenseStatusPayload, + OpenArchiverFeature, +} from '@open-archiver/types'; import { logger } from '@open-archiver/backend'; import { licenseReportingService } from './LicenseReportingService'; import { CACHE_FILE_PATH } from './LicenseReportingService'; @@ -16,98 +20,102 @@ X0SrmkMxGyEd18vMXBGD9piAR3MTRskQ6XOoEo0fio6s9LtgPzKkJBdyVg== type LicenseStatus = 'VALID' | 'INVALID' | 'EXPIRED' | 'NOT_FOUND'; class LicenseService { - public licensePayload: LicenseFilePayload | null = null; - public licenseStatus: LicenseStatus = 'NOT_FOUND'; - public cachedStatus: LicenseStatusPayload | null = null; + public licensePayload: LicenseFilePayload | null = null; + public licenseStatus: LicenseStatus = 'NOT_FOUND'; + public cachedStatus: LicenseStatusPayload | null = null; - constructor() { - this.loadAndVerifyLicense(); - this.loadCachedStatus(); - } + constructor() { + this.loadAndVerifyLicense(); + this.loadCachedStatus(); + } - private loadAndVerifyLicense() { - try { - const licenseKey = process.env.OA_LICENSE_KEY || fs.readFileSync(path.join(__dirname, 'license.jwt'), 'utf-8'); + private loadAndVerifyLicense() { + try { + const licenseKey = + process.env.OA_LICENSE_KEY || + fs.readFileSync(path.join(__dirname, 'license.jwt'), 'utf-8'); - if (!licenseKey) { - this.licenseStatus = 'NOT_FOUND'; - logger.warn('📄 License key not found.'); - return; - } + if (!licenseKey) { + this.licenseStatus = 'NOT_FOUND'; + logger.warn('📄 License key not found.'); + return; + } - const decoded = jwt.verify(licenseKey, PUBLIC_KEY, { - algorithms: ['ES256'], - }) as LicenseFilePayload; + const decoded = jwt.verify(licenseKey, PUBLIC_KEY, { + algorithms: ['ES256'], + }) as LicenseFilePayload; - this.licensePayload = decoded; - this.licenseStatus = 'VALID'; - logger.info(`Enterprise license successfully verified for: ${this.licensePayload.customerName}`); - // Start the reporting service now that we have a valid license payload - licenseReportingService.start(this.licensePayload); + this.licensePayload = decoded; + this.licenseStatus = 'VALID'; + logger.info( + `Enterprise license successfully verified for: ${this.licensePayload.customerName}` + ); + // Start the reporting service now that we have a valid license payload + licenseReportingService.start(this.licensePayload); + } catch (error) { + if (error instanceof jwt.TokenExpiredError) { + this.licenseStatus = 'EXPIRED'; + logger.error('License verification failed: The license has expired.'); + } else if (error instanceof jwt.JsonWebTokenError) { + this.licenseStatus = 'INVALID'; + logger.error(`License verification failed: ${error.message}`); + } else { + this.licenseStatus = 'INVALID'; + logger.error('An unexpected error occurred during license verification:', error); + } + } + } - } catch (error) { - if (error instanceof jwt.TokenExpiredError) { - this.licenseStatus = 'EXPIRED'; - logger.error('License verification failed: The license has expired.'); - } else if (error instanceof jwt.JsonWebTokenError) { - this.licenseStatus = 'INVALID'; - logger.error(`License verification failed: ${error.message}`); - } else { - this.licenseStatus = 'INVALID'; - logger.error('An unexpected error occurred during license verification:', error); - } - } - } + private async loadCachedStatus() { + try { + if (fs.existsSync(CACHE_FILE_PATH)) { + const data = fs.readFileSync(CACHE_FILE_PATH, 'utf-8'); + this.cachedStatus = JSON.parse(data); + logger.info( + `Successfully loaded cached license status: ${this.cachedStatus?.status}` + ); + } else { + // On a new installation, the cache file won't exist. We default to a valid state + logger.info( + `License status cache not found. Assuming 'VALID' until first phone-home.` + ); + this.cachedStatus = { status: 'VALID' }; + } + } catch (error) { + logger.error(`Failed to load or initialize cached license status:`, error); + // Fallback to a valid status if parsing fails to prevent locking out users. + this.cachedStatus = { status: 'VALID' }; + } + } - private async loadCachedStatus() { - try { - if (fs.existsSync(CACHE_FILE_PATH)) { - const data = fs.readFileSync(CACHE_FILE_PATH, 'utf-8'); - this.cachedStatus = JSON.parse(data); - logger.info(`Successfully loaded cached license status: ${this.cachedStatus?.status}`); - } else { - // On a new installation, the cache file won't exist. We default to a valid state - logger.info(`License status cache not found. Assuming 'VALID' until first phone-home.`); - this.cachedStatus = { status: 'VALID' }; - } - } catch (error) { - logger.error(`Failed to load or initialize cached license status:`, error); - // Fallback to a valid status if parsing fails to prevent locking out users. - this.cachedStatus = { status: 'VALID' }; - } - } + public isFeatureEnabled(feature: OpenArchiverFeature): boolean { + // A license payload must exist to know which features are granted. + if (!this.licensePayload) { + return false; + } - public isFeatureEnabled(feature: OpenArchiverFeature): boolean { - // A license payload must exist to know which features are granted. - if (!this.licensePayload) { - return false; - } + // Check if the license is supposed to grant the feature. + const hasAllFeatures = this.licensePayload.features.includes(OpenArchiverFeature.ALL); + const hasSpecificFeature = this.licensePayload.features.includes(feature); - // Check if the license is supposed to grant the feature. - const hasAllFeatures = this.licensePayload.features.includes(OpenArchiverFeature.ALL); - const hasSpecificFeature = this.licensePayload.features.includes(feature); + if (!hasAllFeatures && !hasSpecificFeature) { + return false; + } - if (!hasAllFeatures && !hasSpecificFeature) { - return false; - } + // Now, check the validity. The server's cached status is the highest source of truth. + if (this.cachedStatus?.status === 'REVOKED') { + if (this.cachedStatus.gracePeriodEnds) { + const gracePeriodEnd = new Date(this.cachedStatus.gracePeriodEnds); + // The grace period is active, so the feature is enabled regardless of local JWT status. + return new Date() < gracePeriodEnd; + } + // Revoked and no grace period. + return false; + } - - // Now, check the validity. The server's cached status is the highest source of truth. - if (this.cachedStatus?.status === 'REVOKED') { - if (this.cachedStatus.gracePeriodEnds) { - const gracePeriodEnd = new Date(this.cachedStatus.gracePeriodEnds); - // The grace period is active, so the feature is enabled regardless of local JWT status. - return new Date() < gracePeriodEnd; - } - // Revoked and no grace period. - return false; - } - - - - // If not revoked by the server, the local license JWT must be valid. - return this.licenseStatus === 'VALID'; - } + // If not revoked by the server, the local license JWT must be valid. + return this.licenseStatus === 'VALID'; + } } export const licenseService = new LicenseService(); diff --git a/packages/enterprise/src/modules/license/license.controller.ts b/packages/enterprise/src/modules/license/license.controller.ts index 482db1f..ddf5e74 100644 --- a/packages/enterprise/src/modules/license/license.controller.ts +++ b/packages/enterprise/src/modules/license/license.controller.ts @@ -4,33 +4,36 @@ import { licenseService } from './LicenseService'; import { licenseReportingService } from './LicenseReportingService'; class LicenseController { - public getLicenseStatus = async (req: Request, res: Response) => { - if (!licenseService.licensePayload) { - return res.status(404).json({ error: 'License information not found.' }); - } + public getLicenseStatus = async (req: Request, res: Response) => { + if (!licenseService.licensePayload) { + return res.status(404).json({ error: 'License information not found.' }); + } - const activeSeats = await licenseReportingService.countActiveSeats() + const activeSeats = await licenseReportingService.countActiveSeats(); - const allPossibleFeatures: OpenArchiverFeature[] = Object.values(OpenArchiverFeature); + const allPossibleFeatures: OpenArchiverFeature[] = Object.values(OpenArchiverFeature); - const features = allPossibleFeatures.reduce((acc, feature) => { - acc[feature] = licenseService.isFeatureEnabled(feature); - return acc; - }, {} as { [key in OpenArchiverFeature]?: boolean }); + const features = allPossibleFeatures.reduce( + (acc, feature) => { + acc[feature] = licenseService.isFeatureEnabled(feature); + return acc; + }, + {} as { [key in OpenArchiverFeature]?: boolean } + ); - const response: ConsolidatedLicenseStatus = { - customerName: licenseService.licensePayload.customerName, - planSeats: licenseService.licensePayload.planSeats, - expiresAt: licenseService.licensePayload.expiresAt, - remoteStatus: licenseService.cachedStatus?.status || 'UNKNOWN', - gracePeriodEnds: licenseService.cachedStatus?.gracePeriodEnds, - activeSeats: activeSeats, - isExpired: new Date(licenseService.licensePayload.expiresAt) < new Date(), - features: features, - }; + const response: ConsolidatedLicenseStatus = { + customerName: licenseService.licensePayload.customerName, + planSeats: licenseService.licensePayload.planSeats, + expiresAt: licenseService.licensePayload.expiresAt, + remoteStatus: licenseService.cachedStatus?.status || 'UNKNOWN', + gracePeriodEnds: licenseService.cachedStatus?.gracePeriodEnds, + activeSeats: activeSeats, + isExpired: new Date(licenseService.licensePayload.expiresAt) < new Date(), + features: features, + }; - res.status(200).json(response); - }; + res.status(200).json(response); + }; } export const licenseController = new LicenseController(); diff --git a/packages/enterprise/src/modules/license/license.module.ts b/packages/enterprise/src/modules/license/license.module.ts index eb39dec..3645888 100644 --- a/packages/enterprise/src/modules/license/license.module.ts +++ b/packages/enterprise/src/modules/license/license.module.ts @@ -4,11 +4,11 @@ import { licenseRoutes } from './license.routes'; import { OpenArchiverFeature } from '@open-archiver/types'; class LicenseModule implements ArchiverModule { - name: OpenArchiverFeature = OpenArchiverFeature.STATUS; + name: OpenArchiverFeature = OpenArchiverFeature.STATUS; - async initialize(app: Express, authService: AuthService): Promise { - app.use(`/${config.api.version}/enterprise/status`, licenseRoutes(authService)); - } + async initialize(app: Express, authService: AuthService): Promise { + app.use(`/${config.api.version}/enterprise/status`, licenseRoutes(authService)); + } } export const licenseModule = new LicenseModule(); diff --git a/packages/enterprise/src/modules/license/license.routes.ts b/packages/enterprise/src/modules/license/license.routes.ts index ba223c0..bb7dd61 100644 --- a/packages/enterprise/src/modules/license/license.routes.ts +++ b/packages/enterprise/src/modules/license/license.routes.ts @@ -3,12 +3,10 @@ import { licenseController } from './license.controller'; import { requireAuth, AuthService } from '@open-archiver/backend'; export const licenseRoutes = (authService: AuthService): Router => { - const router = Router(); - router.use(requireAuth(authService)); + const router = Router(); + router.use(requireAuth(authService)); - router.get( - '/license-status' - , licenseController.getLicenseStatus); + router.get('/license-status', licenseController.getLicenseStatus); - return router; + return router; }; diff --git a/packages/enterprise/src/modules/retention-policy/retention-policy.module.ts b/packages/enterprise/src/modules/retention-policy/retention-policy.module.ts index 0cc18a5..0fcef00 100644 --- a/packages/enterprise/src/modules/retention-policy/retention-policy.module.ts +++ b/packages/enterprise/src/modules/retention-policy/retention-policy.module.ts @@ -4,11 +4,14 @@ import { OpenArchiverFeature } from '@open-archiver/types'; import { retentionPolicyRoutes } from './retention-policy.routes'; class RetentionPolicyModule implements ArchiverModule { - name: OpenArchiverFeature = OpenArchiverFeature.RETENTION_POLICY + name: OpenArchiverFeature = OpenArchiverFeature.RETENTION_POLICY; - async initialize(app: Express, authService: AuthService): Promise { - app.use(`/${config.api.version}/enterprise/retention-policy`, retentionPolicyRoutes(authService)); - } + async initialize(app: Express, authService: AuthService): Promise { + app.use( + `/${config.api.version}/enterprise/retention-policy`, + retentionPolicyRoutes(authService) + ); + } } export const retentionPolicyModule = new RetentionPolicyModule(); diff --git a/packages/enterprise/src/modules/retention-policy/retention-policy.routes.ts b/packages/enterprise/src/modules/retention-policy/retention-policy.routes.ts index 1fef108..76969ac 100644 --- a/packages/enterprise/src/modules/retention-policy/retention-policy.routes.ts +++ b/packages/enterprise/src/modules/retention-policy/retention-policy.routes.ts @@ -4,15 +4,15 @@ import { featureEnabled } from '../../middleware/featureEnabled'; import { OpenArchiverFeature } from '@open-archiver/types'; export const retentionPolicyRoutes = (authService: AuthService): Router => { - const router = Router(); + const router = Router(); - // All routes in this module require authentication and the retention-policy feature - router.use(requireAuth(authService), featureEnabled(OpenArchiverFeature.RETENTION_POLICY)); + // All routes in this module require authentication and the retention-policy feature + router.use(requireAuth(authService), featureEnabled(OpenArchiverFeature.RETENTION_POLICY)); - // demonstrating route - router.get('/', (req, res) => { - res.status(200).json({ message: 'Retention policy feature is enabled.' }); - }); + // demonstrating route + router.get('/', (req, res) => { + res.status(200).json({ message: 'Retention policy feature is enabled.' }); + }); - return router; + return router; }; diff --git a/packages/frontend/src/app.d.ts b/packages/frontend/src/app.d.ts index caa903f..f391d06 100644 --- a/packages/frontend/src/app.d.ts +++ b/packages/frontend/src/app.d.ts @@ -16,4 +16,4 @@ declare global { } } -export { }; +export {}; diff --git a/packages/frontend/src/hooks.server.ts b/packages/frontend/src/hooks.server.ts index aa83c34..b68199b 100644 --- a/packages/frontend/src/hooks.server.ts +++ b/packages/frontend/src/hooks.server.ts @@ -23,9 +23,9 @@ export const handle: Handle = async ({ event, resolve }) => { event.locals.accessToken = null; } if (import.meta.env.VITE_ENTERPRISE_MODE === true) { - event.locals.enterpriseMode = true + event.locals.enterpriseMode = true; } else { - event.locals.enterpriseMode = false + event.locals.enterpriseMode = false; } return resolve(event); diff --git a/packages/frontend/src/routes/api/[...slug]/+server.ts b/packages/frontend/src/routes/api/[...slug]/+server.ts index b03a460..297ff85 100644 --- a/packages/frontend/src/routes/api/[...slug]/+server.ts +++ b/packages/frontend/src/routes/api/[...slug]/+server.ts @@ -22,7 +22,10 @@ const handleRequest: RequestHandler = async ({ request, params, fetch }) => { return response; } catch (error) { console.error('Proxy request failed:', error); - return json({ message: `Failed to connect to the backend service. ${JSON.stringify(error)}` }, { status: 500 }); + return json( + { message: `Failed to connect to the backend service. ${JSON.stringify(error)}` }, + { status: 500 } + ); } }; diff --git a/packages/frontend/src/routes/dashboard/admin/jobs/+page.server.ts b/packages/frontend/src/routes/dashboard/admin/jobs/+page.server.ts index 4b00510..4254646 100644 --- a/packages/frontend/src/routes/dashboard/admin/jobs/+page.server.ts +++ b/packages/frontend/src/routes/dashboard/admin/jobs/+page.server.ts @@ -4,21 +4,24 @@ import type { PageServerLoad } from './$types'; import type { IGetQueuesResponse } from '@open-archiver/types'; export const load: PageServerLoad = async (event) => { - try { - const response = await api('/jobs/queues', event); + try { + const response = await api('/jobs/queues', event); - if (!response.ok) { - const responseText = await response.json(); - throw error(response.status as NumericRange<400, 599>, responseText.message || 'Failed to fetch job queues.'); - } + if (!response.ok) { + const responseText = await response.json(); + throw error( + response.status as NumericRange<400, 599>, + responseText.message || 'Failed to fetch job queues.' + ); + } - const data: IGetQueuesResponse = await response.json(); + const data: IGetQueuesResponse = await response.json(); - return { - queues: data.queues, - }; - } catch (e: any) { - console.error('Failed to load job queues:', e); - throw error(e.status || 500, e.body?.message || 'Failed to load job queues'); - } + return { + queues: data.queues, + }; + } catch (e: any) { + console.error('Failed to load job queues:', e); + throw error(e.status || 500, e.body?.message || 'Failed to load job queues'); + } }; diff --git a/packages/frontend/src/routes/dashboard/admin/jobs/[queueName]/+page.server.ts b/packages/frontend/src/routes/dashboard/admin/jobs/[queueName]/+page.server.ts index bb8ac30..64ed08f 100644 --- a/packages/frontend/src/routes/dashboard/admin/jobs/[queueName]/+page.server.ts +++ b/packages/frontend/src/routes/dashboard/admin/jobs/[queueName]/+page.server.ts @@ -4,32 +4,32 @@ import type { PageServerLoad } from './$types'; import type { IGetQueueJobsResponse, JobStatus } from '@open-archiver/types'; export const load: PageServerLoad = async (event) => { - const { queueName } = event.params; - const status = (event.url.searchParams.get('status') || 'failed') as JobStatus; - const page = event.url.searchParams.get('page') || '1'; - const limit = event.url.searchParams.get('limit') || '10'; + const { queueName } = event.params; + const status = (event.url.searchParams.get('status') || 'failed') as JobStatus; + const page = event.url.searchParams.get('page') || '1'; + const limit = event.url.searchParams.get('limit') || '10'; - try { - const response = await api( - `/jobs/queues/${queueName}?status=${status}&page=${page}&limit=${limit}`, - event - ); + try { + const response = await api( + `/jobs/queues/${queueName}?status=${status}&page=${page}&limit=${limit}`, + event + ); - if (!response.ok) { - const responseText = await response.json(); - throw error( - response.status as NumericRange<400, 599>, - responseText.message || 'Failed to fetch job queue details.' - ); - } + if (!response.ok) { + const responseText = await response.json(); + throw error( + response.status as NumericRange<400, 599>, + responseText.message || 'Failed to fetch job queue details.' + ); + } - const data: IGetQueueJobsResponse = await response.json(); + const data: IGetQueueJobsResponse = await response.json(); - return { - queue: data, - }; - } catch (e: any) { - console.error('Failed to load job queue details:', e); - throw error(e.status || 500, e.body?.message || 'Failed to load job queue details'); - } + return { + queue: data, + }; + } catch (e: any) { + console.error('Failed to load job queue details:', e); + throw error(e.status || 500, e.body?.message || 'Failed to load job queue details'); + } }; diff --git a/packages/frontend/src/routes/dashboard/admin/license/+page.server.ts b/packages/frontend/src/routes/dashboard/admin/license/+page.server.ts index ddc8a65..79442d8 100644 --- a/packages/frontend/src/routes/dashboard/admin/license/+page.server.ts +++ b/packages/frontend/src/routes/dashboard/admin/license/+page.server.ts @@ -4,30 +4,36 @@ import type { ConsolidatedLicenseStatus } from '@open-archiver/types'; import { error } from '@sveltejs/kit'; export const load: PageServerLoad = async (event) => { - if (!event.locals.enterpriseMode) { - throw error(403, "This feature is only available in the Enterprise Edition. Please contact Open Archiver to upgrade.") - } - try { - const response = await api('/enterprise/status/license-status', event); - const responseText = await response.json() - if (!response.ok) { - if (response.status === 404) { - throw error(404, responseText.error || JSON.stringify(responseText)); - } - // Handle other potential server errors - throw error(response.status, 'Failed to fetch license status'); - } + if (!event.locals.enterpriseMode) { + throw error( + 403, + 'This feature is only available in the Enterprise Edition. Please contact Open Archiver to upgrade.' + ); + } + try { + const response = await api('/enterprise/status/license-status', event); + const responseText = await response.json(); + if (!response.ok) { + if (response.status === 404) { + throw error(404, responseText.error || JSON.stringify(responseText)); + } + // Handle other potential server errors + throw error(response.status, 'Failed to fetch license status'); + } - const licenseStatus: ConsolidatedLicenseStatus = responseText + const licenseStatus: ConsolidatedLicenseStatus = responseText; - return { - licenseStatus - }; - } catch (e) { - // Catch fetch errors or re-throw kit errors - if (e instanceof Error) { - throw error(500, 'An unexpected error occurred while trying to fetch the license status.'); - } - throw e; - } + return { + licenseStatus, + }; + } catch (e) { + // Catch fetch errors or re-throw kit errors + if (e instanceof Error) { + throw error( + 500, + 'An unexpected error occurred while trying to fetch the license status.' + ); + } + throw e; + } }; diff --git a/packages/frontend/src/routes/dashboard/compliance/audit-log/+page.server.ts b/packages/frontend/src/routes/dashboard/compliance/audit-log/+page.server.ts index fc80f98..a16644f 100644 --- a/packages/frontend/src/routes/dashboard/compliance/audit-log/+page.server.ts +++ b/packages/frontend/src/routes/dashboard/compliance/audit-log/+page.server.ts @@ -4,22 +4,26 @@ import type { PageServerLoad } from './$types'; import type { GetAuditLogsResponse } from '@open-archiver/types'; import { error } from '@sveltejs/kit'; - export const load: PageServerLoad = async (event) => { - if (!event.locals.enterpriseMode) { - throw error(403, "This feature is only available in the Enterprise Edition. Please contact Open Archiver to upgrade.") - } - // Forward search params from the page URL to the API request - const response = await api(`/enterprise/audit-logs?${event.url.searchParams.toString()}`, event); - const res = await response.json(); - if (!response.ok) { - throw error(response.status, res.message || JSON.stringify(res)) - } - - const result: GetAuditLogsResponse = res; - return { - logs: result.data, - meta: result.meta - }; + if (!event.locals.enterpriseMode) { + throw error( + 403, + 'This feature is only available in the Enterprise Edition. Please contact Open Archiver to upgrade.' + ); + } + // Forward search params from the page URL to the API request + const response = await api( + `/enterprise/audit-logs?${event.url.searchParams.toString()}`, + event + ); + const res = await response.json(); + if (!response.ok) { + throw error(response.status, res.message || JSON.stringify(res)); + } + const result: GetAuditLogsResponse = res; + return { + logs: result.data, + meta: result.meta, + }; }; diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index 9c43c97..49d12ba 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -9,7 +9,7 @@ export default defineConfig({ plugins: [tailwindcss(), sveltekit()], define: { // This will be 'true' only during the enterprise build process - 'import.meta.env.VITE_ENTERPRISE_MODE': process.env.VITE_ENTERPRISE_MODE === 'true' + 'import.meta.env.VITE_ENTERPRISE_MODE': process.env.VITE_ENTERPRISE_MODE === 'true', }, server: { port: Number(process.env.PORT_FRONTEND) || 3000, @@ -19,7 +19,7 @@ export default defineConfig({ changeOrigin: true, rewrite: (path) => path.replace(/^\/api/, ''), }, - } + }, }, ssr: { noExternal: ['layerchart'], diff --git a/packages/types/src/audit-log.enums.ts b/packages/types/src/audit-log.enums.ts index 3545ccc..bb3e134 100644 --- a/packages/types/src/audit-log.enums.ts +++ b/packages/types/src/audit-log.enums.ts @@ -1,36 +1,36 @@ export const AuditLogActions = [ - // General CRUD - 'CREATE', - 'READ', - 'UPDATE', - 'DELETE', + // General CRUD + 'CREATE', + 'READ', + 'UPDATE', + 'DELETE', - // User & Session Management - 'LOGIN', - 'LOGOUT', - 'SETUP', // Initial user setup + // User & Session Management + 'LOGIN', + 'LOGOUT', + 'SETUP', // Initial user setup - // Ingestion Actions - 'IMPORT', - 'PAUSE', - 'SYNC', - 'UPLOAD', + // Ingestion Actions + 'IMPORT', + 'PAUSE', + 'SYNC', + 'UPLOAD', - // Other Actions - 'SEARCH', - 'DOWNLOAD', - 'GENERATE' // For API keys + // Other Actions + 'SEARCH', + 'DOWNLOAD', + 'GENERATE', // For API keys ] as const; export const AuditLogTargetTypes = [ - 'ApiKey', - 'ArchivedEmail', - 'Dashboard', - 'IngestionSource', - 'Role', - 'SystemSettings', - 'User', - 'File' // For uploads and downloads + 'ApiKey', + 'ArchivedEmail', + 'Dashboard', + 'IngestionSource', + 'Role', + 'SystemSettings', + 'User', + 'File', // For uploads and downloads ] as const; export type AuditLogAction = (typeof AuditLogActions)[number]; diff --git a/packages/types/src/audit-log.types.ts b/packages/types/src/audit-log.types.ts index bd2ae55..0788d38 100644 --- a/packages/types/src/audit-log.types.ts +++ b/packages/types/src/audit-log.types.ts @@ -1,39 +1,39 @@ import type { AuditLogAction, AuditLogTargetType } from './audit-log.enums'; export interface AuditLogEntry { - id: number; - previousHash: string | null; - timestamp: Date; - actorIdentifier: string; - actorIp: string | null; - actionType: AuditLogAction; - targetType: AuditLogTargetType | null; - targetId: string | null; - details: Record | null; - currentHash: string; + id: number; + previousHash: string | null; + timestamp: Date; + actorIdentifier: string; + actorIp: string | null; + actionType: AuditLogAction; + targetType: AuditLogTargetType | null; + targetId: string | null; + details: Record | null; + currentHash: string; } export type CreateAuditLogEntry = Omit< - AuditLogEntry, - 'id' | 'previousHash' | 'timestamp' | 'currentHash' + AuditLogEntry, + 'id' | 'previousHash' | 'timestamp' | 'currentHash' >; export interface GetAuditLogsOptions { - page?: number; - limit?: number; - startDate?: Date; - endDate?: Date; - actor?: string; - actionType?: AuditLogAction; - targetType?: AuditLogTargetType | null; - sort?: 'asc' | 'desc'; + page?: number; + limit?: number; + startDate?: Date; + endDate?: Date; + actor?: string; + actionType?: AuditLogAction; + targetType?: AuditLogTargetType | null; + sort?: 'asc' | 'desc'; } export interface GetAuditLogsResponse { - data: AuditLogEntry[]; - meta: { - total: number; - page: number; - limit: number; - }; + data: AuditLogEntry[]; + meta: { + total: number; + page: number; + limit: number; + }; } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index d9eb456..f1eda70 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -12,4 +12,4 @@ export * from './audit-log.types'; export * from './audit-log.enums'; export * from './integrity.types'; export * from './jobs.types'; -export * from './license.types' \ No newline at end of file +export * from './license.types'; diff --git a/packages/types/src/integrity.types.ts b/packages/types/src/integrity.types.ts index 92f296e..cb08b3d 100644 --- a/packages/types/src/integrity.types.ts +++ b/packages/types/src/integrity.types.ts @@ -1,7 +1,7 @@ export interface IntegrityCheckResult { - type: 'email' | 'attachment'; - id: string; - filename?: string; - isValid: boolean; - reason?: string; + type: 'email' | 'attachment'; + id: string; + filename?: string; + isValid: boolean; + reason?: string; } diff --git a/packages/types/src/jobs.types.ts b/packages/types/src/jobs.types.ts index 8cbf3b2..efec722 100644 --- a/packages/types/src/jobs.types.ts +++ b/packages/types/src/jobs.types.ts @@ -7,59 +7,59 @@ export type JobStatus = 'active' | 'completed' | 'failed' | 'delayed' | 'waiting * A detailed representation of a job, providing essential information for monitoring and debugging. */ export interface IJob { - id: string | undefined; - name: string; - data: any; - state: string; - failedReason: string | undefined; - timestamp: number; - processedOn: number | undefined; - finishedOn: number | undefined; - attemptsMade: number; - stacktrace: string[]; - returnValue: any; - ingestionSourceId?: string; - error?: any; + id: string | undefined; + name: string; + data: any; + state: string; + failedReason: string | undefined; + timestamp: number; + processedOn: number | undefined; + finishedOn: number | undefined; + attemptsMade: number; + stacktrace: string[]; + returnValue: any; + ingestionSourceId?: string; + error?: any; } /** * Holds the count of jobs in various states for a single queue. */ export interface IQueueCounts { - active: number; - completed: number; - failed: number; - delayed: number; - waiting: number; - paused: number; + active: number; + completed: number; + failed: number; + delayed: number; + waiting: number; + paused: number; } /** * Provides a high-level overview of a queue, including its name and job counts. */ export interface IQueueOverview { - name: string; - counts: IQueueCounts; + name: string; + counts: IQueueCounts; } /** * Represents the pagination details for a list of jobs. */ export interface IPagination { - currentPage: number; - totalPages: number; - totalJobs: number; - limit: number; + currentPage: number; + totalPages: number; + totalJobs: number; + limit: number; } /** * Provides a detailed view of a specific queue, including a paginated list of its jobs. */ export interface IQueueDetails { - name: string; - counts: IQueueCounts; - jobs: IJob[]; - pagination: IPagination; + name: string; + counts: IQueueCounts; + jobs: IJob[]; + pagination: IPagination; } // --- API Request & Response Types --- @@ -68,23 +68,23 @@ export interface IQueueDetails { * Response body for the endpoint that lists all queues. */ export interface IGetQueuesResponse { - queues: IQueueOverview[]; + queues: IQueueOverview[]; } /** * URL parameters for the endpoint that retrieves jobs from a specific queue. */ export interface IGetQueueJobsRequestParams { - queueName: string; + queueName: string; } /** * Query parameters for filtering and paginating jobs within a queue. */ export interface IGetQueueJobsRequestQuery { - status: JobStatus; - page: string; // Received as a string from query params - limit: string; // Received as a string from query params + status: JobStatus; + page: string; // Received as a string from query params + limit: string; // Received as a string from query params } /** diff --git a/packages/types/src/license.types.ts b/packages/types/src/license.types.ts index 863dcf3..7949484 100644 --- a/packages/types/src/license.types.ts +++ b/packages/types/src/license.types.ts @@ -2,48 +2,48 @@ * Features of Open Archiver Enterprise */ export enum OpenArchiverFeature { - AUDIT_LOG = 'audit-log', - RETENTION_POLICY = 'retention-policy', - SSO = 'sso', - STATUS = 'status', - ALL = 'all', + AUDIT_LOG = 'audit-log', + RETENTION_POLICY = 'retention-policy', + SSO = 'sso', + STATUS = 'status', + ALL = 'all', } /** * The payload of the offline license.jwt file. */ export interface LicenseFilePayload { - licenseId: string; // UUID linking to the License Server - customerName: string; - planSeats: number; - features: OpenArchiverFeature[]; - expiresAt: string; // ISO 8601 - issuedAt: string; // ISO 8601 + licenseId: string; // UUID linking to the License Server + customerName: string; + planSeats: number; + features: OpenArchiverFeature[]; + expiresAt: string; // ISO 8601 + issuedAt: string; // ISO 8601 } /** * The structure of the cached response from the License Server. */ export interface LicenseStatusPayload { - status: 'VALID' | 'REVOKED'; - gracePeriodEnds?: string; // ISO 8601, only present if REVOKED + status: 'VALID' | 'REVOKED'; + gracePeriodEnds?: string; // ISO 8601, only present if REVOKED } /** * The consolidated license status object returned by the API. */ export interface ConsolidatedLicenseStatus { - // From the license.jwt file - customerName: string; - planSeats: number; - expiresAt: string; - // From the cached license-status.json - remoteStatus: 'VALID' | 'REVOKED' | 'UNKNOWN'; - gracePeriodEnds?: string; - // Calculated values - activeSeats: number; - isExpired: boolean; - features: { - [key in OpenArchiverFeature]?: boolean; - }; + // From the license.jwt file + customerName: string; + planSeats: number; + expiresAt: string; + // From the cached license-status.json + remoteStatus: 'VALID' | 'REVOKED' | 'UNKNOWN'; + gracePeriodEnds?: string; + // Calculated values + activeSeats: number; + isExpired: boolean; + features: { + [key in OpenArchiverFeature]?: boolean; + }; }