Database Schema Reference
Database Schema Reference
Complete reference for all database tables used by the Alphapy Discord Bot.
Overview
The bot uses PostgreSQL for persistent storage. Schema is managed via Alembic migrations. All tables support multi-guild architecture via guild_id columns.
Tables
bot_settings
Guild-specific bot configuration settings.
Columns:
guild_id(BIGINT, NOT NULL): Discord guild IDscope(TEXT, NOT NULL): Setting scope (e.g., “system”, “reminders”, “gpt”)key(TEXT, NOT NULL): Setting keyvalue(JSONB, NOT NULL): Setting value (stored as JSON)value_type(TEXT): Type of value (e.g., “channel”, “bool”, “int”)updated_by(BIGINT): User ID who last updated this settingupdated_at(TIMESTAMPTZ): Last update timestamp
Primary Key: (guild_id, scope, key)
Indexes: None (primary key provides efficient lookups)
settings_history
Audit trail for settings changes.
Columns:
id(SERIAL PRIMARY KEY)guild_id(BIGINT, NOT NULL)scope(TEXT, NOT NULL)key(TEXT, NOT NULL)old_value(JSONB): Previous valuenew_value(JSONB, NOT NULL): New valuevalue_type(TEXT)changed_by(BIGINT): User ID who made the changechanged_at(TIMESTAMPTZ): Change timestampchange_type(TEXT, NOT NULL): Type of change (e.g., “created”, “updated”, “deleted”, “rollback”)
Indexes:
idx_settings_history_guild_scope_keyon(guild_id, scope, key)idx_settings_history_changed_atonchanged_at
reminders
Scheduled reminders (recurring and one-off events).
Columns:
id(SERIAL PRIMARY KEY)guild_id(BIGINT, NOT NULL)name(TEXT, NOT NULL): Reminder namechannel_id(BIGINT, NOT NULL): Channel to send reminder totime(TIME): Reminder trigger time (T−60 for one-off, reminder time for recurring)call_time(TIME): Event display time (T0, what user sees)days(TEXT[]): Days of week (0=Mon..6=Sun); empty for one-off eventsmessage(TEXT): Reminder message contentcreated_by(BIGINT): User ID who created the reminderorigin_channel_id(BIGINT): Original channel where embed was detectedorigin_message_id(BIGINT): Original message IDevent_time(TIMESTAMPTZ): One-off event timestamp (NULL for recurring)location(TEXT): Event locationlast_sent_at(TIMESTAMPTZ): Last send timestamp (for idempotency)image_url(TEXT): Optional image or banner URL (Premium feature)
Indexes:
idx_reminders_timeontimeidx_reminders_event_timeonevent_time
Notes:
- One-off events:
event_timeis set,daysis empty - Recurring events:
event_timeis NULL,dayscontains weekday numbers timeis the reminder trigger time (T−60),call_timeis the event time (T0)- Premium: reminders with
image_urlrequire an active premium subscription for the creator
premium_subs
Premium subscription status (local fallback when Core-API is unavailable). One active subscription per user; it applies to a single guild. Users can move it via /premium_transfer (or dashboard later).
Columns:
id(SERIAL PRIMARY KEY)user_id(BIGINT, NOT NULL): Discord user IDguild_id(BIGINT, NOT NULL): Guild where Premium is active (can be updated for transfer)tier(TEXT, NOT NULL):monthly,yearly,lifetimestatus(TEXT, NOT NULL):active,cancelled,expiredstripe_subscription_id(TEXT): External subscription ID (for support/cancellation only)expires_at(TIMESTAMPTZ): NULL for lifetime or N/Acreated_at(TIMESTAMPTZ): When the record was created
Indexes:
idx_premium_subs_user_guildon(user_id, guild_id)idx_premium_subs_guild_statuson(guild_id, status)idx_premium_subs_one_active_per_user: unique on(user_id)WHEREstatus = 'active'(enforces one active per user)
GDPR: This table stores only access-control data (user_id, guild_id, tier, status, optional external ID, expiry). No payment details, email, or other PII are stored here.
onboarding
User onboarding responses per guild.
Columns:
guild_id(BIGINT, NOT NULL)user_id(BIGINT, NOT NULL)responses(JSONB): User responses (answers by question index, plus personalization keys)timestamp(TIMESTAMP): Response timestamp
Primary Key: (guild_id, user_id)
Notes:
- Responses stored as JSONB with flexible structure
- Supports multi-select, text input, email validation, and follow-up questions
-
Fixed personalization keys (synthetic steps after guild questions): personalized_opt_in("full""events_only""no"),preferred_language(e.g."en","nl", or"other: <text>")
guild_onboarding_questions
Onboarding question definitions per guild.
Columns:
id(SERIAL PRIMARY KEY)guild_id(BIGINT, NOT NULL)step_order(INTEGER, NOT NULL): Question orderquestion(TEXT, NOT NULL): Question textquestion_type(TEXT, NOT NULL): Type (select, multiselect, text, email)options(JSONB): Options for select/multiselect questionsfollowup(JSONB): Conditional follow-up questionsrequired(BOOLEAN): Whether question is requiredenabled(BOOLEAN): Whether question is enabledcreated_at(TIMESTAMP): Creation timestampupdated_at(TIMESTAMP): Last update timestamp
Unique Constraint: (guild_id, step_order)
guild_rules
Guild rules for onboarding display.
Columns:
id(SERIAL PRIMARY KEY)guild_id(BIGINT, NOT NULL)rule_order(INTEGER, NOT NULL): Display ordertitle(TEXT, NOT NULL): Rule titledescription(TEXT, NOT NULL): Rule descriptionthumbnail_url(TEXT, nullable): Image shown right/top in embed (rechts)image_url(TEXT, nullable): Image shown at bottom in embed (onderaan)enabled(BOOLEAN): Whether rule is enabledcreated_at(TIMESTAMP): Creation timestampupdated_at(TIMESTAMP): Last update timestamp
Unique Constraint: (guild_id, rule_order)
support_tickets
Support ticket system data.
Columns:
id(SERIAL PRIMARY KEY)guild_id(BIGINT, NOT NULL)user_id(BIGINT, NOT NULL): Ticket creatorusername(TEXT): Username at creation timedescription(TEXT, NOT NULL): Ticket descriptionstatus(TEXT): Ticket status (open, claimed, waiting_for_user, escalated, closed, archived)created_at(TIMESTAMPTZ): Creation timestampchannel_id(BIGINT): Private channel ID for this ticketclaimed_by(BIGINT): User ID who claimed the ticketclaimed_at(TIMESTAMPTZ): Claim timestampupdated_at(TIMESTAMPTZ): Last update timestampescalated_to(BIGINT): Role ID for escalationarchived_at(TIMESTAMPTZ): When the ticket was archived (NULL if not archived)archived_by(BIGINT): User ID who archived the ticket
Indexes:
idx_support_tickets_user_idonuser_ididx_support_tickets_statusonstatusidx_support_tickets_channel_idonchannel_id
verification_tickets
AI-assisted verification ticket metadata for payment/checkout verification.
Columns:
id(SERIAL PRIMARY KEY)guild_id(BIGINT, NOT NULL): Discord guild IDuser_id(BIGINT, NOT NULL): User who started the verificationchannel_id(BIGINT, NOT NULL): Private verification channel IDstatus(TEXT, NOT NULL, DEFAULT'pending'): Verification status (pending,verified,manual_review,error,closed_manual)ai_can_verify(BOOLEAN): Whether the AI considered the screenshot sufficient for auto-verificationai_needs_manual_review(BOOLEAN): Whether the AI requested human reviewai_reason(TEXT): Short, sanitized explanation of the AI decision (no raw payment details)created_at(TIMESTAMPTZ, DEFAULT NOW()): When the verification was createdresolved_at(TIMESTAMPTZ): When the verification was completed (NULL while pending)
Indexes:
idx_verification_tickets_guild_statuson(guild_id, status)idx_verification_tickets_channel_idonchannel_id
ticket_summaries
GPT-generated summaries of closed tickets.
Columns:
id(SERIAL PRIMARY KEY)ticket_id(INTEGER, NOT NULL): Reference to support_tickets.idsummary(TEXT, NOT NULL): GPT-generated summarysimilarity_key(TEXT): Key for detecting similar ticketscreated_at(TIMESTAMPTZ): Creation timestamp
Notes:
- Used for FAQ proposal generation when 3+ similar summaries appear
ticket_metrics
Ticket statistics snapshots.
Columns:
id(BIGSERIAL PRIMARY KEY)snapshot(JSONB): Full snapshot datascope(TEXT): Time scope (7d, 30d, all)counts(JSONB): Status → count mappingaverage_cycle_time(BIGINT): Average ticket lifecycle in secondstriggered_by(BIGINT): User ID who triggered the snapshotcreated_at(TIMESTAMPTZ): Snapshot timestamp
faq_entries
FAQ entries for knowledge base.
Columns:
id(SERIAL PRIMARY KEY)title(TEXT): Entry titlesummary(TEXT): Entry summary/contentkeywords(TEXT[]): Search keywordscreated_at(TIMESTAMPTZ): Creation timestamp
Notes:
- Can be created manually or from ticket summaries via “Add to FAQ” button
faq_search_logs
FAQ search analytics.
Columns:
id(SERIAL PRIMARY KEY)user_id(BIGINT): User who searchedquery(TEXT): Search queryresults_count(INTEGER): Number of results returnedsearched_at(TIMESTAMPTZ): Search timestamp
audit_logs
Command usage analytics (automatically tracked).
Columns:
id(SERIAL PRIMARY KEY)guild_id(BIGINT, NOT NULL): Guild where command was executed (0 for DMs)user_id(BIGINT, NOT NULL): User who executed the commandcommand_name(TEXT, NOT NULL): Command namecommand_type(TEXT, NOT NULL): Type (‘slash’ or ‘text’)success(BOOLEAN): Whether command executed successfullyerror_message(TEXT): Error message if command failedcreated_at(TIMESTAMPTZ): Execution timestamp
Indexes:
idx_audit_logs_guild_createdon(guild_id, created_at)idx_audit_logs_commandon(command_name, created_at)
Notes:
- Automatically populated by event handlers in
bot.py(on_app_command_completion,on_command_completion, etc.) - Uses dedicated database connection pool created in bot’s event loop (not FastAPI’s loop)
- Initialized in
on_ready()event handler, persists across bot restarts - Used for command usage analytics, dashboard metrics, and
/command_statsDiscord command - Tracking is non-blocking and failures don’t affect command execution
health_check_history
Historical health check data for trend analysis.
Columns:
id(SERIAL PRIMARY KEY)service(TEXT, NOT NULL): Service nameversion(TEXT, NOT NULL): Bot versionuptime_seconds(INTEGER, NOT NULL): Uptime at check timedb_status(TEXT, NOT NULL): Database statusguild_count(INTEGER): Number of guildsactive_commands_24h(INTEGER): Commands executed in last 24hgpt_status(TEXT): GPT service statusdatabase_pool_size(INTEGER): Database pool sizechecked_at(TIMESTAMPTZ): Health check timestamp
Indexes:
idx_health_check_history_checked_atonchecked_at DESCidx_health_check_history_serviceon(service, checked_at DESC)
Notes:
- Automatically populated on each
/api/healthcall - Auto-cleanup: Records older than 30 days are automatically deleted
Database Connection Architecture
The bot uses asyncpg connection pools for all database operations, providing:
- Better concurrency: Multiple operations can run simultaneously
- Connection reuse: Efficient resource management
- Graceful error handling: Automatic retry and recovery from connection errors
- Event loop isolation: Command tracker uses dedicated pool in bot’s event loop
Connection Pool Configuration
Each component manages its own connection pool with appropriate size limits:
- FastAPI (
api.py): Main pool for API endpoints (shared with bot) - Command Tracker: Dedicated pool in bot’s event loop (min_size=1, max_size=5)
- Reminders Cog: Pool for reminder operations (max_size=10)
- Ticket Bot: Pool for ticket operations (max_size=10)
- FAQ Cog: Pool for FAQ operations (max_size=5)
- Embed Watcher: Pool for embed parsing (max_size=10)
- Other Cogs: Individual pools as needed (typically max_size=5)
- Verification Cog: Pool for verification ticket operations (max_size=10)
All pools include:
- Connection timeout handling
- Graceful shutdown on cog unload
- Error handling for connection failures
- Pool status checks before operations
Schema Management
All schema changes are managed via Alembic migrations. See migrations.md for migration workflow.
Current Migration: 001_initial (baseline)
To view current schema state:
alembic current
To apply migrations:
alembic upgrade head
Multi-Guild Architecture
All tables that store guild-specific data include a guild_id column:
bot_settingssettings_historyremindersonboardingguild_onboarding_questionsguild_rulessupport_ticketsaudit_logs
Tables without guild_id are global:
ticket_summaries(linked viaticket_id→support_tickets→guild_id)ticket_metrics(scope-based, not guild-specific)faq_entries(global knowledge base)faq_search_logs(analytics)health_check_history(system-wide)
Indexes
All tables have appropriate indexes for common query patterns:
- Primary keys provide efficient lookups
- Foreign key columns are indexed where applicable
- Time-based columns have indexes for range queries
- Composite indexes support multi-column filters
Data Retention
- Health check history: Auto-deletes records older than 30 days
- Audit logs: No automatic cleanup (can be configured per guild)
- Ticket summaries: No automatic cleanup
- Other tables: No automatic cleanup (manual maintenance recommended)
Backup Recommendations
Regular backups should include:
- All tables (full database backup recommended)
- Focus on
bot_settings,reminders,support_tickets,onboardingfor critical data audit_logsandhealth_check_historycan be excluded from frequent backups (analytics only)