Files
yu-zhi-ran/platform/backend/app/models.py
T
Yuzhiran Dev 233e23016c feat: 内容数据迁移至数据库,合规审查全链路打通
- 文章 HTML 存储从文件系统迁移至 articles 表,删除 releases 目录
- 合规审查从 DB 读取 HTML,审查结果写回 DB,通过后自动推进至待发布
- 新增 todayCount 筛选按钮,与系统概览统计数据一致
- 全屏预览修复:提升 z-index 超过侧边栏,添加退出全屏/关闭按钮
- 统一 '优化' → '审查' 命名,消除前后端术语不一致
- 调度器创作完成后自动触发审查(生成 → 审查 → 待发布)
- 清理旧备份/调试文件、过期大纲和研究笔记
2026-05-13 17:33:56 +08:00

523 lines
20 KiB
Python

from sqlalchemy import Column, String, Integer, Float, Date, DateTime, Text, Boolean, JSON, ForeignKey, UniqueConstraint
from sqlalchemy.sql import func
from sqlalchemy.orm import relationship
from .database import Base
from datetime import datetime
class AuditLog(Base):
__tablename__ = "audit_logs"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
user_id = Column(Integer, nullable=True, index=True)
username = Column(String, nullable=False)
action = Column(String, nullable=False, index=True)
resource_type = Column(String, nullable=True, index=True)
resource_id = Column(String, nullable=True)
details = Column(JSON, default=dict, nullable=True)
ip_address = Column(String, nullable=True)
user_agent = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
def to_dict(self):
return {
"id": self.id,
"user_id": self.user_id,
"username": self.username,
"action": self.action,
"resource_type": self.resource_type,
"resource_id": self.resource_id,
"details": self.details or {},
"ip_address": self.ip_address,
"user_agent": self.user_agent,
"created_at": self.created_at.isoformat() if self.created_at else None
}
class User(Base):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
username = Column(String, unique=True, nullable=False, index=True)
password_hash = Column(String, nullable=False)
role = Column(String, default="user", nullable=False)
last_login = Column(DateTime(timezone=True), nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
def to_dict(self):
return {
"id": self.id,
"username": self.username,
"role": self.role,
"last_login": self.last_login.isoformat() if self.last_login else None,
"created_at": self.created_at.isoformat() if self.created_at else None
}
class TopicField(Base):
__tablename__ = "topic_fields"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String, nullable=False)
icon = Column(String, nullable=True)
color = Column(String, nullable=True)
description = Column(Text, nullable=True)
parent_id = Column(Integer, ForeignKey("topic_fields.id"), nullable=True)
sort_order = Column(Integer, default=0)
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())
parent = relationship("TopicField", remote_side=[id], backref="children")
scoring_fields = relationship("TopicConfigField", back_populates="field", cascade="all, delete-orphan")
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"icon": self.icon,
"color": self.color,
"description": self.description,
"parent_id": self.parent_id,
"sort_order": self.sort_order,
"is_active": self.is_active,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
class TopicConfigField(Base):
__tablename__ = "topic_config_fields"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
field_id = Column(Integer, ForeignKey("topic_fields.id"), nullable=False)
name = Column(String, nullable=False)
key = Column(String, nullable=False)
field_type = Column(String, default="number") # number/select/multi_select/text
weight = Column(Float, default=1.0)
options = Column(JSON, default=list) # for select/multi_select
min_value = Column(Float, nullable=True)
max_value = Column(Float, nullable=True)
is_required = Column(Boolean, default=False)
sort_order = Column(Integer, default=0)
created_at = Column(DateTime(timezone=True), server_default=func.now())
field = relationship("TopicField", back_populates="scoring_fields")
def to_dict(self):
return {
"id": self.id,
"field_id": self.field_id,
"name": self.name,
"key": self.key,
"field_type": self.field_type,
"weight": self.weight,
"options": self.options or [],
"min_value": self.min_value,
"max_value": self.max_value,
"is_required": self.is_required,
"sort_order": self.sort_order,
"created_at": self.created_at.isoformat() if self.created_at else None,
}
class TopicStatusConfig(Base):
__tablename__ = "topic_status_configs"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
status = Column(String, unique=True, nullable=False)
label = Column(String, nullable=False)
color = Column(String, nullable=True)
icon = Column(String, nullable=True)
sort_order = Column(Integer, default=0)
is_default = Column(Boolean, default=False)
created_at = Column(DateTime(timezone=True), server_default=func.now())
def to_dict(self):
return {
"id": self.id,
"status": self.status,
"label": self.label,
"color": self.color,
"icon": self.icon,
"sort_order": self.sort_order,
"is_default": self.is_default,
"created_at": self.created_at.isoformat() if self.created_at else None,
}
class Topic(Base):
__tablename__ = "topics"
id = Column(String, primary_key=True, index=True)
field_id = Column(Integer, ForeignKey("topic_fields.id"), nullable=True)
field_name = Column(String, nullable=True)
title = Column(String, nullable=False)
format = Column(String)
core_concept = Column(Text)
audience_pain = Column(Text)
unique_angle = Column(Text)
priority = Column(String)
priority_score = Column(Integer, default=0)
total_score = Column(Float)
status = Column(String, default="pending")
cases = Column(JSON, default=list)
source_file = Column(String)
tags = Column(JSON, default=list)
custom_data = Column(JSON, default=dict)
scoring_data = Column(JSON, default=dict)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
generated_at = Column(DateTime(timezone=True), nullable=True)
ready_at = Column(Date)
published_at = Column(Date)
compliance_score = Column(Integer)
platform_urls = Column(JSON, default=dict)
lock_by = Column(String, nullable=True)
lock_at = Column(DateTime, nullable=True)
field = relationship("TopicField", backref="topics")
@property
def field_info(self):
if self.field:
return self.field.to_dict()
return None
class Article(Base):
__tablename__ = "articles"
id = Column(String, primary_key=True)
topic_id = Column(String, ForeignKey("topics.id"), nullable=False)
platform = Column(String, nullable=False)
file_path = Column(String, nullable=False)
status = Column(String, default="draft")
created_at = Column(DateTime(timezone=True), server_default=func.now())
compliance_score = Column(Integer)
html_content = Column(Text)
word_count = Column(Integer, nullable=True)
outline = Column(Text, nullable=True)
class PublishRecord(Base):
__tablename__ = "publish_records"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
topic_id = Column(String, ForeignKey("topics.id"), nullable=False)
platform = Column(String, nullable=False)
action = Column(String, nullable=False)
status = Column(String, nullable=False)
operator = Column(String, nullable=True)
description = Column(Text, nullable=True)
suggestion = Column(Text, nullable=True)
url = Column(String, nullable=True)
error_msg = Column(Text, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
topic = relationship("Topic")
class ContentCalendar(Base):
__tablename__ = "content_calendar"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
topic_id = Column(String, ForeignKey("topics.id"), nullable=True)
field_id = Column(Integer, ForeignKey("topic_fields.id"), nullable=True)
title = Column(String, nullable=False)
planned_date = Column(Date, nullable=False, index=True)
published_date = Column(Date, nullable=True)
platform = Column(String, nullable=True)
status = Column(String, default="planned") # planned/published/delayed/cancelled
reminder_time = Column(DateTime, nullable=True)
notes = Column(Text, nullable=True)
created_by = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
topic = relationship("Topic")
def to_dict(self):
return {
"id": self.id,
"topic_id": self.topic_id,
"field_id": self.field_id,
"title": self.title,
"planned_date": self.planned_date.isoformat() if self.planned_date else None,
"published_date": self.published_date.isoformat() if self.published_date else None,
"platform": self.platform,
"status": self.status,
"reminder_time": self.reminder_time.isoformat() if self.reminder_time else None,
"notes": self.notes,
"created_by": self.created_by,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
class ContentMetrics(Base):
__tablename__ = "content_metrics"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
topic_id = Column(String, ForeignKey("topics.id"), nullable=False, index=True)
platform = Column(String, nullable=False)
publish_url = Column(String, nullable=True)
views = Column(Integer, default=0)
likes = Column(Integer, default=0)
favorites = Column(Integer, default=0)
comments = Column(Integer, default=0)
shares = Column(Integer, default=0)
last_fetched = Column(DateTime(timezone=True), nullable=True)
data_snapshot = Column(JSON, default=dict)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
topic = relationship("Topic")
@property
def engagement_rate(self):
total = self.views or 0
if total == 0:
return 0
return round((self.likes or 0) / total * 100, 2)
def to_dict(self):
return {
"id": self.id,
"topic_id": self.topic_id,
"platform": self.platform,
"publish_url": self.publish_url,
"views": self.views,
"likes": self.likes,
"favorites": self.favorites,
"comments": self.comments,
"shares": self.shares,
"engagement_rate": self.engagement_rate,
"last_fetched": self.last_fetched.isoformat() if self.last_fetched else None,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
class MediaAsset(Base):
__tablename__ = "media_assets"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
filename = Column(String, nullable=False)
file_path = Column(String, nullable=False)
file_url = Column(String, nullable=True)
file_type = Column(String, nullable=False) # image/video/document
mime_type = Column(String, nullable=True)
size = Column(Integer, nullable=True)
width = Column(Integer, nullable=True)
height = Column(Integer, nullable=True)
thumbnail_path = Column(String, nullable=True)
alt_text = Column(String, nullable=True)
tags = Column(JSON, default=list)
topic_ids = Column(JSON, default=list)
usage_count = Column(Integer, default=0)
uploaded_by = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
def to_dict(self):
return {
"id": self.id,
"filename": self.filename,
"file_path": self.file_path,
"file_url": self.file_url,
"file_type": self.file_type,
"mime_type": self.mime_type,
"size": self.size,
"width": self.width,
"height": self.height,
"thumbnail_path": self.thumbnail_path,
"alt_text": self.alt_text,
"tags": self.tags or [],
"topic_ids": self.topic_ids or [],
"usage_count": self.usage_count,
"uploaded_by": self.uploaded_by,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
class PlatformConfig(Base):
__tablename__ = "platform_configs"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
platform = Column(String, unique=True, nullable=False) # zhihu/wechat/xiaohongshu
name = Column(String, nullable=False)
icon = Column(String, nullable=True)
api_endpoint = Column(String, nullable=True)
auth_config = Column(JSON, default=dict)
format_template = Column(JSON, default=dict)
compliance_rules = Column(JSON, default=dict)
default_format = Column(Text, nullable=True)
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())
def to_dict(self):
return {
"id": self.id,
"platform": self.platform,
"name": self.name,
"icon": self.icon,
"api_endpoint": self.api_endpoint,
"format_template": self.format_template or {},
"compliance_rules": self.compliance_rules or {},
"default_format": self.default_format,
"is_active": self.is_active,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
class ContentTask(Base):
__tablename__ = "content_tasks"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
topic_id = Column(String, ForeignKey("topics.id"), nullable=True, index=True)
task_id = Column(String, unique=True, nullable=False)
stage = Column(String, nullable=False) # research/outline/writer/optimizer/format/publish
status = Column(String, default="pending") # pending/running/completed/failed/cancelled
progress = Column(Integer, default=0)
message = Column(Text, nullable=True)
result_data = Column(JSON, default=dict)
error_msg = Column(Text, nullable=True)
started_at = Column(DateTime(timezone=True), nullable=True)
finished_at = Column(DateTime(timezone=True), nullable=True)
duration = Column(Integer, nullable=True)
created_by = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
topic = relationship("Topic")
def to_dict(self):
return {
"id": self.id,
"topic_id": self.topic_id,
"task_id": self.task_id,
"stage": self.stage,
"status": self.status,
"progress": self.progress,
"message": self.message,
"error_msg": self.error_msg,
"started_at": self.started_at.isoformat() if self.started_at else None,
"finished_at": self.finished_at.isoformat() if self.finished_at else None,
"duration": self.duration,
"created_by": self.created_by,
"created_at": self.created_at.isoformat() if self.created_at else None,
}
class Case(Base):
__tablename__ = "cases"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
title = Column(String, nullable=False)
field = Column(String, nullable=False)
summary = Column(Text)
key_metrics = Column(Text, nullable=True)
date = Column(String, nullable=True)
source = Column(String, nullable=True)
credibility_rating = Column(String, nullable=True)
china_applicability = Column(String, nullable=True)
source_url = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
def to_dict(self):
return {
"id": self.id,
"title": self.title,
"field": self.field,
"summary": self.summary,
"key_metrics": self.key_metrics,
"date": self.date,
"source": self.source,
"credibility_rating": self.credibility_rating,
"china_applicability": self.china_applicability,
"source_url": self.source_url,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
class TaskLog(Base):
__tablename__ = "task_logs"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
task_name = Column(String, nullable=False)
topic_id = Column(String, nullable=True)
status = Column(String, nullable=False)
message = Column(Text, nullable=True)
started_at = Column(DateTime(timezone=True), server_default=func.now())
finished_at = Column(DateTime(timezone=True), nullable=True)
duration = Column(Integer, nullable=True)
def to_dict(self):
return {
"id": self.id,
"task_name": self.task_name,
"topic_id": self.topic_id,
"status": self.status,
"message": self.message,
"started_at": self.started_at.isoformat() if self.started_at else None,
"finished_at": self.finished_at.isoformat() if self.finished_at else None,
"duration": self.duration,
}
class LLMConfig(Base):
__tablename__ = "llm_configs"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
name = Column(String, unique=True, nullable=False)
system_prompt = Column(Text, nullable=True)
user_prompt_template = Column(Text, nullable=False)
temperature = Column(Float, default=0.7)
max_tokens = Column(Integer, default=2000)
model = Column(String, nullable=True)
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())
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"system_prompt": self.system_prompt,
"user_prompt_template": self.user_prompt_template,
"temperature": self.temperature,
"max_tokens": self.max_tokens,
"model": self.model,
"is_active": self.is_active,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}
class SystemConfig(Base):
__tablename__ = "system_configs"
id = Column(Integer, primary_key=True, index=True, autoincrement=True)
key = Column(String, unique=True, nullable=False)
value = Column(Text, nullable=True)
description = Column(String, nullable=True)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime(timezone=True), onupdate=func.now())
def to_dict(self):
import json
return {
"id": self.id,
"key": self.key,
"value": json.loads(self.value) if self.value else None,
"description": self.description,
"created_at": self.created_at.isoformat() if self.created_at else None,
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
}