feat: initialize aivideo project
This commit is contained in:
49
backend/app/modules/video_models/repository.py
Normal file
49
backend/app/modules/video_models/repository.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import and_, or_, select
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.models.entities import PricingRule, ProviderModel, VideoModel, VideoModelSupplierBinding
|
||||
|
||||
|
||||
class VideoModelsRepository:
|
||||
def __init__(self, db: Session) -> None:
|
||||
self.db = db
|
||||
|
||||
def list_video_models(self):
|
||||
return self.db.query(VideoModel).order_by(VideoModel.sort_order.asc(), VideoModel.id.asc())
|
||||
|
||||
def get_video_model(self, model_id: int) -> VideoModel | None:
|
||||
return self.db.scalar(select(VideoModel).where(VideoModel.id == model_id))
|
||||
|
||||
def list_bindings(self):
|
||||
return (
|
||||
self.db.query(VideoModelSupplierBinding)
|
||||
.order_by(
|
||||
VideoModelSupplierBinding.video_model_id.asc(),
|
||||
VideoModelSupplierBinding.routing_priority.asc(),
|
||||
)
|
||||
)
|
||||
|
||||
def get_binding(self, binding_id: int) -> VideoModelSupplierBinding | None:
|
||||
return self.db.scalar(
|
||||
select(VideoModelSupplierBinding).where(VideoModelSupplierBinding.id == binding_id)
|
||||
)
|
||||
|
||||
def active_pricing_rule(self, video_model_id: int) -> PricingRule | None:
|
||||
now = datetime.utcnow()
|
||||
return self.db.scalar(
|
||||
select(PricingRule)
|
||||
.where(
|
||||
PricingRule.video_model_id == video_model_id,
|
||||
PricingRule.status == 1,
|
||||
PricingRule.effective_at <= now,
|
||||
or_(PricingRule.expired_at.is_(None), PricingRule.expired_at > now),
|
||||
)
|
||||
.order_by(PricingRule.version_no.desc(), PricingRule.id.desc())
|
||||
)
|
||||
|
||||
def provider_models(self) -> dict[int, ProviderModel]:
|
||||
rows = self.db.scalars(select(ProviderModel)).all()
|
||||
return {row.id: row for row in rows}
|
||||
|
||||
71
backend/app/modules/video_models/router.py
Normal file
71
backend/app/modules/video_models/router.py
Normal file
@@ -0,0 +1,71 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.common.db.session import get_db
|
||||
from app.common.responses.api_response import success_response
|
||||
from app.common.security.deps import require_admin_permission
|
||||
from app.modules.video_models.schema import BindingPayload, VideoModelPayload
|
||||
from app.modules.video_models.service import VideoModelsService
|
||||
|
||||
|
||||
router = APIRouter(tags=["video-models"])
|
||||
|
||||
|
||||
@router.get("/api/v1/video-models")
|
||||
def list_public_video_models(db: Session = Depends(get_db)):
|
||||
return success_response(VideoModelsService(db).list_public_models())
|
||||
|
||||
|
||||
@router.get("/api/v1/admin/video-models")
|
||||
def list_admin_video_models(
|
||||
_=Depends(require_admin_permission()),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return success_response(VideoModelsService(db).list_admin_models())
|
||||
|
||||
|
||||
@router.post("/api/v1/admin/video-models")
|
||||
def create_video_model(
|
||||
payload: VideoModelPayload,
|
||||
_=Depends(require_admin_permission()),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return success_response(VideoModelsService(db).create_model(payload))
|
||||
|
||||
|
||||
@router.put("/api/v1/admin/video-models/{model_id}")
|
||||
def update_video_model(
|
||||
model_id: int,
|
||||
payload: VideoModelPayload,
|
||||
_=Depends(require_admin_permission()),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return success_response(VideoModelsService(db).update_model(model_id, payload))
|
||||
|
||||
|
||||
@router.get("/api/v1/admin/video-model-bindings")
|
||||
def list_bindings(
|
||||
_=Depends(require_admin_permission()),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return success_response(VideoModelsService(db).list_bindings())
|
||||
|
||||
|
||||
@router.post("/api/v1/admin/video-model-bindings")
|
||||
def create_binding(
|
||||
payload: BindingPayload,
|
||||
_=Depends(require_admin_permission()),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return success_response(VideoModelsService(db).create_binding(payload))
|
||||
|
||||
|
||||
@router.put("/api/v1/admin/video-model-bindings/{binding_id}")
|
||||
def update_binding(
|
||||
binding_id: int,
|
||||
payload: BindingPayload,
|
||||
_=Depends(require_admin_permission()),
|
||||
db: Session = Depends(get_db),
|
||||
):
|
||||
return success_response(VideoModelsService(db).update_binding(binding_id, payload))
|
||||
|
||||
23
backend/app/modules/video_models/schema.py
Normal file
23
backend/app/modules/video_models/schema.py
Normal file
@@ -0,0 +1,23 @@
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class VideoModelPayload(BaseModel):
|
||||
model_key: str
|
||||
model_name: str
|
||||
frontend_title: str
|
||||
frontend_description: str = ""
|
||||
default_duration_seconds: int = 8
|
||||
default_ratio: str = "16:9"
|
||||
default_resolution: str = "1280x720"
|
||||
status: int = 1
|
||||
sort_order: int = 0
|
||||
|
||||
|
||||
class BindingPayload(BaseModel):
|
||||
video_model_id: int
|
||||
provider_model_id: int
|
||||
routing_priority: int = 10
|
||||
is_primary: bool = False
|
||||
status: int = 1
|
||||
timeout_seconds_override: int | None = None
|
||||
|
||||
113
backend/app/modules/video_models/service.py
Normal file
113
backend/app/modules/video_models/service.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from app.common.errors.app_error import NotFoundAppError
|
||||
from app.models.entities import ProviderModel, VideoModel, VideoModelSupplierBinding
|
||||
from app.modules.video_models.repository import VideoModelsRepository
|
||||
|
||||
|
||||
class VideoModelsService:
|
||||
def __init__(self, db: Session) -> None:
|
||||
self.db = db
|
||||
self.repository = VideoModelsRepository(db)
|
||||
|
||||
def list_public_models(self) -> list[dict]:
|
||||
items = []
|
||||
for item in self.repository.list_video_models().filter(VideoModel.status == 1).all():
|
||||
pricing = self.repository.active_pricing_rule(item.id)
|
||||
items.append(self.serialize_model(item, pricing))
|
||||
return items
|
||||
|
||||
def list_admin_models(self) -> list[dict]:
|
||||
return [
|
||||
self.serialize_model(item, self.repository.active_pricing_rule(item.id))
|
||||
for item in self.repository.list_video_models().all()
|
||||
]
|
||||
|
||||
def create_model(self, payload) -> dict:
|
||||
item = VideoModel(**payload.model_dump())
|
||||
self.db.add(item)
|
||||
self.db.commit()
|
||||
self.db.refresh(item)
|
||||
return self.serialize_model(item, None)
|
||||
|
||||
def update_model(self, model_id: int, payload) -> dict:
|
||||
item = self.repository.get_video_model(model_id)
|
||||
if not item:
|
||||
raise NotFoundAppError("video model not found", code=50001)
|
||||
for key, value in payload.model_dump().items():
|
||||
setattr(item, key, value)
|
||||
self.db.commit()
|
||||
return self.serialize_model(item, self.repository.active_pricing_rule(item.id))
|
||||
|
||||
def list_bindings(self) -> list[dict]:
|
||||
provider_models = self.repository.provider_models()
|
||||
video_models = {
|
||||
item.id: item for item in self.repository.list_video_models().all()
|
||||
}
|
||||
return [
|
||||
self.serialize_binding(item, provider_models, video_models)
|
||||
for item in self.repository.list_bindings().all()
|
||||
]
|
||||
|
||||
def create_binding(self, payload) -> dict:
|
||||
item = VideoModelSupplierBinding(**payload.model_dump())
|
||||
self.db.add(item)
|
||||
self.db.commit()
|
||||
self.db.refresh(item)
|
||||
return self._serialize_binding_single(item)
|
||||
|
||||
def update_binding(self, binding_id: int, payload) -> dict:
|
||||
item = self.repository.get_binding(binding_id)
|
||||
if not item:
|
||||
raise NotFoundAppError("binding not found", code=60003)
|
||||
for key, value in payload.model_dump().items():
|
||||
setattr(item, key, value)
|
||||
self.db.commit()
|
||||
return self._serialize_binding_single(item)
|
||||
|
||||
@staticmethod
|
||||
def serialize_model(item: VideoModel, pricing) -> dict:
|
||||
return {
|
||||
"id": item.id,
|
||||
"modelKey": item.model_key,
|
||||
"modelName": item.model_name,
|
||||
"frontendTitle": item.frontend_title,
|
||||
"frontendDescription": item.frontend_description,
|
||||
"defaultDurationSeconds": item.default_duration_seconds,
|
||||
"defaultRatio": item.default_ratio,
|
||||
"defaultResolution": item.default_resolution,
|
||||
"status": item.status,
|
||||
"sortOrder": item.sort_order,
|
||||
"pricing": {
|
||||
"pointsPerSecond": pricing.points_per_second if pricing else 0,
|
||||
"minimumPoints": pricing.minimum_points if pricing else 0,
|
||||
},
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def serialize_binding(
|
||||
item: VideoModelSupplierBinding,
|
||||
provider_models: dict[int, ProviderModel],
|
||||
video_models: dict[int, VideoModel],
|
||||
) -> dict:
|
||||
provider_model = provider_models.get(item.provider_model_id)
|
||||
video_model = video_models.get(item.video_model_id)
|
||||
return {
|
||||
"id": item.id,
|
||||
"videoModelId": item.video_model_id,
|
||||
"videoModelName": video_model.model_name if video_model else "",
|
||||
"providerModelId": item.provider_model_id,
|
||||
"providerModelName": provider_model.model_name if provider_model else "",
|
||||
"routingPriority": item.routing_priority,
|
||||
"isPrimary": item.is_primary,
|
||||
"status": item.status,
|
||||
"timeoutSecondsOverride": item.timeout_seconds_override,
|
||||
}
|
||||
|
||||
def _serialize_binding_single(self, item: VideoModelSupplierBinding) -> dict:
|
||||
provider_models = self.repository.provider_models()
|
||||
video_models = {
|
||||
row.id: row for row in self.repository.list_video_models().all()
|
||||
}
|
||||
return self.serialize_binding(item, provider_models, video_models)
|
||||
|
||||
Reference in New Issue
Block a user