from sqlalchemy import Column, Integer, String, DateTime, Text, Boolean, ForeignKey, JSON, Float, BigInteger, Date from sqlalchemy.orm import relationship from sqlalchemy.sql import func from sqlalchemy.dialects.postgresql import UUID from app.database import Base import uuid class Company(Base): """회사 모델""" __tablename__ = "company" company_id = Column(Integer, primary_key=True, index=True) company_name = Column(String, nullable=True) # 관계 users = relationship("User", back_populates="company") class User(Base): """사용자 모델""" __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String, unique=True, index=True, nullable=True) # username 필드 추가 email = Column(String, unique=True, index=True, nullable=False) hashed_password = Column(String, nullable=False) is_active = Column(Boolean, default=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) name = Column(String, nullable=False) # name 필드 복구 refresh_token = Column(String, nullable=True) company_id = Column(Integer, ForeignKey("company.company_id"), nullable=True) address = Column(String, nullable=True) description = Column(String, nullable=True) role = Column(String, nullable=True) # 사용자, 마케터, 프랜차이즈 대표, 관리자 # 관계 company = relationship("Company", back_populates="users") projects = relationship("Project", back_populates="owner") class AdStore(Base): """광고 매장 모델""" __tablename__ = "ad_store" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=True) name = Column(String, nullable=True) category = Column(String, nullable=True) site_address = Column(String, nullable=True) road_address = Column(String, nullable=True) phone_number = Column(String, nullable=True) naver_place_url = Column(String, nullable=True) hashtags = Column(JSON, nullable=False, default=list) description = Column(Text, nullable=True) image_urls = Column(JSON, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) found_year = Column(Integer, nullable=True) business_hours = Column(String, nullable=True) # 매장 오픈, close 시간, 주말, 휴무 관련 employ_count = Column(Integer, nullable=True) # 매장 직원수 specialties = Column(String, nullable=True) # 사업 전문 분야 # 관계 user = relationship("User", backref="ad_stores") projects = relationship("Project", backref="ad_store") class Preset(Base): """프리셋 모델""" __tablename__ = "preset" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=False) name = Column(String, nullable=True) category = Column(String, nullable=True) description = Column(String, nullable=True) favorite = Column(Boolean, default=False) hashtag = Column(JSON, nullable=True) image_urls = Column(JSON, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) last_used_at = Column(DateTime(timezone=True), nullable=True) title = Column(String, nullable=True) # 관계 user = relationship("User", backref="presets") projects = relationship("Project", back_populates="preset") class Template(Base): """템플릿 모델""" __tablename__ = "template" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id"), nullable=True) name = Column(String, nullable=True) description = Column(String, nullable=True) thumbnail = Column(String, nullable=True) template_url = Column(String, nullable=True) category = Column(String, nullable=True) rating = Column(Float, nullable=True) # real type in PostgreSQL maps to Float favortie = Column(Boolean, default=False) # Note: typo in DB column name "favortie" hashtags = Column(JSON, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) usage_count = Column(Integer, nullable=True) # 관계 user = relationship("User", backref="templates") projects = relationship("Project", back_populates="template") class Project(Base): """프로젝트 모델""" __tablename__ = "projects" id = Column(Integer, primary_key=True, index=True) title = Column(String, nullable=False) description = Column(Text) target_url = Column(String, nullable=False) # 크롤링 대상 URL status = Column(String, default="created") # created, crawling, processing, completed, failed owner_id = Column(Integer, ForeignKey("users.id")) created_at = Column(DateTime(timezone=True), server_default=func.now()) # Template and Store references (from database) template_id = Column(Integer, ForeignKey("template.id"), nullable=True) store_id = Column(Integer, ForeignKey("ad_store.id"), nullable=True) preset_id = Column(Integer, ForeignKey("preset.id"), nullable=True) # YouTube 관련 필드 youtube_video_id = Column(String(50), nullable=True) youtube_url = Column(String(255), nullable=True) youtube_upload_status = Column(String(50), nullable=True) youtube_privacy_status = Column(String(20), nullable=True) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # 관계 owner = relationship("User", back_populates="projects") template = relationship("Template", back_populates="projects") preset = relationship("Preset", back_populates="projects") crawl_data = relationship("CrawlData", back_populates="project", uselist=False) video = relationship("Video", back_populates="project", uselist=False) generated_content = relationship("GeneratedContent", back_populates="project", uselist=False) task_statuses = relationship("TaskStatus", back_populates="project") class CrawlData(Base): """크롤링 데이터 모델""" __tablename__ = "crawl_data" id = Column(Integer, primary_key=True, index=True) project_id = Column(Integer, ForeignKey("projects.id")) # Naver Map 데이터 business_name = Column(String) address = Column(String) phone = Column(String) business_type = Column(String) coordinates = Column(JSON) # {"lat": float, "lng": float} # 추가 장소 정보 (crawler4) business_hours = Column(String) # 영업시간 homepage = Column(String) # 홈페이지 URL place_description = Column(Text) # 장소 설명 # 블로그 데이터 blog_urls = Column(JSON) # 수집된 블로그 URL 리스트 blog_content = Column(Text) # 추출된 블로그 내용 advertising_keywords = Column(JSON) # 광고 키워드 리스트 # 상세 블로그 정보 (crawler4) blog_details = Column(JSON) # detailed_blogs 전체 저장 map_blog_reviews = Column(JSON) # 지도 블로그 리뷰 # 이미지 데이터 image_urls = Column(JSON) # 수집된 이미지 URL 리스트 stored_images = Column(JSON) # 저장된 이미지 파일 경로 리스트 # 크롤링 메타데이터 crawl_metadata = Column(JSON) # 통계 정보, 수집 시간 등 created_at = Column(DateTime(timezone=True), server_default=func.now()) # 관계 project = relationship("Project", back_populates="crawl_data") class GeneratedContent(Base): """생성된 콘텐츠 모델""" __tablename__ = "generated_content" id = Column(Integer, primary_key=True, index=True) project_id = Column(Integer, ForeignKey("projects.id")) # 가사 데이터 lyrics = Column(Text) lyrics_prompt = Column(Text) # 사용된 프롬프트 lyrics_generation_time = Column(Integer, default=0) # 가사 생성 시간(초) lyrics_token_count = Column(Integer, default=0) # 사용된 토큰 수 lyrics_model_used = Column(String, default="gpt-3.5-turbo") # 사용된 모델 # BGM 데이터 bgm_url = Column(String) bgm_file_path = Column(String) # 저장된 BGM 파일 경로 bgm_duration = Column(Float, default=30.0) # BGM 길이(초) bgm_generation_time = Column(Integer, default=0) # BGM 생성 시간(초) bgm_model_used = Column(String, default="mureka-v6") # 사용된 모델 created_at = Column(DateTime(timezone=True), server_default=func.now()) # 관계 project = relationship("Project", back_populates="generated_content") class Video(Base): """동영상 모델""" __tablename__ = "videos" id = Column(Integer, primary_key=True, index=True) project_id = Column(Integer, ForeignKey("projects.id")) # 동영상 파일 정보 file_path = Column(String, nullable=False) file_size = Column(Integer) # 바이트 단위 duration = Column(Integer) # 초 단위 resolution = Column(String) # "1920x1080" 형식 # 생성 설정 template_type = Column(String, default="default") generation_settings = Column(JSON) # 동영상 생성 설정 # 상태 status = Column(String, default="processing") # processing, completed, failed error_message = Column(Text) created_at = Column(DateTime(timezone=True), server_default=func.now()) completed_at = Column(DateTime(timezone=True)) # 관계 project = relationship("Project", back_populates="video") class TaskStatus(Base): """작업 상태 추적 모델""" __tablename__ = "task_status" id = Column(Integer, primary_key=True, index=True) task_id = Column(String, unique=True, index=True, nullable=True) # Celery 태스크 ID project_id = Column(Integer, ForeignKey("projects.id"), nullable=True) task_type = Column(String, nullable=True) # crawling, content_generation, video_generation status = Column(String, nullable=True) # pending, started, progress, success, failure progress = Column(Integer, nullable=True) # 0-100 current_step = Column(String, nullable=True) result = Column(JSON, nullable=True) error_message = Column(Text, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), nullable=True) # 관계 project = relationship("Project", back_populates="task_statuses") class UserActivityLog(Base): """사용자 활동 로그 모델""" __tablename__ = "user_activity_logs" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id")) action = Column(String, nullable=False) # login, logout, create_project, etc. details = Column(JSON) # 추가 정보 ip_address = Column(String) user_agent = Column(String) created_at = Column(DateTime(timezone=True), server_default=func.now()) # 관계 user = relationship("User") class SystemStatistics(Base): """시스템 통계 모델""" __tablename__ = "system_statistics" id = Column(Integer, primary_key=True, index=True) stat_date = Column(Date, unique=True, nullable=False) # 사용자 통계 total_users = Column(Integer, nullable=True) active_users = Column(Integer, nullable=True) new_users = Column(Integer, nullable=True) # 프로젝트 통계 total_projects = Column(Integer, nullable=True) completed_projects = Column(Integer, nullable=True) failed_projects = Column(Integer, nullable=True) # 동영상 통계 total_videos = Column(Integer, nullable=True) total_video_size = Column(BigInteger, nullable=True) # bytes new_videos = Column(Integer, nullable=True) # 이번달 새로 생성한 비디오 갯수 # 템플릿 통계 total_templates = Column(Integer, nullable=True) # 전체 템플릿 갯수 # 성능 통계 avg_crawling_time = Column(Float, nullable=True) # seconds avg_content_generation_time = Column(Float, nullable=True) # seconds avg_video_rendering_time = Column(Float, nullable=True) # seconds # 증가율 통계 (월단위) user_growth_rate = Column(Float, nullable=True) # 사용자 증가량-월단위 template_growth_rate = Column(Float, nullable=True) # 템플릿 생성 증가율 - 월단위 video_growth_rate = Column(Float, nullable=True) # 비디오 생성 증가율 -월단위 media_growth_rate = Column(Float, nullable=True) # 미디어 에셋 증가율 - 월단위 created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) class SystemUsage(Base): """시스템 사용량 모델""" __tablename__ = "system_usage" id = Column(Integer, primary_key=True, index=True) stat_date = Column(Date, unique=True, nullable=False) # 스토리지 사용량 (Giga 단위) image_usage = Column(Float, nullable=True) # 이미지 스토리지 사용량 - Giga 단위 video_usage = Column(Float, nullable=True) # 비디오 스토리지 사용량 - Giga 단위 music_usage = Column(Float, nullable=True) # 노래 스토리지 사용량 - Giga 단위 extra_usage = Column(Float, nullable=True) # 기타 스토리지 사용량 - Giga 단위 created_at = Column(DateTime(timezone=True), server_default=func.now()) class SystemAvailability(Base): """시스템 가용성 모델""" __tablename__ = "system_availability" id = Column(Integer, primary_key=True, index=True) stat_date = Column(Date, unique=True, nullable=False) # 가용성 지표 (퍼센트 단위 - smallint로 0-100 범위) ai_availability = Column(Integer, nullable=True) # AI 엔진 가동율 dbase_availability = Column(Integer, nullable=True) # 데이터베이스 가동율 storage_availability = Column(Integer, nullable=True) # 스토리지 서버 가동율 renderer_availability = Column(Integer, nullable=True) # 비디오 Renderer 가동율 created_at = Column(DateTime(timezone=True), server_default=func.now()) class MediaAsset(Base): """미디어 자산 모델""" __tablename__ = "media_assets" __mapper_args__ = {"confirm_deleted_rows": False} id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False) type = Column(String, nullable=False) size = Column(String) file_size_bytes = Column(Integer) dimensions = Column(String) width = Column(Integer) height = Column(Integer) duration = Column(String) duration_seconds = Column(Float) thumbnail = Column(String) file_path = Column(String) url = Column(String) mime_type = Column(String) tags = Column(JSON) usage_count = Column(Integer, default=0) owner_id = Column(Integer, ForeignKey("users.id")) meta_info = Column(JSON) # metadata는 SQLAlchemy 예약어이므로 변경 created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # 관계 owner = relationship("User", backref="media_assets") class Asset(Base): """미디어 자산 모델""" __tablename__ = "media_assets" __mapper_args__ = {"confirm_deleted_rows": False} id = Column(Integer, primary_key=True, index=True) name = Column(String, nullable=False) type = Column(String, nullable=False) size = Column(String) file_size_bytes = Column(Integer) dimensions = Column(String) width = Column(Integer) height = Column(Integer) duration = Column(String) duration_seconds = Column(Float) thumbnail = Column(String) file_path = Column(String) url = Column(String) mime_type = Column(String) tags = Column(JSON) usage_count = Column(Integer, default=0) owner_id = Column(Integer, ForeignKey("users.id")) meta_info = Column(JSON) # metadata는 SQLAlchemy 예약어이므로 변경 created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # 관계 owner = relationship("User", backref="media_assets")