feat: initialize aivideo project

This commit is contained in:
2026-04-17 18:33:05 +08:00
commit 14b18d67fe
162 changed files with 26251 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
from app.models.base import Base
from app.models.entities import (
AdminUser,
CallbackLog,
GrowthRewardRule,
InviteCode,
InviteRelation,
MediaAsset,
OperationLog,
PaymentChannel,
PricingRule,
ProviderAccount,
ProviderModel,
RechargeOrder,
RechargePlan,
RedeemCode,
SystemConfig,
User,
VideoGenerationTask,
VideoModel,
VideoModelSupplierBinding,
VideoTaskEvent,
Wallet,
WalletTransaction,
)
__all__ = [
"Base",
"AdminUser",
"CallbackLog",
"GrowthRewardRule",
"InviteCode",
"InviteRelation",
"MediaAsset",
"OperationLog",
"PaymentChannel",
"PricingRule",
"ProviderAccount",
"ProviderModel",
"RechargeOrder",
"RechargePlan",
"RedeemCode",
"SystemConfig",
"User",
"VideoGenerationTask",
"VideoModel",
"VideoModelSupplierBinding",
"VideoTaskEvent",
"Wallet",
"WalletTransaction",
]

View File

@@ -0,0 +1,18 @@
from datetime import datetime
from sqlalchemy import DateTime, MetaData, func
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
class Base(DeclarativeBase):
metadata = MetaData()
class TimestampMixin:
created_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now(), nullable=False
)
updated_at: Mapped[datetime] = mapped_column(
DateTime, server_default=func.now(), onupdate=func.now(), nullable=False
)

View File

@@ -0,0 +1,438 @@
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)