quick commit
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 6m9s
CI/CD Pipeline / Security Scanning (push) Successful in 26s
CI/CD Pipeline / Tests (3.11) (push) Failing after 5m24s
CI/CD Pipeline / Tests (3.12) (push) Failing after 5m23s
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Deploy to Staging (push) Has been skipped
CI/CD Pipeline / Deploy to Production (push) Has been skipped
CI/CD Pipeline / Notification (push) Successful in 1s
Some checks failed
CI/CD Pipeline / Code Quality Checks (push) Failing after 6m9s
CI/CD Pipeline / Security Scanning (push) Successful in 26s
CI/CD Pipeline / Tests (3.11) (push) Failing after 5m24s
CI/CD Pipeline / Tests (3.12) (push) Failing after 5m23s
CI/CD Pipeline / Build Docker Image (push) Has been skipped
CI/CD Pipeline / Deploy to Staging (push) Has been skipped
CI/CD Pipeline / Deploy to Production (push) Has been skipped
CI/CD Pipeline / Notification (push) Successful in 1s
This commit is contained in:
116
migrations/versions/20260117_add_analytics_models.py
Normal file
116
migrations/versions/20260117_add_analytics_models.py
Normal file
@@ -0,0 +1,116 @@
|
||||
"""Add analytics models for tracking AI checks, user activity, and message stats
|
||||
|
||||
Revision ID: 20260117_analytics
|
||||
Revises: 20260117_add_database_indexes
|
||||
Create Date: 2026-01-17 19:30:00.000000
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
import sqlalchemy as sa
|
||||
from alembic import op
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = "20260117_analytics"
|
||||
down_revision: Union[str, None] = "20260117_add_database_indexes"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Create ai_checks table
|
||||
op.create_table(
|
||||
"ai_checks",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("guild_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("user_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("channel_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("message_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("flagged", sa.Boolean(), nullable=False),
|
||||
sa.Column("confidence", sa.Float(), nullable=False),
|
||||
sa.Column("category", sa.String(50), nullable=True),
|
||||
sa.Column("severity", sa.Integer(), nullable=False),
|
||||
sa.Column("response_time_ms", sa.Float(), nullable=False),
|
||||
sa.Column("provider", sa.String(20), nullable=False),
|
||||
sa.Column("is_false_positive", sa.Boolean(), nullable=False),
|
||||
sa.Column("reviewed_by", sa.BigInteger(), nullable=True),
|
||||
sa.Column("reviewed_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
|
||||
# Add indexes for ai_checks
|
||||
op.create_index("ix_ai_checks_guild_id", "ai_checks", ["guild_id"])
|
||||
op.create_index("ix_ai_checks_user_id", "ai_checks", ["user_id"])
|
||||
op.create_index("ix_ai_checks_is_false_positive", "ai_checks", ["is_false_positive"])
|
||||
op.create_index("ix_ai_checks_created_at", "ai_checks", ["created_at"])
|
||||
op.create_index("ix_ai_checks_guild_created", "ai_checks", ["guild_id", "created_at"])
|
||||
|
||||
# Create message_activity table
|
||||
op.create_table(
|
||||
"message_activity",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("guild_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("date", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("total_messages", sa.Integer(), nullable=False),
|
||||
sa.Column("active_users", sa.Integer(), nullable=False),
|
||||
sa.Column("new_joins", sa.Integer(), nullable=False),
|
||||
sa.Column("automod_triggers", sa.Integer(), nullable=False),
|
||||
sa.Column("ai_checks", sa.Integer(), nullable=False),
|
||||
sa.Column("manual_actions", sa.Integer(), nullable=False),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
|
||||
# Add indexes for message_activity
|
||||
op.create_index("ix_message_activity_guild_id", "message_activity", ["guild_id"])
|
||||
op.create_index("ix_message_activity_date", "message_activity", ["date"])
|
||||
op.create_index(
|
||||
"ix_message_activity_guild_date", "message_activity", ["guild_id", "date"], unique=True
|
||||
)
|
||||
|
||||
# Create user_activity table
|
||||
op.create_table(
|
||||
"user_activity",
|
||||
sa.Column("id", sa.Integer(), nullable=False),
|
||||
sa.Column("guild_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("user_id", sa.BigInteger(), nullable=False),
|
||||
sa.Column("username", sa.String(100), nullable=False),
|
||||
sa.Column("first_seen", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("last_seen", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("last_message", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("message_count", sa.Integer(), nullable=False),
|
||||
sa.Column("command_count", sa.Integer(), nullable=False),
|
||||
sa.Column("strike_count", sa.Integer(), nullable=False),
|
||||
sa.Column("warning_count", sa.Integer(), nullable=False),
|
||||
sa.Column("kick_count", sa.Integer(), nullable=False),
|
||||
sa.Column("ban_count", sa.Integer(), nullable=False),
|
||||
sa.Column("timeout_count", sa.Integer(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at", sa.DateTime(timezone=True), server_default=sa.func.now(), nullable=False
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
|
||||
# Add indexes for user_activity
|
||||
op.create_index("ix_user_activity_guild_id", "user_activity", ["guild_id"])
|
||||
op.create_index("ix_user_activity_user_id", "user_activity", ["user_id"])
|
||||
op.create_index(
|
||||
"ix_user_activity_guild_user", "user_activity", ["guild_id", "user_id"], unique=True
|
||||
)
|
||||
op.create_index("ix_user_activity_last_seen", "user_activity", ["last_seen"])
|
||||
op.create_index("ix_user_activity_strike_count", "user_activity", ["strike_count"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Drop tables in reverse order
|
||||
op.drop_table("user_activity")
|
||||
op.drop_table("message_activity")
|
||||
op.drop_table("ai_checks")
|
||||
87
migrations/versions/20260117_add_automod_thresholds.py
Normal file
87
migrations/versions/20260117_add_automod_thresholds.py
Normal file
@@ -0,0 +1,87 @@
|
||||
"""Add automod thresholds and scam allowlist.
|
||||
|
||||
Revision ID: 20260117_add_automod_thresholds
|
||||
Revises:
|
||||
Create Date: 2026-01-17 00:00:00.000000
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "20260117_add_automod_thresholds"
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"guild_settings",
|
||||
sa.Column("message_rate_limit", sa.Integer(), nullable=False, server_default="5"),
|
||||
)
|
||||
op.add_column(
|
||||
"guild_settings",
|
||||
sa.Column("message_rate_window", sa.Integer(), nullable=False, server_default="5"),
|
||||
)
|
||||
op.add_column(
|
||||
"guild_settings",
|
||||
sa.Column("duplicate_threshold", sa.Integer(), nullable=False, server_default="3"),
|
||||
)
|
||||
op.add_column(
|
||||
"guild_settings",
|
||||
sa.Column("mention_limit", sa.Integer(), nullable=False, server_default="5"),
|
||||
)
|
||||
op.add_column(
|
||||
"guild_settings",
|
||||
sa.Column("mention_rate_limit", sa.Integer(), nullable=False, server_default="10"),
|
||||
)
|
||||
op.add_column(
|
||||
"guild_settings",
|
||||
sa.Column("mention_rate_window", sa.Integer(), nullable=False, server_default="60"),
|
||||
)
|
||||
op.add_column(
|
||||
"guild_settings",
|
||||
sa.Column(
|
||||
"scam_allowlist",
|
||||
postgresql.JSONB(astext_type=sa.Text()),
|
||||
nullable=False,
|
||||
server_default=sa.text("'[]'::jsonb"),
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"guild_settings",
|
||||
sa.Column(
|
||||
"ai_confidence_threshold",
|
||||
sa.Float(),
|
||||
nullable=False,
|
||||
server_default="0.7",
|
||||
),
|
||||
)
|
||||
op.add_column(
|
||||
"guild_settings",
|
||||
sa.Column("ai_log_only", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
)
|
||||
|
||||
op.alter_column("guild_settings", "message_rate_limit", server_default=None)
|
||||
op.alter_column("guild_settings", "message_rate_window", server_default=None)
|
||||
op.alter_column("guild_settings", "duplicate_threshold", server_default=None)
|
||||
op.alter_column("guild_settings", "mention_limit", server_default=None)
|
||||
op.alter_column("guild_settings", "mention_rate_limit", server_default=None)
|
||||
op.alter_column("guild_settings", "mention_rate_window", server_default=None)
|
||||
op.alter_column("guild_settings", "scam_allowlist", server_default=None)
|
||||
op.alter_column("guild_settings", "ai_confidence_threshold", server_default=None)
|
||||
op.alter_column("guild_settings", "ai_log_only", server_default=None)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("guild_settings", "ai_log_only")
|
||||
op.drop_column("guild_settings", "ai_confidence_threshold")
|
||||
op.drop_column("guild_settings", "scam_allowlist")
|
||||
op.drop_column("guild_settings", "mention_rate_window")
|
||||
op.drop_column("guild_settings", "mention_rate_limit")
|
||||
op.drop_column("guild_settings", "mention_limit")
|
||||
op.drop_column("guild_settings", "duplicate_threshold")
|
||||
op.drop_column("guild_settings", "message_rate_window")
|
||||
op.drop_column("guild_settings", "message_rate_limit")
|
||||
125
migrations/versions/20260117_add_database_indexes.py
Normal file
125
migrations/versions/20260117_add_database_indexes.py
Normal file
@@ -0,0 +1,125 @@
|
||||
"""Add database indexes for performance and security.
|
||||
|
||||
Revision ID: 20260117_add_database_indexes
|
||||
Revises: 20260117_add_automod_thresholds
|
||||
Create Date: 2026-01-17 12:00:00.000000
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "20260117_add_database_indexes"
|
||||
down_revision = "20260117_add_automod_thresholds"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
"""Add indexes for common query patterns and performance optimization."""
|
||||
|
||||
# Indexes for moderation_logs table
|
||||
# Primary lookup patterns: by guild, by target user, by moderator, by timestamp
|
||||
op.create_index("idx_moderation_logs_guild_id", "moderation_logs", ["guild_id"])
|
||||
op.create_index("idx_moderation_logs_target_id", "moderation_logs", ["target_id"])
|
||||
op.create_index("idx_moderation_logs_moderator_id", "moderation_logs", ["moderator_id"])
|
||||
op.create_index("idx_moderation_logs_created_at", "moderation_logs", ["created_at"])
|
||||
op.create_index("idx_moderation_logs_action", "moderation_logs", ["action"])
|
||||
op.create_index("idx_moderation_logs_is_automatic", "moderation_logs", ["is_automatic"])
|
||||
|
||||
# Compound indexes for common filtering patterns
|
||||
op.create_index("idx_moderation_logs_guild_target", "moderation_logs", ["guild_id", "target_id"])
|
||||
op.create_index("idx_moderation_logs_guild_created", "moderation_logs", ["guild_id", "created_at"])
|
||||
op.create_index("idx_moderation_logs_target_created", "moderation_logs", ["target_id", "created_at"])
|
||||
|
||||
# Indexes for strikes table
|
||||
# Primary lookup patterns: by guild, by user, active strikes, expiration
|
||||
op.create_index("idx_strikes_guild_id", "strikes", ["guild_id"])
|
||||
op.create_index("idx_strikes_user_id", "strikes", ["user_id"])
|
||||
op.create_index("idx_strikes_moderator_id", "strikes", ["moderator_id"])
|
||||
op.create_index("idx_strikes_is_active", "strikes", ["is_active"])
|
||||
op.create_index("idx_strikes_expires_at", "strikes", ["expires_at"])
|
||||
op.create_index("idx_strikes_created_at", "strikes", ["created_at"])
|
||||
|
||||
# Compound indexes for active strike counting and user history
|
||||
op.create_index("idx_strikes_guild_user_active", "strikes", ["guild_id", "user_id", "is_active"])
|
||||
op.create_index("idx_strikes_user_active", "strikes", ["user_id", "is_active"])
|
||||
op.create_index("idx_strikes_guild_active", "strikes", ["guild_id", "is_active"])
|
||||
|
||||
# Indexes for banned_words table
|
||||
# Primary lookup patterns: by guild, by pattern (for admin management)
|
||||
op.create_index("idx_banned_words_guild_id", "banned_words", ["guild_id"])
|
||||
op.create_index("idx_banned_words_is_regex", "banned_words", ["is_regex"])
|
||||
op.create_index("idx_banned_words_action", "banned_words", ["action"])
|
||||
op.create_index("idx_banned_words_added_by", "banned_words", ["added_by"])
|
||||
|
||||
# Compound index for guild-specific lookups
|
||||
op.create_index("idx_banned_words_guild_regex", "banned_words", ["guild_id", "is_regex"])
|
||||
|
||||
# Indexes for user_notes table (if it exists)
|
||||
# Primary lookup patterns: by guild, by user, by moderator
|
||||
op.create_index("idx_user_notes_guild_id", "user_notes", ["guild_id"])
|
||||
op.create_index("idx_user_notes_user_id", "user_notes", ["user_id"])
|
||||
op.create_index("idx_user_notes_moderator_id", "user_notes", ["moderator_id"])
|
||||
op.create_index("idx_user_notes_created_at", "user_notes", ["created_at"])
|
||||
|
||||
# Compound indexes for user note history
|
||||
op.create_index("idx_user_notes_guild_user", "user_notes", ["guild_id", "user_id"])
|
||||
op.create_index("idx_user_notes_user_created", "user_notes", ["user_id", "created_at"])
|
||||
|
||||
# Indexes for guild_settings table
|
||||
# These are mostly for admin dashboard filtering
|
||||
op.create_index("idx_guild_settings_automod_enabled", "guild_settings", ["automod_enabled"])
|
||||
op.create_index("idx_guild_settings_ai_enabled", "guild_settings", ["ai_moderation_enabled"])
|
||||
op.create_index("idx_guild_settings_verification_enabled", "guild_settings", ["verification_enabled"])
|
||||
|
||||
# Indexes for guilds table
|
||||
op.create_index("idx_guilds_owner_id", "guilds", ["owner_id"])
|
||||
op.create_index("idx_guilds_premium", "guilds", ["premium"])
|
||||
op.create_index("idx_guilds_created_at", "guilds", ["created_at"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
"""Remove the indexes."""
|
||||
|
||||
# Remove all indexes in reverse order
|
||||
op.drop_index("idx_guilds_created_at")
|
||||
op.drop_index("idx_guilds_premium")
|
||||
op.drop_index("idx_guilds_owner_id")
|
||||
|
||||
op.drop_index("idx_guild_settings_verification_enabled")
|
||||
op.drop_index("idx_guild_settings_ai_enabled")
|
||||
op.drop_index("idx_guild_settings_automod_enabled")
|
||||
|
||||
op.drop_index("idx_user_notes_user_created")
|
||||
op.drop_index("idx_user_notes_guild_user")
|
||||
op.drop_index("idx_user_notes_created_at")
|
||||
op.drop_index("idx_user_notes_moderator_id")
|
||||
op.drop_index("idx_user_notes_user_id")
|
||||
op.drop_index("idx_user_notes_guild_id")
|
||||
|
||||
op.drop_index("idx_banned_words_guild_regex")
|
||||
op.drop_index("idx_banned_words_added_by")
|
||||
op.drop_index("idx_banned_words_action")
|
||||
op.drop_index("idx_banned_words_is_regex")
|
||||
op.drop_index("idx_banned_words_guild_id")
|
||||
|
||||
op.drop_index("idx_strikes_guild_active")
|
||||
op.drop_index("idx_strikes_user_active")
|
||||
op.drop_index("idx_strikes_guild_user_active")
|
||||
op.drop_index("idx_strikes_created_at")
|
||||
op.drop_index("idx_strikes_expires_at")
|
||||
op.drop_index("idx_strikes_is_active")
|
||||
op.drop_index("idx_strikes_moderator_id")
|
||||
op.drop_index("idx_strikes_user_id")
|
||||
op.drop_index("idx_strikes_guild_id")
|
||||
|
||||
op.drop_index("idx_moderation_logs_target_created")
|
||||
op.drop_index("idx_moderation_logs_guild_created")
|
||||
op.drop_index("idx_moderation_logs_guild_target")
|
||||
op.drop_index("idx_moderation_logs_is_automatic")
|
||||
op.drop_index("idx_moderation_logs_action")
|
||||
op.drop_index("idx_moderation_logs_created_at")
|
||||
op.drop_index("idx_moderation_logs_moderator_id")
|
||||
op.drop_index("idx_moderation_logs_target_id")
|
||||
op.drop_index("idx_moderation_logs_guild_id")
|
||||
Reference in New Issue
Block a user