Starting to flexh out the sql functions for the backend.

Signed-off-by: Cliff Hill <xlorep@darkhelm.org>
This commit is contained in:
2025-08-22 16:19:01 -04:00
parent 6ae0abfb48
commit a87f4013f9
5 changed files with 182 additions and 7 deletions

View File

@@ -30,7 +30,7 @@ class User(Base):
id: Mapped[int] = mapped_column(primary_key=True, init=False, autoincrement=True)
email: Mapped[str] = mapped_column(index=True, unique=True)
name: Mapped[str | None] = mapped_column(default=None)
name: Mapped[str]
invitees: Mapped[list["Invitee"]] = relationship(
back_populates="user", cascade="all, delete-orphan", init=False
@@ -44,9 +44,9 @@ class Room(Base):
id: Mapped[int] = mapped_column(primary_key=True, init=False, autoincrement=True)
name: Mapped[str] = mapped_column(index=True, unique=True)
location: Mapped[str | None] = mapped_column(default=None)
equipment: Mapped[str | None] = mapped_column(default=None)
capacity: Mapped[int] = mapped_column(default=1)
location: Mapped[str]
equipment: Mapped[str]
capacity: Mapped[int]
bookings: Mapped[list["Booking"]] = relationship(
back_populates="room", cascade="all, delete-orphan", init=False

167
backend/src/backend/sql.py Normal file
View File

@@ -0,0 +1,167 @@
"""SQL functions for the Numinar coding project backend."""
from datetime import datetime
from typing import TypedDict
from typing import Unpack
from sqlalchemy import delete
from sqlalchemy import select
from sqlalchemy import update
from sqlalchemy.ext.asyncio import AsyncSession
from backend.db import sessionize
from backend.models import Booking
from backend.models import Invitee
from backend.models import Room
from backend.models import User
@sessionize
async def get_users(session: AsyncSession):
"""Retrieve all users from the database."""
result = await session.execute(select(User))
return result.scalars().all()
@sessionize
async def get_user_by_email(session: AsyncSession, email: str):
"""Retrieve a user by their email address."""
result = await session.execute(select(User).where(User.email == email))
return result.scalar_one_or_none()
@sessionize
async def get_rooms(session: AsyncSession):
"""Retrieve all rooms from the database."""
result = await session.execute(select(Room))
return result.scalars().all()
@sessionize
async def get_room(session: AsyncSession, room_id: int):
"""Retrieve a room by its ID."""
result = await session.execute(select(Room).where(Room.id == room_id))
return result.scalar_one_or_none()
@sessionize
async def new_room(session: AsyncSession, room: Room):
"""Create a new room in the database."""
session.add(room)
await session.commit()
return room
class RoomParams(TypedDict):
"""Parameters for updating a room."""
name: str
location: str | None
equipment: str | None
capacity: int | None
@sessionize
async def update_room(
session: AsyncSession, room_id: int, **kwargs: Unpack[RoomParams]
):
"""Update an existing room."""
stmt = update(Room).where(Room.id == room_id).values(**kwargs)
await session.execute(stmt)
await session.commit()
return await get_room(session, room_id)
@sessionize
async def delete_room(session: AsyncSession, room_id: int):
"""Delete a room from the database."""
stmt = delete(Room).where(Room.id == room_id)
await session.execute(stmt)
await session.commit()
@sessionize
async def get_bookings_for_room(session: AsyncSession, room_id: int):
"""Retrieve all bookings for a specific room."""
result = await session.execute(select(Booking).where(Booking.room_id == room_id))
return result.scalars().all()
@sessionize
async def get_booking(session: AsyncSession, booking_id: int):
"""Retrieve a booking by its ID."""
result = await session.execute(select(Booking).where(Booking.id == booking_id))
return result.scalar_one_or_none()
@sessionize
async def new_booking(session: AsyncSession, booking: Booking):
"""Create a new booking in the database."""
session.add(booking)
await session.commit()
return booking
class BookingParams(TypedDict):
"""Parameters for updating a booking."""
room_id: int
start_time: datetime
end_time: datetime
@sessionize
async def update_booking(
session: AsyncSession, booking_id: int, **kwargs: Unpack[BookingParams]
):
"""Update an existing booking."""
stmt = update(Booking).where(Booking.id == booking_id).values(**kwargs)
await session.execute(stmt)
await session.commit()
return await get_booking(session, booking_id)
@sessionize
async def delete_booking(session: AsyncSession, booking_id: int):
"""Delete a booking from the database."""
stmt = delete(Booking).where(Booking.id == booking_id)
await session.execute(stmt)
await session.commit()
@sessionize
async def get_invitees_for_booking(session: AsyncSession, booking_id: int):
"""Retrieve all invitees for a specific booking."""
result = await session.execute(
select(Invitee.user).where(Invitee.booking_id == booking_id)
)
return result.scalars().all()
@sessionize
async def add_invitee_to_booking(session: AsyncSession, booking_id: int, email: str):
"""Add an invitee to a booking."""
user = await get_user_by_email(session, email)
if not user:
raise ValueError(f"User with email {email} does not exist.")
invitee = Invitee(booking_id=booking_id, user_id=user.id)
session.add(invitee)
await session.commit()
return invitee
@sessionize
async def remove_invitee_from_booking(
session: AsyncSession, booking_id: int, email: str
):
"""Remove an invitee from a booking."""
user = await get_user_by_email(session, email)
if not user:
raise ValueError(f"User with email {email} does not exist.")
stmt = delete(Invitee).where(
Invitee.booking_id == booking_id, Invitee.user_id == user.id
)
await session.execute(stmt)
await session.commit()

View File

@@ -1,4 +1,8 @@
"""Test cases for the SQL module in the Numinar coding project backend."""
"""Test cases for the SQL module in the Numinar coding project backend.
This primarily focuses on testing the sessionize decorator to ensure it correctly
manages database sessions for asynchronous functions.i
"""
from typing import Any
from typing import AsyncGenerator

View File

@@ -1,3 +1,5 @@
# docker compose file for development environment
services:
backend:
extends:
@@ -8,7 +10,7 @@ services:
- ./backend/poetry.lock:/app/poetry.lock
- ./backend/pyproject.toml:/app/pyproject.toml
environment:
ENVIRONMENT: "development"
ENVIRONMENT: development
ports:
- "8000:8000"
command:

View File

@@ -1,3 +1,5 @@
# docker compose file for production environment
services:
backend:
build:
@@ -6,7 +8,7 @@ services:
expose:
- "8000:8000"
environment:
- ENVIRONMENT=production
ENVIRONMENT: production
depends_on:
- postgres
networks: