- Add Certificate + CertificateCategory models with full CRUD API - Image upload via base64 data URL stored in Text column - Certificate fields: title, issuer, issue_date, expiry_date, image, description - Frontend: card grid with category sidebar filter, create/edit dialog - Include certificates in data backup/export - Fix hasPhaseParent optimization in GoalDetailPage Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
168 lines
6.3 KiB
Python
168 lines
6.3 KiB
Python
"""证书路由"""
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
from typing import Optional, List
|
|
|
|
from app.database import get_db
|
|
from app.models.certificate import Certificate, CertificateCategory
|
|
from app.schemas.certificate import (
|
|
CertificateCreate, CertificateUpdate,
|
|
CertificateListResponse, CertificateDetailResponse,
|
|
CertificateCategoryCreate, CertificateCategoryUpdate, CertificateCategoryResponse,
|
|
)
|
|
from app.schemas.common import DeleteResponse
|
|
from app.utils.crud import get_or_404
|
|
from app.utils.datetime import utcnow
|
|
from app.utils.logger import logger
|
|
|
|
router = APIRouter(prefix="/api", tags=["证书"])
|
|
|
|
|
|
# ============ 证书分类 API ============
|
|
|
|
@router.get("/certificate-categories", response_model=List[CertificateCategoryResponse])
|
|
def get_categories(db: Session = Depends(get_db)):
|
|
try:
|
|
categories = db.query(CertificateCategory).order_by(
|
|
CertificateCategory.sort_order.asc(),
|
|
CertificateCategory.id.asc()
|
|
).all()
|
|
return categories
|
|
except Exception as e:
|
|
logger.error(f"获取证书分类列表失败: {str(e)}")
|
|
raise HTTPException(status_code=500, detail="获取证书分类列表失败")
|
|
|
|
|
|
@router.post("/certificate-categories", response_model=CertificateCategoryResponse, status_code=201)
|
|
def create_category(data: CertificateCategoryCreate, db: Session = Depends(get_db)):
|
|
try:
|
|
cat = CertificateCategory(**data.model_dump())
|
|
db.add(cat)
|
|
db.commit()
|
|
db.refresh(cat)
|
|
logger.info(f"创建证书分类成功: id={cat.id}, name={cat.name}")
|
|
return cat
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"创建证书分类失败: {str(e)}")
|
|
raise HTTPException(status_code=500, detail="创建证书分类失败")
|
|
|
|
|
|
@router.put("/certificate-categories/{category_id}", response_model=CertificateCategoryResponse)
|
|
def update_category(category_id: int, data: CertificateCategoryUpdate, db: Session = Depends(get_db)):
|
|
try:
|
|
cat = get_or_404(db, CertificateCategory, category_id, "证书分类")
|
|
update_data = data.model_dump(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
setattr(cat, field, value)
|
|
db.commit()
|
|
db.refresh(cat)
|
|
logger.info(f"更新证书分类成功: id={category_id}")
|
|
return cat
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"更新证书分类失败: {str(e)}")
|
|
raise HTTPException(status_code=500, detail="更新证书分类失败")
|
|
|
|
|
|
@router.delete("/certificate-categories/{category_id}")
|
|
def delete_category(category_id: int, db: Session = Depends(get_db)):
|
|
try:
|
|
cat = get_or_404(db, CertificateCategory, category_id, "证书分类")
|
|
certs = db.query(Certificate).filter(Certificate.category_id == category_id).all()
|
|
for c in certs:
|
|
c.category_id = None
|
|
db.delete(cat)
|
|
db.commit()
|
|
logger.info(f"删除证书分类成功: id={category_id}")
|
|
return DeleteResponse(message="证书分类删除成功")
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"删除证书分类失败: {str(e)}")
|
|
raise HTTPException(status_code=500, detail="删除证书分类失败")
|
|
|
|
|
|
# ============ 证书 API ============
|
|
|
|
@router.get("/certificates", response_model=List[CertificateListResponse])
|
|
def get_certificates(
|
|
category_id: Optional[int] = Query(None),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
try:
|
|
query = db.query(Certificate)
|
|
if category_id is not None:
|
|
query = query.filter(Certificate.category_id == category_id)
|
|
certs = query.order_by(Certificate.sort_order.asc(), Certificate.created_at.desc()).all()
|
|
return certs
|
|
except Exception as e:
|
|
logger.error(f"获取证书列表失败: {str(e)}")
|
|
raise HTTPException(status_code=500, detail="获取证书列表失败")
|
|
|
|
|
|
@router.post("/certificates", response_model=CertificateDetailResponse, status_code=201)
|
|
def create_certificate(data: CertificateCreate, db: Session = Depends(get_db)):
|
|
try:
|
|
cert = Certificate(**data.model_dump())
|
|
db.add(cert)
|
|
db.commit()
|
|
db.refresh(cert)
|
|
logger.info(f"创建证书成功: id={cert.id}, title={cert.title}")
|
|
return cert
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"创建证书失败: {str(e)}")
|
|
raise HTTPException(status_code=500, detail="创建证书失败")
|
|
|
|
|
|
@router.get("/certificates/{cert_id}", response_model=CertificateDetailResponse)
|
|
def get_certificate(cert_id: int, db: Session = Depends(get_db)):
|
|
try:
|
|
return get_or_404(db, Certificate, cert_id, "证书")
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error(f"获取证书失败: {str(e)}")
|
|
raise HTTPException(status_code=500, detail="获取证书失败")
|
|
|
|
|
|
@router.put("/certificates/{cert_id}", response_model=CertificateDetailResponse)
|
|
def update_certificate(cert_id: int, data: CertificateUpdate, db: Session = Depends(get_db)):
|
|
try:
|
|
cert = get_or_404(db, Certificate, cert_id, "证书")
|
|
update_data = data.model_dump(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
if value is not None or field in data.clearable_fields:
|
|
setattr(cert, field, value)
|
|
cert.updated_at = utcnow()
|
|
db.commit()
|
|
db.refresh(cert)
|
|
logger.info(f"更新证书成功: id={cert_id}")
|
|
return cert
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"更新证书失败: {str(e)}")
|
|
raise HTTPException(status_code=500, detail="更新证书失败")
|
|
|
|
|
|
@router.delete("/certificates/{cert_id}")
|
|
def delete_certificate(cert_id: int, db: Session = Depends(get_db)):
|
|
try:
|
|
cert = get_or_404(db, Certificate, cert_id, "证书")
|
|
db.delete(cert)
|
|
db.commit()
|
|
logger.info(f"删除证书成功: id={cert_id}")
|
|
return DeleteResponse(message="证书删除成功")
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
db.rollback()
|
|
logger.error(f"删除证书失败: {str(e)}")
|
|
raise HTTPException(status_code=500, detail="删除证书失败")
|