Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
19df7a4
Added basic embedded map in newly created components folder under Emb…
MasonMines2006 Oct 21, 2025
86fb542
Implemented basic embed map
MasonMines2006 Oct 21, 2025
d35c431
created react map
MasonMines2006 Oct 28, 2025
c7d3a4e
Created changed to
MasonMines2006 Oct 28, 2025
d11eb73
Merge branch 'main' into embedded-map/#23
MasonMines2006 Oct 28, 2025
5e1be29
Fixed Embedded Map to include the react integration library ( vis.gl/…
MasonMines2006 Oct 28, 2025
ba97c83
Merge branch 'main' into embedded-map/#23
naasanov Nov 3, 2025
df68222
Created Party Demo test page
MasonMines2006 Nov 3, 2025
119936a
Merge remote-tracking branch 'origin/main' into embedded-map/#23
MasonMines2006 Nov 3, 2025
9128996
First mockup of party list props, and page created to be displayed in…
MasonMines2006 Nov 4, 2025
49a441b
Added popup text box and color to map
MasonMines2006 Nov 4, 2025
e527ab9
Removed package and package lock in root directory, updated .env.temp…
MasonMines2006 Nov 4, 2025
e09d155
Created functioning dropdown for party list
MasonMines2006 Nov 4, 2025
1e5c3fa
Implemented Radius Search Route
notvasub Nov 11, 2025
ba6aa22
fixed tests to use mock
notvasub Nov 11, 2025
578e582
hopefully fixed mocks
notvasub Nov 11, 2025
adcf2e0
Modified styling to be accurate with the Lo-Fi wireframe
MasonMines2006 Nov 11, 2025
3e9c0b9
Made changes as requested in ticket appraisal
MasonMines2006 Nov 11, 2025
596f6bc
Merge branch 'main' of https://github.com/cssgunc/party-registration …
naasanov Nov 12, 2025
293ff50
further main merging
naasanov Nov 12, 2025
c7e3d2c
Merge branch 'main' of https://github.com/cssgunc/party-registration …
naasanov Nov 12, 2025
746c095
Fised broken tests from merge
naasanov Nov 12, 2025
a78a4a8
Merge branches 'mason-party-dropdown' and 'main' of https://github.co…
naasanov Nov 14, 2025
cfa1ffd
Merge branch 'main' of https://github.com/cssgunc/party-registration …
naasanov Nov 14, 2025
206e72d
Merge branch 'main' of https://github.com/cssgunc/party-registration …
naasanov Nov 14, 2025
6cca342
Update frontend/src/components/PartyList.tsx
MasonMines2006 Nov 18, 2025
4eda74f
pulled from mason branch, started work on police page. needs lots of …
VidurShah Nov 19, 2025
b942131
Merge branch 'main' of https://github.com/cssgunc/party-registration …
VidurShah Nov 19, 2025
aaead19
Merge branch 'main' of https://github.com/cssgunc/party-registration …
VidurShah Nov 19, 2025
1f471d8
Merge branch 'main' of https://github.com/cssgunc/party-registration …
naasanov Nov 19, 2025
a99e0da
Merge branch 'vasu/radius-search-router' of https://github.com/cssgun…
VidurShah Nov 19, 2025
570d217
wip changed a bunch of things, built police page, added small changes…
VidurShah Nov 20, 2025
36007eb
changed a little margins
VidurShah Nov 20, 2025
4ca441b
Merge branch 'main' of https://github.com/cssgunc/party-registration …
naasanov Nov 20, 2025
94f9b2c
fix: corrected auth in location tests
naasanov Nov 20, 2025
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: 1 addition & 1 deletion backend/src/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,4 @@ class Config(BaseSettings):
GOOGLE_MAPS_API_KEY: str


env = Config() # pyright: ignore[reportCallIssue]
env = Config() # pyright: ignore[reportCallIssue]
40 changes: 38 additions & 2 deletions backend/src/modules/location/location_router.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
from fastapi import APIRouter, Depends, HTTPException, status
from src.core.authentication import (
authenticate_admin,
authenticate_by_role,
authenticate_staff_or_admin,
authenticate_user,
)
from src.modules.account.account_model import Account
from src.modules.location.location_model import (
AddressData,
Location,
LocationCreate,
LocationData,
PaginatedLocationResponse,
)
from src.modules.location.location_service import LocationService
from src.modules.police.police_model import PoliceAccount

from .location_model import AutocompleteInput, AutocompleteResult

Expand All @@ -28,7 +30,9 @@
async def autocomplete_address(
input_data: AutocompleteInput,
location_service: LocationService = Depends(),
user: Account = Depends(authenticate_user),
user: Account | PoliceAccount = Depends(
authenticate_by_role("police", "student", "admin", "staff")
),
) -> list[AutocompleteResult]:
"""
Autocomplete address search endpoint.
Expand All @@ -50,6 +54,38 @@ async def autocomplete_address(
)


@location_router.get(
"/place-details/{place_id}",
response_model=AddressData,
status_code=status.HTTP_200_OK,
summary="Get place details from Google Maps place ID",
description="Returns address details including coordinates for a given place ID.",
)
async def get_place_details(
place_id: str,
location_service: LocationService = Depends(),
user: Account | PoliceAccount = Depends(
authenticate_by_role("police", "student", "admin", "staff")
),
) -> AddressData:
"""
Get place details endpoint.
"""
try:
address_data = await location_service.get_place_details(place_id)
return address_data
except ValueError as e:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=str(e),
)
except Exception:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to fetch place details. Please try again later.",
)


@location_router.get("/", response_model=PaginatedLocationResponse)
async def get_locations(
location_service: LocationService = Depends(),
Expand Down
120 changes: 49 additions & 71 deletions backend/test/modules/location/location_router_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,7 @@
import pytest
import pytest_asyncio
from httpx import ASGITransport, AsyncClient
from src.core.authentication import authenticate_user
from src.main import app
from src.modules.account.account_model import Account, AccountRole
from src.modules.location.location_model import AutocompleteResult
from src.modules.location.location_service import LocationService

Expand All @@ -21,20 +19,9 @@ async def mock_location_service():
async def override_dependencies(mock_location_service: AsyncMock):
"""Override dependencies to provide mock service and auth"""

async def _fake_user():
return Account(
id=1,
email="[email protected]",
first_name="Test",
last_name="User",
pid="123456789",
role=AccountRole.STUDENT,
)

def _get_mock_location_service():
return mock_location_service

app.dependency_overrides[authenticate_user] = _fake_user
app.dependency_overrides[LocationService] = _get_mock_location_service
yield
app.dependency_overrides.clear()
Expand All @@ -52,9 +39,20 @@ def _get_mock_location_service():
app.dependency_overrides.clear()


@pytest_asyncio.fixture
async def admin_client(override_dependencies: Any):
"""Create an authenticated admin client"""
async with AsyncClient(
transport=ASGITransport(app=app),
base_url="http://test",
headers={"Authorization": "Bearer admin"},
) as client:
yield client


@pytest.mark.asyncio
async def test_autocomplete_success(
override_dependencies: Any, mock_location_service: AsyncMock
admin_client: AsyncClient, mock_location_service: AsyncMock
):
"""Test that the endpoint returns multiple address suggestions successfully"""
mock_results = [
Expand All @@ -69,67 +67,53 @@ async def test_autocomplete_success(
]
mock_location_service.autocomplete_address.return_value = mock_results

async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as client:
response = await client.post(
"/api/locations/autocomplete", json={"address": "123 Main St"}
)
response = await admin_client.post(
"/api/locations/autocomplete", json={"address": "123 Main St"}
)

assert response.status_code == 200
data = response.json()
assert len(data) == 2
assert data[0]["formatted_address"] == "123 Main St, Chapel Hill, NC 27514, USA"
assert data[0]["place_id"] == "ChIJTest123"
mock_location_service.autocomplete_address.assert_called_once_with(
"123 Main St"
)
assert response.status_code == 200
data = response.json()
assert len(data) == 2
assert data[0]["formatted_address"] == "123 Main St, Chapel Hill, NC 27514, USA"
assert data[0]["place_id"] == "ChIJTest123"
mock_location_service.autocomplete_address.assert_called_once_with("123 Main St")


@pytest.mark.asyncio
async def test_autocomplete_empty_results(
override_dependencies: Any, mock_location_service: AsyncMock
admin_client: AsyncClient, mock_location_service: AsyncMock
):
"""Test that the endpoint returns an empty list when no addresses match"""
mock_location_service.autocomplete_address.return_value = []

async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as client:
response = await client.post(
"/api/locations/autocomplete",
json={"address": "nonexistentaddress12345xyz"},
)
response = await admin_client.post(
"/api/locations/autocomplete",
json={"address": "nonexistentaddress12345xyz"},
)

assert response.status_code == 200
assert response.json() == []
assert response.status_code == 200
assert response.json() == []


@pytest.mark.asyncio
async def test_autocomplete_missing_address(override_dependencies: Any):
async def test_autocomplete_missing_address(admin_client: AsyncClient):
"""Test that the endpoint returns 422 when address field is missing"""
async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as client:
response = await client.post("/api/locations/autocomplete", json={})
assert response.status_code == 422
response = await admin_client.post("/api/locations/autocomplete", json={})
assert response.status_code == 422


@pytest.mark.asyncio
async def test_autocomplete_empty_string(
override_dependencies: Any, mock_location_service: AsyncMock
admin_client: AsyncClient, mock_location_service: AsyncMock
):
"""Test that the endpoint handles empty string gracefully"""
mock_location_service.autocomplete_address.return_value = []

async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as client:
response = await client.post(
"/api/locations/autocomplete", json={"address": ""}
)
assert response.status_code == 200
assert response.json() == []
response = await admin_client.post(
"/api/locations/autocomplete", json={"address": ""}
)
assert response.status_code == 200
assert response.json() == []


@pytest.mark.asyncio
Expand All @@ -146,39 +130,33 @@ async def test_autocomplete_unauthenticated(override_dependencies_no_auth: Any):

@pytest.mark.asyncio
async def test_autocomplete_service_exception(
override_dependencies: Any, mock_location_service: AsyncMock
admin_client: AsyncClient, mock_location_service: AsyncMock
):
"""Test that service exceptions are handled correctly"""
mock_location_service.autocomplete_address.side_effect = Exception(
"Service temporarily unavailable"
)

async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as client:
response = await client.post(
"/api/locations/autocomplete",
json={"address": "123 Test St"},
)
response = await admin_client.post(
"/api/locations/autocomplete",
json={"address": "123 Test St"},
)

assert response.status_code == 500
assert response.status_code == 500


@pytest.mark.asyncio
async def test_autocomplete_value_error(
override_dependencies: Any, mock_location_service: AsyncMock
admin_client: AsyncClient, mock_location_service: AsyncMock
):
"""Test that ValueError from API is handled correctly"""
mock_location_service.autocomplete_address.side_effect = ValueError(
"Invalid API key provided"
)

async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test"
) as client:
response = await client.post(
"/api/locations/autocomplete",
json={"address": "123 Test St"},
)
response = await admin_client.post(
"/api/locations/autocomplete",
json={"address": "123 Test St"},
)

assert response.status_code == 400
assert response.status_code == 400
6 changes: 4 additions & 2 deletions frontend/.env.template
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
NEXT_PUBLIC_API_BASE_URL=http://localhost:4000
NEXTAUTH_SECRET=REPLACE_ME
NEXT_PUBLIC_API_BASE_URL=http://localhost:8000/api
NEXT_PUBLIC_GOOGLE_MAPS_API_KEY=REPLACE_ME
NEXT_PUBLIC_GOOGLE_MAP_ID=REPLACE_ME
NEXTAUTH_SECRET=REPLACE_ME
22 changes: 21 additions & 1 deletion frontend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
"lint": "eslint"
},
"dependencies": {
"@vis.gl/react-google-maps": "^1.6.1",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-checkbox": "^1.3.3",
"@radix-ui/react-dropdown-menu": "^2.1.16",
Expand Down
2 changes: 1 addition & 1 deletion frontend/src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export default function RootLayout({
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.variable} antialiased`}
className={`${geistSans.variable} ${geistMono.variable} antialiased bg-white`}
>
<Providers>{children}</Providers>
</body>
Expand Down
23 changes: 18 additions & 5 deletions frontend/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@

export default function Home() {
return (
<div className="font-sans min-h-screen flex flex-col items-center justify-center p-8 sm:p-20 max-w-2xl mx-auto">
<h1 className="text-3xl font-bold mb-6 text-center">Party Registration System</h1>
<p className="mb-8 text-lg text-center">A CS+SG project for the Office of Off-Campus Student Life at UNC.</p>
<h2 className="text-xl font-semibold mb-4 text-center">About the Office of Off-Campus Student Life</h2>
<h1 className="text-3xl font-bold mb-6 text-center">
Party Registration System
</h1>
<p className="mb-8 text-lg text-center">
A CS+SG project for the Office of Off-Campus Student Life at UNC.
</p>
<h2 className="text-xl font-semibold mb-4 text-center">
About the Office of Off-Campus Student Life
</h2>
<p className="text-base text-center">
The Office of Off-Campus Student Life serves as a vital resource for students living off-campus, providing support, programs, and services to enhance the off-campus living experience. This office works to connect off-campus students with campus resources, facilitate community building, and ensure student safety and well-being in off-campus environments. Through various initiatives and programs, the office aims to bridge the gap between on-campus and off-campus student experiences, fostering a sense of belonging and engagement for all students regardless of their housing situation.
The Office of Off-Campus Student Life serves as a vital resource for
students living off-campus, providing support, programs, and services to
enhance the off-campus living experience. This office works to connect
off-campus students with campus resources, facilitate community
building, and ensure student safety and well-being in off-campus
environments. Through various initiatives and programs, the office aims
to bridge the gap between on-campus and off-campus student experiences,
fostering a sense of belonging and engagement for all students
regardless of their housing situation.
</p>
</div>
);
Expand Down
Loading