Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions backend/script/reset_dev.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,8 +124,6 @@ async def reset_dev():

for location_data in data["locations"]:
location = LocationEntity(
citation_count=location_data["citation_count"],
warning_count=location_data["warning_count"],
hold_expiration=parse_date(location_data.get("hold_expiration")),
formatted_address=location_data["formatted_address"],
google_place_id=location_data["google_place_id"],
Expand Down
6 changes: 2 additions & 4 deletions backend/src/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from src.modules.account.account_router import account_router
from src.modules.complaint.complaint_router import complaint_router
from src.modules.incident.incident_router import incident_router
from src.modules.location.location_router import location_router
from src.modules.party.party_router import party_router
from src.modules.police.police_router import police_router
from src.modules.student.student_router import student_router

app = FastAPI()
Expand Down Expand Up @@ -45,5 +44,4 @@ def read_root():
app.include_router(party_router)
app.include_router(student_router)
app.include_router(location_router)
app.include_router(police_router)
app.include_router(complaint_router)
app.include_router(incident_router)
4 changes: 2 additions & 2 deletions backend/src/modules/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,15 @@
"""

from .account.account_entity import AccountEntity
from .complaint.complaint_entity import ComplaintEntity
from .incident.incident_entity import IncidentEntity
from .location.location_entity import LocationEntity
from .party.party_entity import PartyEntity
from .police.police_entity import PoliceEntity
from .student.student_entity import StudentEntity

__all__ = [
"AccountEntity",
"ComplaintEntity",
"IncidentEntity",
"LocationEntity",
"PartyEntity",
"PoliceEntity",
Expand Down
17 changes: 0 additions & 17 deletions backend/src/modules/complaint/complaint_model.py

This file was deleted.

76 changes: 0 additions & 76 deletions backend/src/modules/complaint/complaint_router.py

This file was deleted.

92 changes: 0 additions & 92 deletions backend/src/modules/complaint/complaint_service.py

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
from datetime import datetime
from datetime import UTC
from typing import TYPE_CHECKING, Self

from sqlalchemy import DateTime, ForeignKey, Integer, String
from sqlalchemy import DateTime, Enum, ForeignKey, Integer, String
from sqlalchemy.orm import Mapped, MappedAsDataclass, mapped_column, relationship
from src.core.database import EntityBase
from src.modules.complaint.complaint_model import ComplaintData, ComplaintDto
from src.modules.incident.incident_model import IncidentData, IncidentDto, IncidentSeverity

if TYPE_CHECKING:
from src.modules.location.location_entity import LocationEntity


class ComplaintEntity(MappedAsDataclass, EntityBase):
__tablename__ = "complaints"
class IncidentEntity(MappedAsDataclass, EntityBase):
__tablename__ = "incidents"

id: Mapped[int] = mapped_column(Integer, primary_key=True, init=False)
location_id: Mapped[int] = mapped_column(
Integer, ForeignKey("locations.id", ondelete="CASCADE"), nullable=False
)
complaint_datetime: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False)
incident_datetime: Mapped[str] = mapped_column(DateTime(timezone=True), nullable=False)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason this is Mapped[str] and not Mapped[datetime]? I get IDE errors from the type mismatch below

severity: Mapped[IncidentSeverity] = mapped_column(Enum(IncidentSeverity), nullable=False)
description: Mapped[str] = mapped_column(String, nullable=False, default="")

# Relationships
Expand All @@ -26,18 +27,25 @@ class ComplaintEntity(MappedAsDataclass, EntityBase):
)

@classmethod
def from_data(cls, data: ComplaintData) -> Self:
def from_data(cls, data: IncidentData) -> Self:
return cls(
location_id=data.location_id,
complaint_datetime=data.complaint_datetime,
incident_datetime=data.incident_datetime,
severity=data.severity,
description=data.description,
)

def to_dto(self) -> ComplaintDto:
def to_dto(self) -> IncidentDto:
"""Convert entity to model."""
return ComplaintDto(
# Ensure incident_datetime is timezone-aware
incident_dt = self.incident_datetime
if incident_dt.tzinfo is None:
incident_dt = incident_dt.replace(tzinfo=UTC)

return IncidentDto(
id=self.id,
location_id=self.location_id,
complaint_datetime=self.complaint_datetime,
incident_datetime=incident_dt,
description=self.description,
severity=self.severity,
)
24 changes: 24 additions & 0 deletions backend/src/modules/incident/incident_model.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from enum import Enum

from pydantic import AwareDatetime, BaseModel


class IncidentSeverity(Enum):
COMPLAINT = "complaint"
WARNING = "warning"
CITATION = "citation"


class IncidentData(BaseModel):
"""Data DTO for an incident without id."""

location_id: int
incident_datetime: AwareDatetime
description: str = ""
severity: IncidentSeverity


class IncidentDto(IncidentData):
"""Output DTO for an incident."""

id: int
76 changes: 76 additions & 0 deletions backend/src/modules/incident/incident_router.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from fastapi import APIRouter, Depends, status
from src.core.authentication import authenticate_admin, authenticate_staff_or_admin
from src.modules.account.account_model import AccountDto

from .incident_model import IncidentData, IncidentDto
from .incident_service import IncidentService

incident_router = APIRouter(prefix="/api/locations", tags=["incidents"])


@incident_router.get(
"/{location_id}/incidents",
response_model=list[IncidentDto],
status_code=status.HTTP_200_OK,
summary="Get all incidents for a location",
description="Returns all incidents associated with a given location. Staff or admin only.",
)
async def get_incidents_by_location(
location_id: int,
incident_service: IncidentService = Depends(),
_: AccountDto = Depends(authenticate_staff_or_admin),
) -> list[IncidentDto]:
"""Get all incidents for a location."""
return await incident_service.get_incidents_by_location(location_id)


@incident_router.post(
"/{location_id}/incidents",
response_model=IncidentDto,
status_code=status.HTTP_201_CREATED,
summary="Create an incident for a location",
description="Creates a new incident associated with a location. Admin only.",
)
async def create_incident(
location_id: int,
incident_data: IncidentData,
incident_service: IncidentService = Depends(),
_: AccountDto = Depends(authenticate_admin),
) -> IncidentDto:
"""Create an incident for a location."""
return await incident_service.create_incident(location_id, incident_data)


@incident_router.put(
"/{location_id}/incidents/{incident_id}",
response_model=IncidentDto,
status_code=status.HTTP_200_OK,
summary="Update an incident",
description="Updates an existing incident. Admin only.",
)
async def update_incident(
location_id: int,
incident_id: int,
incident_data: IncidentData,
incident_service: IncidentService = Depends(),
_: AccountDto = Depends(authenticate_admin),
) -> IncidentDto:
"""Update an incident."""
return await incident_service.update_incident(incident_id, location_id, incident_data)
Comment on lines +34 to +59
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need a separate IncidentCreeateDto to avoid having to duplicate location_id between body and path params (updating TDD as I find these)



@incident_router.delete(
"/{location_id}/incidents/{incident_id}",
response_model=IncidentDto,
status_code=status.HTTP_200_OK,
summary="Delete an incident",
description="Deletes an incident. Admin only.",
)
async def delete_incident(
location_id: int,
incident_id: int,
incident_service: IncidentService = Depends(),
_: AccountDto = Depends(authenticate_admin),
) -> IncidentDto:
"""Delete an incident."""
return await incident_service.delete_incident(incident_id)
Loading