Initial commit: VoIdeaAI - voice-first AI idea assistant
This commit is contained in:
@@ -0,0 +1,175 @@
|
||||
"""Initial migration — create all tables
|
||||
|
||||
Revision ID: 001
|
||||
Revises:
|
||||
Create Date: 2026-05-11
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "001"
|
||||
down_revision: Union[str, None] = None
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"users",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("email", sa.String(255), unique=True, nullable=False, index=True),
|
||||
sa.Column("password_hash", sa.String(255), nullable=True),
|
||||
sa.Column("display_name", sa.String(255), nullable=False),
|
||||
sa.Column("avatar_url", sa.String(512), nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
sa.Column("is_superuser", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
sa.Column("oauth_provider", sa.String(50), nullable=True),
|
||||
sa.Column("oauth_id", sa.String(255), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"agent_configs",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("agent_name", sa.String(100), unique=True, nullable=False, index=True),
|
||||
sa.Column("is_enabled", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
sa.Column("version", sa.String(20), nullable=False, server_default=sa.text("'1.0.0'")),
|
||||
sa.Column("checksum", sa.String(64), nullable=True),
|
||||
sa.Column("config", sa.Text(), nullable=True),
|
||||
sa.Column("last_run_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"backlog_tasks",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("title", sa.String(255), nullable=False),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("priority", sa.String(20), nullable=False, server_default=sa.text("'medium'"), index=True),
|
||||
sa.Column("status", sa.String(20), nullable=False, server_default=sa.text("'pending'"), index=True),
|
||||
sa.Column("source_agent", sa.String(100), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"ideas",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False, index=True),
|
||||
sa.Column("title", sa.String(255), nullable=False, index=True),
|
||||
sa.Column("content", sa.Text(), nullable=False),
|
||||
sa.Column("status", sa.String(20), nullable=False, server_default=sa.text("'draft'"), index=True),
|
||||
sa.Column("tags", postgresql.ARRAY(sa.String(50)), nullable=True),
|
||||
sa.Column("is_public", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"log_entries",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("level", sa.String(20), nullable=False, index=True),
|
||||
sa.Column("source", sa.String(100), nullable=False, index=True),
|
||||
sa.Column("message", sa.Text(), nullable=False),
|
||||
sa.Column("details", sa.Text(), nullable=True),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=True, index=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
|
||||
op.create_foreign_key(
|
||||
"fk_ideas_user_id", "ideas", "users",
|
||||
["user_id"], ["id"], ondelete="CASCADE",
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_log_entries_user_id", "log_entries", "users",
|
||||
["user_id"], ["id"], ondelete="SET NULL",
|
||||
)
|
||||
|
||||
op.create_table(
|
||||
"conductor_interactions",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=True, index=True),
|
||||
sa.Column("input_text", sa.Text(), nullable=False),
|
||||
sa.Column("detected_intent", sa.String(100), nullable=False),
|
||||
sa.Column("selected_agent", sa.String(100), nullable=False),
|
||||
sa.Column("response_text", sa.Text(), nullable=False),
|
||||
sa.Column("user_rating", sa.Integer(), nullable=True),
|
||||
sa.Column("confidence_score", sa.Integer(), nullable=False, server_default=sa.text("80")),
|
||||
sa.Column("verification_status", sa.String(20), nullable=False, server_default=sa.text("'verified'")),
|
||||
sa.Column("was_auto_routed", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
sa.Column("processing_time_ms", sa.Float(), nullable=False, server_default=sa.text("0.0")),
|
||||
sa.Column("context", sa.Text(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_conductor_user_id", "conductor_interactions", "users",
|
||||
["user_id"], ["id"], ondelete="SET NULL",
|
||||
)
|
||||
|
||||
# ── sessions (чат-сессии = идеи) ──
|
||||
op.create_table(
|
||||
"sessions",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False, index=True),
|
||||
sa.Column("title", sa.String(255), nullable=False, server_default=sa.text("'Новое обсуждение'")),
|
||||
sa.Column("status", sa.String(20), nullable=False, server_default=sa.text("'active'"), index=True),
|
||||
sa.Column("idea_id", postgresql.UUID(as_uuid=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_sessions_user_id", "sessions", "users",
|
||||
["user_id"], ["id"], ondelete="CASCADE",
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_sessions_idea_id", "sessions", "ideas",
|
||||
["idea_id"], ["id"], ondelete="SET NULL",
|
||||
)
|
||||
|
||||
# ── session_id в conductor_interactions ──
|
||||
op.add_column(
|
||||
"conductor_interactions",
|
||||
sa.Column("session_id", postgresql.UUID(as_uuid=True), nullable=True, index=True),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_conductor_session_id", "conductor_interactions", "sessions",
|
||||
["session_id"], ["id"], ondelete="SET NULL",
|
||||
)
|
||||
|
||||
# ── voice_commands (пользовательские голосовые команды) ──
|
||||
op.create_table(
|
||||
"voice_commands",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=False, index=True),
|
||||
sa.Column("phrase", sa.String(255), nullable=False),
|
||||
sa.Column("action", sa.String(50), nullable=False),
|
||||
sa.Column("agent_name", sa.String(100), nullable=True),
|
||||
sa.Column("count", sa.Integer(), nullable=False, server_default=sa.text("0")),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_voice_commands_user_id", "voice_commands", "users",
|
||||
["user_id"], ["id"], ondelete="CASCADE",
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("voice_commands")
|
||||
op.drop_table("sessions")
|
||||
op.drop_constraint("fk_conductor_session_id", "conductor_interactions", type_="foreignkey")
|
||||
op.drop_column("conductor_interactions", "session_id")
|
||||
op.drop_table("conductor_interactions")
|
||||
op.drop_table("log_entries")
|
||||
op.drop_table("ideas")
|
||||
op.drop_table("backlog_tasks")
|
||||
op.drop_table("agent_configs")
|
||||
op.drop_table("users")
|
||||
@@ -0,0 +1,117 @@
|
||||
"""v2.0: Add role system, feedback, tariffs, backlog category
|
||||
|
||||
Revision ID: 002
|
||||
Revises: 001
|
||||
Create Date: 2026-05-11
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "002"
|
||||
down_revision: Union[str, None] = "001"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ── users: add v2.0 role system ──
|
||||
op.add_column("users", sa.Column("role", sa.String(20),
|
||||
server_default=sa.text("'user'"), nullable=False, index=True))
|
||||
op.add_column("users", sa.Column("is_owner", sa.Boolean(),
|
||||
server_default=sa.text("false"), nullable=False))
|
||||
op.add_column("users", sa.Column("permissions", postgresql.JSONB,
|
||||
nullable=True))
|
||||
op.add_column("users", sa.Column("accepted_terms_at",
|
||||
sa.DateTime(timezone=True), nullable=True))
|
||||
op.add_column("users", sa.Column("accepted_terms_version",
|
||||
sa.String(20), nullable=True))
|
||||
|
||||
# ── agent_configs: add description ──
|
||||
op.add_column("agent_configs", sa.Column("description", sa.Text(),
|
||||
nullable=True, server_default=sa.text("''")))
|
||||
|
||||
# ── backlog_tasks: add category ──
|
||||
op.add_column("backlog_tasks", sa.Column("category", sa.String(50),
|
||||
server_default=sa.text("'general'"), nullable=False, index=True))
|
||||
|
||||
# ── feedback ──
|
||||
op.create_table(
|
||||
"feedback",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), nullable=True, index=True),
|
||||
sa.Column("text", sa.Text(), nullable=False),
|
||||
sa.Column("page_url", sa.String(512), nullable=True),
|
||||
sa.Column("status", sa.String(20), nullable=False,
|
||||
server_default=sa.text("'new'"), index=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now()),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_feedback_user_id", "feedback", "users",
|
||||
["user_id"], ["id"], ondelete="SET NULL",
|
||||
)
|
||||
|
||||
# ── tariff_plans ──
|
||||
op.create_table(
|
||||
"tariff_plans",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("name", sa.String(100), nullable=False),
|
||||
sa.Column("code", sa.String(50), unique=True, nullable=False, index=True),
|
||||
sa.Column("description", sa.Text(), nullable=True),
|
||||
sa.Column("price_monthly", sa.Numeric(10, 2), nullable=False,
|
||||
server_default=sa.text("0")),
|
||||
sa.Column("price_yearly", sa.Numeric(10, 2), nullable=True),
|
||||
sa.Column("features", postgresql.JSONB, nullable=True),
|
||||
sa.Column("is_active", sa.Boolean(), nullable=False,
|
||||
server_default=sa.text("true")),
|
||||
sa.Column("sort_order", sa.Integer(), nullable=False,
|
||||
server_default=sa.text("0")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now()),
|
||||
)
|
||||
|
||||
# ── user_subscriptions ──
|
||||
op.create_table(
|
||||
"user_subscriptions",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("user_id", postgresql.UUID(as_uuid=True), unique=True,
|
||||
nullable=False, index=True),
|
||||
sa.Column("plan_id", postgresql.UUID(as_uuid=True), nullable=False),
|
||||
sa.Column("status", sa.String(20), nullable=False,
|
||||
server_default=sa.text("'active'"), index=True),
|
||||
sa.Column("current_period_start", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("current_period_end", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("canceled_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True),
|
||||
server_default=sa.func.now()),
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_user_subscriptions_user_id", "user_subscriptions", "users",
|
||||
["user_id"], ["id"], ondelete="CASCADE",
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_user_subscriptions_plan_id", "user_subscriptions", "tariff_plans",
|
||||
["plan_id"], ["id"], ondelete="RESTRICT",
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("user_subscriptions")
|
||||
op.drop_table("tariff_plans")
|
||||
op.drop_table("feedback")
|
||||
op.drop_column("backlog_tasks", "category")
|
||||
op.drop_column("agent_configs", "description")
|
||||
op.drop_column("users", "accepted_terms_version")
|
||||
op.drop_column("users", "accepted_terms_at")
|
||||
op.drop_column("users", "permissions")
|
||||
op.drop_column("users", "is_owner")
|
||||
op.drop_column("users", "role")
|
||||
@@ -0,0 +1,51 @@
|
||||
"""Add pipeline_stats table and User.pipeline_tuning
|
||||
|
||||
Revision ID: 003
|
||||
Revises: 002
|
||||
Create Date: 2026-05-11
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "003"
|
||||
down_revision: Union[str, None] = "002"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# ── users: add pipeline_tuning ──
|
||||
op.add_column("users", sa.Column(
|
||||
"pipeline_tuning", postgresql.JSONB(), nullable=True
|
||||
))
|
||||
|
||||
# ── pipeline_stats ──
|
||||
op.create_table(
|
||||
"pipeline_stats",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("user_id", sa.String(36),
|
||||
sa.ForeignKey("users.id", ondelete="CASCADE"),
|
||||
nullable=True, index=True),
|
||||
sa.Column("stage", sa.String(50), nullable=False, index=True),
|
||||
sa.Column("passed", sa.Boolean(), nullable=False),
|
||||
sa.Column("reason", sa.String(255), nullable=True),
|
||||
sa.Column("duration_ms", sa.Integer(), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True),
|
||||
nullable=False),
|
||||
)
|
||||
|
||||
# composite index for stats aggregation
|
||||
op.create_index(
|
||||
"ix_pipeline_stats_user_stage",
|
||||
"pipeline_stats",
|
||||
["user_id", "stage"],
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_pipeline_stats_user_stage", table_name="pipeline_stats")
|
||||
op.drop_table("pipeline_stats")
|
||||
op.drop_column("users", "pipeline_tuning")
|
||||
@@ -0,0 +1,34 @@
|
||||
"""Add bot_commands table
|
||||
|
||||
Revision ID: 004
|
||||
Revises: 003
|
||||
Create Date: 2026-05-12
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
revision: str = "004"
|
||||
down_revision: Union[str, None] = "003"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"bot_commands",
|
||||
sa.Column("id", postgresql.UUID(as_uuid=True), primary_key=True),
|
||||
sa.Column("name", sa.String(50), unique=True, nullable=False, index=True),
|
||||
sa.Column("description", sa.String(255), nullable=False),
|
||||
sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.text("true")),
|
||||
sa.Column("requires_auth", sa.Boolean(), nullable=False, server_default=sa.text("false")),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("bot_commands")
|
||||
@@ -0,0 +1,27 @@
|
||||
"""Add public_slug column to ideas table
|
||||
|
||||
Revision ID: 005
|
||||
Revises: 004
|
||||
Create Date: 2026-05-12
|
||||
"""
|
||||
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
revision: str = "005"
|
||||
down_revision: Union[str, None] = "004"
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column(
|
||||
"ideas",
|
||||
sa.Column("public_slug", sa.String(64), unique=True, nullable=True, index=True),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column("ideas", "public_slug")
|
||||
Reference in New Issue
Block a user