Initial commit: clean project structure
- Backend: FastAPI + SQLAlchemy + Celery (Python 3.11+) - Frontend: Vue 3 + TypeScript + Pinia + Tailwind - Admin Frontend: separate Vue 3 app for management - Docker Compose: 9 services orchestration - Specs: design prototypes, memory system PRD, product roadmap Cleanup performed: - Removed temporary debug scripts from backend root - Removed deprecated admin_app.py (embedded UI) - Removed duplicate docs from admin-frontend - Updated .gitignore for Vite cache and egg-info
This commit is contained in:
20
backend/alembic/README.md
Normal file
20
backend/alembic/README.md
Normal file
@@ -0,0 +1,20 @@
|
||||
# Alembic 使用说明
|
||||
|
||||
1. 安装依赖(在后端虚拟环境内)
|
||||
```
|
||||
pip install alembic
|
||||
```
|
||||
|
||||
2. 设置环境变量,确保 `DATABASE_URL` 指向目标数据库。
|
||||
|
||||
3. 运行迁移:
|
||||
```
|
||||
alembic upgrade head
|
||||
```
|
||||
|
||||
4. 生成新迁移(如有模型变更):
|
||||
```
|
||||
alembic revision -m "message" --autogenerate
|
||||
```
|
||||
|
||||
说明:`alembic/env.py` 会从 `app.core.config` 读取数据库 URL,并包含 admin/provider 模型。
|
||||
68
backend/alembic/env.py
Normal file
68
backend/alembic/env.py
Normal file
@@ -0,0 +1,68 @@
|
||||
import asyncio
|
||||
from logging.config import fileConfig
|
||||
|
||||
from sqlalchemy import pool
|
||||
from sqlalchemy.engine import Connection
|
||||
from sqlalchemy.ext.asyncio import async_engine_from_config
|
||||
|
||||
from alembic import context
|
||||
from app.core.config import settings
|
||||
from app.db import models, admin_models # ensure models are imported
|
||||
|
||||
# this is the Alembic Config object, which provides
|
||||
# access to the values within the .ini file in use.
|
||||
config = context.config
|
||||
|
||||
# Interpret the config file for Python logging.
|
||||
fileConfig(config.config_file_name)
|
||||
|
||||
# override sqlalchemy.url from settings
|
||||
config.set_main_option("sqlalchemy.url", settings.database_url)
|
||||
|
||||
target_metadata = models.Base.metadata
|
||||
|
||||
|
||||
def run_migrations_offline():
|
||||
"""Run migrations in 'offline' mode."""
|
||||
url = config.get_main_option("sqlalchemy.url")
|
||||
context.configure(
|
||||
url=url,
|
||||
target_metadata=target_metadata,
|
||||
literal_binds=True,
|
||||
compare_type=True,
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
def do_run_migrations(connection: Connection):
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
compare_type=True,
|
||||
)
|
||||
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
|
||||
|
||||
async def run_migrations_online():
|
||||
"""Run migrations in 'online' mode."""
|
||||
connectable = async_engine_from_config(
|
||||
config.get_section(config.config_ini_section),
|
||||
prefix="sqlalchemy.",
|
||||
poolclass=pool.NullPool,
|
||||
connect_args={"statement_cache_size": 0},
|
||||
)
|
||||
|
||||
async with connectable.connect() as connection:
|
||||
await connection.run_sync(do_run_migrations)
|
||||
|
||||
await connectable.dispose()
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
run_migrations_offline()
|
||||
else:
|
||||
asyncio.run(run_migrations_online())
|
||||
@@ -0,0 +1,45 @@
|
||||
"""init providers and story mode"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0001_init_providers"
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"providers",
|
||||
sa.Column("id", sa.String(length=36), primary_key=True),
|
||||
sa.Column("name", sa.String(length=100), nullable=False),
|
||||
sa.Column("type", sa.String(length=50), nullable=False),
|
||||
sa.Column("adapter", sa.String(length=100), nullable=False),
|
||||
sa.Column("model", sa.String(length=200), nullable=True),
|
||||
sa.Column("api_base", sa.String(length=300), nullable=True),
|
||||
sa.Column("timeout_ms", sa.Integer(), server_default="60000", nullable=False),
|
||||
sa.Column("max_retries", sa.Integer(), server_default="1", nullable=False),
|
||||
sa.Column("weight", sa.Integer(), server_default="1", nullable=False),
|
||||
sa.Column("priority", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.Column("enabled", sa.Boolean(), server_default=sa.text("true"), nullable=False),
|
||||
sa.Column("config_ref", sa.String(length=100), nullable=True),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("updated_at", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("updated_by", sa.String(length=100), nullable=True),
|
||||
)
|
||||
|
||||
with op.batch_alter_table("stories", schema=None) as batch_op:
|
||||
batch_op.add_column(
|
||||
sa.Column("mode", sa.String(length=20), server_default="generated", nullable=False)
|
||||
)
|
||||
batch_op.alter_column("mode", server_default=None)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
with op.batch_alter_table("stories", schema=None) as batch_op:
|
||||
batch_op.drop_column("mode")
|
||||
|
||||
op.drop_table("providers")
|
||||
29
backend/alembic/versions/0002_add_api_key_to_providers.py
Normal file
29
backend/alembic/versions/0002_add_api_key_to_providers.py
Normal file
@@ -0,0 +1,29 @@
|
||||
"""add api_key to providers
|
||||
|
||||
Revision ID: 0002_add_api_key_to_providers
|
||||
Revises: 0001_init_providers_and_story_mode
|
||||
Create Date: 2025-01-01
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0002_add_api_key"
|
||||
down_revision = "0001_init_providers"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# 添加 api_key 列,可为空,优先于 config_ref 使用
|
||||
with op.batch_alter_table("providers", schema=None) as batch_op:
|
||||
batch_op.add_column(
|
||||
sa.Column("api_key", sa.String(length=500), nullable=True)
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
with op.batch_alter_table("providers", schema=None) as batch_op:
|
||||
batch_op.drop_column("api_key")
|
||||
100
backend/alembic/versions/0003_add_provider_monitoring_tables.py
Normal file
100
backend/alembic/versions/0003_add_provider_monitoring_tables.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""add provider monitoring tables
|
||||
|
||||
Revision ID: 0003_add_monitoring
|
||||
Revises: 0002_add_api_key
|
||||
Create Date: 2025-01-01
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0003_add_monitoring"
|
||||
down_revision = "0002_add_api_key"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# 创建 provider_metrics 表
|
||||
op.create_table(
|
||||
"provider_metrics",
|
||||
sa.Column("id", sa.Integer(), autoincrement=True, nullable=False),
|
||||
sa.Column("provider_id", sa.String(length=36), nullable=False),
|
||||
sa.Column(
|
||||
"timestamp",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("success", sa.Boolean(), nullable=False),
|
||||
sa.Column("latency_ms", sa.Integer(), nullable=True),
|
||||
sa.Column("cost_usd", sa.Numeric(precision=10, scale=6), nullable=True),
|
||||
sa.Column("error_message", sa.Text(), nullable=True),
|
||||
sa.Column("request_id", sa.String(length=100), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["provider_id"],
|
||||
["providers.id"],
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
)
|
||||
op.create_index(
|
||||
"ix_provider_metrics_provider_id",
|
||||
"provider_metrics",
|
||||
["provider_id"],
|
||||
unique=False,
|
||||
)
|
||||
op.create_index(
|
||||
"ix_provider_metrics_timestamp",
|
||||
"provider_metrics",
|
||||
["timestamp"],
|
||||
unique=False,
|
||||
)
|
||||
|
||||
# 创建 provider_health 表
|
||||
op.create_table(
|
||||
"provider_health",
|
||||
sa.Column("provider_id", sa.String(length=36), nullable=False),
|
||||
sa.Column("is_healthy", sa.Boolean(), server_default=sa.text("true"), nullable=True),
|
||||
sa.Column("last_check", sa.DateTime(timezone=True), nullable=True),
|
||||
sa.Column("consecutive_failures", sa.Integer(), server_default=sa.text("0"), nullable=True),
|
||||
sa.Column("last_error", sa.Text(), nullable=True),
|
||||
sa.ForeignKeyConstraint(
|
||||
["provider_id"],
|
||||
["providers.id"],
|
||||
ondelete="CASCADE",
|
||||
),
|
||||
sa.PrimaryKeyConstraint("provider_id"),
|
||||
)
|
||||
|
||||
# 创建 provider_secrets 表
|
||||
op.create_table(
|
||||
"provider_secrets",
|
||||
sa.Column("id", sa.String(length=36), nullable=False),
|
||||
sa.Column("name", sa.String(length=100), nullable=False),
|
||||
sa.Column("encrypted_value", sa.Text(), nullable=False),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
server_default=sa.text("now()"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.PrimaryKeyConstraint("id"),
|
||||
sa.UniqueConstraint("name"),
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_table("provider_secrets")
|
||||
op.drop_table("provider_health")
|
||||
op.drop_index("ix_provider_metrics_timestamp", table_name="provider_metrics")
|
||||
op.drop_index("ix_provider_metrics_provider_id", table_name="provider_metrics")
|
||||
op.drop_table("provider_metrics")
|
||||
42
backend/alembic/versions/0004_add_child_profiles.py
Normal file
42
backend/alembic/versions/0004_add_child_profiles.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""add child profiles
|
||||
|
||||
Revision ID: 0004_add_child_profiles
|
||||
Revises: 0003_add_monitoring
|
||||
Create Date: 2025-12-22
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0004_add_child_profiles"
|
||||
down_revision = "0003_add_monitoring"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
"child_profiles",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column("user_id", sa.String(255), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
|
||||
sa.Column("name", sa.String(50), nullable=False),
|
||||
sa.Column("avatar_url", sa.String(500)),
|
||||
sa.Column("birth_date", sa.Date()),
|
||||
sa.Column("gender", sa.String(10)),
|
||||
sa.Column("interests", sa.JSON(), server_default="[]", nullable=False),
|
||||
sa.Column("growth_themes", sa.JSON(), server_default="[]", nullable=False),
|
||||
sa.Column("reading_preferences", sa.JSON(), server_default="{}", nullable=False),
|
||||
sa.Column("stories_count", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.Column("total_reading_time", sa.Integer(), server_default="0", nullable=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()),
|
||||
sa.UniqueConstraint("user_id", "name", name="uq_child_profile_user_name"),
|
||||
)
|
||||
op.create_index("idx_child_profiles_user_id", "child_profiles", ["user_id"])
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_index("idx_child_profiles_user_id", table_name="child_profiles")
|
||||
op.drop_table("child_profiles")
|
||||
@@ -0,0 +1,67 @@
|
||||
"""add story universes and story links
|
||||
|
||||
Revision ID: 0005_add_story_universes_and_story_links
|
||||
Revises: 0004_add_child_profiles
|
||||
Create Date: 2025-12-22
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = "0005_add_story_universes_and_story_links"
|
||||
down_revision = "0004_add_child_profiles"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
"story_universes",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column(
|
||||
"child_profile_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("child_profiles.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column("name", sa.String(100), nullable=False),
|
||||
sa.Column("protagonist", sa.JSON(), nullable=False),
|
||||
sa.Column("recurring_characters", sa.JSON(), server_default="[]", nullable=False),
|
||||
sa.Column("world_settings", sa.JSON(), server_default="{}", nullable=False),
|
||||
sa.Column("achievements", sa.JSON(), server_default="[]", nullable=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_index("idx_story_universes_child_id", "story_universes", ["child_profile_id"])
|
||||
op.create_index("idx_story_universes_updated_at", "story_universes", ["updated_at"])
|
||||
|
||||
op.add_column("stories", sa.Column("child_profile_id", sa.String(36), nullable=True))
|
||||
op.add_column("stories", sa.Column("universe_id", sa.String(36), nullable=True))
|
||||
op.create_foreign_key(
|
||||
"fk_stories_child_profile",
|
||||
"stories",
|
||||
"child_profiles",
|
||||
["child_profile_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
op.create_foreign_key(
|
||||
"fk_stories_universe",
|
||||
"stories",
|
||||
"story_universes",
|
||||
["universe_id"],
|
||||
["id"],
|
||||
ondelete="SET NULL",
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_constraint("fk_stories_universe", "stories", type_="foreignkey")
|
||||
op.drop_constraint("fk_stories_child_profile", "stories", type_="foreignkey")
|
||||
op.drop_column("stories", "universe_id")
|
||||
op.drop_column("stories", "child_profile_id")
|
||||
|
||||
op.drop_index("idx_story_universes_updated_at", table_name="story_universes")
|
||||
op.drop_index("idx_story_universes_child_id", table_name="story_universes")
|
||||
op.drop_table("story_universes")
|
||||
@@ -0,0 +1,78 @@
|
||||
"""add reading events and memory items
|
||||
|
||||
Revision ID: 0006_add_reading_events_and_memory_items
|
||||
Revises: 0005_add_story_universes_and_story_links
|
||||
Create Date: 2025-12-22
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
revision = "0006_add_reading_events_and_memory_items"
|
||||
down_revision = "0005_add_story_universes_and_story_links"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table(
|
||||
"reading_events",
|
||||
sa.Column("id", sa.Integer(), primary_key=True, autoincrement=True),
|
||||
sa.Column(
|
||||
"child_profile_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("child_profiles.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"story_id",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("stories.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("event_type", sa.String(20), nullable=False),
|
||||
sa.Column("reading_time", sa.Integer(), server_default="0", nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
)
|
||||
op.create_index("idx_reading_events_profile", "reading_events", ["child_profile_id"])
|
||||
op.create_index("idx_reading_events_story", "reading_events", ["story_id"])
|
||||
op.create_index("idx_reading_events_created", "reading_events", ["created_at"])
|
||||
|
||||
op.create_table(
|
||||
"memory_items",
|
||||
sa.Column("id", sa.String(36), primary_key=True),
|
||||
sa.Column(
|
||||
"child_profile_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("child_profiles.id", ondelete="CASCADE"),
|
||||
nullable=False,
|
||||
),
|
||||
sa.Column(
|
||||
"universe_id",
|
||||
sa.String(36),
|
||||
sa.ForeignKey("story_universes.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("type", sa.String(50), nullable=False),
|
||||
sa.Column("value", sa.JSON(), nullable=False),
|
||||
sa.Column("base_weight", sa.Float(), server_default="1.0", nullable=False),
|
||||
sa.Column("last_used_at", sa.DateTime(timezone=True)),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.Column("ttl_days", sa.Integer()),
|
||||
)
|
||||
op.create_index("idx_memory_items_profile", "memory_items", ["child_profile_id"])
|
||||
op.create_index("idx_memory_items_universe", "memory_items", ["universe_id"])
|
||||
op.create_index("idx_memory_items_last_used", "memory_items", ["last_used_at"])
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_index("idx_memory_items_last_used", table_name="memory_items")
|
||||
op.drop_index("idx_memory_items_universe", table_name="memory_items")
|
||||
op.drop_index("idx_memory_items_profile", table_name="memory_items")
|
||||
op.drop_table("memory_items")
|
||||
|
||||
op.drop_index("idx_reading_events_created", table_name="reading_events")
|
||||
op.drop_index("idx_reading_events_story", table_name="reading_events")
|
||||
op.drop_index("idx_reading_events_profile", table_name="reading_events")
|
||||
op.drop_table("reading_events")
|
||||
68
backend/alembic/versions/0007_add_push_configs_and_events.py
Normal file
68
backend/alembic/versions/0007_add_push_configs_and_events.py
Normal file
@@ -0,0 +1,68 @@
|
||||
"""Add push configs and events.
|
||||
|
||||
Revision ID: 0007_add_push_configs_and_events
|
||||
Revises: 0006_add_reading_events_and_memory_items
|
||||
Create Date: 2025-12-24 16:40:00.000000
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = "0007_add_push_configs_and_events"
|
||||
down_revision = "0006_add_reading_events_and_memory_items"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.create_table(
|
||||
"push_configs",
|
||||
sa.Column("id", sa.String(length=36), primary_key=True),
|
||||
sa.Column("user_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("child_profile_id", sa.String(length=36), nullable=False),
|
||||
sa.Column("push_time", sa.Time(), nullable=True),
|
||||
sa.Column("push_days", sa.JSON(), nullable=False, server_default="[]"),
|
||||
sa.Column("enabled", sa.Boolean(), nullable=False, server_default=sa.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(),
|
||||
onupdate=sa.func.now(),
|
||||
),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["child_profile_id"], ["child_profiles.id"], ondelete="CASCADE"),
|
||||
sa.UniqueConstraint("child_profile_id", name="uq_push_config_child"),
|
||||
)
|
||||
op.create_index("ix_push_configs_user_id", "push_configs", ["user_id"])
|
||||
op.create_index("ix_push_configs_child_profile_id", "push_configs", ["child_profile_id"])
|
||||
|
||||
op.create_table(
|
||||
"push_events",
|
||||
sa.Column("id", sa.String(length=36), primary_key=True),
|
||||
sa.Column("user_id", sa.String(length=255), nullable=False),
|
||||
sa.Column("child_profile_id", sa.String(length=36), nullable=False),
|
||||
sa.Column("trigger_type", sa.String(length=20), nullable=False),
|
||||
sa.Column("status", sa.String(length=20), nullable=False),
|
||||
sa.Column("reason", sa.Text(), nullable=True),
|
||||
sa.Column("sent_at", sa.DateTime(timezone=True), nullable=False),
|
||||
sa.Column("created_at", sa.DateTime(timezone=True), server_default=sa.func.now()),
|
||||
sa.ForeignKeyConstraint(["user_id"], ["users.id"], ondelete="CASCADE"),
|
||||
sa.ForeignKeyConstraint(["child_profile_id"], ["child_profiles.id"], ondelete="CASCADE"),
|
||||
)
|
||||
op.create_index("ix_push_events_user_id", "push_events", ["user_id"])
|
||||
op.create_index("ix_push_events_child_profile_id", "push_events", ["child_profile_id"])
|
||||
op.create_index("ix_push_events_sent_at", "push_events", ["sent_at"])
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_push_events_sent_at", table_name="push_events")
|
||||
op.drop_index("ix_push_events_child_profile_id", table_name="push_events")
|
||||
op.drop_index("ix_push_events_user_id", table_name="push_events")
|
||||
op.drop_table("push_events")
|
||||
|
||||
op.drop_index("ix_push_configs_child_profile_id", table_name="push_configs")
|
||||
op.drop_index("ix_push_configs_user_id", table_name="push_configs")
|
||||
op.drop_table("push_configs")
|
||||
25
backend/alembic/versions/0008_add_pages_to_stories.py
Normal file
25
backend/alembic/versions/0008_add_pages_to_stories.py
Normal file
@@ -0,0 +1,25 @@
|
||||
"""add pages column to stories
|
||||
|
||||
Revision ID: 0008_add_pages_to_stories
|
||||
Revises: 0007_add_push_configs_and_events
|
||||
Create Date: 2026-01-20
|
||||
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '0008_add_pages_to_stories'
|
||||
down_revision = '0007_add_push_configs_and_events'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.add_column('stories', sa.Column('pages', postgresql.JSON(), nullable=True))
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_column('stories', 'pages')
|
||||
Reference in New Issue
Block a user