feat: 添加 SQLAlchemy 模型 + Alembic 数据库迁移
This commit is contained in:
+11
@@ -1,7 +1,17 @@
|
||||
import logging
|
||||
from contextlib import asynccontextmanager
|
||||
from fastapi import FastAPI
|
||||
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
|
||||
from app.config import settings
|
||||
from app.models.announcement import Base
|
||||
|
||||
engine = create_async_engine(settings.database_url, echo=settings.debug)
|
||||
async_session = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
|
||||
|
||||
|
||||
async def get_db() -> AsyncSession:
|
||||
async with async_session() as session:
|
||||
yield session
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -11,6 +21,7 @@ async def lifespan(app: FastAPI):
|
||||
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
||||
)
|
||||
yield
|
||||
await engine.dispose()
|
||||
|
||||
|
||||
app = FastAPI(
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import hashlib
|
||||
from datetime import datetime, date
|
||||
from sqlalchemy import String, Boolean, DateTime, Integer, Text, func
|
||||
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
|
||||
|
||||
|
||||
class Base(DeclarativeBase):
|
||||
pass
|
||||
|
||||
|
||||
class Announcement(Base):
|
||||
__tablename__ = "announcements"
|
||||
|
||||
id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
|
||||
title: Mapped[str] = mapped_column(String(500), nullable=False)
|
||||
publish_date: Mapped[datetime] = mapped_column(DateTime, nullable=False)
|
||||
purchase_name: Mapped[str] = mapped_column(String(200), default="")
|
||||
content_url: Mapped[str] = mapped_column(Text, default="")
|
||||
source_code: Mapped[str] = mapped_column(String(50), nullable=False)
|
||||
source_name: Mapped[str] = mapped_column(String(100), nullable=False)
|
||||
announcement_type: Mapped[str] = mapped_column(String(50), default="purchase")
|
||||
content_hash: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
|
||||
crawl_mode: Mapped[str] = mapped_column(String(20), default="auto")
|
||||
is_new: Mapped[bool] = mapped_column(Boolean, default=True)
|
||||
is_sent: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
keyword_matched: Mapped[bool] = mapped_column(Boolean, default=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
||||
updated_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now(), onupdate=func.now())
|
||||
|
||||
@staticmethod
|
||||
def generate_hash(title: str, publish_date: str, purchase_name: str,
|
||||
content_url: str, source_code: str) -> str:
|
||||
content = f"{title}|{publish_date}|{purchase_name}|{content_url}|{source_code}"
|
||||
return hashlib.sha256(content.encode("utf-8")).hexdigest()
|
||||
|
||||
@staticmethod
|
||||
def source_map() -> dict:
|
||||
import json
|
||||
from app.config import settings
|
||||
return json.loads(settings.announcement_sources)
|
||||
@@ -0,0 +1,56 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class AnnouncementResponse(BaseModel):
|
||||
id: int
|
||||
title: str
|
||||
publish_date: datetime
|
||||
purchase_name: str
|
||||
content_url: str
|
||||
source_code: str
|
||||
source_name: str
|
||||
announcement_type: str
|
||||
crawl_mode: str
|
||||
is_new: bool
|
||||
is_sent: bool
|
||||
keyword_matched: bool
|
||||
created_at: datetime
|
||||
|
||||
model_config = {"from_attributes": True}
|
||||
|
||||
|
||||
class AnnouncementListResponse(BaseModel):
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
items: list[AnnouncementResponse]
|
||||
|
||||
|
||||
class CrawlTriggerRequest(BaseModel):
|
||||
keywords: Optional[list[str]] = None
|
||||
sources: Optional[list[str]] = None
|
||||
manual: bool = False
|
||||
|
||||
|
||||
class CrawlStatusResponse(BaseModel):
|
||||
running: bool
|
||||
last_crawl_time: Optional[datetime] = None
|
||||
total_sources: int
|
||||
|
||||
|
||||
class SourceInfo(BaseModel):
|
||||
code: str
|
||||
name: str
|
||||
type: str
|
||||
|
||||
|
||||
class SourcesResponse(BaseModel):
|
||||
sources: list[SourceInfo]
|
||||
|
||||
|
||||
class JobResponse(BaseModel):
|
||||
id: str
|
||||
name: str
|
||||
next_run_time: Optional[str] = None
|
||||
Reference in New Issue
Block a user