Skip to content

Commit 642468a

Browse files
authored
Merge branch 'main' into vasu/party-csv-export
2 parents 4cfa07b + bfd9aa9 commit 642468a

22 files changed

+3059
-137
lines changed

backend/src/main.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
from fastapi import FastAPI, HTTPException, Request
22
from fastapi.middleware.cors import CORSMiddleware
33
from fastapi.responses import JSONResponse
4+
from src.modules.account.account_router import account_router
45
from src.modules.location.location_router import location_router
5-
from src.modules.student.student_router import student_router
66
from src.modules.party.party_router import party_router
7+
from src.modules.student.student_router import student_router
78

89
app = FastAPI()
910

@@ -37,6 +38,7 @@ def read_root():
3738
return {"message": "Successful Test"}
3839

3940

41+
app.include_router(account_router)
4042
app.include_router(party_router)
4143
app.include_router(student_router)
4244
app.include_router(location_router)
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
from fastapi import APIRouter, Depends, Query
2+
from src.core.authentication import authenticate_admin
3+
from src.modules.account.account_model import Account, AccountData, AccountRole
4+
from src.modules.account.account_service import AccountService
5+
from src.modules.police.police_model import PoliceAccount, PoliceAccountUpdate
6+
from src.modules.police.police_service import PoliceService
7+
8+
account_router = APIRouter(prefix="/api/accounts", tags=["accounts"])
9+
10+
11+
@account_router.get("/police")
12+
async def get_police_credentials(
13+
police_service: PoliceService = Depends(),
14+
_=Depends(authenticate_admin),
15+
) -> PoliceAccount:
16+
police_entity = await police_service.get_police()
17+
return PoliceAccount(email=police_entity.email)
18+
19+
20+
@account_router.put("/police")
21+
async def update_police_credentials(
22+
data: PoliceAccountUpdate,
23+
police_service: PoliceService = Depends(),
24+
_=Depends(authenticate_admin),
25+
) -> PoliceAccount:
26+
police_entity = await police_service.update_police(data.email, data.password)
27+
return PoliceAccount(email=police_entity.email)
28+
29+
30+
@account_router.get("")
31+
async def list_accounts(
32+
role: list[AccountRole] | None = Query(
33+
None, description="Filter by role(s): admin, staff, student"
34+
),
35+
account_service: AccountService = Depends(),
36+
_=Depends(authenticate_admin),
37+
) -> list[Account]:
38+
return await account_service.get_accounts_by_roles(role)
39+
40+
41+
@account_router.post("")
42+
async def create_account(
43+
data: AccountData,
44+
account_service: AccountService = Depends(),
45+
_=Depends(authenticate_admin),
46+
) -> Account:
47+
return await account_service.create_account(data)
48+
49+
50+
@account_router.put("/{account_id}")
51+
async def update_account(
52+
account_id: int,
53+
data: AccountData,
54+
account_service: AccountService = Depends(),
55+
_=Depends(authenticate_admin),
56+
) -> Account:
57+
return await account_service.update_account(account_id, data)
58+
59+
60+
@account_router.delete("/{account_id}")
61+
async def delete_account(
62+
account_id: int,
63+
account_service: AccountService = Depends(),
64+
_=Depends(authenticate_admin),
65+
) -> Account:
66+
return await account_service.delete_account(account_id)

backend/src/modules/account/account_service.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ async def get_accounts(self) -> list[Account]:
5050
accounts = result.scalars().all()
5151
return [Account.from_entity(account) for account in accounts]
5252

53+
async def get_accounts_by_roles(
54+
self, roles: list[AccountRole] | None = None
55+
) -> list[Account]:
56+
if not roles:
57+
return await self.get_accounts()
58+
59+
result = await self.session.execute(
60+
select(AccountEntity).where(AccountEntity.role.in_(roles))
61+
)
62+
accounts = result.scalars().all()
63+
return [Account.from_entity(account) for account in accounts]
64+
5365
async def get_account_by_id(self, account_id: int) -> Account:
5466
account_entity = await self._get_account_entity_by_id(account_id)
5567
return Account.from_entity(account_entity)

backend/src/modules/location/location_model.py

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,17 @@
55
from src.core.models import PaginatedResponse
66

77

8+
class AutocompleteInput(BaseModel):
9+
# Input for address autocomplete
10+
address: str
11+
12+
13+
class AutocompleteResult(BaseModel):
14+
# Result from Google Maps autocomplete
15+
formatted_address: str
16+
place_id: str
17+
18+
819
class AddressData(BaseModel):
920
# Location data without OCSL-specific fields
1021
google_place_id: str
@@ -65,9 +76,3 @@ class LocationCreate(BaseModel):
6576
warning_count: int = 0
6677
citation_count: int = 0
6778
hold_expiration: datetime | None = None
68-
69-
70-
class AutocompleteResult(BaseModel):
71-
# Result from Google Maps autocomplete
72-
formatted_address: str
73-
place_id: str

backend/src/modules/location/location_router.py

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
1-
from fastapi import APIRouter, Depends
2-
from src.core.authentication import authenticate_admin, authenticate_staff_or_admin
1+
from fastapi import APIRouter, Depends, HTTPException, status
2+
from src.core.authentication import (
3+
authenticate_admin,
4+
authenticate_staff_or_admin,
5+
authenticate_user,
6+
)
7+
from src.modules.account.account_model import Account
38
from src.modules.location.location_model import (
49
Location,
510
LocationCreate,
@@ -8,9 +13,43 @@
813
)
914
from src.modules.location.location_service import LocationService
1015

16+
from .location_model import AutocompleteInput, AutocompleteResult
17+
1118
location_router = APIRouter(prefix="/api/locations", tags=["locations"])
1219

1320

21+
@location_router.post(
22+
"/autocomplete",
23+
response_model=list[AutocompleteResult],
24+
status_code=status.HTTP_200_OK,
25+
summary="Autocomplete address search",
26+
description="Returns address suggestions based on user input using Google Maps Places API.",
27+
)
28+
async def autocomplete_address(
29+
input_data: AutocompleteInput,
30+
location_service: LocationService = Depends(),
31+
user: Account = Depends(authenticate_user),
32+
) -> list[AutocompleteResult]:
33+
"""
34+
Autocomplete address search endpoint.
35+
"""
36+
try:
37+
results = await location_service.autocomplete_address(input_data.address)
38+
return results
39+
except ValueError as e:
40+
# Handle validation errors from service
41+
raise HTTPException(
42+
status_code=status.HTTP_400_BAD_REQUEST,
43+
detail=str(e),
44+
)
45+
except Exception:
46+
# Log error in production
47+
raise HTTPException(
48+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
49+
detail="Failed to fetch address suggestions. Please try again later.",
50+
)
51+
52+
1453
@location_router.get("/", response_model=PaginatedLocationResponse)
1554
async def get_locations(
1655
location_service: LocationService = Depends(),

0 commit comments

Comments
 (0)