Skip to content
Merged
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
4 changes: 4 additions & 0 deletions backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@
from app.routers import (
budget,
carousel,
committees,
districts,
events,
finance,
health,
leadership,
legislation,
news,
pages,
Expand All @@ -36,6 +38,8 @@
app.include_router(health.router)
app.include_router(news.router)
app.include_router(senators.router)
app.include_router(leadership.router)
app.include_router(committees.router)
app.include_router(carousel.router)
app.include_router(districts.router)
app.include_router(staff.router)
Expand Down
104 changes: 104 additions & 0 deletions backend/app/routers/committees.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session, selectinload

from app.database import get_db
from app.models import Committee, CommitteeMembership
from app.schemas import CommitteeDTO

router = APIRouter(prefix="/api/committees", tags=["committees"])

@router.get("/", response_model=list[CommitteeDTO])
def get_committees(db: Session = Depends(get_db)):
committees = (
db.query(Committee)
.options(
selectinload(Committee.memberships)
.selectinload(CommitteeMembership.senator)
)
.filter(Committee.is_active)
.order_by(Committee.name)
.all()
)

result = []
for committee in committees:
members = []
for membership in committee.memberships:
senator = membership.senator
committees_list = [
{
"committee_id": membership.committee.id,
"committee_name": membership.committee.name,
"role": membership.role
}
]
members.append({
"id": senator.id,
"first_name": senator.first_name,
"last_name": senator.last_name,
"email": senator.email,
"headshot_url": senator.headshot_url,
"district_id": senator.district,
"is_active": senator.is_active,
"session_number": senator.session_number,
"committees": committees_list
})
result.append({
"id": committee.id,
"name": committee.name,
"description": committee.description,
"chair_name": committee.chair_name,
"chair_email": committee.chair_email,
"is_active": committee.is_active,
"members": members
})

return result

@router.get("/{id}", response_model=CommitteeDTO)
def get_committee(id: int, db: Session = Depends(get_db)):
committee = (
db.query(Committee)
.options(
selectinload(Committee.memberships)
.selectinload(CommitteeMembership.senator)
)
.filter(Committee.id == id)
.first()
)

if not committee:
raise HTTPException(status_code=404, detail="Committee not found")

members = []

for membership in committee.memberships:
senator = membership.senator

members.append({
"id": senator.id,
"first_name": senator.first_name,
"last_name": senator.last_name,
"email": senator.email,
"headshot_url": senator.headshot_url,
"district_id": senator.district,
"is_active": senator.is_active,
"session_number": senator.session_number,
"committees": [
{
"committee_id": membership.committee.id,
"committee_name": membership.committee.name,
"role": membership.role
}
]
})

return {
"id": committee.id,
"name": committee.name,
"description": committee.description,
"chair_name": committee.chair_name,
"chair_email": committee.chair_email,
"members": members,
"is_active": committee.is_active
}
48 changes: 48 additions & 0 deletions backend/app/routers/leadership.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import func
from sqlalchemy.orm import Session

from app.database import get_db
from app.models import Leadership
from app.schemas import LeadershipDTO

router = APIRouter(prefix="/api/leadership", tags=["leadership"])


def _current_session(db: Session) -> int:
"""Return the highest session_number in the leadership table."""
result = db.query(func.max(Leadership.session_number)).scalar()
return result or 1

@router.get("/", response_model=list[LeadershipDTO])
def get_leadership(session_number: int | None = None, db: Session = Depends(get_db)):

target_session = session_number if session_number is not None else _current_session(db)
query = db.query(Leadership).filter(Leadership.session_number == target_session)

leadership = query.order_by(Leadership.title).all()

# dynamically add is_current based on is_active
for leader in leadership:
leader.is_current = leader.is_active
leader.photo_url = leader.headshot_url

return leadership


@router.get("/{id}", response_model=LeadershipDTO)
def get_leadership_by_id(id: int, db: Session = Depends(get_db)):

leadership = (
db.query(Leadership)
.filter(Leadership.id == id)
.first()
)

if leadership is None:
raise HTTPException(status_code=404, detail="Leadership record not found")

leadership.is_current = leadership.is_active
leadership.photo_url = leadership.headshot_url

return leadership
8 changes: 3 additions & 5 deletions backend/app/routers/news.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@


def _news_to_dict(news: News) -> dict[str, Any]:
"""Convert a News ORM row to a dict compatible with PR #37's NewsDTO.
"""Convert a News ORM row to a dict compatible with NewsDTO.

PR #37's NewsDTO expects a field called ``admin`` (not ``author``) so we
NewsDTO expects a field called ``admin`` (not ``author``) so we
remap the relationship here rather than in the model.
"""
return {
Expand All @@ -42,9 +42,7 @@ def _news_to_dict(news: News) -> dict[str, Any]:
"image_url": news.image_url,
"date_published": news.date_published,
"date_last_edited": news.date_last_edited,
# PR #37's NewsDTO has a computed ``author_name`` field; provide it here
# so model_validate() can pick it up once the schema is available.
"author_name": f"{news.author.first_name} {news.author.last_name}" if news.author else "Unknown",
"admin": news.author,
}


Expand Down
15 changes: 15 additions & 0 deletions backend/app/schemas/AccountDTO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from typing import Literal

from pydantic import BaseModel, ConfigDict


class AccountDTO(BaseModel):
id: int
email: str
pid: str
first_name: str
last_name: str
role: Literal["admin", "staff"]

model_config = ConfigDict(from_attributes=True)

19 changes: 19 additions & 0 deletions backend/app/schemas/BudgetDataDTO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from __future__ import annotations

from typing import List, Optional

from pydantic import BaseModel, ConfigDict


class BudgetDataDTO(BaseModel):
id: int
fiscal_year: str
category: str
amount: float
description: Optional[str] = None
children: List["BudgetDataDTO"] = []

model_config = ConfigDict(from_attributes=True)


BudgetDataDTO.model_rebuild()
16 changes: 16 additions & 0 deletions backend/app/schemas/CalendarEventDTO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from datetime import datetime
from typing import Optional

from pydantic import BaseModel, ConfigDict


class CalendarEventDTO(BaseModel):
id: int
title: str
description: str
start_datetime: datetime
end_datetime: datetime
location: Optional[str] = None
event_type: str

model_config = ConfigDict(from_attributes=True)
13 changes: 13 additions & 0 deletions backend/app/schemas/CarouselSlideDTO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from pydantic import BaseModel, ConfigDict


class CarouselSlideDTO(BaseModel):
id: int
image_url: str
overlay_text: str
link_url: str
display_order: int
is_active: bool

model_config = ConfigDict(from_attributes=True)

11 changes: 11 additions & 0 deletions backend/app/schemas/CommitteeAssignmentDTO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from __future__ import annotations

from pydantic import BaseModel, ConfigDict


class CommitteeAssignmentDTO(BaseModel):
committee_id: int
committee_name: str
role: str

model_config = ConfigDict(from_attributes=True)
19 changes: 19 additions & 0 deletions backend/app/schemas/CommitteeDTO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from __future__ import annotations

from typing import List

from pydantic import BaseModel, ConfigDict

from .SenatorDTO import SenatorDTO


class CommitteeDTO(BaseModel):
id: int
name: str
description: str
chair_name: str
chair_email: str
members: List[SenatorDTO]
is_active: bool

model_config = ConfigDict(from_attributes=True)
14 changes: 14 additions & 0 deletions backend/app/schemas/DistrictDTO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
from typing import List, Optional

from pydantic import BaseModel, ConfigDict

from .SenatorDTO import SenatorDTO


class DistrictDTO(BaseModel):
id: int
district_name: str
description: Optional[str] = None
senator: Optional[List[SenatorDTO]] = None

model_config = ConfigDict(from_attributes=True)
15 changes: 15 additions & 0 deletions backend/app/schemas/FinanceHearingConfigDTO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from datetime import date
from typing import List, Optional

from pydantic import BaseModel, ConfigDict

from .FinanceHearingDateDTO import FinanceHearingDateDTO


class FinanceHearingConfigDTO(BaseModel):
is_active: bool
season_start: Optional[date] = None
season_end: Optional[date] = None
dates: List[FinanceHearingDateDTO]

model_config = ConfigDict(from_attributes=True)
15 changes: 15 additions & 0 deletions backend/app/schemas/FinanceHearingDateDTO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from datetime import date, time
from typing import Optional

from pydantic import BaseModel, ConfigDict


class FinanceHearingDateDTO(BaseModel):
id: int
hearing_date: date
hearing_time: time
location: Optional[str] = None
description: Optional[str] = None
is_full: bool

model_config = ConfigDict(from_attributes=True)
16 changes: 16 additions & 0 deletions backend/app/schemas/LeadershipDTO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import Optional

from pydantic import BaseModel, ConfigDict


class LeadershipDTO(BaseModel):
id: int
title: str
first_name: str
last_name: str
email: str
photo_url: Optional[str] = None
session_number: int
is_current: bool

model_config = ConfigDict(from_attributes=True)
12 changes: 12 additions & 0 deletions backend/app/schemas/LegislationActionDTO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
from datetime import date

from pydantic import BaseModel, ConfigDict


class LegislationActionDTO(BaseModel):
id: int
action_date: date
description: str
action_type: str

model_config = ConfigDict(from_attributes=True)
23 changes: 23 additions & 0 deletions backend/app/schemas/LegislationDTO.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from datetime import date
from typing import List

from pydantic import BaseModel, ConfigDict

from .LegislationActionDTO import LegislationActionDTO


class LegislationDTO(BaseModel):
id: int
title: str
bill_number: str
session_number: int
sponsor_name: str
summary: str
full_text: str
status: str
type: str
date_introduced: date
date_last_action: date
actions: List[LegislationActionDTO]

model_config = ConfigDict(from_attributes=True)
Loading
Loading