import { pgTable, serial, varchar, text, boolean, integer, timestamp, decimal, pgEnum, uniqueIndex, index } from 'drizzle-orm/pg-core'; import { relations } from 'drizzle-orm'; // Enums export const roleEnum = pgEnum('role', ['admin', 'moderator']); export const sourceTypeEnum = pgEnum('source_type', [ 'website', 'facebook_page', 'facebook_group', 'instagram', 'blog', 'news_site', 'youtube', 'tiktok', 'telegram', 'other' ]); export const sourceStatusEnum = pgEnum('source_status', [ 'pending', 'verified', 'rejected', 'under_review' ]); export const languageEnum = pgEnum('language', ['sk', 'cs', 'en', 'other']); export const priorityEnum = pgEnum('priority', ['low', 'medium', 'high', 'urgent']); export const reportStatusEnum = pgEnum('report_status', [ 'pending', 'in_review', 'approved', 'rejected', 'duplicate' ]); // Users table export const users = pgTable('users', { id: serial('id').primaryKey(), email: varchar('email', { length: 255 }).notNull().unique(), passwordHash: varchar('password_hash', { length: 255 }).notNull(), name: varchar('name', { length: 100 }).notNull(), role: roleEnum('role').default('moderator'), isActive: boolean('is_active').default(true), lastLogin: timestamp('last_login'), createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow() }); // Categories table export const categories = pgTable('categories', { id: serial('id').primaryKey(), name: varchar('name', { length: 100 }).notNull().unique(), slug: varchar('slug', { length: 100 }).notNull().unique(), description: text('description'), color: varchar('color', { length: 7 }).default('#6B7280'), priority: integer('priority').default(1), icon: varchar('icon', { length: 50 }), isActive: boolean('is_active').default(true), createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow() }, (table) => { return { slugIdx: uniqueIndex('idx_categories_slug').on(table.slug), priorityIdx: index('idx_categories_priority').on(table.priority) }; }); // Sources table export const sources = pgTable('sources', { id: serial('id').primaryKey(), url: varchar('url', { length: 1000 }).notNull().unique(), domain: varchar('domain', { length: 255 }).notNull(), title: varchar('title', { length: 500 }), description: text('description'), type: sourceTypeEnum('type').notNull(), status: sourceStatusEnum('status').default('pending'), riskLevel: integer('risk_level').default(1), language: languageEnum('language').default('sk'), evidenceUrls: text('evidence_urls'), // JSON reportedBy: varchar('reported_by', { length: 255 }), verifiedBy: integer('verified_by').references(() => users.id), rejectionReason: text('rejection_reason'), followerCount: integer('follower_count').default(0), lastChecked: timestamp('last_checked'), metadata: text('metadata').default('{}'), // JSON createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow() }, (table) => { return { domainIdx: index('idx_sources_domain').on(table.domain), statusIdx: index('idx_sources_status').on(table.status), riskLevelIdx: index('idx_sources_risk_level').on(table.riskLevel), typeIdx: index('idx_sources_type').on(table.type), createdAtIdx: index('idx_sources_created_at').on(table.createdAt), verifiedByIdx: index('idx_sources_verified_by').on(table.verifiedBy), statusRiskIdx: index('idx_sources_status_risk').on(table.status, table.riskLevel) }; }); // Source Categories junction table export const sourceCategories = pgTable('source_categories', { id: serial('id').primaryKey(), sourceId: integer('source_id').notNull().references(() => sources.id, { onDelete: 'cascade' }), categoryId: integer('category_id').notNull().references(() => categories.id, { onDelete: 'cascade' }), confidenceScore: decimal('confidence_score', { precision: 3, scale: 2 }).default('1.0'), addedBy: integer('added_by').references(() => users.id), createdAt: timestamp('created_at').defaultNow() }, (table) => { return { sourceIdIdx: index('idx_source_categories_source_id').on(table.sourceId), categoryIdIdx: index('idx_source_categories_category_id').on(table.categoryId), uniqueSourceCategory: uniqueIndex('unique_source_category').on(table.sourceId, table.categoryId) }; }); // Reports table export const reports = pgTable('reports', { id: serial('id').primaryKey(), sourceUrl: varchar('source_url', { length: 1000 }).notNull(), sourceDomain: varchar('source_domain', { length: 255 }).notNull(), reporterEmail: varchar('reporter_email', { length: 255 }), reporterName: varchar('reporter_name', { length: 100 }), categorySuggestions: text('category_suggestions'), // JSON description: text('description').notNull(), evidenceUrls: text('evidence_urls'), // JSON priority: priorityEnum('priority').default('medium'), status: reportStatusEnum('status').default('pending'), assignedTo: integer('assigned_to').references(() => users.id), adminNotes: text('admin_notes'), processedAt: timestamp('processed_at'), ipAddress: varchar('ip_address', { length: 45 }), userAgent: text('user_agent'), createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow() }, (table) => { return { statusIdx: index('idx_reports_status').on(table.status), sourceDomainIdx: index('idx_reports_source_domain').on(table.sourceDomain), priorityIdx: index('idx_reports_priority').on(table.priority), createdAtIdx: index('idx_reports_created_at').on(table.createdAt), assignedToIdx: index('idx_reports_assigned_to').on(table.assignedTo) }; }); // API Keys table export const apiKeys = pgTable('api_keys', { id: serial('id').primaryKey(), keyHash: varchar('key_hash', { length: 255 }).notNull().unique(), name: varchar('name', { length: 100 }).notNull(), description: text('description'), ownerEmail: varchar('owner_email', { length: 255 }).notNull(), permissions: text('permissions').default('["read"]'), // JSON rateLimit: integer('rate_limit').default(1000), isActive: boolean('is_active').default(true), usageCount: integer('usage_count').default(0), lastUsed: timestamp('last_used'), expiresAt: timestamp('expires_at'), createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow() }, (table) => { return { keyHashIdx: uniqueIndex('idx_api_keys_hash').on(table.keyHash), ownerIdx: index('idx_api_keys_owner').on(table.ownerEmail) }; }); // Audit Logs table export const auditLogs = pgTable('audit_logs', { id: serial('id').primaryKey(), userId: integer('user_id').references(() => users.id), action: varchar('action', { length: 50 }).notNull(), resourceType: varchar('resource_type', { length: 50 }).notNull(), resourceId: integer('resource_id'), details: text('details'), // JSON ipAddress: varchar('ip_address', { length: 45 }), createdAt: timestamp('created_at').defaultNow() }, (table) => { return { userIdIdx: index('idx_audit_logs_user_id').on(table.userId), createdAtIdx: index('idx_audit_logs_created_at').on(table.createdAt), actionIdx: index('idx_audit_logs_action').on(table.action), resourceTypeIdx: index('idx_audit_logs_resource_type').on(table.resourceType) }; }); // Relations export const usersRelations = relations(users, ({ many }) => ({ verifiedSources: many(sources), sourceCategories: many(sourceCategories), assignedReports: many(reports), auditLogs: many(auditLogs) })); export const categoriesRelations = relations(categories, ({ many }) => ({ sourceCategories: many(sourceCategories) })); export const sourcesRelations = relations(sources, ({ one, many }) => ({ verifiedBy: one(users, { fields: [sources.verifiedBy], references: [users.id] }), sourceCategories: many(sourceCategories) })); export const sourceCategoriesRelations = relations(sourceCategories, ({ one }) => ({ source: one(sources, { fields: [sourceCategories.sourceId], references: [sources.id] }), category: one(categories, { fields: [sourceCategories.categoryId], references: [categories.id] }), addedBy: one(users, { fields: [sourceCategories.addedBy], references: [users.id] }) })); export const reportsRelations = relations(reports, ({ one }) => ({ assignedTo: one(users, { fields: [reports.assignedTo], references: [users.id] }) })); export const auditLogsRelations = relations(auditLogs, ({ one }) => ({ user: one(users, { fields: [auditLogs.userId], references: [users.id] }) })); // System Metrics table export const systemMetrics = pgTable('system_metrics', { id: serial('id').primaryKey(), metricType: varchar('metric_type', { length: 50 }).notNull(), // 'cpu', 'memory', 'disk', 'api_calls', 'db_connections' value: decimal('value', { precision: 10, scale: 2 }).notNull(), unit: varchar('unit', { length: 20 }), // '%', 'MB', 'GB', 'count', 'ms' timestamp: timestamp('timestamp').defaultNow() }, (table) => { return { typeIdx: index('idx_system_metrics_type').on(table.metricType), timestampIdx: index('idx_system_metrics_timestamp').on(table.timestamp), typeTimestampIdx: index('idx_system_metrics_type_timestamp').on(table.metricType, table.timestamp) }; }); // Analytics Events table export const analyticsEvents = pgTable('analytics_events', { id: serial('id').primaryKey(), eventType: varchar('event_type', { length: 50 }).notNull(), // 'source_lookup', 'api_call', 'report_submitted', 'source_verified' sourceUrl: varchar('source_url', { length: 1000 }), sourceId: integer('source_id').references(() => sources.id), userId: integer('user_id').references(() => users.id), apiKeyId: integer('api_key_id').references(() => apiKeys.id), ipAddress: varchar('ip_address', { length: 45 }), userAgent: text('user_agent'), responseTime: integer('response_time'), // in milliseconds statusCode: integer('status_code'), metadata: text('metadata').default('{}'), // JSON timestamp: timestamp('timestamp').defaultNow() }, (table) => { return { eventTypeIdx: index('idx_analytics_events_type').on(table.eventType), timestampIdx: index('idx_analytics_events_timestamp').on(table.timestamp), sourceIdIdx: index('idx_analytics_events_source_id').on(table.sourceId), apiKeyIdIdx: index('idx_analytics_events_api_key_id').on(table.apiKeyId), ipAddressIdx: index('idx_analytics_events_ip').on(table.ipAddress) }; }); // Notifications table export const notifications = pgTable('notifications', { id: serial('id').primaryKey(), title: varchar('title', { length: 200 }).notNull(), message: text('message').notNull(), type: varchar('type', { length: 50 }).default('info'), // 'info', 'warning', 'error', 'success' userId: integer('user_id').references(() => users.id), isRead: boolean('is_read').default(false), actionUrl: varchar('action_url', { length: 500 }), metadata: text('metadata').default('{}'), // JSON createdAt: timestamp('created_at').defaultNow(), readAt: timestamp('read_at') }, (table) => { return { userIdIdx: index('idx_notifications_user_id').on(table.userId), isReadIdx: index('idx_notifications_is_read').on(table.isRead), createdAtIdx: index('idx_notifications_created_at').on(table.createdAt), typeIdx: index('idx_notifications_type').on(table.type) }; }); // System Settings table export const systemSettings = pgTable('system_settings', { id: serial('id').primaryKey(), key: varchar('key', { length: 100 }).notNull().unique(), value: text('value'), type: varchar('type', { length: 50 }).default('string'), // 'string', 'number', 'boolean', 'json' description: text('description'), category: varchar('category', { length: 50 }).default('general'), isPublic: boolean('is_public').default(false), updatedBy: integer('updated_by').references(() => users.id), createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow() }, (table) => { return { keyIdx: uniqueIndex('idx_system_settings_key').on(table.key), categoryIdx: index('idx_system_settings_category').on(table.category) }; }); // Webhooks table export const webhooks = pgTable('webhooks', { id: serial('id').primaryKey(), name: varchar('name', { length: 100 }).notNull(), url: varchar('url', { length: 1000 }).notNull(), events: text('events').notNull(), // JSON array of event types secret: varchar('secret', { length: 255 }), isActive: boolean('is_active').default(true), headers: text('headers').default('{}'), // JSON lastTriggered: timestamp('last_triggered'), successCount: integer('success_count').default(0), failureCount: integer('failure_count').default(0), createdBy: integer('created_by').references(() => users.id), createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow() }, (table) => { return { createdByIdx: index('idx_webhooks_created_by').on(table.createdBy), isActiveIdx: index('idx_webhooks_is_active').on(table.isActive) }; }); // Source Analytics table export const sourceAnalytics = pgTable('source_analytics', { id: serial('id').primaryKey(), sourceId: integer('source_id').notNull().references(() => sources.id, { onDelete: 'cascade' }), date: timestamp('date').notNull(), lookupCount: integer('lookup_count').default(0), reportCount: integer('report_count').default(0), viewCount: integer('view_count').default(0), riskScore: decimal('risk_score', { precision: 3, scale: 2 }), metadata: text('metadata').default('{}'), // JSON }, (table) => { return { sourceIdIdx: index('idx_source_analytics_source_id').on(table.sourceId), dateIdx: index('idx_source_analytics_date').on(table.date), uniqueSourceDate: uniqueIndex('unique_source_date').on(table.sourceId, table.date) }; }); // Email Templates table export const emailTemplates = pgTable('email_templates', { id: serial('id').primaryKey(), name: varchar('name', { length: 100 }).notNull().unique(), subject: varchar('subject', { length: 200 }).notNull(), htmlBody: text('html_body').notNull(), textBody: text('text_body'), variables: text('variables').default('[]'), // JSON array of available variables isActive: boolean('is_active').default(true), createdBy: integer('created_by').references(() => users.id), createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow() }, (table) => { return { nameIdx: uniqueIndex('idx_email_templates_name').on(table.name), isActiveIdx: index('idx_email_templates_is_active').on(table.isActive) }; }); // Scheduled Jobs table export const scheduledJobs = pgTable('scheduled_jobs', { id: serial('id').primaryKey(), name: varchar('name', { length: 100 }).notNull(), type: varchar('type', { length: 50 }).notNull(), // 'report_generation', 'data_cleanup', 'backup', 'analytics' schedule: varchar('schedule', { length: 100 }).notNull(), // cron expression isActive: boolean('is_active').default(true), lastRun: timestamp('last_run'), nextRun: timestamp('next_run'), lastResult: varchar('last_result', { length: 50 }), // 'success', 'failure', 'running' config: text('config').default('{}'), // JSON createdBy: integer('created_by').references(() => users.id), createdAt: timestamp('created_at').defaultNow(), updatedAt: timestamp('updated_at').defaultNow() }, (table) => { return { nameIdx: index('idx_scheduled_jobs_name').on(table.name), typeIdx: index('idx_scheduled_jobs_type').on(table.type), nextRunIdx: index('idx_scheduled_jobs_next_run').on(table.nextRun), isActiveIdx: index('idx_scheduled_jobs_is_active').on(table.isActive) }; }); // Additional Relations export const systemMetricsRelations = relations(systemMetrics, ({ one }) => ({})); export const analyticsEventsRelations = relations(analyticsEvents, ({ one }) => ({ source: one(sources, { fields: [analyticsEvents.sourceId], references: [sources.id] }), user: one(users, { fields: [analyticsEvents.userId], references: [users.id] }), apiKey: one(apiKeys, { fields: [analyticsEvents.apiKeyId], references: [apiKeys.id] }) })); export const notificationsRelations = relations(notifications, ({ one }) => ({ user: one(users, { fields: [notifications.userId], references: [users.id] }) })); export const systemSettingsRelations = relations(systemSettings, ({ one }) => ({ updatedBy: one(users, { fields: [systemSettings.updatedBy], references: [users.id] }) })); export const webhooksRelations = relations(webhooks, ({ one }) => ({ createdBy: one(users, { fields: [webhooks.createdBy], references: [users.id] }) })); export const sourceAnalyticsRelations = relations(sourceAnalytics, ({ one }) => ({ source: one(sources, { fields: [sourceAnalytics.sourceId], references: [sources.id] }) })); export const emailTemplatesRelations = relations(emailTemplates, ({ one }) => ({ createdBy: one(users, { fields: [emailTemplates.createdBy], references: [users.id] }) })); export const scheduledJobsRelations = relations(scheduledJobs, ({ one }) => ({ createdBy: one(users, { fields: [scheduledJobs.createdBy], references: [users.id] }) }));