This commit is contained in:
2026-05-25 17:08:18 +08:00
parent df501f6151
commit f1259b71b5
9178 changed files with 1626125 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
from app.models.base import Base, TimestampMixin, engine, async_session, init_database, close_database
from app.models.user import User
from app.models.project import Project
from app.models.generation_record import GenerationRecord
from app.models.credit_record import CreditRecord
from app.models.model_config import ModelConfig
from app.models.system_config import SystemConfig
from app.models.notification import Notification
from app.models.notification_read import NotificationRead
from app.models.payment_order import PaymentOrder
from app.models.token_usage import TokenUsage
from app.models.industry_config import IndustryConfig
from app.models.video_engine import VideoEngine
from app.models.credit_ratio import CreditRatio
from app.models.menu_config import MenuConfig
from app.models.recharge_package import RechargePackage
from app.models.operation_log import OperationLog
__all__ = [
"Base", "TimestampMixin", "engine", "async_session",
"init_database", "close_database",
"User", "Project", "GenerationRecord", "CreditRecord",
"ModelConfig", "SystemConfig", "Notification", "PaymentOrder",
"TokenUsage", "IndustryConfig", "VideoEngine", "CreditRatio",
"MenuConfig", "RechargePackage", "OperationLog",
]
+32
View File
@@ -0,0 +1,32 @@
from datetime import datetime
from sqlalchemy import DateTime, func
from sqlalchemy.ext.asyncio import AsyncAttrs, async_sessionmaker, create_async_engine
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from app.config import settings
engine = create_async_engine(settings.DATABASE_URL, echo=settings.DEBUG)
async_session = async_sessionmaker(engine, expire_on_commit=False)
class Base(AsyncAttrs, DeclarativeBase):
pass
class TimestampMixin:
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)
async def init_database() -> None:
async with engine.begin() as conn:
await conn.run_sync(Base.metadata.create_all)
async def close_database() -> None:
await engine.dispose()
+18
View File
@@ -0,0 +1,18 @@
from sqlalchemy import Float, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class CreditRatio(Base, TimestampMixin):
__tablename__ = "credit_ratios"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
model_config_id: Mapped[str] = mapped_column(
String(32), ForeignKey("model_configs.id")
)
gen_type: Mapped[str] = mapped_column(String(16), default="video")
resolution: Mapped[str] = mapped_column(String(16), nullable=False)
ratio: Mapped[float] = mapped_column(Float, nullable=False)
base_credits: Mapped[float] = mapped_column(Float, default=80.0)
per_second_credits: Mapped[float] = mapped_column(Float, default=2.0)
+18
View File
@@ -0,0 +1,18 @@
from sqlalchemy import Float, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class CreditRecord(Base, TimestampMixin):
__tablename__ = "credit_records"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
user_id: Mapped[str] = mapped_column(
String(32), ForeignKey("users.id", ondelete="CASCADE"), index=True
)
type: Mapped[str] = mapped_column(String(16))
amount: Mapped[float] = mapped_column(Float)
balance_after: Mapped[float] = mapped_column(Float)
description: Mapped[str] = mapped_column(String(256))
related_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
@@ -0,0 +1,46 @@
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Integer, String, Text, Float
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class GenerationRecord(Base, TimestampMixin):
__tablename__ = "generation_records"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
user_id: Mapped[str] = mapped_column(
String(32), ForeignKey("users.id", ondelete="CASCADE"), index=True
)
project_id: Mapped[str] = mapped_column(
String(32), ForeignKey("projects.id", ondelete="CASCADE"), index=True
)
original_prompt: Mapped[str] = mapped_column(Text)
optimized_prompt: Mapped[str | None] = mapped_column(Text, nullable=True)
gen_type: Mapped[str] = mapped_column(String(16), default="video")
duration: Mapped[int | None] = mapped_column(Integer, nullable=True)
aspect_ratio: Mapped[str | None] = mapped_column(String(8), nullable=True)
resolution: Mapped[str | None] = mapped_column(String(8), nullable=True)
image_size: Mapped[str | None] = mapped_column(String(8), nullable=True)
image_proportion: Mapped[str | None] = mapped_column(String(8), nullable=True)
image_px: Mapped[str | None] = mapped_column(String(10), nullable=True)
status: Mapped[str] = mapped_column(String(32), default="prompt_optimized")
video_url: Mapped[str | None] = mapped_column(String(512), nullable=True)
image_url: Mapped[str | None] = mapped_column(String(512), nullable=True)
media_references: Mapped[str | None] = mapped_column(Text, nullable=True)
video_url_expires_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
seedance_task_id: Mapped[str | None] = mapped_column(String(128), nullable=True)
credits_cost: Mapped[float] = mapped_column(Float, default=0.0)
text_credits_cost: Mapped[float] = mapped_column(Float, default=0.0)
text_tokens_used: Mapped[int] = mapped_column(Integer, default=0)
video_tokens_used: Mapped[int] = mapped_column(Integer, default=0)
image_tokens_used: Mapped[int] = mapped_column(Integer, default=0)
generated_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
error_message: Mapped[str | None] = mapped_column(Text, nullable=True)
idempotency_key: Mapped[str | None] = mapped_column(String(64), nullable=True, index=True)
+22
View File
@@ -0,0 +1,22 @@
from sqlalchemy import Boolean, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class ImageEngine(Base, TimestampMixin):
__tablename__ = "image_engines"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
name: Mapped[str] = mapped_column(String(64), nullable=False)
provider: Mapped[str] = mapped_column(String(32), nullable=False)
api_base: Mapped[str] = mapped_column(String(512), nullable=False)
api_key: Mapped[str] = mapped_column(String(256), default="")
model_name: Mapped[str] = mapped_column(String(128), default="")
supported_models: Mapped[str] = mapped_column(Text, default='["doubao-seedream-5-0-260128"]')
# {"2K":{"1:1":"2048×2048",...}, "4K":{"1:1":"4096×4096",...}}
supported_sizes: Mapped[str] = mapped_column(Text, default='{}')
default_size: Mapped[str] = mapped_column(String(32), default="2K")
generate_url: Mapped[str | None] = mapped_column(String(512), nullable=True, default="")
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
priority: Mapped[int] = mapped_column(Integer, default=0)
@@ -0,0 +1,17 @@
from sqlalchemy import Boolean, Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class IndustryConfig(Base, TimestampMixin):
__tablename__ = "industry_configs"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
key: Mapped[str] = mapped_column(String(64), unique=True, nullable=False)
label: Mapped[str] = mapped_column(String(64), nullable=False)
icon: Mapped[str] = mapped_column(String(64), default="")
description: Mapped[str] = mapped_column(String(256), default="")
skills: Mapped[str] = mapped_column(Text, default="[]")
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
sort_order: Mapped[int] = mapped_column(Integer, default=0)
+19
View File
@@ -0,0 +1,19 @@
from sqlalchemy import Boolean, Integer, String, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class MenuConfig(Base, TimestampMixin):
__tablename__ = "menu_configs"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
label: Mapped[str] = mapped_column(String(64))
path: Mapped[str] = mapped_column(String(128), default="")
icon: Mapped[str] = mapped_column(String(64), default="")
sort_order: Mapped[int] = mapped_column(Integer, default=0)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
parent_id: Mapped[str | None] = mapped_column(String(32), ForeignKey("menu_configs.id"), nullable=True)
menu_type: Mapped[str] = mapped_column(String(16), default="page") # page / group
menu_target: Mapped[str] = mapped_column(String(16), default="frontend") # frontend / admin / both
is_default: Mapped[bool] = mapped_column(Boolean, default=False) # default show for new users
+20
View File
@@ -0,0 +1,20 @@
from sqlalchemy import Boolean, Float, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class ModelConfig(Base, TimestampMixin):
__tablename__ = "model_configs"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
name: Mapped[str] = mapped_column(String(64))
provider: Mapped[str] = mapped_column(String(32))
api_base: Mapped[str] = mapped_column(String(512))
api_key: Mapped[str] = mapped_column(String(256))
model_name: Mapped[str] = mapped_column(String(128))
weight: Mapped[int] = mapped_column(Integer, default=1)
max_tokens: Mapped[int] = mapped_column(Integer, default=4096)
temperature: Mapped[float] = mapped_column(Float, default=0.7)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
priority: Mapped[int] = mapped_column(Integer, default=0)
+18
View File
@@ -0,0 +1,18 @@
from sqlalchemy import Boolean, ForeignKey, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class Notification(Base, TimestampMixin):
__tablename__ = "notifications"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
user_id: Mapped[str | None] = mapped_column(
String(32), ForeignKey("users.id", ondelete="CASCADE"), nullable=True, index=True
)
title: Mapped[str] = mapped_column(String(128))
content: Mapped[str] = mapped_column(Text)
type: Mapped[str] = mapped_column(String(32), default="system")
is_read: Mapped[bool] = mapped_column(Boolean, default=False)
related_id: Mapped[str | None] = mapped_column(String(64), nullable=True)
@@ -0,0 +1,14 @@
from sqlalchemy import String, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class NotificationRead(Base, TimestampMixin):
__tablename__ = "notification_reads"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
notification_id: Mapped[str] = mapped_column(
String(32), ForeignKey("notifications.id"), index=True
)
user_id: Mapped[str] = mapped_column(String(32), index=True)
+17
View File
@@ -0,0 +1,17 @@
from sqlalchemy import Integer, String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class OperationLog(Base, TimestampMixin):
__tablename__ = "operation_logs"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
user_id: Mapped[str] = mapped_column(String(32), index=True)
username: Mapped[str] = mapped_column(String(64))
action: Mapped[str] = mapped_column(String(128)) # e.g. "创建用户", "修改菜单"
method: Mapped[str] = mapped_column(String(10)) # POST/PUT/DELETE
path: Mapped[str] = mapped_column(String(256))
detail: Mapped[str | None] = mapped_column(Text, nullable=True) # JSON detail
ip: Mapped[str | None] = mapped_column(String(64), nullable=True)
+24
View File
@@ -0,0 +1,24 @@
from datetime import datetime
from sqlalchemy import DateTime, Float, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class PaymentOrder(Base, TimestampMixin):
__tablename__ = "payment_orders"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
user_id: Mapped[str] = mapped_column(
String(32), ForeignKey("users.id", ondelete="CASCADE"), index=True
)
order_no: Mapped[str] = mapped_column(String(64), unique=True)
amount: Mapped[float] = mapped_column(Float)
credits: Mapped[float] = mapped_column(Float)
payment_method: Mapped[str] = mapped_column(String(16))
status: Mapped[str] = mapped_column(String(16), default="pending")
paid_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
trade_no: Mapped[str | None] = mapped_column(String(128), nullable=True)
+15
View File
@@ -0,0 +1,15 @@
from sqlalchemy import ForeignKey, String
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class Project(Base, TimestampMixin):
__tablename__ = "projects"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
user_id: Mapped[str] = mapped_column(
String(32), ForeignKey("users.id", ondelete="CASCADE"), index=True
)
name: Mapped[str] = mapped_column(String(128))
industry: Mapped[str] = mapped_column(String(32))
@@ -0,0 +1,19 @@
from sqlalchemy import Boolean, Integer, String, Float
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class RechargePackage(Base, TimestampMixin):
__tablename__ = "recharge_packages"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
name: Mapped[str] = mapped_column(String(64))
credits: Mapped[float] = mapped_column(Float)
price: Mapped[float] = mapped_column(Float)
bonus_credits: Mapped[float] = mapped_column(Float, default=0.0)
description: Mapped[str | None] = mapped_column(String(256), nullable=True)
package_type: Mapped[str] = mapped_column(String(32), default="normal")
is_gift: Mapped[bool] = mapped_column(Boolean, default=False)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
sort_order: Mapped[int] = mapped_column(Integer, default=0)
+13
View File
@@ -0,0 +1,13 @@
from sqlalchemy import String, Text
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class SystemConfig(Base, TimestampMixin):
__tablename__ = "system_configs"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
key: Mapped[str] = mapped_column(String(64), unique=True, index=True)
value: Mapped[str] = mapped_column(Text)
description: Mapped[str | None] = mapped_column(String(256), nullable=True)
+21
View File
@@ -0,0 +1,21 @@
from datetime import datetime
from sqlalchemy import DateTime, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class TokenUsage(Base, TimestampMixin):
__tablename__ = "token_usage"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
model_config_id: Mapped[str | None] = mapped_column(
String(32), ForeignKey("model_configs.id", ondelete="SET NULL"), nullable=True
)
user_id: Mapped[str | None] = mapped_column(
String(32), ForeignKey("users.id", ondelete="SET NULL"), nullable=True, index=True
)
input_tokens: Mapped[int] = mapped_column(Integer, default=0)
output_tokens: Mapped[int] = mapped_column(Integer, default=0)
total_tokens: Mapped[int] = mapped_column(Integer, default=0)
+25
View File
@@ -0,0 +1,25 @@
from datetime import datetime
from sqlalchemy import Boolean, DateTime, Float, Integer, String, JSON
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class User(Base, TimestampMixin):
__tablename__ = "users"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
username: Mapped[str] = mapped_column(String(64), unique=True, index=True)
email: Mapped[str | None] = mapped_column(String(255), unique=True, nullable=True)
phone: Mapped[str | None] = mapped_column(String(20), unique=True, nullable=True)
hashed_password: Mapped[str] = mapped_column(String(128))
avatar: Mapped[str | None] = mapped_column(String(512), nullable=True)
credits: Mapped[float] = mapped_column(Float, default=0.0)
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
is_admin: Mapped[bool] = mapped_column(Boolean, default=False)
user_type: Mapped[str] = mapped_column(String(16), default="frontend")
last_login_at: Mapped[datetime | None] = mapped_column(
DateTime(timezone=True), nullable=True
)
allowed_menus: Mapped[list | None] = mapped_column(JSON, nullable=True)
+23
View File
@@ -0,0 +1,23 @@
from sqlalchemy import Boolean, Integer, String
from sqlalchemy.orm import Mapped, mapped_column
from app.models.base import Base, TimestampMixin
class VideoEngine(Base, TimestampMixin):
__tablename__ = "video_engines"
id: Mapped[str] = mapped_column(String(32), primary_key=True)
name: Mapped[str] = mapped_column(String(64), nullable=False)
provider: Mapped[str] = mapped_column(String(32), nullable=False)
api_base: Mapped[str] = mapped_column(String(512), nullable=False)
api_key: Mapped[str] = mapped_column(String(256), default="")
model_name: Mapped[str] = mapped_column(String(128), default="")
supported_ratios: Mapped[str] = mapped_column(String(256), default='["16:9","4:3","1:1","3:4","9:16","21:9"]')
supported_resolutions: Mapped[str] = mapped_column(String(256), default='["480p","720p","1080p"]')
supported_durations: Mapped[str] = mapped_column(String(256), nullable=True, default='[4,5,6,7,8,9,10,11,12,13,14,15]')
max_duration: Mapped[int] = mapped_column(Integer, default=15)
generate_url: Mapped[str | None] = mapped_column(String(512), nullable=True, default="")
query_url: Mapped[str | None] = mapped_column(String(512), nullable=True, default="")
is_active: Mapped[bool] = mapped_column(Boolean, default=True)
priority: Mapped[int] = mapped_column(Integer, default=0)