FastApi-ISS/database.py
2025-09-25 02:15:20 +03:30

138 lines
5.4 KiB
Python

# database.py
import os
import uuid
from sqlalchemy import (
create_engine, Column, Integer, String, Float, DateTime, Boolean, Text,
ForeignKey, UniqueConstraint, Index
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
from sqlalchemy.sql import func
from sqlalchemy.dialects.postgresql import UUID, JSONB
# -----------------------------------------------------------------------------
# Database URL
# -----------------------------------------------------------------------------
DATABASE_URL = os.getenv("DATABASE_URL")
if not DATABASE_URL:
POSTGRES_USER = os.getenv("POSTGRES_USER", "fastapi_user")
POSTGRES_PASSWORD = os.getenv("POSTGRES_PASSWORD", "fastapi_password")
POSTGRES_HOST = os.getenv("POSTGRES_HOST", "postgres")
POSTGRES_PORT = os.getenv("POSTGRES_PORT", "5432")
POSTGRES_DB = os.getenv("POSTGRES_DB", "fastapi_db")
DATABASE_URL = (
f"postgresql://{POSTGRES_USER}:{POSTGRES_PASSWORD}"
f"@{POSTGRES_HOST}:{POSTGRES_PORT}/{POSTGRES_DB}"
)
# -----------------------------------------------------------------------------
# Engine / Session / Base
# -----------------------------------------------------------------------------
engine = create_engine(DATABASE_URL)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
# -----------------------------------------------------------------------------
# Basic Item model (kept for backward compatibility / health checks)
# -----------------------------------------------------------------------------
class Item(Base):
__tablename__ = "items"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
description = Column(String, nullable=True)
price = Column(Float, nullable=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
# -----------------------------------------------------------------------------
# Cursor for BoxAPI pagination (persist next_max_id per username)
# -----------------------------------------------------------------------------
class InstaCursor(Base):
__tablename__ = "insta_cursors"
id = Column(Integer, primary_key=True)
username = Column(String(150), nullable=False, unique=True)
next_max_id = Column(String(255), nullable=True)
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
__table_args__ = (
UniqueConstraint("username", name="uq_insta_cursor_username"),
Index("ix_insta_cursors_username", "username"),
)
# -----------------------------------------------------------------------------
# Instagram Account
# -----------------------------------------------------------------------------
class InstagramAccount(Base):
__tablename__ = "instagram_accounts"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
seller_id = Column(UUID(as_uuid=True), nullable=False)
username = Column(String(100), nullable=False, unique=True, index=True)
is_active = Column(Boolean, default=True)
last_synced = Column(DateTime(timezone=True), nullable=True)
posts = relationship(
"InstagramPost",
back_populates="account",
cascade="all, delete-orphan",
passive_deletes=True,
)
__table_args__ = (
UniqueConstraint("username", name="uq_instagram_accounts_username"),
Index("ix_instagram_accounts_username", "username"),
Index("ix_instagram_accounts_seller_id", "seller_id"),
)
# -----------------------------------------------------------------------------
# Instagram Post
# -----------------------------------------------------------------------------
class InstagramPost(Base):
__tablename__ = "instagram_posts"
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
account_id = Column(
UUID(as_uuid=True),
ForeignKey("instagram_accounts.id", ondelete="CASCADE"),
nullable=False,
index=True,
)
ig_post_id = Column(String(100), nullable=False, unique=True, index=True)
media_type = Column(String(10), nullable=False) # 'image', 'video', 'carousel'
caption = Column(Text, nullable=True)
thumbnail_url = Column(Text, nullable=True)
# List[str] of public S3 URLs (stored as JSON array)
local_media = Column(JSONB, nullable=True)
# Original post time on Instagram
created_at = Column(DateTime(timezone=True), nullable=False, index=True)
# When we scraped it
fetched_at = Column(DateTime(timezone=True), server_default=func.now())
account = relationship("InstagramAccount", back_populates="posts")
__table_args__ = (
UniqueConstraint("ig_post_id", name="uq_instagram_posts_ig_post_id"),
Index("ix_instagram_posts_created_at", "created_at"),
Index("ix_instagram_posts_media_type", "media_type"),
)
# -----------------------------------------------------------------------------
# Session dependency
# -----------------------------------------------------------------------------
def get_db():
db = SessionLocal()
try:
yield db
finally:
db.close()
# -----------------------------------------------------------------------------
# Table creation
# -----------------------------------------------------------------------------
def create_tables():
Base.metadata.create_all(bind=engine)