Spaces:
Sleeping
Sleeping
| -- PostgreSQL DDL for Afool Group Bot (v1) | |
| -- Generated on 2026-01-31 | |
| CREATE EXTENSION IF NOT EXISTS pgcrypto; | |
| -- 1) Admins and sessions | |
| CREATE TABLE IF NOT EXISTS admins ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| email text UNIQUE NOT NULL, | |
| password_hash text NOT NULL, | |
| telegram_user_id bigint, | |
| role text NOT NULL DEFAULT 'admin', | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_admins_telegram_user_id | |
| ON admins (telegram_user_id); | |
| CREATE TABLE IF NOT EXISTS admin_sessions ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| admin_id uuid NOT NULL REFERENCES admins(id) ON DELETE CASCADE, | |
| token text UNIQUE NOT NULL, | |
| expires_at timestamptz NOT NULL, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_admin_sessions_admin_id | |
| ON admin_sessions (admin_id); | |
| CREATE INDEX IF NOT EXISTS idx_admin_sessions_expires_at | |
| ON admin_sessions (expires_at); | |
| -- 2) Groups and members | |
| CREATE TABLE IF NOT EXISTS groups ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| telegram_group_id bigint UNIQUE NOT NULL, | |
| title text NOT NULL, | |
| type text NOT NULL, | |
| owner_user_id bigint, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_groups_owner_user_id | |
| ON groups (owner_user_id); | |
| CREATE TABLE IF NOT EXISTS group_members ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| telegram_user_id bigint NOT NULL, | |
| username text, | |
| display_name text, | |
| role text NOT NULL DEFAULT 'member', | |
| joined_at timestamptz, | |
| updated_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE UNIQUE INDEX IF NOT EXISTS uq_group_members_group_user | |
| ON group_members (group_id, telegram_user_id); | |
| CREATE INDEX IF NOT EXISTS idx_group_members_group_id | |
| ON group_members (group_id); | |
| CREATE INDEX IF NOT EXISTS idx_group_members_group_role | |
| ON group_members (group_id, role); | |
| CREATE INDEX IF NOT EXISTS idx_group_members_group_updated_at | |
| ON group_members (group_id, updated_at); | |
| CREATE TABLE IF NOT EXISTS group_admins ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| telegram_user_id bigint NOT NULL, | |
| role text NOT NULL DEFAULT 'admin', | |
| synced_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE UNIQUE INDEX IF NOT EXISTS uq_group_admins_group_user | |
| ON group_admins (group_id, telegram_user_id); | |
| CREATE INDEX IF NOT EXISTS idx_group_admins_group_id | |
| ON group_admins (group_id); | |
| -- 3) Group config | |
| CREATE TABLE IF NOT EXISTS group_configs ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL UNIQUE REFERENCES groups(id) ON DELETE CASCADE, | |
| config_json jsonb NOT NULL DEFAULT '{}'::jsonb, | |
| updated_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_group_configs_config_json | |
| ON group_configs USING gin (config_json); | |
| -- 4) Verification | |
| CREATE TABLE IF NOT EXISTS verification_sessions ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| telegram_user_id bigint NOT NULL, | |
| verify_mode text NOT NULL, | |
| status text NOT NULL DEFAULT 'pending', | |
| challenge_json jsonb, | |
| expires_at timestamptz NOT NULL, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_verification_sessions_group_user | |
| ON verification_sessions (group_id, telegram_user_id); | |
| CREATE INDEX IF NOT EXISTS idx_verification_sessions_group_status | |
| ON verification_sessions (group_id, status); | |
| CREATE INDEX IF NOT EXISTS idx_verification_sessions_expires_at | |
| ON verification_sessions (expires_at); | |
| CREATE TABLE IF NOT EXISTS verification_attempts ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| session_id uuid NOT NULL REFERENCES verification_sessions(id) ON DELETE CASCADE, | |
| result text NOT NULL, | |
| reason text, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_verification_attempts_session_id | |
| ON verification_attempts (session_id); | |
| CREATE INDEX IF NOT EXISTS idx_verification_attempts_created_at | |
| ON verification_attempts (created_at); | |
| CREATE TABLE IF NOT EXISTS verification_cooldowns ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| telegram_user_id bigint NOT NULL, | |
| cooldown_until timestamptz NOT NULL | |
| ); | |
| CREATE UNIQUE INDEX IF NOT EXISTS uq_verification_cooldowns_group_user | |
| ON verification_cooldowns (group_id, telegram_user_id); | |
| CREATE INDEX IF NOT EXISTS idx_verification_cooldowns_until | |
| ON verification_cooldowns (cooldown_until); | |
| -- 5) Rules (generic) | |
| CREATE TABLE IF NOT EXISTS rules ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| type text NOT NULL, | |
| name text NOT NULL, | |
| enabled bool NOT NULL DEFAULT true, | |
| created_at timestamptz NOT NULL DEFAULT now(), | |
| updated_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_rules_group_type | |
| ON rules (group_id, type); | |
| CREATE INDEX IF NOT EXISTS idx_rules_group_enabled | |
| ON rules (group_id, enabled); | |
| CREATE TABLE IF NOT EXISTS rule_conditions ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| rule_id uuid NOT NULL REFERENCES rules(id) ON DELETE CASCADE, | |
| match_type text NOT NULL, | |
| match_value text NOT NULL, | |
| flags text | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_rule_conditions_rule_id | |
| ON rule_conditions (rule_id); | |
| CREATE INDEX IF NOT EXISTS idx_rule_conditions_match_type | |
| ON rule_conditions (match_type); | |
| CREATE TABLE IF NOT EXISTS rule_actions ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| rule_id uuid NOT NULL REFERENCES rules(id) ON DELETE CASCADE, | |
| action_type text NOT NULL, | |
| payload jsonb NOT NULL DEFAULT '{}'::jsonb | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_rule_actions_rule_id | |
| ON rule_actions (rule_id); | |
| CREATE INDEX IF NOT EXISTS idx_rule_actions_payload | |
| ON rule_actions USING gin (payload); | |
| -- 6) Auto replies | |
| CREATE TABLE IF NOT EXISTS auto_replies ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| match_type text NOT NULL, | |
| match_value text NOT NULL, | |
| random_pick bool NOT NULL DEFAULT false, | |
| delete_mode text NOT NULL DEFAULT 'none', | |
| delete_after int, | |
| enabled bool NOT NULL DEFAULT true, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_auto_replies_group_id | |
| ON auto_replies (group_id); | |
| CREATE INDEX IF NOT EXISTS idx_auto_replies_group_enabled | |
| ON auto_replies (group_id, enabled); | |
| CREATE TABLE IF NOT EXISTS auto_reply_items ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| auto_reply_id uuid NOT NULL REFERENCES auto_replies(id) ON DELETE CASCADE, | |
| content_type text NOT NULL, | |
| content text, | |
| media_id uuid, | |
| buttons_json jsonb | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_auto_reply_items_reply_id | |
| ON auto_reply_items (auto_reply_id); | |
| -- 7) Auto delete / auto ban rules | |
| CREATE TABLE IF NOT EXISTS delete_rules ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| match_type text NOT NULL, | |
| match_value text NOT NULL, | |
| enabled bool NOT NULL DEFAULT true, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_delete_rules_group_enabled | |
| ON delete_rules (group_id, enabled); | |
| CREATE INDEX IF NOT EXISTS idx_delete_rules_group_match_type | |
| ON delete_rules (group_id, match_type); | |
| CREATE TABLE IF NOT EXISTS ban_rules ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| match_type text NOT NULL, | |
| match_value text NOT NULL, | |
| action text NOT NULL, | |
| duration int, | |
| enabled bool NOT NULL DEFAULT true, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_ban_rules_group_enabled | |
| ON ban_rules (group_id, enabled); | |
| CREATE INDEX IF NOT EXISTS idx_ban_rules_group_action | |
| ON ban_rules (group_id, action); | |
| -- 8) AI moderation | |
| CREATE TABLE IF NOT EXISTS ai_models ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| name text NOT NULL, | |
| version text NOT NULL, | |
| type text NOT NULL, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_ai_models_type | |
| ON ai_models (type); | |
| CREATE INDEX IF NOT EXISTS idx_ai_models_name_version | |
| ON ai_models (name, version); | |
| CREATE TABLE IF NOT EXISTS media_assets ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| telegram_file_id text UNIQUE NOT NULL, | |
| file_hash text UNIQUE NOT NULL, | |
| file_type text NOT NULL, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE TABLE IF NOT EXISTS message_events ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| chat_id bigint, | |
| telegram_message_id bigint NOT NULL, | |
| telegram_user_id bigint NOT NULL, | |
| content_type text NOT NULL, | |
| text text, | |
| media_id uuid REFERENCES media_assets(id), | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE UNIQUE INDEX IF NOT EXISTS uq_message_events_group_message | |
| ON message_events (group_id, telegram_message_id); | |
| CREATE INDEX IF NOT EXISTS idx_message_events_group_created | |
| ON message_events (group_id, created_at); | |
| CREATE INDEX IF NOT EXISTS idx_message_events_group_user | |
| ON message_events (group_id, telegram_user_id); | |
| CREATE TABLE IF NOT EXISTS ai_detections ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| message_id uuid NOT NULL REFERENCES message_events(id) ON DELETE CASCADE, | |
| type text NOT NULL, | |
| score numeric(5,4) NOT NULL, | |
| threshold numeric(5,4) NOT NULL, | |
| model_id uuid REFERENCES ai_models(id), | |
| result text NOT NULL, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_ai_detections_group_created | |
| ON ai_detections (group_id, created_at); | |
| CREATE INDEX IF NOT EXISTS idx_ai_detections_group_result | |
| ON ai_detections (group_id, result); | |
| CREATE INDEX IF NOT EXISTS idx_ai_detections_message_id | |
| ON ai_detections (message_id); | |
| -- 9) Logs | |
| CREATE TABLE IF NOT EXISTS moderation_actions ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| telegram_user_id bigint NOT NULL, | |
| action text NOT NULL, | |
| reason text, | |
| duration int, | |
| source text NOT NULL, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_moderation_actions_group_created | |
| ON moderation_actions (group_id, created_at); | |
| CREATE INDEX IF NOT EXISTS idx_moderation_actions_group_user | |
| ON moderation_actions (group_id, telegram_user_id); | |
| CREATE INDEX IF NOT EXISTS idx_moderation_actions_group_action | |
| ON moderation_actions (group_id, action); | |
| CREATE TABLE IF NOT EXISTS audit_logs ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| admin_id uuid NOT NULL REFERENCES admins(id) ON DELETE CASCADE, | |
| action text NOT NULL, | |
| payload jsonb NOT NULL DEFAULT '{}'::jsonb, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_audit_logs_group_created | |
| ON audit_logs (group_id, created_at); | |
| CREATE INDEX IF NOT EXISTS idx_audit_logs_admin_id | |
| ON audit_logs (admin_id); | |
| -- 10) Scheduling | |
| CREATE TABLE IF NOT EXISTS scheduled_messages ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| content_type text NOT NULL, | |
| content text, | |
| media_id uuid REFERENCES media_assets(id), | |
| buttons_json jsonb | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_scheduled_messages_group_id | |
| ON scheduled_messages (group_id); | |
| CREATE TABLE IF NOT EXISTS schedules ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| scheduled_message_id uuid NOT NULL REFERENCES scheduled_messages(id) ON DELETE CASCADE, | |
| cron_expr text NOT NULL, | |
| next_run_at timestamptz NOT NULL, | |
| enabled bool NOT NULL DEFAULT true | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_schedules_next_run_at | |
| ON schedules (next_run_at); | |
| CREATE INDEX IF NOT EXISTS idx_schedules_group_enabled | |
| ON schedules (group_id, enabled); | |
| -- 11) Spam counters | |
| CREATE TABLE IF NOT EXISTS spam_counters ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| telegram_user_id bigint NOT NULL, | |
| window_start timestamptz NOT NULL, | |
| count int NOT NULL DEFAULT 0 | |
| ); | |
| CREATE UNIQUE INDEX IF NOT EXISTS uq_spam_counters_group_user | |
| ON spam_counters (group_id, telegram_user_id); | |
| CREATE INDEX IF NOT EXISTS idx_spam_counters_window_start | |
| ON spam_counters (window_start); | |
| -- 12) Stats | |
| CREATE TABLE IF NOT EXISTS stats_messages_daily ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| date date NOT NULL, | |
| message_count int NOT NULL DEFAULT 0, | |
| active_users int NOT NULL DEFAULT 0 | |
| ); | |
| CREATE UNIQUE INDEX IF NOT EXISTS uq_stats_messages_daily_group_date | |
| ON stats_messages_daily (group_id, date); | |
| CREATE INDEX IF NOT EXISTS idx_stats_messages_daily_group_date | |
| ON stats_messages_daily (group_id, date); | |
| CREATE TABLE IF NOT EXISTS stats_wordcloud_terms ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| date date NOT NULL, | |
| term text NOT NULL, | |
| count int NOT NULL DEFAULT 0 | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_wordcloud_terms_group_date | |
| ON stats_wordcloud_terms (group_id, date); | |
| CREATE INDEX IF NOT EXISTS idx_wordcloud_terms_group_term | |
| ON stats_wordcloud_terms (group_id, term); | |
| -- 13) Invite links | |
| CREATE TABLE IF NOT EXISTS invite_links ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| invite_link text NOT NULL, | |
| created_by bigint NOT NULL, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_invite_links_group_id | |
| ON invite_links (group_id); | |
| CREATE UNIQUE INDEX IF NOT EXISTS uq_invite_links_link | |
| ON invite_links (invite_link); | |
| CREATE TABLE IF NOT EXISTS invite_records ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| inviter_user_id bigint NOT NULL, | |
| invitee_user_id bigint NOT NULL, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_invite_records_group_created | |
| ON invite_records (group_id, created_at); | |
| CREATE INDEX IF NOT EXISTS idx_invite_records_group_inviter_created | |
| ON invite_records (group_id, inviter_user_id, created_at); | |
| -- 14) Verified users | |
| CREATE TABLE IF NOT EXISTS verified_users ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| telegram_user_id bigint NOT NULL, | |
| status text NOT NULL DEFAULT 'pending', | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE UNIQUE INDEX IF NOT EXISTS uq_verified_users_group_user | |
| ON verified_users (group_id, telegram_user_id); | |
| CREATE INDEX IF NOT EXISTS idx_verified_users_group_status | |
| ON verified_users (group_id, status); | |
| CREATE TABLE IF NOT EXISTS verified_profiles ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| verified_user_id uuid NOT NULL REFERENCES verified_users(id) ON DELETE CASCADE, | |
| profile_json jsonb NOT NULL DEFAULT '{}'::jsonb | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_verified_profiles_user | |
| ON verified_profiles (verified_user_id); | |
| CREATE TABLE IF NOT EXISTS verified_forms ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL UNIQUE REFERENCES groups(id) ON DELETE CASCADE, | |
| fields_json jsonb NOT NULL DEFAULT '{}'::jsonb | |
| ); | |
| CREATE TABLE IF NOT EXISTS verified_reviews ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| reviewer_user_id bigint NOT NULL, | |
| target_user_id bigint NOT NULL, | |
| rating int NOT NULL, | |
| content text, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_verified_reviews_group_created | |
| ON verified_reviews (group_id, created_at); | |
| CREATE INDEX IF NOT EXISTS idx_verified_reviews_group_target | |
| ON verified_reviews (group_id, target_user_id); | |
| -- 15) Member events | |
| CREATE TABLE IF NOT EXISTS member_events ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| telegram_user_id bigint NOT NULL, | |
| event_type text NOT NULL, | |
| old_value text, | |
| new_value text, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_member_events_group_created | |
| ON member_events (group_id, created_at); | |
| CREATE INDEX IF NOT EXISTS idx_member_events_group_user | |
| ON member_events (group_id, telegram_user_id); | |
| -- 16) Broadcasts | |
| CREATE TABLE IF NOT EXISTS broadcasts ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| content_type text NOT NULL, | |
| content text, | |
| media_id uuid REFERENCES media_assets(id), | |
| buttons_json jsonb, | |
| pin bool NOT NULL DEFAULT false, | |
| status text NOT NULL DEFAULT 'pending', | |
| telegram_message_id bigint, | |
| created_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_broadcasts_group_created | |
| ON broadcasts (group_id, created_at); | |
| CREATE INDEX IF NOT EXISTS idx_broadcasts_group_status | |
| ON broadcasts (group_id, status); | |
| -- 17) Posts (controllerbot-like) | |
| CREATE TABLE IF NOT EXISTS posts ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| title text, | |
| content_type text NOT NULL, | |
| content text, | |
| media_id uuid REFERENCES media_assets(id), | |
| buttons_json jsonb, | |
| target_chat_id bigint, | |
| pin bool NOT NULL DEFAULT false, | |
| status text NOT NULL DEFAULT 'draft', | |
| telegram_message_id bigint, | |
| created_at timestamptz NOT NULL DEFAULT now(), | |
| updated_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_posts_group_created | |
| ON posts (group_id, created_at); | |
| CREATE INDEX IF NOT EXISTS idx_posts_group_status | |
| ON posts (group_id, status); | |
| -- 18) Heroes | |
| CREATE TABLE IF NOT EXISTS heroes ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| group_id uuid NOT NULL REFERENCES groups(id) ON DELETE CASCADE, | |
| name text NOT NULL, | |
| intro text, | |
| parse_mode text, | |
| media_ids_json jsonb, | |
| created_at timestamptz NOT NULL DEFAULT now(), | |
| updated_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_heroes_group_created | |
| ON heroes (group_id, created_at); | |
| -- 19) Post schedules | |
| CREATE TABLE IF NOT EXISTS post_schedules ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| post_id uuid NOT NULL UNIQUE REFERENCES posts(id) ON DELETE CASCADE, | |
| cron_expr text NOT NULL, | |
| next_run_at timestamptz NOT NULL, | |
| enabled bool NOT NULL DEFAULT true, | |
| created_at timestamptz NOT NULL DEFAULT now(), | |
| updated_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_post_schedules_next_run | |
| ON post_schedules (next_run_at); | |
| CREATE INDEX IF NOT EXISTS idx_post_schedules_enabled | |
| ON post_schedules (enabled); | |
| -- 19) Message events add chat_id | |
| ALTER TABLE message_events ADD COLUMN IF NOT EXISTS chat_id bigint; | |
| -- 20) Queue jobs (PostgreSQL-backed worker queue) | |
| CREATE TABLE IF NOT EXISTS queue_jobs ( | |
| id uuid PRIMARY KEY DEFAULT gen_random_uuid(), | |
| topic text NOT NULL, | |
| payload jsonb NOT NULL DEFAULT '{}', | |
| status text NOT NULL DEFAULT 'pending', | |
| created_at timestamptz NOT NULL DEFAULT now(), | |
| processed_at timestamptz | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_queue_jobs_status_created | |
| ON queue_jobs (status, created_at); | |
| CREATE INDEX IF NOT EXISTS idx_queue_jobs_topic_status | |
| ON queue_jobs (topic, status); | |
| -- 21) Login throttles (auth rate limiting) | |
| CREATE TABLE IF NOT EXISTS login_throttles ( | |
| key text PRIMARY KEY, | |
| failure_count integer NOT NULL DEFAULT 0, | |
| window_until timestamptz, | |
| lock_until timestamptz, | |
| updated_at timestamptz NOT NULL DEFAULT now() | |
| ); | |
| CREATE INDEX IF NOT EXISTS idx_login_throttles_lock_until | |
| ON login_throttles (lock_until); | |