from __future__ import annotations from datetime import datetime from decimal import Decimal from typing import Any from sqlalchemy import ( JSON, BigInteger, Boolean, DateTime, ForeignKey, Integer, Numeric, String, Text, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from app.models.base import Base, TimestampMixin PKBigInt = BigInteger().with_variant(Integer, "sqlite") class User(Base, TimestampMixin): __tablename__ = "users" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) public_id: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) username: Mapped[str | None] = mapped_column(String(64), unique=True) nickname: Mapped[str] = mapped_column(String(100), default="") avatar_url: Mapped[str] = mapped_column(String(500), default="") email: Mapped[str | None] = mapped_column(String(191), unique=True) mobile: Mapped[str | None] = mapped_column(String(32), unique=True) password_hash: Mapped[str] = mapped_column(String(255), default="") status: Mapped[int] = mapped_column(Integer, default=1) register_ip: Mapped[str] = mapped_column(String(64), default="") last_login_at: Mapped[datetime | None] = mapped_column(DateTime) last_login_ip: Mapped[str] = mapped_column(String(64), default="") wallet: Mapped["Wallet"] = relationship(back_populates="user", uselist=False) class AdminUser(Base, TimestampMixin): __tablename__ = "admin_users" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) username: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) password_hash: Mapped[str] = mapped_column(String(255), nullable=False) nickname: Mapped[str] = mapped_column(String(100), default="") avatar_url: Mapped[str] = mapped_column(String(500), default="") status: Mapped[int] = mapped_column(Integer, default=1) is_super_admin: Mapped[bool] = mapped_column(Boolean, default=False) last_login_at: Mapped[datetime | None] = mapped_column(DateTime) last_login_ip: Mapped[str] = mapped_column(String(64), default="") class Wallet(Base, TimestampMixin): __tablename__ = "wallets" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), unique=True, nullable=False) balance_points: Mapped[int] = mapped_column(BigInteger, default=0) frozen_points: Mapped[int] = mapped_column(BigInteger, default=0) total_recharged_points: Mapped[int] = mapped_column(BigInteger, default=0) total_consumed_points: Mapped[int] = mapped_column(BigInteger, default=0) total_refunded_points: Mapped[int] = mapped_column(BigInteger, default=0) user: Mapped[User] = relationship(back_populates="wallet") class WalletTransaction(Base): __tablename__ = "wallet_transactions" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) transaction_no: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False) wallet_id: Mapped[int] = mapped_column(ForeignKey("wallets.id"), nullable=False) biz_type: Mapped[str] = mapped_column(String(32), nullable=False) direction: Mapped[str] = mapped_column(String(16), nullable=False) amount_points: Mapped[int] = mapped_column(BigInteger, nullable=False) balance_before_points: Mapped[int] = mapped_column(BigInteger, nullable=False) balance_after_points: Mapped[int] = mapped_column(BigInteger, nullable=False) frozen_before_points: Mapped[int] = mapped_column(BigInteger, default=0) frozen_after_points: Mapped[int] = mapped_column(BigInteger, default=0) related_type: Mapped[str] = mapped_column(String(32), default="") related_id: Mapped[int | None] = mapped_column(BigInteger) remark: Mapped[str] = mapped_column(String(255), default="") operator_type: Mapped[str] = mapped_column(String(16), default="system") operator_id: Mapped[int | None] = mapped_column(BigInteger) extra_json: Mapped[dict[str, Any] | None] = mapped_column(JSON) created_at: Mapped[datetime] = mapped_column( DateTime, nullable=False ) class GrowthRewardRule(Base, TimestampMixin): __tablename__ = "growth_reward_rules" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) rule_type: Mapped[str] = mapped_column(String(32), unique=True, nullable=False) enabled: Mapped[bool] = mapped_column(Boolean, default=False) reward_points: Mapped[int] = mapped_column(BigInteger, default=0) trigger_condition: Mapped[str] = mapped_column(String(64), default="") min_consume_points: Mapped[int] = mapped_column(BigInteger, default=0) remark: Mapped[str] = mapped_column(String(255), default="") updated_by_admin_id: Mapped[int | None] = mapped_column(ForeignKey("admin_users.id")) class RedeemCode(Base, TimestampMixin): __tablename__ = "redeem_codes" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) batch_no: Mapped[str] = mapped_column(String(64), nullable=False) redeem_code: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) points: Mapped[int] = mapped_column(BigInteger, nullable=False) status: Mapped[str] = mapped_column(String(32), default="unused") used_by_user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id")) wallet_transaction_id: Mapped[int | None] = mapped_column( ForeignKey("wallet_transactions.id") ) used_ip: Mapped[str] = mapped_column(String(64), default="") used_user_agent: Mapped[str] = mapped_column(String(255), default="") expired_at: Mapped[datetime | None] = mapped_column(DateTime) used_at: Mapped[datetime | None] = mapped_column(DateTime) created_by_admin_id: Mapped[int | None] = mapped_column(ForeignKey("admin_users.id")) remark: Mapped[str] = mapped_column(String(255), default="") class InviteCode(Base, TimestampMixin): __tablename__ = "invite_codes" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False) invite_code: Mapped[str] = mapped_column(String(32), unique=True, nullable=False) invite_link: Mapped[str] = mapped_column(String(255), default="") status: Mapped[int] = mapped_column(Integer, default=1) is_default: Mapped[bool] = mapped_column(Boolean, default=False) max_use_count: Mapped[int | None] = mapped_column(Integer) used_count: Mapped[int] = mapped_column(Integer, default=0) class InviteRelation(Base, TimestampMixin): __tablename__ = "invite_relations" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) inviter_user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False) invitee_user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), unique=True, nullable=False) invite_code_id: Mapped[int] = mapped_column(ForeignKey("invite_codes.id"), nullable=False) reward_status: Mapped[str] = mapped_column(String(32), default="pending") reward_points: Mapped[int] = mapped_column(BigInteger, default=0) first_consumed_task_id: Mapped[int | None] = mapped_column(BigInteger) first_consumed_at: Mapped[datetime | None] = mapped_column(DateTime) rewarded_at: Mapped[datetime | None] = mapped_column(DateTime) reward_wallet_transaction_id: Mapped[int | None] = mapped_column( ForeignKey("wallet_transactions.id") ) register_ip: Mapped[str] = mapped_column(String(64), default="") register_device_fingerprint: Mapped[str] = mapped_column(String(128), default="") risk_status: Mapped[str] = mapped_column(String(32), default="normal") class RechargePlan(Base, TimestampMixin): __tablename__ = "recharge_plans" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(String(100), nullable=False) pay_amount: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False) point_ratio: Mapped[int] = mapped_column(Integer, default=100) give_points: Mapped[int] = mapped_column(BigInteger, default=0) bonus_points: Mapped[int] = mapped_column(BigInteger, default=0) sort_order: Mapped[int] = mapped_column(Integer, default=0) status: Mapped[int] = mapped_column(Integer, default=1) start_at: Mapped[datetime | None] = mapped_column(DateTime) end_at: Mapped[datetime | None] = mapped_column(DateTime) class PaymentChannel(Base, TimestampMixin): __tablename__ = "payment_channels" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) channel_code: Mapped[str] = mapped_column(String(32), unique=True, nullable=False) channel_name: Mapped[str] = mapped_column(String(100), nullable=False) provider_type: Mapped[str] = mapped_column(String(32), default="manual") config_json: Mapped[dict[str, Any] | None] = mapped_column(JSON) status: Mapped[int] = mapped_column(Integer, default=1) sort_order: Mapped[int] = mapped_column(Integer, default=0) class RechargeOrder(Base, TimestampMixin): __tablename__ = "recharge_orders" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) order_no: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False) recharge_plan_id: Mapped[int | None] = mapped_column(ForeignKey("recharge_plans.id")) payment_channel_id: Mapped[int | None] = mapped_column(ForeignKey("payment_channels.id")) payment_channel_code: Mapped[str] = mapped_column(String(32), nullable=False) pay_amount: Mapped[Decimal] = mapped_column(Numeric(10, 2), nullable=False) point_ratio_snapshot: Mapped[int] = mapped_column(Integer, nullable=False) give_points: Mapped[int] = mapped_column(BigInteger, default=0) bonus_points: Mapped[int] = mapped_column(BigInteger, default=0) arrival_points: Mapped[int] = mapped_column(BigInteger, default=0) currency: Mapped[str] = mapped_column(String(16), default="CNY") status: Mapped[str] = mapped_column(String(32), default="pending") third_party_order_no: Mapped[str] = mapped_column(String(100), default="") client_ip: Mapped[str] = mapped_column(String(64), default="") paid_at: Mapped[datetime | None] = mapped_column(DateTime) expired_at: Mapped[datetime | None] = mapped_column(DateTime) callback_payload: Mapped[dict[str, Any] | None] = mapped_column(JSON) class ProviderAccount(Base, TimestampMixin): __tablename__ = "provider_accounts" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) provider_code: Mapped[str] = mapped_column(String(32), unique=True, nullable=False) provider_name: Mapped[str] = mapped_column(String(100), nullable=False) api_format: Mapped[str] = mapped_column(String(64), nullable=False) base_url: Mapped[str] = mapped_column(String(255), nullable=False) api_key_encrypted: Mapped[str] = mapped_column(Text, nullable=False) api_secret_encrypted: Mapped[str | None] = mapped_column(Text) webhook_secret_encrypted: Mapped[str | None] = mapped_column(Text) timeout_seconds: Mapped[int] = mapped_column(Integer, default=60) max_retries: Mapped[int] = mapped_column(Integer, default=3) status: Mapped[int] = mapped_column(Integer, default=1) remark: Mapped[str] = mapped_column(String(255), default="") class ProviderModel(Base, TimestampMixin): __tablename__ = "provider_models" __table_args__ = ( UniqueConstraint("provider_account_id", "model_code", name="uk_provider_models"), ) id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) provider_account_id: Mapped[int] = mapped_column( ForeignKey("provider_accounts.id"), nullable=False ) model_code: Mapped[str] = mapped_column(String(64), nullable=False) model_name: Mapped[str] = mapped_column(String(100), nullable=False) request_content_type: Mapped[str] = mapped_column( String(64), default="application/json" ) scene_type: Mapped[str] = mapped_column(String(32), default="video_generation") supports_text_to_video: Mapped[bool] = mapped_column(Boolean, default=True) supports_image_to_video: Mapped[bool] = mapped_column(Boolean, default=False) supports_video_reference: Mapped[bool] = mapped_column(Boolean, default=False) supports_audio_reference: Mapped[bool] = mapped_column(Boolean, default=False) supports_generate_audio: Mapped[bool] = mapped_column(Boolean, default=False) supports_remix: Mapped[bool] = mapped_column(Boolean, default=False) supports_webhook: Mapped[bool] = mapped_column(Boolean, default=False) min_duration: Mapped[int] = mapped_column(Integer, default=4) max_duration: Mapped[int] = mapped_column(Integer, default=15) status: Mapped[int] = mapped_column(Integer, default=1) default_ratio: Mapped[str] = mapped_column(String(20), default="16:9") default_resolution: Mapped[str] = mapped_column(String(20), default="1280x720") class VideoModel(Base, TimestampMixin): __tablename__ = "video_models" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) model_key: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) model_name: Mapped[str] = mapped_column(String(100), nullable=False) frontend_title: Mapped[str] = mapped_column(String(100), nullable=False) frontend_description: Mapped[str] = mapped_column(String(255), default="") default_duration_seconds: Mapped[int] = mapped_column(Integer, default=8) default_ratio: Mapped[str] = mapped_column(String(20), default="16:9") default_resolution: Mapped[str] = mapped_column(String(20), default="1280x720") status: Mapped[int] = mapped_column(Integer, default=1) sort_order: Mapped[int] = mapped_column(Integer, default=0) class VideoModelSupplierBinding(Base, TimestampMixin): __tablename__ = "video_model_supplier_bindings" __table_args__ = ( UniqueConstraint( "video_model_id", "provider_model_id", name="uk_video_model_supplier_binding", ), ) id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) video_model_id: Mapped[int] = mapped_column(ForeignKey("video_models.id"), nullable=False) provider_model_id: Mapped[int] = mapped_column( ForeignKey("provider_models.id"), nullable=False ) routing_priority: Mapped[int] = mapped_column(Integer, default=100) is_primary: Mapped[bool] = mapped_column(Boolean, default=False) status: Mapped[int] = mapped_column(Integer, default=1) timeout_seconds_override: Mapped[int | None] = mapped_column(Integer) class PricingRule(Base, TimestampMixin): __tablename__ = "pricing_rules" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) rule_name: Mapped[str] = mapped_column(String(100), nullable=False) video_model_id: Mapped[int] = mapped_column(ForeignKey("video_models.id"), nullable=False) billing_mode: Mapped[str] = mapped_column(String(32), default="per_second") points_per_second: Mapped[int] = mapped_column(Integer, nullable=False) minimum_points: Mapped[int] = mapped_column(Integer, default=0) status: Mapped[int] = mapped_column(Integer, default=1) effective_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) expired_at: Mapped[datetime | None] = mapped_column(DateTime) version_no: Mapped[int] = mapped_column(Integer, default=1) class MediaAsset(Base, TimestampMixin): __tablename__ = "media_assets" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) asset_no: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False) media_type: Mapped[str] = mapped_column(String(16), nullable=False) source_type: Mapped[str] = mapped_column(String(16), default="upload") original_filename: Mapped[str] = mapped_column(String(255), nullable=False) mime_type: Mapped[str] = mapped_column(String(100), default="") file_ext: Mapped[str] = mapped_column(String(32), default="") file_size: Mapped[int] = mapped_column(BigInteger, default=0) storage_provider: Mapped[str] = mapped_column(String(32), default="local") storage_bucket: Mapped[str] = mapped_column(String(100), default="") storage_key: Mapped[str] = mapped_column(String(255), nullable=False) public_url: Mapped[str] = mapped_column(String(500), default="") sha256: Mapped[str] = mapped_column(String(64), default="") width: Mapped[int | None] = mapped_column(Integer) height: Mapped[int | None] = mapped_column(Integer) duration_seconds: Mapped[int | None] = mapped_column(Integer) status: Mapped[str] = mapped_column(String(32), default="active") deleted_at: Mapped[datetime | None] = mapped_column(DateTime) class VideoGenerationTask(Base, TimestampMixin): __tablename__ = "video_generation_tasks" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) task_no: Mapped[str] = mapped_column(String(64), unique=True, nullable=False) user_id: Mapped[int] = mapped_column(ForeignKey("users.id"), nullable=False) video_model_id: Mapped[int] = mapped_column(ForeignKey("video_models.id"), nullable=False) provider_account_id: Mapped[int] = mapped_column( ForeignKey("provider_accounts.id"), nullable=False ) provider_model_id: Mapped[int] = mapped_column(ForeignKey("provider_models.id"), nullable=False) provider_binding_id: Mapped[int | None] = mapped_column( ForeignKey("video_model_supplier_bindings.id") ) pricing_rule_id: Mapped[int] = mapped_column(ForeignKey("pricing_rules.id"), nullable=False) external_task_id: Mapped[str] = mapped_column(String(100), default="") submit_mode: Mapped[str] = mapped_column(String(32), default="async") task_status: Mapped[str] = mapped_column(String(32), nullable=False) generation_mode: Mapped[str] = mapped_column(String(32), nullable=False) prompt_text: Mapped[str | None] = mapped_column(Text) request_payload: Mapped[dict[str, Any]] = mapped_column(JSON, nullable=False) response_payload: Mapped[dict[str, Any] | None] = mapped_column(JSON) duration_seconds: Mapped[int] = mapped_column(Integer, default=5) ratio: Mapped[str] = mapped_column(String(20), default="16:9") resolution: Mapped[str] = mapped_column(String(20), default="1280x720") generate_audio: Mapped[bool] = mapped_column(Boolean, default=False) estimated_points: Mapped[int] = mapped_column(BigInteger, default=0) frozen_points: Mapped[int] = mapped_column(BigInteger, default=0) final_points: Mapped[int] = mapped_column(BigInteger, default=0) supplier_cost_amount: Mapped[Decimal] = mapped_column(Numeric(10, 4), default=0) supplier_cost_currency: Mapped[str] = mapped_column(String(16), default="") result_asset_id: Mapped[int | None] = mapped_column(ForeignKey("media_assets.id")) fail_reason: Mapped[str] = mapped_column(String(500), default="") submitted_at: Mapped[datetime | None] = mapped_column(DateTime) started_at: Mapped[datetime | None] = mapped_column(DateTime) finished_at: Mapped[datetime | None] = mapped_column(DateTime) next_poll_at: Mapped[datetime | None] = mapped_column(DateTime) poll_count: Mapped[int] = mapped_column(Integer, default=0) user_visible: Mapped[bool] = mapped_column(Boolean, default=True) user_deleted_at: Mapped[datetime | None] = mapped_column(DateTime) class VideoTaskEvent(Base): __tablename__ = "video_task_events" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) video_task_id: Mapped[int] = mapped_column( ForeignKey("video_generation_tasks.id"), nullable=False ) event_type: Mapped[str] = mapped_column(String(32), nullable=False) event_message: Mapped[str] = mapped_column(String(255), default="") payload: Mapped[dict[str, Any] | None] = mapped_column(JSON) created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False) class CallbackLog(Base, TimestampMixin): __tablename__ = "callback_logs" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) source_type: Mapped[str] = mapped_column(String(32), nullable=False) source_code: Mapped[str] = mapped_column(String(32), nullable=False) related_no: Mapped[str] = mapped_column(String(64), default="") request_headers: Mapped[dict[str, Any] | None] = mapped_column(JSON) request_body: Mapped[dict[str, Any] | None] = mapped_column(JSON) verify_status: Mapped[str] = mapped_column(String(32), default="pending") process_status: Mapped[str] = mapped_column(String(32), default="pending") response_body: Mapped[str | None] = mapped_column(Text) error_message: Mapped[str] = mapped_column(String(500), default="") class SystemConfig(Base, TimestampMixin): __tablename__ = "system_configs" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) config_key: Mapped[str] = mapped_column(String(128), unique=True, nullable=False) config_value: Mapped[str] = mapped_column(Text, nullable=False) value_type: Mapped[str] = mapped_column(String(32), default="string") group_name: Mapped[str] = mapped_column(String(64), default="default") description: Mapped[str] = mapped_column(String(255), default="") is_public: Mapped[bool] = mapped_column(Boolean, default=False) updated_by_admin_id: Mapped[int | None] = mapped_column(ForeignKey("admin_users.id")) class OperationLog(Base): __tablename__ = "operation_logs" id: Mapped[int] = mapped_column(PKBigInt, primary_key=True, autoincrement=True) admin_user_id: Mapped[int | None] = mapped_column(ForeignKey("admin_users.id")) user_id: Mapped[int | None] = mapped_column(ForeignKey("users.id")) module_name: Mapped[str] = mapped_column(String(64), default="") action_name: Mapped[str] = mapped_column(String(64), default="") related_type: Mapped[str] = mapped_column(String(32), default="") related_id: Mapped[int | None] = mapped_column(BigInteger) request_method: Mapped[str] = mapped_column(String(16), default="") request_path: Mapped[str] = mapped_column(String(255), default="") request_ip: Mapped[str] = mapped_column(String(64), default="") user_agent: Mapped[str] = mapped_column(String(255), default="") request_body: Mapped[dict[str, Any] | None] = mapped_column(JSON) response_body: Mapped[dict[str, Any] | None] = mapped_column(JSON) status: Mapped[str] = mapped_column(String(32), default="success") error_message: Mapped[str] = mapped_column(String(500), default="") created_at: Mapped[datetime] = mapped_column(DateTime, nullable=False)