scaffold-api.sh 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  1. #!/usr/bin/env bash
  2. # Generate FastAPI endpoint boilerplate
  3. #
  4. # Usage: scaffold-api.sh <resource_name>
  5. # Example: scaffold-api.sh user
  6. set -euo pipefail
  7. RESOURCE="${1:-}"
  8. if [[ -z "$RESOURCE" ]]; then
  9. echo "Usage: scaffold-api.sh <resource_name>"
  10. echo "Example: scaffold-api.sh user"
  11. exit 1
  12. fi
  13. # Convert to different cases
  14. RESOURCE_LOWER=$(echo "$RESOURCE" | tr '[:upper:]' '[:lower:]')
  15. RESOURCE_UPPER=$(echo "$RESOURCE" | tr '[:lower:]' '[:upper:]')
  16. RESOURCE_TITLE=$(echo "$RESOURCE_LOWER" | sed 's/\b\(.\)/\u\1/g')
  17. RESOURCE_PLURAL="${RESOURCE_LOWER}s"
  18. cat << EOF
  19. # =============================================================================
  20. # ${RESOURCE_TITLE} Models
  21. # =============================================================================
  22. from pydantic import BaseModel, Field
  23. from datetime import datetime
  24. class ${RESOURCE_TITLE}Create(BaseModel):
  25. """Create ${RESOURCE_LOWER} request."""
  26. name: str = Field(..., min_length=1, max_length=100)
  27. # Add more fields
  28. class ${RESOURCE_TITLE}Update(BaseModel):
  29. """Update ${RESOURCE_LOWER} request (partial)."""
  30. name: str | None = None
  31. # Add more fields
  32. class ${RESOURCE_TITLE}Response(BaseModel):
  33. """${RESOURCE_TITLE} response."""
  34. id: int
  35. name: str
  36. created_at: datetime
  37. updated_at: datetime
  38. model_config = {"from_attributes": True}
  39. # =============================================================================
  40. # ${RESOURCE_TITLE} Router
  41. # =============================================================================
  42. from fastapi import APIRouter, Depends, HTTPException
  43. from typing import Annotated
  44. router = APIRouter(prefix="/${RESOURCE_PLURAL}", tags=["${RESOURCE_PLURAL}"])
  45. @router.get("/", response_model=list[${RESOURCE_TITLE}Response])
  46. async def list_${RESOURCE_PLURAL}(
  47. db: DB,
  48. skip: int = 0,
  49. limit: int = 10,
  50. ):
  51. """List all ${RESOURCE_PLURAL}."""
  52. result = await db.execute(
  53. select(${RESOURCE_TITLE}).offset(skip).limit(limit)
  54. )
  55. return result.scalars().all()
  56. @router.post("/", response_model=${RESOURCE_TITLE}Response, status_code=201)
  57. async def create_${RESOURCE_LOWER}(data: ${RESOURCE_TITLE}Create, db: DB):
  58. """Create a new ${RESOURCE_LOWER}."""
  59. ${RESOURCE_LOWER} = ${RESOURCE_TITLE}(**data.model_dump())
  60. db.add(${RESOURCE_LOWER})
  61. await db.commit()
  62. await db.refresh(${RESOURCE_LOWER})
  63. return ${RESOURCE_LOWER}
  64. @router.get("/{${RESOURCE_LOWER}_id}", response_model=${RESOURCE_TITLE}Response)
  65. async def get_${RESOURCE_LOWER}(${RESOURCE_LOWER}_id: int, db: DB):
  66. """Get a ${RESOURCE_LOWER} by ID."""
  67. ${RESOURCE_LOWER} = await db.get(${RESOURCE_TITLE}, ${RESOURCE_LOWER}_id)
  68. if not ${RESOURCE_LOWER}:
  69. raise HTTPException(status_code=404, detail="${RESOURCE_TITLE} not found")
  70. return ${RESOURCE_LOWER}
  71. @router.patch("/{${RESOURCE_LOWER}_id}", response_model=${RESOURCE_TITLE}Response)
  72. async def update_${RESOURCE_LOWER}(
  73. ${RESOURCE_LOWER}_id: int,
  74. data: ${RESOURCE_TITLE}Update,
  75. db: DB,
  76. ):
  77. """Update a ${RESOURCE_LOWER}."""
  78. ${RESOURCE_LOWER} = await db.get(${RESOURCE_TITLE}, ${RESOURCE_LOWER}_id)
  79. if not ${RESOURCE_LOWER}:
  80. raise HTTPException(status_code=404, detail="${RESOURCE_TITLE} not found")
  81. for field, value in data.model_dump(exclude_unset=True).items():
  82. setattr(${RESOURCE_LOWER}, field, value)
  83. await db.commit()
  84. await db.refresh(${RESOURCE_LOWER})
  85. return ${RESOURCE_LOWER}
  86. @router.delete("/{${RESOURCE_LOWER}_id}", status_code=204)
  87. async def delete_${RESOURCE_LOWER}(${RESOURCE_LOWER}_id: int, db: DB):
  88. """Delete a ${RESOURCE_LOWER}."""
  89. ${RESOURCE_LOWER} = await db.get(${RESOURCE_TITLE}, ${RESOURCE_LOWER}_id)
  90. if not ${RESOURCE_LOWER}:
  91. raise HTTPException(status_code=404, detail="${RESOURCE_TITLE} not found")
  92. await db.delete(${RESOURCE_LOWER})
  93. await db.commit()
  94. # =============================================================================
  95. # Include in main app:
  96. # from routers.${RESOURCE_PLURAL} import router as ${RESOURCE_PLURAL}_router
  97. # app.include_router(${RESOURCE_PLURAL}_router, prefix="/api/v1")
  98. # =============================================================================
  99. EOF