Skip to content

Commit 71f0052

Browse files
committed
chore(database): init alembic migrations and update dependencies
1 parent be5410d commit 71f0052

File tree

5 files changed

+6036
-0
lines changed

5 files changed

+6036
-0
lines changed

database/migrations/env.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import asyncio
2+
import os
3+
from logging.config import fileConfig
4+
5+
from alembic import context
6+
from dotenv import load_dotenv
7+
from sqlalchemy import pool
8+
from sqlalchemy.engine import Connection
9+
from sqlalchemy.ext.asyncio import async_engine_from_config
10+
11+
load_dotenv()
12+
13+
config = context.config
14+
15+
if config.config_file_name is not None:
16+
fileConfig(config.config_file_name)
17+
18+
19+
# 1. Створюємо універсальну функцію отримання URL
20+
def get_url():
21+
user = os.getenv("POSTGRES_USER", "seratonin")
22+
password = os.getenv("POSTGRES_PASSWORD", "seratonin_pass")
23+
24+
# Використовуємо загальні назви DB_HOST та DB_PORT.
25+
# Якщо їх немає в .env — беремо дефолти для тунелю.
26+
host = os.getenv("DB_HOST", "127.0.0.1")
27+
port = os.getenv("DB_PORT", os.getenv("EXTERNAL_POSTGRES_PORT", "5433"))
28+
29+
db = os.getenv("POSTGRES_DB", "seratonin_db")
30+
31+
return f"postgresql+asyncpg://{user}:{password}@{host}:{port}/{db}"
32+
33+
34+
# 2. Встановлюємо URL в конфігурацію
35+
config.set_main_option("sqlalchemy.url", get_url())
36+
37+
target_metadata = None
38+
39+
# other values from the config, defined by the needs of env.py,
40+
# can be acquired:
41+
# my_important_option = config.get_main_option("my_important_option")
42+
# ... etc.
43+
44+
45+
def run_migrations_offline() -> None:
46+
"""Run migrations in 'offline' mode.
47+
48+
This configures the context with just a URL
49+
and not an Engine, though an Engine is acceptable
50+
here as well. By skipping the Engine creation
51+
we don't even need a DBAPI to be available.
52+
53+
Calls to context.execute() here emit the given string to the
54+
script output.
55+
56+
"""
57+
url = config.get_main_option("sqlalchemy.url")
58+
context.configure(
59+
url=url,
60+
target_metadata=target_metadata,
61+
literal_binds=True,
62+
dialect_opts={"paramstyle": "named"},
63+
)
64+
65+
with context.begin_transaction():
66+
context.run_migrations()
67+
68+
69+
def do_run_migrations(connection: Connection) -> None:
70+
context.configure(connection=connection, target_metadata=target_metadata)
71+
72+
with context.begin_transaction():
73+
context.run_migrations()
74+
75+
76+
async def run_async_migrations() -> None:
77+
"""In this scenario we need to create an Engine
78+
and associate a connection with the context.
79+
80+
"""
81+
82+
connectable = async_engine_from_config(
83+
config.get_section(config.config_ini_section, {}),
84+
prefix="sqlalchemy.",
85+
poolclass=pool.NullPool,
86+
)
87+
88+
async with connectable.connect() as connection:
89+
await connection.run_sync(do_run_migrations)
90+
91+
await connectable.dispose()
92+
93+
94+
def run_migrations_online() -> None:
95+
"""Run migrations in 'online' mode."""
96+
97+
asyncio.run(run_async_migrations())
98+
99+
100+
if context.is_offline_mode():
101+
run_migrations_offline()
102+
else:
103+
run_migrations_online()

database/migrations/script.py.mako

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
"""${message}
2+
3+
Revision ID: ${up_revision}
4+
Revises: ${down_revision | comma,n}
5+
Create Date: ${create_date}
6+
7+
"""
8+
from typing import Sequence, Union
9+
10+
from alembic import op
11+
import sqlalchemy as sa
12+
${imports if imports else ""}
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = ${repr(up_revision)}
16+
down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
17+
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
18+
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
19+
20+
21+
def upgrade() -> None:
22+
"""Upgrade schema."""
23+
${upgrades if upgrades else "pass"}
24+
25+
26+
def downgrade() -> None:
27+
"""Downgrade schema."""
28+
${downgrades if downgrades else "pass"}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
"""initial_schema
2+
3+
Revision ID: 0c23d660630f
4+
Revises:
5+
Create Date: 2026-03-31 09:48:06.028203
6+
7+
"""
8+
9+
from typing import Sequence, Union
10+
11+
import sqlalchemy as sa
12+
from alembic import op
13+
14+
# revision identifiers, used by Alembic.
15+
revision: str = "0c23d660630f"
16+
down_revision: Union[str, Sequence[str], None] = None
17+
branch_labels: Union[str, Sequence[str], None] = None
18+
depends_on: Union[str, Sequence[str], None] = None
19+
20+
21+
def upgrade() -> None:
22+
op.create_table(
23+
"users",
24+
sa.Column("id", sa.Integer(), primary_key=True),
25+
sa.Column("username", sa.String(length=255), nullable=False, unique=True),
26+
sa.Column(
27+
"created_at",
28+
sa.DateTime(timezone=True),
29+
server_default=sa.text("now()"),
30+
nullable=False,
31+
),
32+
)
33+
op.create_table(
34+
"drafts",
35+
sa.Column("id", sa.Integer(), primary_key=True),
36+
sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=False),
37+
sa.Column("topic", sa.String(length=255), nullable=False),
38+
sa.Column("content", sa.Text(), nullable=True),
39+
sa.Column("status", sa.String(length=50), nullable=False),
40+
sa.Column(
41+
"created_at",
42+
sa.DateTime(timezone=True),
43+
server_default=sa.text("now()"),
44+
nullable=False,
45+
),
46+
sa.Column(
47+
"updated_at",
48+
sa.DateTime(timezone=True),
49+
server_default=sa.text("now()"),
50+
nullable=False,
51+
),
52+
)
53+
op.create_table(
54+
"published_posts",
55+
sa.Column("id", sa.Integer(), primary_key=True),
56+
sa.Column("draft_id", sa.Integer(), sa.ForeignKey("drafts.id"), nullable=False),
57+
sa.Column("platform", sa.String(length=50), nullable=False),
58+
sa.Column("post_url", sa.String(length=500), nullable=False),
59+
sa.Column(
60+
"published_at",
61+
sa.DateTime(timezone=True),
62+
server_default=sa.text("now()"),
63+
nullable=False,
64+
),
65+
)
66+
op.create_table(
67+
"feedback",
68+
sa.Column("id", sa.Integer(), primary_key=True),
69+
sa.Column("draft_id", sa.Integer(), sa.ForeignKey("drafts.id"), nullable=False),
70+
sa.Column("user_id", sa.Integer(), sa.ForeignKey("users.id"), nullable=False),
71+
sa.Column("comment", sa.Text(), nullable=False),
72+
sa.Column(
73+
"created_at",
74+
sa.DateTime(timezone=True),
75+
server_default=sa.text("now()"),
76+
nullable=False,
77+
),
78+
)
79+
op.create_table(
80+
"task_results",
81+
sa.Column("id", sa.Integer(), primary_key=True),
82+
sa.Column("task_id", sa.String(length=255), nullable=False, unique=True),
83+
sa.Column("status", sa.String(length=50), nullable=False),
84+
sa.Column("result", sa.JSON(), nullable=True),
85+
sa.Column(
86+
"created_at",
87+
sa.DateTime(timezone=True),
88+
server_default=sa.text("now()"),
89+
nullable=False,
90+
),
91+
)
92+
93+
94+
def downgrade() -> None:
95+
op.drop_table("task_results")
96+
op.drop_table("feedback")
97+
op.drop_table("published_posts")
98+
op.drop_table("drafts")
99+
op.drop_table("users")

0 commit comments

Comments
 (0)