Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
5a0d435
Create schedules model
janealsh Oct 26, 2024
c8907d9
add dependencies
mmiqball Nov 3, 2024
05ac366
add pdyantic user models
mmiqball Nov 3, 2024
cf4cff6
add Firebase authentication initialization
mmiqball Nov 3, 2024
287d434
implement user creation endpoint
mmiqball Nov 3, 2024
5f8fb34
add user creation unit test
mmiqball Nov 3, 2024
b44baf9
separate user service initialization in user routes
mmiqball Nov 3, 2024
a2bc89a
simplify firebase initialization
mmiqball Nov 8, 2024
50c554e
load env before code executes
mmiqball Nov 8, 2024
a1abe9b
construct firebase service account key path from pwd
mmiqball Nov 8, 2024
637db5c
Create schedules model
janealsh Oct 26, 2024
7caddb5
Merge branch 'janealsh/LLSC-24-schedules-model' of https://github.com…
sunbagel Nov 12, 2024
5ad9a78
create TimeBlock model
sunbagel Nov 12, 2024
6a72e13
finalized schedule model
janealsh Nov 12, 2024
e52dad2
Finalize schedules model
janealsh Nov 12, 2024
c925fff
set up boilerplate code for schedules
sunbagel Nov 14, 2024
f2e74a0
create outline for schedules and schemas
sunbagel Nov 22, 2024
55d8848
update schedule model
sunbagel Nov 22, 2024
789ffc8
add create_schedule and update schemas
sunbagel Nov 22, 2024
37091d0
update Schedule and TimeBlock models to include int id
sunbagel Nov 22, 2024
4b4bd41
add alembic migration for new models
sunbagel Nov 22, 2024
4bf5014
add schedule_states initialization in alembic
sunbagel Nov 22, 2024
a845a46
add create_schedule endpoint
sunbagel Nov 24, 2024
d353aea
testing schedule endpoint
sunbagel Nov 26, 2024
c43780d
update pdm lock
sunbagel Dec 7, 2024
8c200f2
update Schedule schema
sunbagel Dec 8, 2024
c628efb
update ScheduleInDB pydantic schemas
sunbagel Dec 12, 2024
488a556
Merge branch 'main' into merging-branch
sunbagel Dec 19, 2024
c6d711c
Fix scheduleservice paths
sunbagel Dec 19, 2024
e669131
update schedule_status
sunbagel Feb 5, 2025
96a2e24
address comments
sunbagel Feb 5, 2025
b658eb9
remove schedule service interface
sunbagel Feb 12, 2025
8ccb174
update revision name
sunbagel Feb 12, 2025
76f8365
Fixing Nits (#24)
emilyniee Feb 12, 2025
dc656e2
Merge branch 'main' into alex-branch
emilyniee Mar 11, 2025
8670ccd
remove create_time_block
sunbagel Feb 24, 2025
e0284c6
update ScheduleInDB to ScheduleEntity
sunbagel Feb 24, 2025
9f27772
create new schemas
sunbagel Mar 11, 2025
7933a6d
Refactoring scheduling schema
sunbagel Mar 11, 2025
6995730
merge origin
sunbagel Mar 12, 2025
9423e3a
add matches model (#27)
RohanNankani Mar 12, 2025
551d6bd
update match schema
sunbagel Mar 12, 2025
7499b20
fix migration
sunbagel Mar 18, 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
8 changes: 4 additions & 4 deletions backend/alembic.ini
Original file line number Diff line number Diff line change
Expand Up @@ -77,10 +77,10 @@ version_path_separator = os # Use os.pathsep. Default configuration used for ne
# black.options = -l 79 REVISION_SCRIPT_FILENAME

# lint with attempts to fix using "ruff" - use the exec runner, execute a binary
hooks = ruff
ruff.type = exec
ruff.executable = %(here)s/.venv/bin/ruff
ruff.options = check --fix REVISION_SCRIPT_FILENAME
; hooks = ruff
; ruff.type = exec
; ruff.executable = %(here)s/.venv/bin/ruff
; ruff.options = check --fix REVISION_SCRIPT_FILENAME

# Logging configuration
# Every time you want to define a new sub-logger, you need to add it to loggers or it won't show up.
Expand Down
13 changes: 13 additions & 0 deletions backend/app/models/AvailableTime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from sqlalchemy import Column, ForeignKey, Table

from .Base import Base

# AvailableTimes as a pure association table
# only exists to establish a relationship between Users and Time Blocks
# a User has an Availability which is composed of many time blocks
available_times = Table(
"available_times",
Base.metadata,
Column("time_block_id", ForeignKey("time_blocks.id"), primary_key=True),
Column("user_id", ForeignKey("users.id"), primary_key=True),
)
45 changes: 45 additions & 0 deletions backend/app/models/Match.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import enum

from sqlalchemy import Column, DateTime, ForeignKey, Integer
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from sqlalchemy.sql import func

from .Base import Base


class Match(Base):
__tablename__ = "matches"

id = Column(Integer, primary_key=True)

participant_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)
volunteer_id = Column(UUID(as_uuid=True), ForeignKey("users.id"), nullable=False)

# the chosen time block
chosen_time_block_id = Column(Integer, ForeignKey("time_blocks.id"), nullable=True)

match_status_id = Column(
Integer, ForeignKey("match_status.id"), nullable=False, default=1
)

created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(
DateTime(timezone=True), server_default=func.now(), onupdate=func.now()
)

match_status = relationship("MatchStatus")

participant = relationship(
"User", foreign_keys=[participant_id], back_populates="matches"
)
volunteer = relationship(
"User", foreign_keys=[volunteer_id], back_populates="matches"
)

confirmed_time = relationship(
"TimeBlock", back_populates="confirmed_match", uselist=False
)
suggested_time_blocks = relationship(
"TimeBlock", back_populates="suggested_matches"
)
9 changes: 9 additions & 0 deletions backend/app/models/MatchStatus.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
from sqlalchemy import Column, Integer, String

from .Base import Base


class MatchStatus(Base):
__tablename__ = "match_status"
id = Column(Integer, primary_key=True)
name = Column(String(80), nullable=False)
13 changes: 13 additions & 0 deletions backend/app/models/SuggestedTime.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from sqlalchemy import Column, ForeignKey, Table

from .Base import Base

# SuggestedTimes as a pure association table
# only exists to establish a relationship between Matches and Time Blocks (many to many)
suggested_times = Table(
"suggested_times",
Base.metadata,
# composite key of match and time block
Column("match_id", ForeignKey("matches.id"), primary_key=True),
Column("time_block_id", ForeignKey("time_blocks.id"), primary_key=True),
)
25 changes: 25 additions & 0 deletions backend/app/models/TimeBlock.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from sqlalchemy import Column, DateTime, Integer
from sqlalchemy.orm import relationship

from .Base import Base


class TimeBlock(Base):
__tablename__ = "time_blocks"
id = Column(Integer, primary_key=True)
start_time = Column(DateTime)

# if a match has been confirmed on this time block, this is non null
confirmed_match = relationship(
"Match", back_populates="confirmed_time", uselist=False
)

# suggested matches
suggested_matches = relationship(
"Match", secondary="suggested_times", back_populates="suggested_time_blocks"
)

# the availability that the timeblock is a part of for a given user
users = relationship(
"User", secondary="available_times", back_populates="availability"
)
9 changes: 9 additions & 0 deletions backend/app/models/User.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,12 @@ class User(Base):
auth_id = Column(String, nullable=False)

role = relationship("Role")

# time blocks in an availability for a user
availability = relationship(
"TimeBlock", secondary="available_times", back_populates="users"
)

participant_matches = relationship("Match", back_populates="participant")

volunteer_matches = relationship("Match", back_populates="volunteer")
18 changes: 17 additions & 1 deletion backend/app/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,30 @@

from app.utilities.constants import LOGGER_NAME

from .AvailableTime import available_times

# Make sure all models are here to reflect all current models
# when autogenerating new migration
from .Base import Base
from .Match import Match
from .MatchStatus import MatchStatus
from .Role import Role
from .SuggestedTime import suggested_times
from .TimeBlock import TimeBlock
from .User import User

# Used to avoid import errors for the models
__all__ = ["Base", "User", "Role"]
__all__ = [
"Base",
"User",
"Role",
"TimeBlock",
"Match",
"MatchStatus",
"User",
"available_times",
"suggested_times",
]

log = logging.getLogger(LOGGER_NAME("models"))

Expand Down
30 changes: 30 additions & 0 deletions backend/app/routes/schedule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.orm import Session

from app.schemas.schedule import ScheduleCreateRequest, ScheduleEntity
from app.services.implementations.schedule_service import ScheduleService
from app.utilities.db_utils import get_db

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


def get_schedule_service(db: Session = Depends(get_db)):
return ScheduleService(db)


@router.post("/", response_model=ScheduleEntity)
async def create_schedule(
schedule: ScheduleCreateRequest,
schedule_service: ScheduleService = Depends(get_schedule_service),
):
try:
created_schedule = await schedule_service.create_schedule(schedule)
return created_schedule
except HTTPException as http_ex:
raise http_ex
except Exception as e:
print(e)
raise HTTPException(status_code=500, detail=str(e))
58 changes: 58 additions & 0 deletions backend/app/schemas/schedule.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
from datetime import datetime, timedelta
from enum import Enum
from typing import List, Optional
from uuid import UUID

from pydantic import BaseModel, ConfigDict

from app.schemas.time_block import TimeBlockBase, TimeBlockFull, TimeBlockId


class ScheduleStatus(str, Enum):
PENDING_VOLUNTEER = "PENDING_VOLUNTEER_RESPONSE"
Copy link
Collaborator

Choose a reason for hiding this comment

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

nit: keep these in the same order as in the DB, just in case someone uses this as a reference for the ordering.

PENDING_PARTICIPANT = "PENDING_PARTICIPANT_RESPONSE"
SCHEDULED = "SCHEDULED"
COMPLETED = "COMPLETED"

@classmethod
def to_schedule_status_id(cls, state: "ScheduleStatus") -> int:
status_map = {
cls.PENDING_VOLUNTEER: 1,
cls.PENDING_PARTICIPANT: 2,
cls.SCHEDULED: 3,
cls.COMPLETED: 4,
}

return status_map[state]


class ScheduleBase(BaseModel):
scheduled_time: Optional[datetime]
duration: Optional[timedelta]
status_id: int


class ScheduleEntity(ScheduleBase):
id: int

model_config = ConfigDict(from_attributes=True)


# Provides both Schedule data and full TimeBlock data
class ScheduleGetResponse(ScheduleEntity):
time_blocks: List[TimeBlockFull]


# List of Start and End times to Create a Schedule with
class ScheduleCreateRequest(BaseModel):
time_blocks: List[TimeBlockBase]


class ScheduleUpdateRequest(BaseModel):
schedule_id: UUID
time_blocks: List[TimeBlockBase]


class ScheduleDeleteRequest(BaseModel):
schedule_id: UUID
time_blocks: List[TimeBlockId]
29 changes: 29 additions & 0 deletions backend/app/schemas/time_block.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from datetime import datetime
from uuid import UUID

from pydantic import BaseModel


class TimeBlockBase(BaseModel):
start_time: datetime
end_time: datetime


class TimeBlockId(BaseModel):
id: UUID


class TimeBlockFull(TimeBlockBase, TimeBlockId):
"""
Combines TimeBlockBase and TimeBlockId.
Represents a full time block with an ID and time range.
"""

pass


class TimeBlockEntity(BaseModel):
id: UUID
schedule_id: int
start_time: datetime
end_time: datetime
7 changes: 4 additions & 3 deletions backend/app/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .routes import send_email, user
from .utilities.constants import LOGGER_NAME
from .utilities.firebase_init import initialize_firebase
from .utilities.ses.ses_init import ensure_ses_templates
# from .utilities.ses.ses_init import ensure_ses_templates

load_dotenv()

Expand All @@ -19,7 +19,7 @@
@asynccontextmanager
async def lifespan(_: FastAPI):
log.info("Starting up...")
ensure_ses_templates()
# ensure_ses_templates()
models.run_migrations()
initialize_firebase()
yield
Expand All @@ -30,7 +30,8 @@ async def lifespan(_: FastAPI):
# running-alembic-migrations-on-fastapi-startup
app = FastAPI(lifespan=lifespan)
app.include_router(user.router)
app.include_router(send_email.router)
# app.include_router(schedule.router)
# app.include_router(send_email.router)


@app.get("/")
Expand Down
Loading
Loading