From e86f364400af85f2a2e5aede3e8cc62daafbe4cd Mon Sep 17 00:00:00 2001 From: Johnny Bouder Date: Wed, 28 May 2025 16:45:28 -0400 Subject: [PATCH 1/3] Refactor for cases instead of spacecraft. --- app/{spacecraft => applicants}/__init__.py | 0 app/applicants/models.py | 27 ++++++ app/applicants/router.py | 53 ++++++++++++ app/applicants/schemas.py | 33 ++++++++ app/applicants/services.py | 71 ++++++++++++++++ app/cases/__init__.py | 0 app/cases/models.py | 19 +++++ app/cases/router.py | 47 +++++++++++ app/cases/schemas.py | 33 ++++++++ app/cases/services.py | 98 ++++++++++++++++++++++ app/main.py | 6 +- app/spacecraft/models.py | 15 ---- app/spacecraft/router.py | 53 ------------ app/spacecraft/schemas.py | 20 ----- app/spacecraft/services.py | 60 ------------- migrations/env.py | 7 +- tests/test_applicants.py | 69 +++++++++++++++ tests/test_cases.py | 67 +++++++++++++++ tests/test_spacecraft.py | 56 ------------- 19 files changed, 524 insertions(+), 210 deletions(-) rename app/{spacecraft => applicants}/__init__.py (100%) create mode 100644 app/applicants/models.py create mode 100644 app/applicants/router.py create mode 100644 app/applicants/schemas.py create mode 100644 app/applicants/services.py create mode 100644 app/cases/__init__.py create mode 100644 app/cases/models.py create mode 100644 app/cases/router.py create mode 100644 app/cases/schemas.py create mode 100644 app/cases/services.py delete mode 100644 app/spacecraft/models.py delete mode 100644 app/spacecraft/router.py delete mode 100644 app/spacecraft/schemas.py delete mode 100644 app/spacecraft/services.py create mode 100644 tests/test_applicants.py create mode 100644 tests/test_cases.py delete mode 100644 tests/test_spacecraft.py diff --git a/app/spacecraft/__init__.py b/app/applicants/__init__.py similarity index 100% rename from app/spacecraft/__init__.py rename to app/applicants/__init__.py diff --git a/app/applicants/models.py b/app/applicants/models.py new file mode 100644 index 0000000..1010286 --- /dev/null +++ b/app/applicants/models.py @@ -0,0 +1,27 @@ +from sqlalchemy import Column, Date, Integer, String +from sqlalchemy.orm import relationship + +from app.db import Base + + +class DBApplicant(Base): + __tablename__ = "applicants" + + id = Column(Integer, primary_key=True, index=True) + first_name = Column(String, nullable=False) + last_name = Column(String, nullable=False) + middle_name = Column(String, nullable=True) + gender = Column(String, nullable=False) + date_of_birth = Column(Date, nullable=False) + ssn = Column(String, nullable=False, unique=True) + email = Column(String, nullable=True) + home_phone = Column(String, nullable=True) + mobile_phone = Column(String, nullable=True) + address = Column(String, nullable=True) + city = Column(String, nullable=True) + state = Column(String, nullable=True) + zip = Column(String, nullable=True) + country = Column(String, nullable=False) + created_at = Column(Date, nullable=False) + updated_at = Column(Date, nullable=False) + case = relationship("DBCase", back_populates="applicant") diff --git a/app/applicants/router.py b/app/applicants/router.py new file mode 100644 index 0000000..f9b4305 --- /dev/null +++ b/app/applicants/router.py @@ -0,0 +1,53 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends +from sqlalchemy.orm import Session +from starlette import status + +import app.applicants.services as service +from app.applicants.schemas import Applicant, ApplicantPayload +from app.db import get_db + +router = APIRouter( + prefix="/api", + tags=["Applicants"], + responses={404: {"description": "Endpoint not found"}}, +) + +# Database dependency injection session +db_session = Annotated[Session, Depends(get_db)] + + +@router.get( + "/applicants", status_code=status.HTTP_200_OK, response_model=ApplicantPayload +) +async def get_applicants(db: db_session, page_number: int = 0, page_size: int = 100): + return service.get_items(db, page_number, page_size) + + +@router.get( + "/applicants/{id}", status_code=status.HTTP_200_OK, response_model=Applicant +) +async def get_applicant(id: int, db: db_session): + return service.get_item(db, id) + + +@router.put( + "/applicants/{id}", status_code=status.HTTP_200_OK, response_model=Applicant +) +async def update_applicant(id: int, applicant: Applicant, db: db_session): + db_applicant = service.update_item(db, id, applicant) + return db_applicant + + +@router.post( + "/applicants", status_code=status.HTTP_201_CREATED, response_model=Applicant +) +async def create_applicant(applicant: Applicant, db: db_session): + db_applicant = service.create_item(db, applicant) + return db_applicant + + +@router.delete("/applicants/{id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_applicant(id: int, db: db_session): + service.delete_item(db, id) diff --git a/app/applicants/schemas.py b/app/applicants/schemas.py new file mode 100644 index 0000000..e4e485d --- /dev/null +++ b/app/applicants/schemas.py @@ -0,0 +1,33 @@ +from datetime import date, datetime + +from pydantic import BaseModel, ConfigDict + + +# Pydantic Models +class Applicant(BaseModel): + model_config = ConfigDict(from_attributes=True) + id: int | None = None + first_name: str + last_name: str + middle_name: str | None = None + gender: str + date_of_birth: date + ssn: str + email: str | None = None + home_phone: str | None = None + mobile_phone: str | None = None + address: str | None = None + city: str | None = None + state: str | None = None + zip: str | None = None + country: str = "USA" + created_at: datetime + updated_at: datetime + + +class ApplicantPayload(BaseModel): + items: list[Applicant] + item_count: int = 0 + page_count: int = 0 + prev_page: int | None = None + next_page: int | None = None diff --git a/app/applicants/services.py b/app/applicants/services.py new file mode 100644 index 0000000..7b728a8 --- /dev/null +++ b/app/applicants/services.py @@ -0,0 +1,71 @@ +from datetime import datetime + +from fastapi import HTTPException +from sqlalchemy.orm import Session + +from app.applicants.models import DBApplicant +from app.applicants.schemas import Applicant +from app.utils import get_next_page, get_page_count, get_prev_page + + +def get_items(db: Session, page_number: int, page_size: int): + item_count = db.query(DBApplicant).count() + items = db.query(DBApplicant).limit(page_size).offset(page_number * page_size).all() + + return { + "items": items, + "item_count": item_count, + "page_count": get_page_count(item_count, page_size), + "prev_page": get_prev_page(page_number), + "next_page": get_next_page(item_count, page_number, page_size), + } + + +def get_item(db: Session, applicant_id: int): + return db.query(DBApplicant).where(DBApplicant.id == applicant_id).first() + + +def update_item(db: Session, id: int, applicant: Applicant): + db_applicant = db.query(DBApplicant).filter(DBApplicant.id == id).first() + if db_applicant is None: + raise HTTPException(status_code=404, detail="Applicant not founds") + + db_applicant.first_name = applicant.first_name + db_applicant.last_name = applicant.last_name + db_applicant.middle_name = applicant.middle_name + db_applicant.gender = applicant.gender + db_applicant.date_of_birth = applicant.date_of_birth + db_applicant.ssn = applicant.ssn + db_applicant.email = applicant.email + db_applicant.home_phone = applicant.home_phone + db_applicant.mobile_phone = applicant.mobile_phone + db_applicant.address = applicant.address + db_applicant.city = applicant.city + db_applicant.state = applicant.state + db_applicant.zip = applicant.zip + db_applicant.country = applicant.country + db_applicant.date_of_birth = applicant.date_of_birth + db_applicant.updated_at = datetime.now() + db.add(db_applicant) + db.commit() + db.refresh(db_applicant) + + return db_applicant + + +def create_item(db: Session, applicant: Applicant): + db_applicant = DBApplicant(**applicant.model_dump()) + db.add(db_applicant) + db.commit() + db.refresh(db_applicant) + + return db_applicant + + +def delete_item(db: Session, id: int): + db_applicant = db.query(DBApplicant).filter(DBApplicant.id == id).first() + if db_applicant is None: + raise HTTPException(status_code=404, detail="Applicant not founds") + + db.query(DBApplicant).filter(DBApplicant.id == id).delete() + db.commit() diff --git a/app/cases/__init__.py b/app/cases/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/cases/models.py b/app/cases/models.py new file mode 100644 index 0000000..f398e11 --- /dev/null +++ b/app/cases/models.py @@ -0,0 +1,19 @@ +from sqlalchemy import Column, Date, ForeignKey, Integer, String +from sqlalchemy.orm import relationship + +from app.db import Base + + +# SQLAlchemy Model +class DBCase(Base): + __tablename__ = "cases" + + id = Column(Integer, primary_key=True, index=True) + status = Column(String, nullable=False) + assigned_to = Column(String, nullable=True) + created_at = Column(Date, nullable=False) + updated_at = Column(Date, nullable=False) + applicant_id = Column( + Integer, ForeignKey("applicants.id", ondelete="CASCADE"), nullable=False + ) + applicant = relationship("DBApplicant", back_populates="case") diff --git a/app/cases/router.py b/app/cases/router.py new file mode 100644 index 0000000..2d539cf --- /dev/null +++ b/app/cases/router.py @@ -0,0 +1,47 @@ +from typing import Annotated + +from fastapi import APIRouter, Depends +from sqlalchemy.orm import Session +from starlette import status + +import app.cases.services as service +from app.cases.schemas import Case, CasePayload, CaseWithApplicant +from app.db import get_db + +router = APIRouter( + prefix="/api", + tags=["Cases"], + responses={404: {"description": "Endpoint not found"}}, +) + +# Database dependency injection session +db_session = Annotated[Session, Depends(get_db)] + + +@router.get("/cases", status_code=status.HTTP_200_OK, response_model=CasePayload) +async def get_cases(db: db_session, page_number: int = 0, page_size: int = 100): + return service.get_items(db, page_number, page_size) + + +@router.get( + "/cases/{id}", status_code=status.HTTP_200_OK, response_model=CaseWithApplicant +) +async def get_case(id: int, db: db_session): + return service.get_item(db, id) + + +@router.put("/cases/{id}", status_code=status.HTTP_200_OK, response_model=Case) +async def update_case(id: int, case: Case, db: db_session): + db_case = service.update_item(db, id, case) + return db_case + + +@router.post("/cases", status_code=status.HTTP_201_CREATED, response_model=Case) +async def create_case(case: Case, db: db_session): + db_case = service.create_item(db, case) + return db_case + + +@router.delete("/cases/{id}", status_code=status.HTTP_204_NO_CONTENT) +async def delete_case(id: int, db: db_session): + service.delete_item(db, id) diff --git a/app/cases/schemas.py b/app/cases/schemas.py new file mode 100644 index 0000000..de65b4d --- /dev/null +++ b/app/cases/schemas.py @@ -0,0 +1,33 @@ +from datetime import datetime + +from pydantic import BaseModel, ConfigDict + +from app.applicants.schemas import Applicant + + +# Pydantic Models +class Case(BaseModel): + model_config = ConfigDict(from_attributes=True) + id: int | None = None + status: str + assigned_to: str | None = None + created_at: datetime + updated_at: datetime + applicant_id: int | None = None + + +class CaseWithApplicant(BaseModel): + id: int + status: str + assigned_to: str | None = None + created_at: datetime + updated_at: datetime + applicant: Applicant | None = None + + +class CasePayload(BaseModel): + items: list[CaseWithApplicant] + item_count: int = 0 + page_count: int = 0 + prev_page: int | None = None + next_page: int | None = None diff --git a/app/cases/services.py b/app/cases/services.py new file mode 100644 index 0000000..571a4e0 --- /dev/null +++ b/app/cases/services.py @@ -0,0 +1,98 @@ +from datetime import datetime + +from fastapi import HTTPException +from sqlalchemy.orm import Session, joinedload + +from app.cases.models import DBCase +from app.cases.schemas import Case +from app.utils import get_next_page, get_page_count, get_prev_page + + +def get_items(db: Session, page_number: int, page_size: int): + item_count = db.query(DBCase).count() + items = db.query(DBCase).limit(page_size).offset(page_number * page_size).all() + + return { + "items": items, + "item_count": item_count, + "page_count": get_page_count(item_count, page_size), + "prev_page": get_prev_page(page_number), + "next_page": get_next_page(item_count, page_number, page_size), + } + + +def get_item(db: Session, case_id: int): + case = ( + db.query(DBCase) + .options(joinedload(DBCase.applicant)) + .where(DBCase.id == case_id) + .first() + ) + + if case is None: + raise HTTPException(status_code=404, detail="Case not found") + + # Handle case where applicant might be None + applicant_data = None + if case.applicant: + applicant_data = { + "id": case.applicant.id, + "first_name": case.applicant.first_name, + "last_name": case.applicant.last_name, + "middle_name": case.applicant.middle_name, + "email": case.applicant.email, + "gender": case.applicant.gender, + "date_of_birth": case.applicant.date_of_birth, + "ssn": case.applicant.ssn, + "home_phone": case.applicant.home_phone, + "mobile_phone": case.applicant.mobile_phone, + "address": case.applicant.address, + "city": case.applicant.city, + "state": case.applicant.state, + "zip": case.applicant.zip, + "country": case.applicant.country, + "created_at": case.applicant.created_at, + "updated_at": case.applicant.updated_at, + } + + return { + "id": case.id, + "status": case.status, + "applicant": applicant_data, + "assigned_to": case.assigned_to, + "created_at": case.created_at, + "updated_at": case.updated_at, + } + + +def update_item(db: Session, id: int, case: Case): + db_case = db.query(DBCase).filter(DBCase.id == id).first() + if db_case is None: + raise HTTPException(status_code=404, detail="Case not founds") + + db_case.status = case.status + db_case.assigned_to = case.assigned_to + db_case.updated_at = datetime.now() + db.add(db_case) + db.commit() + db.refresh(db_case) + + return db_case + + +def create_item(db: Session, case: Case): + db_case = DBCase(**case.model_dump()) + db.add(db_case) + db.commit() + db.refresh(db_case) + + return db_case + + +def delete_item(db: Session, id: int): + db_case = db.query(DBCase).filter(DBCase.id == id).first() + if db_case is None: + raise HTTPException(status_code=404, detail="Case not founds") + + db.query(DBCase).filter(DBCase.id == id).delete() + db.commit() diff --git a/app/main.py b/app/main.py index 1b23c49..e8e1ef6 100644 --- a/app/main.py +++ b/app/main.py @@ -1,9 +1,10 @@ from fastapi import FastAPI from app.admin.router import router as admin_router +from app.applicants.router import router as applicants_router +from app.cases.router import router as cases_router from app.db import Base, engine from app.health.router import router as health_router -from app.spacecraft.router import router as spacecraft_router from app.users.router import router as users_router # Create the app @@ -11,7 +12,8 @@ # Create database Base.metadata.create_all(bind=engine) # Add routes -app.include_router(spacecraft_router) +app.include_router(cases_router) +app.include_router(applicants_router) app.include_router(users_router) app.include_router(admin_router) app.include_router(health_router) diff --git a/app/spacecraft/models.py b/app/spacecraft/models.py deleted file mode 100644 index f050756..0000000 --- a/app/spacecraft/models.py +++ /dev/null @@ -1,15 +0,0 @@ -from sqlalchemy import Column, Integer, String - -from app.db import Base - - -# SQLAlchemy Model -class DBSpacecraft(Base): - __tablename__ = "spacecraft" - - id = Column(Integer, primary_key=True, index=True) - name = Column(String, nullable=False) - description = Column(String, nullable=True) - affiliation = Column(String, nullable=True) - dimensions = Column(String, nullable=True) - appearances = Column(Integer, nullable=False) diff --git a/app/spacecraft/router.py b/app/spacecraft/router.py deleted file mode 100644 index b256493..0000000 --- a/app/spacecraft/router.py +++ /dev/null @@ -1,53 +0,0 @@ -from typing import Annotated - -from fastapi import APIRouter, Depends -from sqlalchemy.orm import Session -from starlette import status - -import app.spacecraft.services as service -from app.db import get_db -from app.spacecraft.schemas import Spacecraft, SpacecraftPayload - -router = APIRouter( - prefix="/api", - tags=["Spacecraft"], - responses={404: {"description": "Endpoint not found"}}, -) - -# Database dependency injection session -db_session = Annotated[Session, Depends(get_db)] - - -@router.get( - "/spacecraft", status_code=status.HTTP_200_OK, response_model=SpacecraftPayload -) -async def get_items(db: db_session, page_number: int = 0, page_size: int = 100): - return service.get_items(db, page_number, page_size) - - -@router.get( - "/spacecraft/{id}", status_code=status.HTTP_200_OK, response_model=Spacecraft -) -async def get_spacecraft(id: int, db: db_session): - return service.get_item(db, id) - - -@router.put( - "/spacecraft/{id}", status_code=status.HTTP_200_OK, response_model=Spacecraft -) -async def update_spacecraft(id: int, spacecraft: Spacecraft, db: db_session): - db_spacecraft = service.update_item(db, id, spacecraft) - return db_spacecraft - - -@router.post( - "/spacecraft", status_code=status.HTTP_201_CREATED, response_model=Spacecraft -) -async def create_spacecraft(spacecraft: Spacecraft, db: db_session): - db_spacecraft = service.create_item(db, spacecraft) - return db_spacecraft - - -@router.delete("/spacecraft/{id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_spacecraft(id: int, db: db_session): - service.delete_item(db, id) diff --git a/app/spacecraft/schemas.py b/app/spacecraft/schemas.py deleted file mode 100644 index c9216af..0000000 --- a/app/spacecraft/schemas.py +++ /dev/null @@ -1,20 +0,0 @@ -from pydantic import BaseModel, ConfigDict - - -# Pydantic Models -class Spacecraft(BaseModel): - model_config = ConfigDict(from_attributes=True) - id: int | None = None - name: str - description: str | None = None - affiliation: str | None = None - dimensions: str | None = None - appearances: int = 1 - - -class SpacecraftPayload(BaseModel): - items: list[Spacecraft] - item_count: int = 0 - page_count: int = 0 - prev_page: int | None = None - next_page: int | None = None diff --git a/app/spacecraft/services.py b/app/spacecraft/services.py deleted file mode 100644 index 14aea40..0000000 --- a/app/spacecraft/services.py +++ /dev/null @@ -1,60 +0,0 @@ -from fastapi import HTTPException -from sqlalchemy.orm import Session - -from app.spacecraft.models import DBSpacecraft -from app.spacecraft.schemas import Spacecraft -from app.utils import get_next_page, get_page_count, get_prev_page - - -def get_items(db: Session, page_number: int, page_size: int): - item_count = db.query(DBSpacecraft).count() - items = ( - db.query(DBSpacecraft).limit(page_size).offset(page_number * page_size).all() - ) - - return { - "items": items, - "item_count": item_count, - "page_count": get_page_count(item_count, page_size), - "prev_page": get_prev_page(page_number), - "next_page": get_next_page(item_count, page_number, page_size), - } - - -def get_item(db: Session, spacecraft_id: int): - return db.query(DBSpacecraft).where(DBSpacecraft.id == spacecraft_id).first() - - -def update_item(db: Session, id: int, spacecraft: Spacecraft): - db_spacecraft = db.query(DBSpacecraft).filter(DBSpacecraft.id == id).first() - if db_spacecraft is None: - raise HTTPException(status_code=404, detail="Spacecraft not founds") - - db_spacecraft.name = spacecraft.name - db_spacecraft.description = spacecraft.description - db_spacecraft.affiliation = spacecraft.affiliation - db_spacecraft.dimensions = spacecraft.dimensions - db_spacecraft.appearances = spacecraft.appearances - db.add(db_spacecraft) - db.commit() - db.refresh(db_spacecraft) - - return db_spacecraft - - -def create_item(db: Session, spacecraft: Spacecraft): - db_spacecraft = DBSpacecraft(**spacecraft.model_dump()) - db.add(db_spacecraft) - db.commit() - db.refresh(db_spacecraft) - - return db_spacecraft - - -def delete_item(db: Session, id: int): - db_spacecraft = db.query(DBSpacecraft).filter(DBSpacecraft.id == id).first() - if db_spacecraft is None: - raise HTTPException(status_code=404, detail="Spacecraft not founds") - - db.query(DBSpacecraft).filter(DBSpacecraft.id == id).delete() - db.commit() diff --git a/migrations/env.py b/migrations/env.py index 8aeb1aa..4ca8fb6 100644 --- a/migrations/env.py +++ b/migrations/env.py @@ -1,10 +1,10 @@ from logging.config import fileConfig +from alembic import context from sqlalchemy import engine_from_config, pool -from alembic import context +from app.cases.models import DBCase from app.config import settings -from app.spacecraft.models import DBSpacecraft # this is the Alembic Config object, which provides # access to the values within the .ini file in use. @@ -22,8 +22,7 @@ # for 'autogenerate' support # from myapp import mymodel # target_metadata = mymodel.Base.metadata -# target_metadata = None -target_metadata = DBSpacecraft.metadata +target_metadata = None # other values from the config, defined by the needs of env.py, # can be acquired: diff --git a/tests/test_applicants.py b/tests/test_applicants.py new file mode 100644 index 0000000..f9a716a --- /dev/null +++ b/tests/test_applicants.py @@ -0,0 +1,69 @@ +base_applicant = { + "id": 0, + "first_name": "John", + "last_name": "Doe", + "middle_name": "A", + "gender": "M", + "date_of_birth": "2023-01-01", + "ssn": "023456789", + "email": "john@test.com", + "home_phone": "123-456-7890", + "mobile_phone": "098-765-4321", + "address": "123 Main St", + "city": "Anytown", + "state": "CA", + "zip": "12345", + "country": "USA", + "created_at": "2023-10-01T00:00:00", + "updated_at": "2023-10-01T00:00:00", +} + + +def test_create_applicant(client): + response = client.post("/api/applicants/", json=base_applicant) + assert response.status_code == 201 + assert response.json() == base_applicant + + +def test_get_all_applicants(client): + response = client.get("/api/applicants") + assert response.status_code == 200 + assert len(response.json()) > 0 + + +def test_get_applicants_paged(client): + response = client.get("/api/applicants?page_number=0&page_size=10") + assert response.status_code == 200 + assert len(response.json()) > 0 + + +def test_get_applicant(client): + response = client.get("/api/applicants/0") + assert response.status_code == 200 + assert response.json() == base_applicant + + +def test_update_applicant(client): + updated_applicant = base_applicant.copy() + updated_applicant["middle_name"] = "test" + + response = client.put("/api/applicants/0", json=updated_applicant) + response_json = response.json() + response_json["updated_at"] = updated_applicant["updated_at"] + assert response.status_code == 200 + assert response_json == updated_applicant + + +def test_update_applicant_invalid_id(client): + response = client.put("/api/applicants/-1", json=base_applicant) + assert response.status_code == 404 + + +def test_delete_applicant(client): + response = client.delete("/api/applicants/0") + assert response.status_code == 204 + + +def test_delete_applicant_invalid_id(client): + response = client.delete("/api/applicants/-1") + assert response.status_code == 404 diff --git a/tests/test_cases.py b/tests/test_cases.py new file mode 100644 index 0000000..cd50b70 --- /dev/null +++ b/tests/test_cases.py @@ -0,0 +1,67 @@ +base_case = { + "id": 0, + "status": "Not Started", + "assigned_to": "Test User", + "created_at": "2023-10-01T00:00:00", + "updated_at": "2023-10-01T00:00:00", + "applicant_id": 0, +} + +base_case_with_applicant = { + "id": 0, + "status": "Not Started", + "assigned_to": "Test User", + "created_at": "2023-10-01T00:00:00", + "updated_at": "2023-10-01T00:00:00", + "applicant": None, # This will be None if no applicant is associated +} + + +def test_create_case(client): + response = client.post("/api/cases/", json=base_case) + assert response.status_code == 201 + assert response.json() == base_case + + +def test_get_all_cases(client): + response = client.get("/api/cases") + assert response.status_code == 200 + assert len(response.json()) > 0 + + +def test_get_cases_paged(client): + response = client.get("/api/cases?page_number=0&page_size=10") + assert response.status_code == 200 + assert len(response.json()) > 0 + + +def test_get_cases(client): + response = client.get("/api/cases/0") + assert response.status_code == 200 + assert response.json() == base_case_with_applicant + + +def test_update_case(client): + updated_case = base_case.copy() + updated_case["assigned_to"] = "test user" + + response = client.put("/api/cases/0", json=updated_case) + response_json = response.json() + response_json["updated_at"] = updated_case["updated_at"] + assert response.status_code == 200 + assert response_json == updated_case + + +def test_update_case_invalid_id(client): + response = client.put("/api/cases/-1", json=base_case) + assert response.status_code == 404 + + +def test_delete_case(client): + response = client.delete("/api/cases/0") + assert response.status_code == 204 + + +def test_delete_case_invalid_id(client): + response = client.delete("/api/cases/-1") + assert response.status_code == 404 diff --git a/tests/test_spacecraft.py b/tests/test_spacecraft.py deleted file mode 100644 index 74a9d57..0000000 --- a/tests/test_spacecraft.py +++ /dev/null @@ -1,56 +0,0 @@ -base_spacecraft = { - "id": 0, - "name": "test craft", - "description": "test description", - "affiliation": "test affiliation", - "dimensions": "test dimensions", - "appearances": 0, -} - - -def test_create_spacecraft(client): - response = client.post("/api/spacecraft/", json=base_spacecraft) - assert response.status_code == 201 - assert response.json() == base_spacecraft - - -def test_get_all_spacecraft(client): - response = client.get("/api/spacecraft") - assert response.status_code == 200 - assert len(response.json()) > 0 - - -def test_get_spacecraft_paged(client): - response = client.get("/api/spacecraft?page_number=0&page_size=10") - assert response.status_code == 200 - assert len(response.json()) > 0 - - -def test_get_spacecraft(client): - response = client.get("/api/spacecraft/0") - assert response.status_code == 200 - assert response.json() == base_spacecraft - - -def test_update_spacecraft(client): - updated_spacecraft = base_spacecraft.copy() - updated_spacecraft["appearances"] = 1 - - response = client.put("/api/spacecraft/0", json=updated_spacecraft) - assert response.status_code == 200 - assert response.json() == updated_spacecraft - - -def test_update_spacecraft_invalid_id(client): - response = client.put("/api/spacecraft/-1", json=base_spacecraft) - assert response.status_code == 404 - - -def test_delete_spacecraft(client): - response = client.delete("/api/spacecraft/0") - assert response.status_code == 204 - - -def test_delete_spacecraft_invalid_id(client): - response = client.delete("/api/spacecraft/-1") - assert response.status_code == 404 From e95c3696d1ec9e000cef407866670ee2aeac2c9d Mon Sep 17 00:00:00 2001 From: Johnny Bouder Date: Wed, 28 May 2025 16:50:25 -0400 Subject: [PATCH 2/3] Update migration readme. --- migrations/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/migrations/README.md b/migrations/README.md index df626b5..8eafe13 100644 --- a/migrations/README.md +++ b/migrations/README.md @@ -13,10 +13,10 @@ alembic revision -m "SOME MESSAGE" ```py # Add column example def upgrade() -> None: - op.add_column('spacecraft', sa.Column('speed', sa.Integer(), nullable=True) + op.add_column('cases', sa.Column('priority', sa.Integer(), nullable=True) def downgrade() -> None: - op.drop_column('spacecraft', 'speed') + op.drop_column('cases', 'priority') ``` From 6cda9f11c9e8cc2fe8c4a76096b5d31a76551184 Mon Sep 17 00:00:00 2001 From: Johnny Bouder Date: Wed, 28 May 2025 17:14:39 -0400 Subject: [PATCH 3/3] Add new docs. --- docs/index.html | 45 +++-- docs/openapi.json | 1 + docs/openapi.yaml | 486 ---------------------------------------------- 3 files changed, 31 insertions(+), 501 deletions(-) create mode 100644 docs/openapi.json delete mode 100644 docs/openapi.yaml diff --git a/docs/index.html b/docs/index.html index 83ed911..ec7618d 100644 --- a/docs/index.html +++ b/docs/index.html @@ -2189,7 +2189,7 @@ -

FastAPI (0.1.0)

Download OpenAPI specification:Download

Spacecraft

Get Items

query Parameters
page_number
any (Page Number)
Default: 0
page_size
any (Page Size)
Default: 100

Responses

FastAPI (0.1.0)

Download OpenAPI specification:Download

Cases

Get Cases

query Parameters
page_number
integer (Page Number)
Default: 0
page_size
integer (Page Size)
Default: 100

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "item_count": 0,
  • "page_count": 0,
  • "prev_page": { },
  • "next_page": { }
}

Create Spacecraft

Request Body schema: application/json
id
required
any (Id)
name
required
any (Name)
Description (any) or Description (any) (Description)
Affiliation (any) or Affiliation (any) (Affiliation)
Dimensions (any) or Dimensions (any) (Dimensions)
appearances
required
any (Appearances)

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "item_count": 0,
  • "page_count": 0,
  • "prev_page": 0,
  • "next_page": 0
}

Create Case

Request Body schema: application/json
Id (integer) or Id (null) (Id)
status
required
string (Status)
Assigned To (string) or Assigned To (null) (Assigned To)
created_at
required
string <date-time> (Created At)
updated_at
required
string <date-time> (Updated At)
Applicant Id (integer) or Applicant Id (null) (Applicant Id)

Responses

Request samples

Content type
application/json
{
  • "id": null,
  • "name": null,
  • "description": { },
  • "affiliation": { },
  • "dimensions": { },
  • "appearances": null
}

Response samples

Content type
application/json
{
  • "id": null,
  • "name": null,
  • "description": { },
  • "affiliation": { },
  • "dimensions": { },
  • "appearances": null
}

Get Spacecraft

path Parameters
id
required
any (Id)

Responses

Request samples

Content type
application/json
{
  • "id": 0,
  • "status": "string",
  • "assigned_to": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "applicant_id": 0
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "status": "string",
  • "assigned_to": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "applicant_id": 0
}

Get Case

path Parameters
id
required
integer (Id)

Responses

Response samples

Content type
application/json
{
  • "id": null,
  • "name": null,
  • "description": { },
  • "affiliation": { },
  • "dimensions": { },
  • "appearances": null
}

Update Spacecraft

path Parameters
id
required
any (Id)
Request Body schema: application/json
id
required
any (Id)
name
required
any (Name)
Description (any) or Description (any) (Description)
Affiliation (any) or Affiliation (any) (Affiliation)
Dimensions (any) or Dimensions (any) (Dimensions)
appearances
required
any (Appearances)

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "status": "string",
  • "assigned_to": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "applicant": {
    }
}

Update Case

path Parameters
id
required
integer (Id)
Request Body schema: application/json
Id (integer) or Id (null) (Id)
status
required
string (Status)
Assigned To (string) or Assigned To (null) (Assigned To)
created_at
required
string <date-time> (Created At)
updated_at
required
string <date-time> (Updated At)
Applicant Id (integer) or Applicant Id (null) (Applicant Id)

Responses

Request samples

Content type
application/json
{
  • "id": null,
  • "name": null,
  • "description": { },
  • "affiliation": { },
  • "dimensions": { },
  • "appearances": null
}

Response samples

Content type
application/json
{
  • "id": null,
  • "name": null,
  • "description": { },
  • "affiliation": { },
  • "dimensions": { },
  • "appearances": null
}

Delete Spacecraft

path Parameters
id
required
any (Id)

Responses

Request samples

Content type
application/json
{
  • "id": 0,
  • "status": "string",
  • "assigned_to": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "applicant_id": 0
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "status": "string",
  • "assigned_to": "string",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z",
  • "applicant_id": 0
}

Delete Case

path Parameters
id
required
integer (Id)

Responses

Response samples

Content type
application/json
{
  • "detail": [
    ]
}

Users

Get Items

query Parameters
page_number
any (Page Number)
Default: 0
page_size
any (Page Size)
Default: 100

Responses

Response samples

Content type
application/json
{
  • "detail": [
    ]
}

Applicants

Get Applicants

query Parameters
page_number
integer (Page Number)
Default: 0
page_size
integer (Page Size)
Default: 100

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "item_count": 0,
  • "page_count": 0,
  • "prev_page": { },
  • "next_page": { }
}

Create Item

Request Body schema: application/json
Id (any) or Id (any) (Id)
user_id
required
any (User Id)
first_name
required
any (First Name)
last_name
required
any (Last Name)
display_name
required
any (Display Name)
email
required
any (Email)
is_active
any (Is Active)
Default: true
Created (any) or Created (any) (Created)
created_by
required
any (Created By)
Modified (any) or Modified (any) (Modified)
modified_by
required
any (Modified By)

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "item_count": 0,
  • "page_count": 0,
  • "prev_page": 0,
  • "next_page": 0
}

Create Applicant

Request Body schema: application/json
Id (integer) or Id (null) (Id)
first_name
required
string (First Name)
last_name
required
string (Last Name)
Middle Name (string) or Middle Name (null) (Middle Name)
gender
required
string (Gender)
date_of_birth
required
string <date> (Date Of Birth)
ssn
required
string (Ssn)
Email (string) or Email (null) (Email)
Home Phone (string) or Home Phone (null) (Home Phone)
Mobile Phone (string) or Mobile Phone (null) (Mobile Phone)
Address (string) or Address (null) (Address)
City (string) or City (null) (City)
State (string) or State (null) (State)
Zip (string) or Zip (null) (Zip)
country
string (Country)
Default: "USA"
created_at
required
string <date-time> (Created At)
updated_at
required
string <date-time> (Updated At)

Responses

Request samples

Content type
application/json
{
  • "id": { },
  • "user_id": null,
  • "first_name": null,
  • "last_name": null,
  • "display_name": null,
  • "email": null,
  • "is_active": true,
  • "created": { },
  • "created_by": null,
  • "modified": { },
  • "modified_by": null
}

Response samples

Content type
application/json
{
  • "id": { },
  • "user_id": null,
  • "first_name": null,
  • "last_name": null,
  • "display_name": null,
  • "email": null,
  • "is_active": true,
  • "created": { },
  • "created_by": null,
  • "modified": { },
  • "modified_by": null
}

Get Item

path Parameters
id
required
any (Id)

Responses

Request samples

Content type
application/json
{
  • "id": 0,
  • "first_name": "string",
  • "last_name": "string",
  • "middle_name": "string",
  • "gender": "string",
  • "date_of_birth": "2019-08-24",
  • "ssn": "string",
  • "email": "string",
  • "home_phone": "string",
  • "mobile_phone": "string",
  • "address": "string",
  • "city": "string",
  • "state": "string",
  • "zip": "string",
  • "country": "USA",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "first_name": "string",
  • "last_name": "string",
  • "middle_name": "string",
  • "gender": "string",
  • "date_of_birth": "2019-08-24",
  • "ssn": "string",
  • "email": "string",
  • "home_phone": "string",
  • "mobile_phone": "string",
  • "address": "string",
  • "city": "string",
  • "state": "string",
  • "zip": "string",
  • "country": "USA",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Get Applicant

path Parameters
id
required
integer (Id)

Responses

Response samples

Content type
application/json
{
  • "id": { },
  • "user_id": null,
  • "first_name": null,
  • "last_name": null,
  • "display_name": null,
  • "email": null,
  • "is_active": true,
  • "created": { },
  • "created_by": null,
  • "modified": { },
  • "modified_by": null
}

Update Item

path Parameters
id
required
any (Id)
Request Body schema: application/json
Id (any) or Id (any) (Id)
user_id
required
any (User Id)
first_name
required
any (First Name)
last_name
required
any (Last Name)
display_name
required
any (Display Name)
email
required
any (Email)
is_active
any (Is Active)
Default: true
Created (any) or Created (any) (Created)
created_by
required
any (Created By)
Modified (any) or Modified (any) (Modified)
modified_by
required
any (Modified By)

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "first_name": "string",
  • "last_name": "string",
  • "middle_name": "string",
  • "gender": "string",
  • "date_of_birth": "2019-08-24",
  • "ssn": "string",
  • "email": "string",
  • "home_phone": "string",
  • "mobile_phone": "string",
  • "address": "string",
  • "city": "string",
  • "state": "string",
  • "zip": "string",
  • "country": "USA",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Update Applicant

path Parameters
id
required
integer (Id)
Request Body schema: application/json
Id (integer) or Id (null) (Id)
first_name
required
string (First Name)
last_name
required
string (Last Name)
Middle Name (string) or Middle Name (null) (Middle Name)
gender
required
string (Gender)
date_of_birth
required
string <date> (Date Of Birth)
ssn
required
string (Ssn)
Email (string) or Email (null) (Email)
Home Phone (string) or Home Phone (null) (Home Phone)
Mobile Phone (string) or Mobile Phone (null) (Mobile Phone)
Address (string) or Address (null) (Address)
City (string) or City (null) (City)
State (string) or State (null) (State)
Zip (string) or Zip (null) (Zip)
country
string (Country)
Default: "USA"
created_at
required
string <date-time> (Created At)
updated_at
required
string <date-time> (Updated At)

Responses

Request samples

Content type
application/json
{
  • "id": { },
  • "user_id": null,
  • "first_name": null,
  • "last_name": null,
  • "display_name": null,
  • "email": null,
  • "is_active": true,
  • "created": { },
  • "created_by": null,
  • "modified": { },
  • "modified_by": null
}

Response samples

Content type
application/json
{
  • "id": { },
  • "user_id": null,
  • "first_name": null,
  • "last_name": null,
  • "display_name": null,
  • "email": null,
  • "is_active": true,
  • "created": { },
  • "created_by": null,
  • "modified": { },
  • "modified_by": null
}

Delete Item

path Parameters
id
required
any (Id)

Responses

Request samples

Content type
application/json
{
  • "id": 0,
  • "first_name": "string",
  • "last_name": "string",
  • "middle_name": "string",
  • "gender": "string",
  • "date_of_birth": "2019-08-24",
  • "ssn": "string",
  • "email": "string",
  • "home_phone": "string",
  • "mobile_phone": "string",
  • "address": "string",
  • "city": "string",
  • "state": "string",
  • "zip": "string",
  • "country": "USA",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "first_name": "string",
  • "last_name": "string",
  • "middle_name": "string",
  • "gender": "string",
  • "date_of_birth": "2019-08-24",
  • "ssn": "string",
  • "email": "string",
  • "home_phone": "string",
  • "mobile_phone": "string",
  • "address": "string",
  • "city": "string",
  • "state": "string",
  • "zip": "string",
  • "country": "USA",
  • "created_at": "2019-08-24T14:15:22Z",
  • "updated_at": "2019-08-24T14:15:22Z"
}

Delete Applicant

path Parameters
id
required
integer (Id)

Responses

Response samples

Content type
application/json
{
  • "detail": [
    ]
}

Admin

Get Current User

Authorizations:
HTTPBearer

Responses

Response samples

Content type
application/json
null

Health

Get Health

Responses

Response samples

Content type
application/json
null
+

Response samples

Content type
application/json
{
  • "detail": [
    ]
}

Users

Get Items

query Parameters
page_number
integer (Page Number)
Default: 0
page_size
integer (Page Size)
Default: 100

Responses

Response samples

Content type
application/json
{
  • "items": [
    ],
  • "item_count": 0,
  • "page_count": 0,
  • "prev_page": 0,
  • "next_page": 0
}

Create Item

Request Body schema: application/json
Id (integer) or Id (null) (Id)
user_id
required
string (User Id)
first_name
required
string (First Name)
last_name
required
string (Last Name)
display_name
required
string (Display Name)
email
required
string (Email)
is_active
boolean (Is Active)
Default: true
Created (string) or Created (null) (Created)
created_by
required
string (Created By)
Modified (string) or Modified (null) (Modified)
modified_by
required
string (Modified By)

Responses

Request samples

Content type
application/json
{
  • "id": 0,
  • "user_id": "string",
  • "first_name": "string",
  • "last_name": "string",
  • "display_name": "string",
  • "email": "string",
  • "is_active": true,
  • "created": "2019-08-24T14:15:22Z",
  • "created_by": "string",
  • "modified": "2019-08-24T14:15:22Z",
  • "modified_by": "string"
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "user_id": "string",
  • "first_name": "string",
  • "last_name": "string",
  • "display_name": "string",
  • "email": "string",
  • "is_active": true,
  • "created": "2019-08-24T14:15:22Z",
  • "created_by": "string",
  • "modified": "2019-08-24T14:15:22Z",
  • "modified_by": "string"
}

Get Item

path Parameters
id
required
integer (Id)

Responses

Response samples

Content type
application/json
{
  • "id": 0,
  • "user_id": "string",
  • "first_name": "string",
  • "last_name": "string",
  • "display_name": "string",
  • "email": "string",
  • "is_active": true,
  • "created": "2019-08-24T14:15:22Z",
  • "created_by": "string",
  • "modified": "2019-08-24T14:15:22Z",
  • "modified_by": "string"
}

Update Item

path Parameters
id
required
integer (Id)
Request Body schema: application/json
Id (integer) or Id (null) (Id)
user_id
required
string (User Id)
first_name
required
string (First Name)
last_name
required
string (Last Name)
display_name
required
string (Display Name)
email
required
string (Email)
is_active
boolean (Is Active)
Default: true
Created (string) or Created (null) (Created)
created_by
required
string (Created By)
Modified (string) or Modified (null) (Modified)
modified_by
required
string (Modified By)

Responses

Request samples

Content type
application/json
{
  • "id": 0,
  • "user_id": "string",
  • "first_name": "string",
  • "last_name": "string",
  • "display_name": "string",
  • "email": "string",
  • "is_active": true,
  • "created": "2019-08-24T14:15:22Z",
  • "created_by": "string",
  • "modified": "2019-08-24T14:15:22Z",
  • "modified_by": "string"
}

Response samples

Content type
application/json
{
  • "id": 0,
  • "user_id": "string",
  • "first_name": "string",
  • "last_name": "string",
  • "display_name": "string",
  • "email": "string",
  • "is_active": true,
  • "created": "2019-08-24T14:15:22Z",
  • "created_by": "string",
  • "modified": "2019-08-24T14:15:22Z",
  • "modified_by": "string"
}

Delete Item

path Parameters
id
required
integer (Id)

Responses

Response samples

Content type
application/json
{
  • "detail": [
    ]
}

Admin

Get Current User

Authorizations:
HTTPBearer

Responses

Response samples

Content type
application/json
null

Health

Get Health

Responses

Response samples

Content type
application/json
null