121 lines
4.9 KiB
Python
121 lines
4.9 KiB
Python
# database.py
|
|
import os
|
|
import uuid
|
|
from sqlalchemy import (
|
|
create_engine, Column, Integer, String, Float, DateTime, Boolean, Text,
|
|
ForeignKey
|
|
)
|
|
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) # unique → implicit unique index
|
|
next_max_id = Column(String(255), nullable=True)
|
|
updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now())
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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, index=True) # index for lookups by seller
|
|
username = Column(String(100), nullable=False, unique=True) # unique → implicit unique index
|
|
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,
|
|
)
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# 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) # unique → implicit unique index
|
|
media_type = Column(String(10), nullable=False, index=True) # '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")
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Session dependency
|
|
# -----------------------------------------------------------------------------
|
|
def get_db():
|
|
db = SessionLocal()
|
|
try:
|
|
yield db
|
|
finally:
|
|
db.close()
|
|
|
|
# -----------------------------------------------------------------------------
|
|
# Table creation
|
|
# -----------------------------------------------------------------------------
|
|
def create_tables():
|
|
Base.metadata.create_all(bind=engine)
|