Database Schema
Complete reference of the PostgreSQL database schema
Database Schema
This page documents the complete PostgreSQL database schema for Alita Robot.
Overview
- Application Tables: 38
- Migration Metadata:
schema_migrations - Database Type: PostgreSQL
- ORM: GORM
- Migration Tool: Custom SQL migration runner (
alita/db/migrations/runner.go) - Migrations: Timestamped forward SQL files using
YYYYMMDDHHMMSS_description.sqlnaming
Design Patterns
Surrogate Key Pattern
All tables use an auto-incremented id field as the primary key (internal identifier), while external identifiers like user_id and chat_id (Telegram IDs) are stored with unique constraints.
Benefits:
- Decouples internal schema from external systems
- Provides stability if external IDs change
- Simplifies GORM operations with consistent integer primary keys
- Better performance for joins and indexing
Chat Membership
Chat membership is managed via the JSONB users column on the chats table (an Int64Array of user IDs). The physical chat_users join table was dropped by migration and there is no corresponding GORM model.
Tables
admin
Stores admin settings per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
anon_admin |
BOOLEAN |
YES | false |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Indexes
idx_admin_chat_id(unique, onchat_id)
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
ai_spam_settings
Stores the per-chat opt-in switch for the AI spam filter. A missing row means the filter is off; /aispam on and /aispam off both upsert the row.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGSERIAL |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
enabled |
BOOLEAN |
NO | FALSE |
— |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
updated_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Indexes
UNIQUE(chat_id)— no separate index; the constraint index serves every lookup.
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
Note: The flag lives in the database rather than Redis: it is the chat’s moderation policy, and a cache flush must not silently stop the filter in a chat an admin enabled it for.
antiflood_settings
Stores anti-flood configuration per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
flood_limit |
BIGINT |
YES | 5 |
CHECK (flood_limit >= 0) |
action |
TEXT |
YES | 'mute' |
CHECK (action IN ('mute','ban','kick','warn','tban','tmute')) |
delete_antiflood_message |
BOOLEAN |
YES | false |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Note: Previous
modeandlimitcolumns were dropped by migrations (20260420120000_consolidate_duplicate_fields.sql,20250814100000_fix_antiflood_column_duplication.sql). Onlyflood_limitis used.
Indexes
idx_antiflood_settings_chat_id(unique, onchat_id)idx_antiflood_chat_flood_active(conditional:flood_limit > 0)
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
blacklists
Stores blacklisted words and their actions per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE (composite: chat_id, word) |
word |
TEXT |
NO | — | UNIQUE (composite: chat_id, word) |
action |
TEXT |
YES | 'warn' |
CHECK (action IN ('warn','mute','ban','kick','tban','tmute','delete','none')) |
reason |
TEXT |
YES | — | — |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Indexes
uk_blacklists_chat_word(unique constraint:chat_id,word)
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
captcha_attempts
Tracks active captcha verification attempts for users.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGSERIAL |
NO | auto-increment | PRIMARY KEY |
user_id |
BIGINT |
NO | — | UNIQUE (composite: user_id, chat_id) |
chat_id |
BIGINT |
NO | — | UNIQUE (composite: user_id, chat_id) |
answer |
VARCHAR(255) |
NO | — | — |
attempts |
INTEGER |
YES | 0 |
— |
message_id |
BIGINT |
YES | — | — |
refresh_count |
INTEGER |
YES | 0 |
— |
expires_at |
TIMESTAMPTZ |
NO | — | CHECK (expires_at > created_at) |
created_at |
TIMESTAMPTZ |
YES | CURRENT_TIMESTAMP |
— |
updated_at |
TIMESTAMPTZ |
YES | CURRENT_TIMESTAMP |
— |
Indexes
uk_captcha_user_chat(unique index:user_id,chat_id; one attempt per user per chat)idx_captcha_attempts_chat_id
Foreign Keys
user_id→users(user_id)ON DELETE CASCADEchat_id→chats(chat_id)ON DELETE CASCADE
captcha_muted_users
Tracks users who failed captcha with mute action, for automatic un-mute scheduling.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGSERIAL |
NO | auto-increment | PRIMARY KEY |
user_id |
BIGINT |
NO | — | UNIQUE (composite: user_id, chat_id) |
chat_id |
BIGINT |
NO | — | UNIQUE (composite: user_id, chat_id) |
unmute_at |
TIMESTAMPTZ |
NO | — | — |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Indexes
uk_captcha_muted_user_chat(unique constraint:user_id,chat_id)idx_captcha_muted_users_chat_id(onchat_id)idx_captcha_unmute_at
Foreign Keys
user_id→users(user_id)ON DELETE CASCADEchat_id→chats(chat_id)ON DELETE CASCADE
captcha_settings
Stores captcha configuration per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGSERIAL |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
enabled |
BOOLEAN |
YES | FALSE |
— |
captcha_mode |
VARCHAR(10) |
YES | 'math' |
CHECK (captcha_mode IN ('math','text')) |
timeout |
INTEGER |
YES | 2 |
CHECK (timeout BETWEEN 1 AND 10) |
failure_action |
VARCHAR(10) |
YES | 'kick' |
CHECK (failure_action IN ('kick','ban','mute')) |
max_attempts |
INTEGER |
YES | 3 |
CHECK (max_attempts BETWEEN 1 AND 10) |
created_at |
TIMESTAMP |
YES | CURRENT_TIMESTAMP |
— |
updated_at |
TIMESTAMP |
YES | CURRENT_TIMESTAMP |
— |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE
channels
Stores channel metadata and linked channel relationships.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
channel_id |
BIGINT |
YES | — | — |
channel_name |
TEXT |
YES | — | — |
username |
TEXT |
YES | — | UNIQUE on LOWER(username) where username <> '' (case-insensitive) |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Indexes
idx_channels_chat_id(unique, onchat_id)idx_channels_username(unique, onLOWER(username)WHEREusername <> ''; migration20260730010000)
Foreign Keys
Note: All foreign key constraints on this table have been dropped by migrations (
20260117104821_fix_invalid_channels_fk_constraint.sql,20260117120000_drop_channels_chat_fk.sql). Thechat_idcolumn stores the channel’s own Telegram ID for identification.
chats
Main table storing chat/group information.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
chat_name |
TEXT |
YES | — | — |
language |
TEXT |
YES | — | — |
users |
JSONB |
YES | — | — |
is_inactive |
BOOLEAN |
YES | false |
— |
last_activity |
TIMESTAMPTZ |
YES | CURRENT_TIMESTAMP |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Indexes
uk_chats_chat_id(unique constraint onchat_id)idx_chats_covering(onchat_idINCLUDElanguage,is_inactive)idx_chats_activity_status
connection
Per-user connection state (a user has at most one connected chat).
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
user_id |
BIGINT |
NO | — | UNIQUE |
chat_id |
BIGINT |
NO | — | — |
connected |
BOOLEAN |
YES | false |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Indexes
uk_connection_user_id(unique constraint onuser_id; one saved connection per user)idx_connection_chat_id(onchat_id; backs the foreign key)
Foreign Keys
user_id→users(user_id)ON DELETE CASCADE ON UPDATE CASCADEchat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
connection_settings
Chat-level connection configuration.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
allow_connect |
BOOLEAN |
YES | true |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
devs
Bot developers and sudo users.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
user_id |
BIGINT |
NO | — | UNIQUE |
is_dev |
BOOLEAN |
YES | false |
— |
sudo |
BOOLEAN |
YES | false |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
user_id→users(user_id)ON DELETE CASCADE ON UPDATE CASCADE
disable
Per-command disable state for chats.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE (composite: chat_id, command) |
command |
TEXT |
NO | — | UNIQUE (composite: chat_id, command) |
disabled |
BOOLEAN |
YES | true |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
disable_chat_settings
Chat-level disable configuration for command deletion behavior.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
delete_commands |
BOOLEAN |
NO | false |
— |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
updated_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
federations
Federation definitions; one per owner.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGSERIAL |
NO | auto-increment | PRIMARY KEY |
fed_id |
VARCHAR(36) |
NO | — | — |
owner_id |
BIGINT |
NO | — | — |
name |
VARCHAR(64) |
NO | — | — |
require_reason |
BOOLEAN |
NO | FALSE |
— |
notify_owner |
BOOLEAN |
NO | TRUE |
— |
log_chat_id |
BIGINT |
NO | 0 |
— |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
updated_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Indexes
idx_federations_fed_id(unique, onfed_id)idx_federations_owner_id(unique, onowner_id)
Foreign Keys
owner_id→users(user_id)ON DELETE CASCADE ON UPDATE CASCADE
federation_admins
Federation admins other than the owner.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGSERIAL |
NO | auto-increment | PRIMARY KEY |
fed_id |
VARCHAR(36) |
NO | — | — |
user_id |
BIGINT |
NO | — | — |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Indexes
idx_fed_admins_fed_user(unique:fed_id,user_id)idx_federation_admins_user_id(onuser_id)
Foreign Keys
fed_id→federations(fed_id)ON DELETE CASCADE ON UPDATE CASCADEuser_id→users(user_id)ON DELETE CASCADE ON UPDATE CASCADE
federation_bans
Users banned across a federation.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGSERIAL |
NO | auto-increment | PRIMARY KEY |
fed_id |
VARCHAR(36) |
NO | — | — |
user_id |
BIGINT |
NO | — | — |
reason |
TEXT |
NO | '' |
— |
banned_by |
BIGINT |
NO | — | — |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
updated_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Indexes
idx_fed_bans_fed_user(unique:fed_id,user_id)idx_federation_bans_user_id(onuser_id)
Foreign Keys
fed_id→federations(fed_id)ON DELETE CASCADE ON UPDATE CASCADEuser_id→users(user_id)ON DELETE CASCADE ON UPDATE CASCADE
federation_chats
Chats that have joined a federation.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGSERIAL |
NO | auto-increment | PRIMARY KEY |
fed_id |
VARCHAR(36) |
NO | — | — |
chat_id |
BIGINT |
NO | — | — |
quiet |
BOOLEAN |
NO | FALSE |
— |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
updated_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Indexes
idx_federation_chats_chat_id(unique, onchat_id; a chat joins at most one federation)idx_federation_chats_fed_id(onfed_id)
Foreign Keys
fed_id→federations(fed_id)ON DELETE CASCADE ON UPDATE CASCADEchat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
federation_subs
Federation-to-federation ban subscriptions.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGSERIAL |
NO | auto-increment | PRIMARY KEY |
fed_id |
VARCHAR(36) |
NO | — | — |
subscribed_fed_id |
VARCHAR(36) |
NO | — | — |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Indexes
idx_fed_subs_pair(unique:fed_id,subscribed_fed_id)idx_federation_subs_subscribed_fed_id(onsubscribed_fed_id)
Foreign Keys
fed_id→federations(fed_id)ON DELETE CASCADE ON UPDATE CASCADEsubscribed_fed_id→federations(fed_id)ON DELETE CASCADE ON UPDATE CASCADE
filters
Custom keyword filters per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE (composite: chat_id, keyword) |
keyword |
TEXT |
NO | — | UNIQUE (composite: chat_id, keyword) |
filter_reply |
TEXT |
YES | — | — |
msgtype |
BIGINT |
YES | — | — |
fileid |
TEXT |
YES | — | — |
nonotif |
BOOLEAN |
YES | false |
— |
filter_buttons |
JSONB |
YES | — | — |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
greetings
Welcome and goodbye message settings per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
clean_service_settings |
BOOLEAN |
YES | false |
— |
welcome_clean_old |
BOOLEAN |
YES | false |
— |
welcome_last_msg_id |
BIGINT |
YES | — | — |
welcome_enabled |
BOOLEAN |
YES | true |
— |
welcome_text |
TEXT |
YES | — | — |
welcome_file_id |
TEXT |
YES | — | — |
welcome_type |
BIGINT |
YES | — (GORM writes 1 on insert) | — |
welcome_btns |
JSONB |
YES | — | — |
goodbye_clean_old |
BOOLEAN |
YES | false |
— |
goodbye_last_msg_id |
BIGINT |
YES | — | — |
goodbye_enabled |
BOOLEAN |
YES | true |
— |
goodbye_text |
TEXT |
YES | — | — |
goodbye_file_id |
TEXT |
YES | — | — |
goodbye_type |
BIGINT |
YES | — (GORM writes 1 on insert) | — |
goodbye_btns |
JSONB |
YES | — | — |
auto_approve |
BOOLEAN |
YES | false |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
locks
Locked permissions per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE (composite: chat_id, lock_type) |
lock_type |
TEXT |
NO | — | UNIQUE (composite: chat_id, lock_type) |
locked |
BOOLEAN |
YES | false |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
log_channels
Per-chat admin log channel and enabled categories.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGSERIAL |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | — |
log_channel_id |
BIGINT |
NO | — | — |
cat_settings |
BOOLEAN |
NO | TRUE |
— |
cat_admin |
BOOLEAN |
NO | TRUE |
— |
cat_user |
BOOLEAN |
NO | TRUE |
— |
cat_automated |
BOOLEAN |
NO | TRUE |
— |
cat_reports |
BOOLEAN |
NO | TRUE |
— |
cat_other |
BOOLEAN |
NO | TRUE |
— |
cat_warns |
BOOLEAN |
NO | FALSE |
— |
cat_joins |
BOOLEAN |
NO | FALSE |
— |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
updated_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Indexes
idx_log_channels_chat_id(unique, onchat_id)
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
notes
Saved notes/tags per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE (composite: chat_id, note_name) |
note_name |
TEXT |
NO | — | UNIQUE (composite: chat_id, note_name) |
note_content |
TEXT |
YES | — | — |
file_id |
TEXT |
YES | — | — |
msg_type |
BIGINT |
YES | — | — |
buttons |
JSONB |
YES | — | — |
admin_only |
BOOLEAN |
YES | false |
— |
private_only |
BOOLEAN |
YES | false |
— |
group_only |
BOOLEAN |
YES | false |
— |
web_preview |
BOOLEAN |
YES | true |
— |
is_protected |
BOOLEAN |
YES | false |
— |
no_notif |
BOOLEAN |
YES | false |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
notes_settings
Note settings per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
private |
BOOLEAN |
YES | false |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
pins
Pinned message settings per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
msg_id |
BIGINT |
YES | — | — |
clean_linked |
BOOLEAN |
YES | false |
— |
anti_channel_pin |
BOOLEAN |
YES | false |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
reactions
Stores per-chat keyword-to-emoji reaction mappings.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGSERIAL |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE (composite: chat_id, keyword) |
keyword |
TEXT |
NO | — | UNIQUE (composite: chat_id, keyword) |
emoji |
TEXT |
NO | — | — |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
updated_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Indexes
- Unique composite index on (
chat_id,keyword)
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
report_chat_settings
Report settings per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
enabled |
BOOLEAN |
YES | true |
— |
status |
BOOLEAN |
YES | true |
— |
blocked_list |
JSONB |
YES | — | — |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
report_user_settings
Report settings per user.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
user_id |
BIGINT |
NO | — | UNIQUE |
enabled |
BOOLEAN |
YES | true |
— |
status |
BOOLEAN |
YES | true |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
user_id→users(user_id)ON DELETE CASCADE ON UPDATE CASCADE
rules
Chat rules text.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
rules |
TEXT |
YES | — | — |
rules_btn |
TEXT |
YES | — | — |
private |
BOOLEAN |
YES | false |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
stored_messages
Stores messages sent by users before completing captcha verification.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGSERIAL |
NO | auto-increment | PRIMARY KEY |
user_id |
BIGINT |
NO | — | — |
chat_id |
BIGINT |
NO | — | — |
message_type |
INTEGER |
NO | 1 |
— |
content |
TEXT |
YES | — | — |
file_id |
TEXT |
YES | — | — |
caption |
TEXT |
YES | — | — |
attempt_id |
BIGINT |
NO | — | — |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Indexes
idx_stored_attempt
Foreign Keys
attempt_id→captcha_attempts(id)ON DELETE CASCADE
users
Main table storing user information.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
user_id |
BIGINT |
NO | — | UNIQUE |
username |
TEXT |
YES | — | INDEXED |
name |
TEXT |
YES | — | — |
language |
TEXT |
YES | 'en' |
— |
last_activity |
TIMESTAMPTZ |
YES | CURRENT_TIMESTAMP |
— |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Indexes
uk_users_user_id(unique constraint onuser_id)idx_users_user_name(onusername)idx_users_last_activity
approved_users
Users approved for a chat (immune to anti-spam).
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE (composite: user_id, chat_id) |
user_id |
BIGINT |
NO | — | UNIQUE (composite: user_id, chat_id) |
reason |
TEXT |
YES | '' |
— |
approved_by |
BIGINT |
NO | 0 |
— |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
updated_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Indexes
idx_approved_users_chat_id
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
antiraid_settings
Anti-raid configuration per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
raid_time |
INT |
NO | 21600 |
CHECK (raid_time >= 0) |
raid_action_time |
INT |
NO | 3600 |
CHECK (raid_action_time >= 0) |
auto_antiraid_threshold |
INT |
NO | 0 |
CHECK (auto_antiraid_threshold >= 0) |
created_at |
TIMESTAMPTZ |
YES | NOW() |
— |
updated_at |
TIMESTAMPTZ |
YES | NOW() |
— |
Indexes
antiraid_settings_chat_id_key(unique constraint onchat_id)
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
warns_settings
Warning system settings per chat. warn_action_time is the tban/tmute duration in seconds; warn_expiry is how long a warn counts, in seconds, with 0 meaning warns never expire. Two CHECKs cover warn_mode: chk_warn_mode (NOT VALID) also accepts '', but the older validated chk_warns_mode does not, so PostgreSQL rejects an empty string.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE |
warn_limit |
BIGINT |
YES | 3 |
CHECK (warn_limit > 0) |
warn_mode |
TEXT |
YES | — | CHECK (warn_mode IS NULL OR warn_mode IN ('ban','kick','mute','tban','tmute')) |
warn_action_time |
INTEGER |
NO | 0 |
CHECK (warn_action_time >= 0) |
warn_expiry |
INTEGER |
NO | 0 |
CHECK (warn_expiry >= 0) |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
warns_users
User warnings per chat.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
user_id |
BIGINT |
NO | — | UNIQUE (composite: user_id, chat_id) |
chat_id |
BIGINT |
NO | — | UNIQUE (composite: user_id, chat_id) |
num_warns |
BIGINT |
YES | 0 |
CHECK (num_warns >= 0) |
warns |
JSONB |
YES | — | — |
created_at |
TIMESTAMPTZ |
YES | — | — |
updated_at |
TIMESTAMPTZ |
YES | — | — |
Indexes
- Unique composite index on (
user_id,chat_id) (uk_warns_users_user_chat) idx_warns_users_chat_id
Foreign Keys
user_id→users(user_id)ON DELETE CASCADE ON UPDATE CASCADEchat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
warn_entries
One row per warn: its reason, who issued it and when. warns_users keeps the count and the warns reasons array, which the bot still writes for older releases.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
user_id |
BIGINT |
NO | — | — |
chat_id |
BIGINT |
NO | — | — |
reason |
TEXT |
NO | '' |
— |
warned_by |
BIGINT |
NO | 0 |
— |
anonymous |
BOOLEAN |
NO | FALSE |
— |
warned_at |
TIMESTAMPTZ |
YES | — | — |
warned_by = 0 with a NULL warned_at marks a warn issued before this table existed. anonymous hides the issuer, an anonymous admin; backups export warned_by = 0 for those rows. Backups export a warn the bot issued itself as warned_by = -1 (models.WarnedByBot), because the bot’s own ID means nothing to another bot account, and importing such a backup stores -1 unchanged. /warns shows these warns, and warns whose warned_by is the bot’s own ID, as automatic.
Indexes
idx_warn_entries_chat_user(onchat_id,user_id,id)idx_warn_entries_user_id(onuser_id)
Foreign Keys
user_id→users(user_id)ON DELETE CASCADE ON UPDATE CASCADEchat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
There is no foreign key to warns_users: the bot deletes a user’s entries in the same transaction as their warns_users row.
ban_records
One row per ban the bot issues in a chat: /ban and its variants, the restrict, report and join-request buttons, the warn limit, antiflood, blacklists, antiraid, captcha, federation bans, the bots lock and the spam databases. Bans made in Telegram’s own interface are not recorded, and the table starts empty. /baninfo reads it. It is not part of /export.
Columns
| Column | Type | Nullable | Default | Constraints |
|---|---|---|---|---|
id |
BIGINT |
NO | auto-increment | PRIMARY KEY |
chat_id |
BIGINT |
NO | — | UNIQUE while open (composite: chat_id, user_id) |
user_id |
BIGINT |
NO | — | UNIQUE while open (composite: chat_id, user_id) |
banned_by |
BIGINT |
NO | 0 |
— |
reason |
TEXT |
NO | '' |
— |
source |
VARCHAR(16) |
NO | — | CHECK (manual, warn, antiflood, blacklist, antiraid, captcha, fed, lock, report, join_request, spamdb) |
source_detail |
VARCHAR(64) |
NO | '' |
— |
created_at |
TIMESTAMPTZ |
NO | NOW() |
— |
until |
TIMESTAMPTZ |
YES | — | — |
lifted_at |
TIMESTAMPTZ |
YES | — | — |
lifted_by |
BIGINT |
YES | — | — |
banned_by = 0 marks a ban the bot made on its own. source_detail holds the federation ID of a federation ban, the spam database’s name, or the lock (bots). until is the end of a temporary ban, NULL for a permanent one; an open row past until counts as expired. The bot sets lifted_at and lifted_by when it unbans the user. Only IDs are stored; names are read from users when shown.
A chat holds at most one open row (lifted_at IS NULL) per user: a repeat ban updates it. An hourly sweep deletes lifted rows 90 days after lifted_at and expired temporary bans 90 days after until; open permanent bans are kept.
Indexes
- Unique partial index on (
chat_id,user_id) wherelifted_at IS NULL(uq_ban_records_open) idx_ban_records_chat_user_id(onchat_id,user_id,id)idx_ban_records_lifted_at(onlifted_at, wherelifted_at IS NOT NULL)idx_ban_records_open_until(onuntil, wherelifted_at IS NULL AND until IS NOT NULL)
Foreign Keys
chat_id→chats(chat_id)ON DELETE CASCADE ON UPDATE CASCADE
user_id, banned_by and lifted_by have no foreign key: a banned user often has no users row.
Entity Relationships
Core Entities
- Users: Telegram users who interact with the bot
- Chats: Telegram groups/channels managed by the bot
- Chat Users: Managed via JSONB
usersarray on thechatstable (not a physical join table)
Relationship Patterns
- User ↔ Chat: Many-to-many through JSONB
usersfield onchats - Chat → Settings: One-to-one (module-specific settings like
warns_settings,antiflood_settings,pins) - Chat → Content: One-to-many (
filters,notes,blacklists,reactions) - User → Chat Warnings: One-to-many through
warns_users, onewarn_entriesrow per warn - Chat → Captcha: One-to-one (
captcha_settings) with one-to-many attempts (captcha_attempts) - User → Federation: one-to-one ownership (
federations.owner_idis unique) - Federation → Members: one-to-many (
federation_admins,federation_bans,federation_chats), plus federation-to-federation subscriptions viafederation_subs - Chat → Federation: at most one (
federation_chats.chat_idis unique) - Chat → Log channel: one-to-one (
log_channels) - Chat → Ban records: one-to-many (
ban_records), at most one open row per user