import { sqliteTable, text, integer, real } from 'drizzle-orm/sqlite-core'; import { pgTable, uuid, varchar, text as pgText, timestamp, decimal, integer as pgInteger } from 'drizzle-orm/pg-core'; // Type to determine which schema to use const dbType = process.env.DB_TYPE || 'sqlite'; // ==================== SQLite Schema ==================== export const sqliteUsers = sqliteTable('users', { id: text('id').primaryKey(), email: text('email').notNull().unique(), password: text('password'), // Nullable for unclaimed accounts name: text('name').notNull(), phone: text('phone'), role: text('role', { enum: ['admin', 'organizer', 'staff', 'marketing', 'user'] }).notNull().default('user'), languagePreference: text('language_preference'), // New fields for progressive accounts and OAuth isClaimed: integer('is_claimed', { mode: 'boolean' }).notNull().default(true), googleId: text('google_id'), rucNumber: text('ruc_number'), accountStatus: text('account_status', { enum: ['active', 'unclaimed', 'suspended'] }).notNull().default('active'), createdAt: text('created_at').notNull(), updatedAt: text('updated_at').notNull(), }); // Magic link tokens for passwordless login export const sqliteMagicLinkTokens = sqliteTable('magic_link_tokens', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => sqliteUsers.id), token: text('token').notNull().unique(), type: text('type', { enum: ['login', 'reset_password', 'claim_account', 'email_verification'] }).notNull(), expiresAt: text('expires_at').notNull(), usedAt: text('used_at'), createdAt: text('created_at').notNull(), }); // User sessions for session management export const sqliteUserSessions = sqliteTable('user_sessions', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => sqliteUsers.id), token: text('token').notNull().unique(), userAgent: text('user_agent'), ipAddress: text('ip_address'), lastActiveAt: text('last_active_at').notNull(), expiresAt: text('expires_at').notNull(), createdAt: text('created_at').notNull(), }); // Invoices table export const sqliteInvoices = sqliteTable('invoices', { id: text('id').primaryKey(), paymentId: text('payment_id').notNull().references(() => sqlitePayments.id), userId: text('user_id').notNull().references(() => sqliteUsers.id), invoiceNumber: text('invoice_number').notNull().unique(), rucNumber: text('ruc_number'), legalName: text('legal_name'), amount: real('amount').notNull(), currency: text('currency').notNull().default('PYG'), pdfUrl: text('pdf_url'), status: text('status', { enum: ['generated', 'voided'] }).notNull().default('generated'), createdAt: text('created_at').notNull(), }); export const sqliteEvents = sqliteTable('events', { id: text('id').primaryKey(), title: text('title').notNull(), titleEs: text('title_es'), description: text('description').notNull(), descriptionEs: text('description_es'), startDatetime: text('start_datetime').notNull(), endDatetime: text('end_datetime'), location: text('location').notNull(), locationUrl: text('location_url'), price: real('price').notNull().default(0), currency: text('currency').notNull().default('PYG'), capacity: integer('capacity').notNull().default(50), status: text('status', { enum: ['draft', 'published', 'cancelled', 'completed', 'archived'] }).notNull().default('draft'), bannerUrl: text('banner_url'), createdAt: text('created_at').notNull(), updatedAt: text('updated_at').notNull(), }); export const sqliteTickets = sqliteTable('tickets', { id: text('id').primaryKey(), userId: text('user_id').notNull().references(() => sqliteUsers.id), eventId: text('event_id').notNull().references(() => sqliteEvents.id), attendeeFirstName: text('attendee_first_name').notNull(), attendeeLastName: text('attendee_last_name'), attendeeEmail: text('attendee_email'), attendeePhone: text('attendee_phone'), attendeeRuc: text('attendee_ruc'), // Paraguayan tax ID for invoicing preferredLanguage: text('preferred_language'), status: text('status', { enum: ['pending', 'confirmed', 'cancelled', 'checked_in'] }).notNull().default('pending'), checkinAt: text('checkin_at'), qrCode: text('qr_code'), adminNote: text('admin_note'), createdAt: text('created_at').notNull(), }); export const sqlitePayments = sqliteTable('payments', { id: text('id').primaryKey(), ticketId: text('ticket_id').notNull().references(() => sqliteTickets.id), provider: text('provider', { enum: ['bancard', 'lightning', 'cash', 'bank_transfer', 'tpago'] }).notNull(), amount: real('amount').notNull(), currency: text('currency').notNull().default('PYG'), status: text('status', { enum: ['pending', 'pending_approval', 'paid', 'refunded', 'failed', 'cancelled'] }).notNull().default('pending'), reference: text('reference'), userMarkedPaidAt: text('user_marked_paid_at'), // When user clicked "I Have Paid" paidAt: text('paid_at'), paidByAdminId: text('paid_by_admin_id'), adminNote: text('admin_note'), // Internal admin notes createdAt: text('created_at').notNull(), updatedAt: text('updated_at').notNull(), }); // Payment Options Configuration Table (global settings) export const sqlitePaymentOptions = sqliteTable('payment_options', { id: text('id').primaryKey(), // TPago configuration tpagoEnabled: integer('tpago_enabled', { mode: 'boolean' }).notNull().default(false), tpagoLink: text('tpago_link'), tpagoInstructions: text('tpago_instructions'), tpagoInstructionsEs: text('tpago_instructions_es'), // Bank Transfer configuration bankTransferEnabled: integer('bank_transfer_enabled', { mode: 'boolean' }).notNull().default(false), bankName: text('bank_name'), bankAccountHolder: text('bank_account_holder'), bankAccountNumber: text('bank_account_number'), bankAlias: text('bank_alias'), bankPhone: text('bank_phone'), bankNotes: text('bank_notes'), bankNotesEs: text('bank_notes_es'), // Lightning configuration lightningEnabled: integer('lightning_enabled', { mode: 'boolean' }).notNull().default(true), // Cash configuration cashEnabled: integer('cash_enabled', { mode: 'boolean' }).notNull().default(true), cashInstructions: text('cash_instructions'), cashInstructionsEs: text('cash_instructions_es'), // Metadata updatedAt: text('updated_at').notNull(), updatedBy: text('updated_by').references(() => sqliteUsers.id), }); // Event-specific payment overrides export const sqliteEventPaymentOverrides = sqliteTable('event_payment_overrides', { id: text('id').primaryKey(), eventId: text('event_id').notNull().references(() => sqliteEvents.id), // Override flags (null means use global) tpagoEnabled: integer('tpago_enabled', { mode: 'boolean' }), tpagoLink: text('tpago_link'), tpagoInstructions: text('tpago_instructions'), tpagoInstructionsEs: text('tpago_instructions_es'), bankTransferEnabled: integer('bank_transfer_enabled', { mode: 'boolean' }), bankName: text('bank_name'), bankAccountHolder: text('bank_account_holder'), bankAccountNumber: text('bank_account_number'), bankAlias: text('bank_alias'), bankPhone: text('bank_phone'), bankNotes: text('bank_notes'), bankNotesEs: text('bank_notes_es'), lightningEnabled: integer('lightning_enabled', { mode: 'boolean' }), cashEnabled: integer('cash_enabled', { mode: 'boolean' }), cashInstructions: text('cash_instructions'), cashInstructionsEs: text('cash_instructions_es'), // Metadata createdAt: text('created_at').notNull(), updatedAt: text('updated_at').notNull(), }); export const sqliteContacts = sqliteTable('contacts', { id: text('id').primaryKey(), name: text('name').notNull(), email: text('email').notNull(), message: text('message').notNull(), status: text('status', { enum: ['new', 'read', 'replied'] }).notNull().default('new'), createdAt: text('created_at').notNull(), }); export const sqliteEmailSubscribers = sqliteTable('email_subscribers', { id: text('id').primaryKey(), email: text('email').notNull().unique(), name: text('name'), status: text('status', { enum: ['active', 'unsubscribed'] }).notNull().default('active'), createdAt: text('created_at').notNull(), }); export const sqliteMedia = sqliteTable('media', { id: text('id').primaryKey(), fileUrl: text('file_url').notNull(), type: text('type', { enum: ['image', 'video', 'document'] }).notNull(), relatedId: text('related_id'), relatedType: text('related_type'), createdAt: text('created_at').notNull(), }); export const sqliteAuditLogs = sqliteTable('audit_logs', { id: text('id').primaryKey(), userId: text('user_id').references(() => sqliteUsers.id), action: text('action').notNull(), target: text('target'), targetId: text('target_id'), details: text('details'), timestamp: text('timestamp').notNull(), }); export const sqliteEmailTemplates = sqliteTable('email_templates', { id: text('id').primaryKey(), name: text('name').notNull().unique(), slug: text('slug').notNull().unique(), subject: text('subject').notNull(), subjectEs: text('subject_es'), bodyHtml: text('body_html').notNull(), bodyHtmlEs: text('body_html_es'), bodyText: text('body_text'), bodyTextEs: text('body_text_es'), description: text('description'), variables: text('variables'), // JSON array of available variables isSystem: integer('is_system', { mode: 'boolean' }).notNull().default(false), isActive: integer('is_active', { mode: 'boolean' }).notNull().default(true), createdAt: text('created_at').notNull(), updatedAt: text('updated_at').notNull(), }); export const sqliteEmailLogs = sqliteTable('email_logs', { id: text('id').primaryKey(), templateId: text('template_id').references(() => sqliteEmailTemplates.id), eventId: text('event_id').references(() => sqliteEvents.id), recipientEmail: text('recipient_email').notNull(), recipientName: text('recipient_name'), subject: text('subject').notNull(), bodyHtml: text('body_html'), status: text('status', { enum: ['pending', 'sent', 'failed', 'bounced'] }).notNull().default('pending'), errorMessage: text('error_message'), sentAt: text('sent_at'), sentBy: text('sent_by').references(() => sqliteUsers.id), createdAt: text('created_at').notNull(), }); export const sqliteEmailSettings = sqliteTable('email_settings', { id: text('id').primaryKey(), key: text('key').notNull().unique(), value: text('value').notNull(), updatedAt: text('updated_at').notNull(), }); // ==================== PostgreSQL Schema ==================== export const pgUsers = pgTable('users', { id: uuid('id').primaryKey(), email: varchar('email', { length: 255 }).notNull().unique(), password: varchar('password', { length: 255 }), // Nullable for unclaimed accounts name: varchar('name', { length: 255 }).notNull(), phone: varchar('phone', { length: 50 }), role: varchar('role', { length: 20 }).notNull().default('user'), languagePreference: varchar('language_preference', { length: 10 }), // New fields for progressive accounts and OAuth isClaimed: pgInteger('is_claimed').notNull().default(1), googleId: varchar('google_id', { length: 255 }), rucNumber: varchar('ruc_number', { length: 15 }), accountStatus: varchar('account_status', { length: 20 }).notNull().default('active'), createdAt: timestamp('created_at').notNull(), updatedAt: timestamp('updated_at').notNull(), }); // Magic link tokens for passwordless login export const pgMagicLinkTokens = pgTable('magic_link_tokens', { id: uuid('id').primaryKey(), userId: uuid('user_id').notNull().references(() => pgUsers.id), token: varchar('token', { length: 255 }).notNull().unique(), type: varchar('type', { length: 30 }).notNull(), expiresAt: timestamp('expires_at').notNull(), usedAt: timestamp('used_at'), createdAt: timestamp('created_at').notNull(), }); // User sessions for session management export const pgUserSessions = pgTable('user_sessions', { id: uuid('id').primaryKey(), userId: uuid('user_id').notNull().references(() => pgUsers.id), token: varchar('token', { length: 500 }).notNull().unique(), userAgent: pgText('user_agent'), ipAddress: varchar('ip_address', { length: 45 }), lastActiveAt: timestamp('last_active_at').notNull(), expiresAt: timestamp('expires_at').notNull(), createdAt: timestamp('created_at').notNull(), }); // Invoices table export const pgInvoices = pgTable('invoices', { id: uuid('id').primaryKey(), paymentId: uuid('payment_id').notNull().references(() => pgPayments.id), userId: uuid('user_id').notNull().references(() => pgUsers.id), invoiceNumber: varchar('invoice_number', { length: 50 }).notNull().unique(), rucNumber: varchar('ruc_number', { length: 15 }), legalName: varchar('legal_name', { length: 255 }), amount: decimal('amount', { precision: 10, scale: 2 }).notNull(), currency: varchar('currency', { length: 10 }).notNull().default('PYG'), pdfUrl: varchar('pdf_url', { length: 500 }), status: varchar('status', { length: 20 }).notNull().default('generated'), createdAt: timestamp('created_at').notNull(), }); export const pgEvents = pgTable('events', { id: uuid('id').primaryKey(), title: varchar('title', { length: 255 }).notNull(), titleEs: varchar('title_es', { length: 255 }), description: pgText('description').notNull(), descriptionEs: pgText('description_es'), startDatetime: timestamp('start_datetime').notNull(), endDatetime: timestamp('end_datetime'), location: varchar('location', { length: 500 }).notNull(), locationUrl: varchar('location_url', { length: 500 }), price: decimal('price', { precision: 10, scale: 2 }).notNull().default('0'), currency: varchar('currency', { length: 10 }).notNull().default('PYG'), capacity: pgInteger('capacity').notNull().default(50), status: varchar('status', { length: 20 }).notNull().default('draft'), bannerUrl: varchar('banner_url', { length: 500 }), createdAt: timestamp('created_at').notNull(), updatedAt: timestamp('updated_at').notNull(), }); export const pgTickets = pgTable('tickets', { id: uuid('id').primaryKey(), userId: uuid('user_id').notNull().references(() => pgUsers.id), eventId: uuid('event_id').notNull().references(() => pgEvents.id), attendeeFirstName: varchar('attendee_first_name', { length: 255 }).notNull(), attendeeLastName: varchar('attendee_last_name', { length: 255 }), attendeeEmail: varchar('attendee_email', { length: 255 }), attendeePhone: varchar('attendee_phone', { length: 50 }), attendeeRuc: varchar('attendee_ruc', { length: 15 }), // Paraguayan tax ID for invoicing preferredLanguage: varchar('preferred_language', { length: 10 }), status: varchar('status', { length: 20 }).notNull().default('pending'), checkinAt: timestamp('checkin_at'), qrCode: varchar('qr_code', { length: 255 }), adminNote: pgText('admin_note'), createdAt: timestamp('created_at').notNull(), }); export const pgPayments = pgTable('payments', { id: uuid('id').primaryKey(), ticketId: uuid('ticket_id').notNull().references(() => pgTickets.id), provider: varchar('provider', { length: 50 }).notNull(), amount: decimal('amount', { precision: 10, scale: 2 }).notNull(), currency: varchar('currency', { length: 10 }).notNull().default('PYG'), status: varchar('status', { length: 20 }).notNull().default('pending'), reference: varchar('reference', { length: 255 }), userMarkedPaidAt: timestamp('user_marked_paid_at'), paidAt: timestamp('paid_at'), paidByAdminId: uuid('paid_by_admin_id'), adminNote: pgText('admin_note'), createdAt: timestamp('created_at').notNull(), updatedAt: timestamp('updated_at').notNull(), }); // Payment Options Configuration Table (global settings) export const pgPaymentOptions = pgTable('payment_options', { id: uuid('id').primaryKey(), tpagoEnabled: pgInteger('tpago_enabled').notNull().default(0), tpagoLink: varchar('tpago_link', { length: 500 }), tpagoInstructions: pgText('tpago_instructions'), tpagoInstructionsEs: pgText('tpago_instructions_es'), bankTransferEnabled: pgInteger('bank_transfer_enabled').notNull().default(0), bankName: varchar('bank_name', { length: 255 }), bankAccountHolder: varchar('bank_account_holder', { length: 255 }), bankAccountNumber: varchar('bank_account_number', { length: 100 }), bankAlias: varchar('bank_alias', { length: 100 }), bankPhone: varchar('bank_phone', { length: 50 }), bankNotes: pgText('bank_notes'), bankNotesEs: pgText('bank_notes_es'), lightningEnabled: pgInteger('lightning_enabled').notNull().default(1), cashEnabled: pgInteger('cash_enabled').notNull().default(1), cashInstructions: pgText('cash_instructions'), cashInstructionsEs: pgText('cash_instructions_es'), updatedAt: timestamp('updated_at').notNull(), updatedBy: uuid('updated_by').references(() => pgUsers.id), }); // Event-specific payment overrides export const pgEventPaymentOverrides = pgTable('event_payment_overrides', { id: uuid('id').primaryKey(), eventId: uuid('event_id').notNull().references(() => pgEvents.id), tpagoEnabled: pgInteger('tpago_enabled'), tpagoLink: varchar('tpago_link', { length: 500 }), tpagoInstructions: pgText('tpago_instructions'), tpagoInstructionsEs: pgText('tpago_instructions_es'), bankTransferEnabled: pgInteger('bank_transfer_enabled'), bankName: varchar('bank_name', { length: 255 }), bankAccountHolder: varchar('bank_account_holder', { length: 255 }), bankAccountNumber: varchar('bank_account_number', { length: 100 }), bankAlias: varchar('bank_alias', { length: 100 }), bankPhone: varchar('bank_phone', { length: 50 }), bankNotes: pgText('bank_notes'), bankNotesEs: pgText('bank_notes_es'), lightningEnabled: pgInteger('lightning_enabled'), cashEnabled: pgInteger('cash_enabled'), cashInstructions: pgText('cash_instructions'), cashInstructionsEs: pgText('cash_instructions_es'), createdAt: timestamp('created_at').notNull(), updatedAt: timestamp('updated_at').notNull(), }); export const pgContacts = pgTable('contacts', { id: uuid('id').primaryKey(), name: varchar('name', { length: 255 }).notNull(), email: varchar('email', { length: 255 }).notNull(), message: pgText('message').notNull(), status: varchar('status', { length: 20 }).notNull().default('new'), createdAt: timestamp('created_at').notNull(), }); export const pgEmailSubscribers = pgTable('email_subscribers', { id: uuid('id').primaryKey(), email: varchar('email', { length: 255 }).notNull().unique(), name: varchar('name', { length: 255 }), status: varchar('status', { length: 20 }).notNull().default('active'), createdAt: timestamp('created_at').notNull(), }); export const pgMedia = pgTable('media', { id: uuid('id').primaryKey(), fileUrl: varchar('file_url', { length: 500 }).notNull(), type: varchar('type', { length: 20 }).notNull(), relatedId: uuid('related_id'), relatedType: varchar('related_type', { length: 50 }), createdAt: timestamp('created_at').notNull(), }); export const pgAuditLogs = pgTable('audit_logs', { id: uuid('id').primaryKey(), userId: uuid('user_id').references(() => pgUsers.id), action: varchar('action', { length: 100 }).notNull(), target: varchar('target', { length: 100 }), targetId: uuid('target_id'), details: pgText('details'), timestamp: timestamp('timestamp').notNull(), }); export const pgEmailTemplates = pgTable('email_templates', { id: uuid('id').primaryKey(), name: varchar('name', { length: 255 }).notNull().unique(), slug: varchar('slug', { length: 100 }).notNull().unique(), subject: varchar('subject', { length: 500 }).notNull(), subjectEs: varchar('subject_es', { length: 500 }), bodyHtml: pgText('body_html').notNull(), bodyHtmlEs: pgText('body_html_es'), bodyText: pgText('body_text'), bodyTextEs: pgText('body_text_es'), description: pgText('description'), variables: pgText('variables'), // JSON array of available variables isSystem: pgInteger('is_system').notNull().default(0), isActive: pgInteger('is_active').notNull().default(1), createdAt: timestamp('created_at').notNull(), updatedAt: timestamp('updated_at').notNull(), }); export const pgEmailLogs = pgTable('email_logs', { id: uuid('id').primaryKey(), templateId: uuid('template_id').references(() => pgEmailTemplates.id), eventId: uuid('event_id').references(() => pgEvents.id), recipientEmail: varchar('recipient_email', { length: 255 }).notNull(), recipientName: varchar('recipient_name', { length: 255 }), subject: varchar('subject', { length: 500 }).notNull(), bodyHtml: pgText('body_html'), status: varchar('status', { length: 20 }).notNull().default('pending'), errorMessage: pgText('error_message'), sentAt: timestamp('sent_at'), sentBy: uuid('sent_by').references(() => pgUsers.id), createdAt: timestamp('created_at').notNull(), }); export const pgEmailSettings = pgTable('email_settings', { id: uuid('id').primaryKey(), key: varchar('key', { length: 100 }).notNull().unique(), value: pgText('value').notNull(), updatedAt: timestamp('updated_at').notNull(), }); // Export the appropriate schema based on DB_TYPE export const users = dbType === 'postgres' ? pgUsers : sqliteUsers; export const events = dbType === 'postgres' ? pgEvents : sqliteEvents; export const tickets = dbType === 'postgres' ? pgTickets : sqliteTickets; export const payments = dbType === 'postgres' ? pgPayments : sqlitePayments; export const contacts = dbType === 'postgres' ? pgContacts : sqliteContacts; export const emailSubscribers = dbType === 'postgres' ? pgEmailSubscribers : sqliteEmailSubscribers; export const media = dbType === 'postgres' ? pgMedia : sqliteMedia; export const auditLogs = dbType === 'postgres' ? pgAuditLogs : sqliteAuditLogs; export const emailTemplates = dbType === 'postgres' ? pgEmailTemplates : sqliteEmailTemplates; export const emailLogs = dbType === 'postgres' ? pgEmailLogs : sqliteEmailLogs; export const emailSettings = dbType === 'postgres' ? pgEmailSettings : sqliteEmailSettings; export const paymentOptions = dbType === 'postgres' ? pgPaymentOptions : sqlitePaymentOptions; export const eventPaymentOverrides = dbType === 'postgres' ? pgEventPaymentOverrides : sqliteEventPaymentOverrides; export const magicLinkTokens = dbType === 'postgres' ? pgMagicLinkTokens : sqliteMagicLinkTokens; export const userSessions = dbType === 'postgres' ? pgUserSessions : sqliteUserSessions; export const invoices = dbType === 'postgres' ? pgInvoices : sqliteInvoices; // Type exports export type User = typeof sqliteUsers.$inferSelect; export type NewUser = typeof sqliteUsers.$inferInsert; export type Event = typeof sqliteEvents.$inferSelect; export type NewEvent = typeof sqliteEvents.$inferInsert; export type Ticket = typeof sqliteTickets.$inferSelect; export type NewTicket = typeof sqliteTickets.$inferInsert; export type Payment = typeof sqlitePayments.$inferSelect; export type NewPayment = typeof sqlitePayments.$inferInsert; export type Contact = typeof sqliteContacts.$inferSelect; export type NewContact = typeof sqliteContacts.$inferInsert; export type EmailTemplate = typeof sqliteEmailTemplates.$inferSelect; export type NewEmailTemplate = typeof sqliteEmailTemplates.$inferInsert; export type EmailLog = typeof sqliteEmailLogs.$inferSelect; export type NewEmailLog = typeof sqliteEmailLogs.$inferInsert; export type PaymentOptions = typeof sqlitePaymentOptions.$inferSelect; export type NewPaymentOptions = typeof sqlitePaymentOptions.$inferInsert; export type EventPaymentOverride = typeof sqliteEventPaymentOverrides.$inferSelect; export type NewEventPaymentOverride = typeof sqliteEventPaymentOverrides.$inferInsert; export type MagicLinkToken = typeof sqliteMagicLinkTokens.$inferSelect; export type NewMagicLinkToken = typeof sqliteMagicLinkTokens.$inferInsert; export type UserSession = typeof sqliteUserSessions.$inferSelect; export type NewUserSession = typeof sqliteUserSessions.$inferInsert; export type Invoice = typeof sqliteInvoices.$inferSelect; export type NewInvoice = typeof sqliteInvoices.$inferInsert;