Skip to content

Commit bcc1b12

Browse files
committed
feat(db): add platform and scheduled_at to Draft model for scheduling and dashboard
Details: - Updated Enum with SCHEDULED status. - Migrated Draft table to include platform and scheduled_at. - Expanded DraftRepository with get_recent_drafts and get_due_scheduled_drafts methods. - Generated Alembic migration.
1 parent 387fcf5 commit bcc1b12

9 files changed

Lines changed: 91 additions & 4 deletions

File tree

backend/api/routes/drafts.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ async def create_draft(
3030
2. Відправляє фонову задачу генерації в Taskiq.
3131
3. Повертає task_id клієнту.
3232
"""
33-
draft_in = DraftCreate(topic=request.topic, user_id=request.user_id)
33+
draft_in = DraftCreate(topic=request.topic, user_id=request.user_id, platform=request.platform)
3434
await draft_repo.create(draft_in)
3535

3636
# Викликаємо фонову задачу

backend/models/db_models.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ class Draft(Base):
3535
topic: Mapped[str] = mapped_column(String(255), nullable=False)
3636
content: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
3737
status: Mapped[str] = mapped_column(String(50), nullable=False)
38+
39+
# --- НОВІ ПОЛЯ ---
40+
platform: Mapped[str] = mapped_column(
41+
String(50), nullable=False, server_default="telegram"
42+
)
43+
scheduled_at: Mapped[Optional[datetime]] = mapped_column(
44+
DateTime(timezone=True), nullable=True
45+
)
46+
# -----------------
47+
3848
created_at: Mapped[datetime] = mapped_column(
3949
DateTime(timezone=True), server_default=func.now(), nullable=False
4050
)

backend/models/enums.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ class DraftStatus(str, Enum):
1313
GENERATED = "generated"
1414
FAILED = "failed"
1515
PUBLISHED = "published"
16+
SCHEDULED = "scheduled"
1617

1718

1819
class TaskStatus(str, Enum):

backend/models/schemas.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,23 @@ class DraftBase(BaseModel):
3232

3333
class DraftCreate(DraftBase):
3434
user_id: int
35+
platform: Platform
3536

3637

3738
class DraftUpdate(BaseModel):
3839
content: Optional[str] = None
3940
status: Optional[DraftStatus] = None
41+
platform: Optional[Platform] = None
42+
scheduled_at: Optional[datetime] = None
4043

4144

4245
class DraftResponse(DraftBase, ORMModel):
4346
id: int
4447
user_id: int
4548
content: Optional[str]
4649
status: str
50+
platform: str
51+
scheduled_at: Optional[datetime] # ДОДАНО
4752
created_at: datetime
4853
updated_at: datetime
4954

backend/repositories/draft_repository.py

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from datetime import datetime, timezone
2+
13
from sqlalchemy import select, update
24
from sqlalchemy.ext.asyncio import AsyncSession
35

@@ -11,7 +13,10 @@ def __init__(self, session: AsyncSession):
1113

1214
async def create(self, draft_in: DraftCreate) -> Draft:
1315
db_draft = Draft(
14-
user_id=draft_in.user_id, topic=draft_in.topic, status="pending"
16+
user_id=draft_in.user_id,
17+
topic=draft_in.topic,
18+
status="pending",
19+
platform=draft_in.platform,
1520
)
1621
self.session.add(db_draft)
1722
await self.session.flush()
@@ -36,3 +41,24 @@ async def update(self, draft_id: int, draft_update: DraftUpdate) -> Draft | None
3641
)
3742
result = await self.session.execute(stmt)
3843
return result.scalar_one_or_none()
44+
45+
async def get_recent_drafts(
46+
self, limit: int = 10, platform: str | None = None
47+
) -> list[Draft]:
48+
"""Витягує останні драфти (для Дашборду в Slack)"""
49+
stmt = select(Draft).order_by(Draft.updated_at.desc())
50+
if platform:
51+
stmt = stmt.where(Draft.platform == platform)
52+
stmt = stmt.limit(limit)
53+
54+
result = await self.session.execute(stmt)
55+
return list(result.scalars().all())
56+
57+
async def get_due_scheduled_drafts(self) -> list[Draft]:
58+
"""Витягує всі пости, час яких настав, але вони ще не опубліковані (для Планувальника)"""
59+
now = datetime.now(timezone.utc)
60+
stmt = select(Draft).where(
61+
Draft.status == "scheduled", Draft.scheduled_at <= now
62+
)
63+
result = await self.session.execute(stmt)
64+
return list(result.scalars().all())

backend/services/content_generator.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ async def generate_draft(
6565
topic: str,
6666
platform: str,
6767
source_url: str | None = None,
68-
max_retries: int = 2,
68+
max_retries: int = 1,
6969
) -> str:
7070
logger.info(
7171
"draft_generation_started",

backend/workers/broker.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@
22

33
from taskiq_redis import ListQueueBroker, RedisAsyncResultBackend
44

5+
# from .scheduled_post import scheduled_post_task
56
from backend.config.settings import settings
67
from backend.workers.middlewares.logging import StructlogMiddleware
78
from backend.workers.middlewares.metrics import PrometheusMetricsMiddleware
89
from backend.workers.middlewares.retry import RetryTrackerMiddleware
910

11+
# from .scheduled_post import scheduled_post_task
1012
redis_url = f"redis://{settings.REDIS_HOST}:{settings.REDIS_PORT}/0"
1113

1214
# Ініціалізуємо брокер з явно вказаною чергою (ізоляція від інших проєктів)

database/migrations/env.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
from sqlalchemy.engine import Connection
99
from sqlalchemy.ext.asyncio import async_engine_from_config
1010

11+
from backend.models.db_models import Base
12+
1113
load_dotenv()
1214

1315
config = context.config
@@ -36,7 +38,7 @@ def get_url():
3638
# 2. Встановлюємо URL в конфігурацію
3739
config.set_main_option("sqlalchemy.url", get_url())
3840

39-
target_metadata = None
41+
target_metadata = Base.metadata
4042

4143
# other values from the config, defined by the needs of env.py,
4244
# can be acquired:
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
"""add_platform_and_scheduled_at_to_drafts
2+
3+
Revision ID: 3ee4fb291951
4+
Revises: 0c23d660630f
5+
Create Date: 2026-04-05 01:28:32.995752
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 = "3ee4fb291951"
16+
down_revision: Union[str, Sequence[str], None] = "0c23d660630f"
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+
"""Upgrade schema."""
23+
# ### commands auto generated by Alembic - please adjust! ###
24+
op.add_column(
25+
"drafts",
26+
sa.Column(
27+
"platform", sa.String(length=50), server_default="telegram", nullable=False
28+
),
29+
)
30+
op.add_column(
31+
"drafts", sa.Column("scheduled_at", sa.DateTime(timezone=True), nullable=True)
32+
)
33+
# ### end Alembic commands ###
34+
35+
36+
def downgrade() -> None:
37+
"""Downgrade schema."""
38+
# ### commands auto generated by Alembic - please adjust! ###
39+
op.drop_column("drafts", "scheduled_at")
40+
op.drop_column("drafts", "platform")
41+
# ### end Alembic commands ###

0 commit comments

Comments
 (0)