Files
trendyol-analiz/backend/database.py
furkanyigit34 1c10a701cf feat: Miller Columns kategori seçici + JSON tree tabanlı mimari
Ne yaptık:
- Sahibinden.com tarzı Miller Columns kategori seçici (CategorySelector.jsx)
- Trendyol API'den 3971 kategori ağacı çekildi (Playwright ile)
- Backend: JSON tree tabanlı kategori endpoint'leri (/api/category-tree/*)
- Backend: Rapor oluşturma artık DB kategorilerine bağımlı değil
- Report tablosundaki category_id FK constraint kaldırıldı
- Dockerfile'a trendyol_category_tree.json eklendi

Neden yaptık:
- DB'deki kategori tablosu boştu, Trendyol API ID'leri ile Excel ID'leri farklıydı
- Playwright ile Trendyol'un kendi kategori ağacını çektik (3971 kategori, gerçek API ID'leri)
- Miller Columns ile kullanıcı adım adım derinleşerek kategori seçebiliyor
- Arama özelliği ile kelime bazlı kategori bulma da mümkün

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 02:24:22 +03:00

104 lines
3.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Database setup and models - PostgreSQL
"""
from sqlalchemy import create_engine, Column, Integer, String, DateTime, Boolean, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker, relationship
from datetime import datetime
import os
from logging_config import get_logger
log = get_logger("db")
# PostgreSQL database - configurable via environment variable
# Default: Local PostgreSQL for development
# Docker: postgresql://postgres:trendyol123@postgres:5432/trendyol_db
DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:trendyol123@localhost:5433/trendyol_db")
engine = create_engine(DATABASE_URL, pool_pre_ping=True)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
Base = declarative_base()
class Category(Base):
"""Category model - hierarchical structure"""
__tablename__ = "categories"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False)
parent_id = Column(Integer, ForeignKey('categories.id'), nullable=True)
trendyol_category_id = Column(Integer, nullable=True)
trendyol_url = Column(String, nullable=True)
path_model = Column(String, nullable=True) # URL slug for search API (e.g. "elbise-x-c56")
is_active = Column(Boolean, default=True)
created_at = Column(DateTime, default=datetime.utcnow)
# Relationships
children = relationship("Category", backref="parent", remote_side=[id])
snapshots = relationship("Snapshot", back_populates="category")
class Snapshot(Base):
"""Snapshot model - monthly data captures"""
__tablename__ = "snapshots"
id = Column(Integer, primary_key=True, index=True)
category_id = Column(Integer, ForeignKey('categories.id'), nullable=False, index=True)
snapshot_month = Column(String, nullable=False, index=True) # "2024-11", "2024-12"
total_products = Column(Integer, default=0)
avg_price = Column(Integer, default=0)
json_file_path = Column(String, nullable=True)
scraped_at = Column(DateTime, default=datetime.utcnow)
# Relationships
category = relationship("Category", back_populates="snapshots")
class Report(Base):
"""Report model - saved dashboard reports"""
__tablename__ = "reports"
id = Column(Integer, primary_key=True, index=True)
name = Column(String, nullable=False) # "Kasım Ayı Kozmetik Raporu"
category_id = Column(Integer, nullable=True, index=True) # Trendyol API category ID (no FK)
total_products = Column(Integer, default=0)
total_subcategories = Column(Integer, default=0)
json_file_path = Column(String, nullable=True)
html_file_path = Column(String, nullable=True)
created_at = Column(DateTime, default=datetime.utcnow)
class EnrichmentError(Base):
"""Persistent log for external enrichment errors"""
__tablename__ = "enrichment_errors"
id = Column(Integer, primary_key=True, index=True)
report_id = Column(Integer, nullable=True)
product_id = Column(Integer, nullable=True)
merchant_id = Column(Integer, nullable=True)
endpoint = Column(String, nullable=False) # reviews | social | questions | similar | followers
error_type = Column(String, nullable=True) # timeout | dns | reset | http | other
message = Column(String, nullable=True)
status_code = Column(Integer, nullable=True)
attempt = Column(Integer, default=1)
created_at = Column(DateTime, default=datetime.utcnow)
def init_db():
"""Initialize database - create tables"""
Base.metadata.create_all(bind=engine)
log.info("Database initialized successfully")
def get_db():
"""Get database session"""
db = SessionLocal()
try:
yield db
finally:
db.close()
if __name__ == "__main__":
init_db()