"""存储目标 CRUD API。""" import asyncio from fastapi import APIRouter, HTTPException from pydantic import ValidationError from app.core.storage.factory import create_storage as make_storage_impl from app.deps import AdminUser, DBSession from app.models.storage import Storage from app.schemas.storage import ( LocalStorageConfig, S3StorageConfig, StorageCreate, StorageOut, StorageTestResponse, StorageUpdate, ) from app.utils.crypto import decrypt_dict, encrypt_dict router = APIRouter(prefix="/storages", tags=["storages"]) def _validate_config(type_: str, config: dict) -> dict: if type_ == "local": return LocalStorageConfig(**config).model_dump() if type_ == "s3": return S3StorageConfig(**config).model_dump() raise HTTPException(status_code=400, detail=f"Unsupported type: {type_}") @router.get("", response_model=list[StorageOut]) def list_storages(db: DBSession, _: AdminUser) -> list[Storage]: return db.query(Storage).order_by(Storage.id).all() @router.post("", response_model=StorageOut, status_code=201) def create_storage_endpoint(payload: StorageCreate, db: DBSession, _: AdminUser) -> Storage: if db.query(Storage).filter(Storage.name == payload.name).first(): raise HTTPException(status_code=400, detail="名称已存在") config = _validate_config(payload.type, payload.config) row = Storage( name=payload.name, type=payload.type, config_json=encrypt_dict(config), is_default=payload.is_default, ) if payload.is_default: db.query(Storage).update({Storage.is_default: False}) db.add(row) db.commit() db.refresh(row) return row @router.get("/{storage_id}", response_model=StorageOut) def get_storage(storage_id: int, db: DBSession, _: AdminUser) -> Storage: row = db.get(Storage, storage_id) if not row: raise HTTPException(status_code=404, detail="Not found") return row @router.put("/{storage_id}", response_model=StorageOut) def update_storage(storage_id: int, payload: StorageUpdate, db: DBSession, _: AdminUser) -> Storage: row = db.get(Storage, storage_id) if not row: raise HTTPException(status_code=404, detail="Not found") if payload.name is not None: if db.query(Storage).filter(Storage.name == payload.name, Storage.id != storage_id).first(): raise HTTPException(status_code=400, detail="名称已存在") row.name = payload.name if payload.config is not None: config = _validate_config(row.type, payload.config) row.config_json = encrypt_dict(config) if payload.is_default is not None: if payload.is_default: db.query(Storage).update({Storage.is_default: False}) row.is_default = payload.is_default db.commit() db.refresh(row) return row @router.delete("/{storage_id}", status_code=204) def delete_storage(storage_id: int, db: DBSession, _: AdminUser) -> None: row = db.get(Storage, storage_id) if not row: raise HTTPException(status_code=404, detail="Not found") db.delete(row) db.commit() @router.post("/{storage_id}/test", response_model=StorageTestResponse) async def test_storage(storage_id: int, db: DBSession, _: AdminUser) -> StorageTestResponse: row = db.get(Storage, storage_id) if not row: raise HTTPException(status_code=404, detail="Not found") storage = make_storage_impl(row) ok, msg = await storage.test_connection() return StorageTestResponse(ok=ok, message=msg)