diff --git a/server/app/admin/contact.py b/server/app/admin/contact.py index 3963f1f..4433abc 100644 --- a/server/app/admin/contact.py +++ b/server/app/admin/contact.py @@ -2,10 +2,16 @@ import smtplib from email.mime.text import MIMEText from os import getenv -HOST = "grapefruitswebsite@gmail.com" +from app.constants import HOST def send_email(subject: str, body: str) -> None: + """ + Sends an email using the Gmail SMTP server. + + :param str subject: The subject of the email + :param str body: The body of the email + """ password = getenv("APP_PASSWORD") email = getenv("EMAIL") msg = MIMEText(body) diff --git a/server/app/admin/images.py b/server/app/admin/images.py index 336a69b..a8f9215 100644 --- a/server/app/admin/images.py +++ b/server/app/admin/images.py @@ -1,7 +1,6 @@ # Set your Cloudinary credentials # ============================== -from pprint import pprint from dotenv import load_dotenv @@ -22,10 +21,20 @@ uploader = cloudinary.uploader class CloudinaryException(Exception): + """ + Custom exception for Cloudinary errors. + """ + pass def delete_image(public_id: str) -> None: + """ + Deletes an image from the Cloudinary cloud. + + :param str public_id: The public ID of the image to delete + :raises CloudinaryException: If the image deletion fails + """ result = uploader.destroy(public_id) if result.get("result") != "ok": @@ -33,16 +42,25 @@ def delete_image(public_id: str) -> None: def get_image_data(public_id: str) -> dict: + """ + Retrieves the metadata for an image from the Cloudinary cloud. + + :param str public_id: The public ID of the image to retrieve + :return dict: The metadata for the image + """ data = cloudinary.api.resource(public_id) return data def get_image_url(public_id: str) -> str: + """ + Retrieves the URL for an image from the Cloudinary cloud. + + :param str public_id: The public ID of the image to retrieve + :raises CloudinaryException: If the image URL retrieval fails + :return str: The URL of the image + """ url = cloudinary.utils.cloudinary_url(public_id)[0] if url is None: raise CloudinaryException("Failed to get image URL") return url - - -if __name__ == "__main__": - image_id = "coco_copy_jywbxm" diff --git a/server/app/constants/__init__.py b/server/app/constants/__init__.py index 4042b25..bcccab0 100644 --- a/server/app/constants/__init__.py +++ b/server/app/constants/__init__.py @@ -7,3 +7,6 @@ EVENT_TABLE = "events" GROUP_TABLE = "group_table" MUSICIAN_TABLE = "musicians" USER_TABLE = "users" + +# contact form email +HOST = "grapefruitswebsite@gmail.com" diff --git a/server/app/controllers/base_controller.py b/server/app/controllers/base_controller.py index b21079b..696c051 100644 --- a/server/app/controllers/base_controller.py +++ b/server/app/controllers/base_controller.py @@ -17,21 +17,21 @@ class BaseController: """ def __init__(self) -> None: + """ + Initializes the BaseController with a BaseQueries object. + """ self.db: BaseQueries = None # type: ignore self.ALL_FILES = ALLOWED_FILES_TYPES self.MAX_FILE_SIZE = ONE_MB def verify_image(self, file: UploadFile) -> bytes: - """Verifies that the file is an image and is within the maximum file size. + """ + Verifies that the file is an image and is within the maximum file size. - Args: - file (UploadFile): The file to be verified - - Raises: - HTTPException: If the file is not an image or exceeds the maximum file size (status code 400) - - Returns: - bytes: The file contents as bytes + :param UploadFile file: The file to be verified + :raises HTTPException: If the file type is not allowed (status code 400) + :raises HTTPException: If the file size exceeds the maximum (status code 400) + :return bytes: The image file as bytes """ if file.content_type not in self.ALL_FILES: raise HTTPException( @@ -48,10 +48,10 @@ class BaseController: return image_file def log_error(self, e: Exception) -> None: - """Logs an error to a timestamped text file in the logs directory. + """ + Logs an error to a timestamped text file in the logs directory. - Args: - e (Exception): Any exception object + :param Exception e: The exception to be logged """ curr_dir = Path(__file__).parent log_dir = curr_dir / "logs" diff --git a/server/app/controllers/controller.py b/server/app/controllers/controller.py index 28af7d7..9cbd481 100644 --- a/server/app/controllers/controller.py +++ b/server/app/controllers/controller.py @@ -1,3 +1,5 @@ +from typing import Optional + from fastapi import HTTPException, UploadFile, status from fastapi.security import HTTPAuthorizationCredentials from icecream import ic @@ -22,19 +24,11 @@ from app.models.user import User class MainController: """ The main controller and entry point for all API requests. - All methods are either pass-throughs to the appropriate controller or - are used to coordinate multiple controllers. - - All methods are asynchronous to facilitate asynchronous calls from the Router layer. - - token-based authentication is handled here as needed per the nature of the data being accessed. - - Testing: pass mocked sub-controllers to the constructor. """ def __init__( self, - event_controller=event_controller, + event_controller: EventController = event_controller, musicians_controller=musicians_controller, user_controller=user_controller, group_controller=group_controller, @@ -47,9 +41,20 @@ class MainController: self.oauth_token = oauth_token async def get_musicians(self) -> list[Musician]: + """ + Retrieves all musicians and returns them as a list. + + :return list[Musician]: _description_ + """ return self.musician_controller.get_musicians() async def get_musician(self, musician_id: int) -> Musician: + """ + Retrieves a single musician by numeric ID. + + :param int musician_id: The ID of the musician to retrieve + :return Musician: The musician object for a response body + """ return self.musician_controller.get_musician(musician_id) async def update_musician( @@ -57,8 +62,18 @@ class MainController: musician: Musician, url_param_id: int, token: HTTPAuthorizationCredentials, - file: UploadFile | None = None, + file: Optional[UploadFile] = None, ) -> Musician: + """ + Updates a musician in the database and returns the updated musician object. + + :param Musician musician: The musician object to update + :param int url_param_id: The ID of the musician in the URL + :param HTTPAuthorizationCredentials token: The OAuth token + :param Optional[UploadFile] file: The new headshot file, defaults to None + :raises HTTPException: If the ID in the URL does not match the ID in the request body (status code 400) + :return Musician: The updated musician object which is suitable for a response body + """ if musician.id != url_param_id: raise HTTPException( @@ -74,14 +89,32 @@ class MainController: ) async def get_events(self) -> list[EventSeries]: + """ + Retrieves all event series and returns them as a list. + + :return list[EventSeries]: a list of EventSeries objects for a response body + """ return self.event_controller.get_all_series() async def get_event(self, series_id: int) -> EventSeries: + """ + Retrieves a single event series by numeric ID. + + :param int series_id: The ID of the event series to retrieve + :return EventSeries: The event series object for a response body + """ return self.event_controller.get_one_series_by_id(series_id) async def create_event( self, series: NewEventSeries, token: HTTPAuthorizationCredentials ) -> EventSeries: + """ + Creates a new event series and returns the created event series object. + + :param NewEventSeries series: The new event series object + :param HTTPAuthorizationCredentials token: The OAuth token + :return EventSeries: The newly created event series object which is suitable for a response body + """ _, sub = self.oauth_token.email_and_sub(token) self.user_controller.get_user_by_sub(sub) return self.event_controller.create_series(series) @@ -89,6 +122,14 @@ class MainController: async def add_series_poster( self, series_id: int, poster: UploadFile, token: HTTPAuthorizationCredentials ) -> EventSeries: + """ + Adds a poster to an event series and returns the updated event series object. + + :param int series_id: The ID of the event series to update + :param UploadFile poster: The image file to upload + :param HTTPAuthorizationCredentials token: The OAuth token + :return EventSeries: The updated event series object which is suitable for a response body + """ _, sub = self.oauth_token.email_and_sub(token) self.user_controller.get_user_by_sub(sub) return self.event_controller.add_series_poster(series_id, poster) @@ -96,6 +137,12 @@ class MainController: async def delete_series( self, series_id: int, token: HTTPAuthorizationCredentials ) -> None: + """ + Deletes an event series by numeric ID. + + :param int series_id: The ID of the event series to delete + :param HTTPAuthorizationCredentials token: The OAuth token + """ _, sub = self.oauth_token.email_and_sub(token) self.user_controller.get_user_by_sub(sub) self.event_controller.delete_series(series_id) @@ -103,35 +150,63 @@ class MainController: async def update_series( self, route_id: int, series: EventSeries, token: HTTPAuthorizationCredentials ) -> EventSeries: + """ + Updates an event series and returns the updated event series object. + + :param int route_id: The ID of the event series in the URL + :param EventSeries series: The updated event series object + :param HTTPAuthorizationCredentials token: The OAuth token + :return EventSeries: The updated event series object which is suitable for a response body + """ _, sub = self.oauth_token.email_and_sub(token) self.user_controller.get_user_by_sub(sub) return self.event_controller.update_series(route_id, series) async def get_users(self) -> list[User]: + """ + Retrieves all users and returns them as a list. + + :return list[User]: a list of User objects for a response body + """ return self.user_controller.get_users() async def get_user(self, user_id: int) -> User: + """ + Retrieves a single user by numeric ID. + + :param int user_id: The ID of the user to retrieve + :return User: The user object for a response body + """ return self.user_controller.get_user_by_id(user_id) async def create_user(self, token: HTTPAuthorizationCredentials) -> User: - """This method does NOT post a user to the database. - Instead, it retrieves the user's information from the OAuth token, - updates the user's sub in the db if needed, and returns the user object. + """ + Creates a new user and returns the created user object. + Does NOT post a user to the database (this is not supported). - Args: - token (HTTPAuthorizationCredentials): The OAuth token - - Returns: - User: The User object + :param HTTPAuthorizationCredentials token: The OAuth token + :return User: The newly created user object which is suitable for a response body """ return self.user_controller.create_user(token) async def get_group(self) -> Group: + """ + Retrieves the group object and returns it. + + :return Group: The group object for a response body + """ return self.group_controller.get_group() async def update_group_bio( self, bio: str, token: HTTPAuthorizationCredentials ) -> Group: + """ + Updates the group's bio and returns the updated group object. + + :param str bio: The new bio for the group + :param HTTPAuthorizationCredentials token: The OAuth token + :return Group: The updated group object which is suitable for a response body + """ _, sub = self.oauth_token.email_and_sub(token) self.user_controller.get_user_by_sub(sub) return self.group_controller.update_group_bio(bio) diff --git a/server/app/controllers/events.py b/server/app/controllers/events.py index 8976b8c..46b38a1 100644 --- a/server/app/controllers/events.py +++ b/server/app/controllers/events.py @@ -11,26 +11,26 @@ from app.models.event import Event, EventSeries, NewEventSeries class EventController(BaseController): """ - Handles all event-related operations and serves as an intermediate controller between - the main controller and the model layer. + Handles all event-related operations. Inherits from BaseController, which provides logging and other generic methods. - - Testing: pass a mocked EventQueries object to the constructor. """ - def __init__(self, event_queries=event_queries) -> None: + def __init__(self, event_queries: EventQueries = event_queries) -> None: + """ + Initializes the EventController with an EventQueries object. + + :param EventQueries event_queries: object for querying event data, defaults to event_queries + """ super().__init__() self.db: EventQueries = event_queries def _all_series(self, data_rows: list[dict]) -> dict[str, EventSeries]: - """Creates and returns a dictionary of EventSeries objects from a list of sql rows (as dicts). - Should only be used internally. + """ + Builds a dictionary of EventSeries objects from a list of dictionaries. + Must only be used internally. - Args: - data_rows (list[dict]): List of dicts, each representing a row from the database. `event_id` may be null - - Returns: - dict[str, EventSeries]: A dictionary of EventSeries objects, keyed by series name + :param list[dict] data_rows: The list of sql rows as dictionaries + :return dict[str, EventSeries]: A dictionary of EventSeries objects keyed by series name """ all_series: dict[str, EventSeries] = {} @@ -44,13 +44,11 @@ class EventController(BaseController): return all_series def get_all_series(self) -> list[EventSeries]: - """Retrieves all EventSeries objects from the database and returns them as a list. + """ + Retrieves all EventSeries objects and returns them as a list. - Raises: - HTTPException: If any error occurs during the retrieval process (status code 500) - - Returns: - list[EventSeries]: A list of EventSeries objects which are suitable for a response body + :raises HTTPException: If any error occurs (status code 500) + :return list[EventSeries]: A list of EventSeries objects suitable for a response body """ series_data = self.db.select_all_series() @@ -64,17 +62,13 @@ class EventController(BaseController): ) def get_one_series_by_id(self, series_id: int) -> EventSeries: - """Builds and returns a single EventSeries object by its numeric ID. + """ + Retrieves a single EventSeries object by numeric ID. - Args: - series_id (int): The numeric id of the series to retrieve - - Raises: - HTTPException: If the series is not found (status code 404) - HTTPException: If an error occurs (status code 500) - - Returns: - EventSeries: A single EventSeries object + :param int series_id: The ID of the series to retrieve + :raises HTTPException: If the series is not found (status code 404) + :raises HTTPException: If any error occurs during the instantiation process (status code 500) + :return EventSeries: The EventSeries object which is suitable for a response body """ if not (rows := self.db.select_one_by_id(series_id)): raise HTTPException( @@ -91,16 +85,12 @@ class EventController(BaseController): ) def create_series(self, series: NewEventSeries) -> EventSeries: - """Takes a NewEventSeries object and passes it to the database layer for insertion. + """ + Passes a new EventSeries object to the database for creation and returns the created object. - Args: - series (NewEventSeries): A NewEventSeries object which does not yet have an ID - - Raises: - HTTPException: If the series name already exists (status code 400) - - Returns: - EventSeries: The newly created EventSeries object with an ID + :param NewEventSeries series: The new EventSeries object to create + :raises HTTPException: If the series name already exists (status code 400) + :return EventSeries: The created EventSeries object which is suitable for a response body """ try: inserted_id = self.db.insert_one_series(series) @@ -114,14 +104,12 @@ class EventController(BaseController): ) def add_series_poster(self, series_id: int, poster: UploadFile) -> EventSeries: - """Adds (or updates) a poster image to a series. + """ + Updates the poster image for an EventSeries object and returns the updated object. - Args: - series_id (int): The numeric ID of the series to update - poster (UploadFile): The image file to upload - - Returns: - EventSeries: The updated EventSeries object + :param int series_id: The numeric ID of the series + :param UploadFile poster: The new poster image file + :return EventSeries: The updated EventSeries object with the new poster image """ series = self.get_one_series_by_id(series_id) series.poster_id = self._upload_poster(poster) @@ -129,17 +117,12 @@ class EventController(BaseController): return self.get_one_series_by_id(series.series_id) def _upload_poster(self, poster: UploadFile) -> str: - """Uploads a poster image to Cloudinary and returns the public ID for storage in the database. - Should only be used internally. + """ + Uploads an image file to the cloud and returns the public ID. - Args: - poster (UploadFile): The image file to upload - - Raises: - HTTPException: If an error occurs during the upload process (status code 500) - - Returns: - str: The public ID of the uploaded image + :param UploadFile poster: The image file to upload + :raises HTTPException: If any error occurs during the upload process (status code 500) + :return str: The public ID of the uploaded image """ image_file = self.verify_image(poster) try: @@ -152,27 +135,23 @@ class EventController(BaseController): ) def delete_series(self, id: int) -> None: - """Ensures an EventSeries object exists and then deletes it from the database. + """ + Deletes an EventSeries object from the database. - Args: - id (int): The numeric ID of the series to delete + :param int id: The numeric ID of the series to delete """ series = self.get_one_series_by_id(id) self.db.delete_one_series(series) def update_series(self, route_id: int, series: EventSeries) -> EventSeries: - """Updates an existing EventSeries object in the database. + """ + Updates an EventSeries object in the database and returns the updated object. - Args: - route_id (int): The numeric ID of the series in the URL - series (EventSeries): The updated EventSeries object - - Raises: - HTTPException: if the ID in the URL does not match the ID in the request body (status code 400) - HTTPException: if the poster ID is updated directly (status code 400) - - Returns: - EventSeries: The updated EventSeries object with updated info + :param int route_id: The numeric ID in the URL + :param EventSeries series: The updated EventSeries object + :raises HTTPException: If the ID in the URL does not match the ID in the request body (status code 400) + :raises HTTPException: If the poster ID is updated directly (status code 400) + :return EventSeries: The updated EventSeries object which is suitable for a response body """ if route_id != series.series_id: raise HTTPException( diff --git a/server/app/controllers/group.py b/server/app/controllers/group.py index 5ee2380..4c68b1f 100644 --- a/server/app/controllers/group.py +++ b/server/app/controllers/group.py @@ -8,28 +8,26 @@ from app.models.group import Group class GroupController(BaseController): """ - Handles all group-related operations and serves as an intermediate controller between - the main controller and the model layer. + Handles all group-related operations. Inherits from BaseController, which provides logging and other generic methods. - - The corresponding table contains only one row. - - Testing: pass a mocked GroupQueries object to the constructor. """ def __init__(self, group_queries=group_queries) -> None: + """ + Initializes the GroupController with a GroupQueries object. + + :param GroupQueries group_queries: object for quering group data, defaults to group_queries + """ super().__init__() self.group_queries: GroupQueries = group_queries def get_group(self) -> Group: - """Retrieves the group from the database and returns it as a Group object. + """ + Instantiates a Group object and retuns it for a response body. - Raises: - HTTPException: If the group is not found (status code 404) - HTTPException: If any error occurs during the retrieval process (status code 500) - - Returns: - Group: A Group object which is suitable for a response body + :raises HTTPException: If the group is not found (status code 404) + :raises HTTPException: If any error occurs during the instantiation process (status code 500) + :return Group: The Group object which is suitable for a response body """ if (data := self.group_queries.select_one_by_id()) is None: raise HTTPException( @@ -44,16 +42,12 @@ class GroupController(BaseController): ) def update_group_bio(self, bio: str) -> Group: - """Updates the group's bio in the database and returns the updated Group object. + """ + Updates the group's bio in the database and returns the updated Group object. - Args: - bio (str): The new bio for the group - - Raises: - HTTPException: If any error occurs during the update process (status code 500) - - Returns: - Group: The updated Group object which is suitable for a response body + :param str bio: The new bio for the group + :raises HTTPException: If any error occurs during the update process (status code 500) + :return Group: The updated Group object which is suitable for a response body """ try: self.group_queries.update_group_bio(bio) diff --git a/server/app/controllers/musicians.py b/server/app/controllers/musicians.py index 7f843c8..3fa6027 100644 --- a/server/app/controllers/musicians.py +++ b/server/app/controllers/musicians.py @@ -1,3 +1,5 @@ +from typing import Optional + from fastapi import HTTPException, UploadFile, status from icecream import ic @@ -10,25 +12,25 @@ from app.models.musician import Musician class MusicianController(BaseController): """ - Handles all musician-related operations and serves as an intermediate controller between - the main controller and the model layer. + Handles all musician-related operations. Inherits from BaseController, which provides logging and other generic methods. - - Testing: pass a mocked MusicianQueries object to the constructor. """ - def __init__(self, musician_queries=musician_queries) -> None: + def __init__(self, musician_queries: MusicianQueries = musician_queries) -> None: + """ + Initializes the MusicianController with a MusicianQueries object. + + :param MusicianQueries musician_queries: object for querying musician data, defaults to musician_queries + """ super().__init__() self.db: MusicianQueries = musician_queries def get_musicians(self) -> list[Musician]: - """Retrieves all musicians from the database and returns them as a list of Musician objects. + """ + Retrieves all musicians and returns them as a list. - Raises: - HTTPException: If any error occurs during the retrieval process (status code 500) - - Returns: - list[Musician]: A list of Musician objects which are suitable for a response body + :raises HTTPException: If any error occurs during the retrieval process (status code 500) + :return list[Musician]: A list of Musician objects suitable for a response body """ data = self.db.select_all_series() try: @@ -40,17 +42,13 @@ class MusicianController(BaseController): ) def get_musician(self, musician_id: int) -> Musician: - """Retrieves a single musician from the database and returns it as a Musician object. + """ + Retrieves a single musician by numeric ID. - Args: - id (int): The ID of the musician to retrieve - - Raises: - HTTPException: If the musician is not found (status code 404) - HTTPException: If any error occurs during the retrieval process (status code 500) - - Returns: - Musician: A Musician object which is suitable for a response body + :param int musician_id: The ID of the musician to retrieve + :raises HTTPException: If the musician is not found (status code 404) + :raises HTTPException: If any error occurs during the instantiation process (status code 500) + :return Musician: The musician object for a response body """ if (data := self.db.select_one_by_id(musician_id)) is None: raise HTTPException( @@ -68,20 +66,16 @@ class MusicianController(BaseController): self, musician_id: int, new_bio: str, - file: UploadFile | None = None, + file: Optional[UploadFile] = None, ) -> Musician: - """Updates a musician's bio and/or headshot by conditionally calling the appropriate methods. + """ + Updates a musician in the database and returns the updated musician object. - Args: - musician_id (int): The numeric ID of the musician to update - new_bio (str): The new biography for the musician - file (UploadFile | None, optional): The new headshot file. Defaults to None. - - Raises: - HTTPException: If the musician is not found (status code 404) - - Returns: - Musician: The updated Musician object + :param int musician_id: The ID of the musician to update + :param str new_bio: The new biography for the musician + :param Optional[UploadFile] file: The new headshot file, defaults to None + :raises HTTPException: _description_ + :return Musician: _description_ """ musician = self.get_musician(musician_id) if new_bio != musician.bio: @@ -97,17 +91,13 @@ class MusicianController(BaseController): def _update_musician_headshot( self, musician: Musician, headshot_id: str ) -> Musician: - """Updates a musician's headshot in the database. + """ + Updates a musician's headshot in the database. - Args: - musician (Musician): The musician object to update - headshot_id (str): The public ID of the new headshot (as determined by Cloudinary) - - Raises: - HTTPException: If any error occurs during the update process (status code 500) - - Returns: - Musician: The updated Musician object + :param Musician musician: The musician object to update + :param str headshot_id: The new public ID for the headshot + :raises HTTPException: If any error occurs during the update process (status code 500) + :return Musician: The updated Musician object """ try: self.db.update_headshot(musician, headshot_id) @@ -119,17 +109,13 @@ class MusicianController(BaseController): return self.get_musician(musician.id) def _update_musician_bio(self, musician: Musician, bio: str) -> Musician: - """Updates a musician's bio in the database. + """ + Updates a musician's biography in the database. - Args: - musician (Musician): The musician object to update - bio (str): The new biography for the musician - - Raises: - HTTPException: If any error occurs during the update process (status code 500) - - Returns: - Musician: The updated Musician object + :param Musician musician: The musician object to update + :param str bio: The new biography for the musician + :raises HTTPException: If any error occurs during the update process (status code 500) + :return Musician: The updated Musician object """ try: self.db.update_bio(musician, bio) @@ -141,17 +127,13 @@ class MusicianController(BaseController): return self.get_musician(musician.id) def _upload_headshot(self, musician: Musician, file: UploadFile) -> Musician: - """Uploads a new headshot for a musician and updates the database with the new public ID. + """ + Uploads a new headshot image for a musician and returns the updated musician object. - Args: - musician (Musician): The musician object to update - file (UploadFile): The new headshot file - - Raises: - HTTPException: If the file is not an image or exceeds the maximum file size (status code 400) - - Returns: - Musician: The updated Musician object + :param Musician musician: The musician object to update + :param UploadFile file: The new headshot file + :raises HTTPException: If any error occurs during the upload process (status code 500) + :return Musician: The updated Musician object """ image_file = self.verify_image(file) data = uploader.upload(image_file) diff --git a/server/app/controllers/users.py b/server/app/controllers/users.py index f28bf3d..45cf635 100644 --- a/server/app/controllers/users.py +++ b/server/app/controllers/users.py @@ -10,26 +10,25 @@ from app.models.user import User class UserController(BaseController): """ - Handles all user-related operations and serves as an intermediate controller between - the main controller and the model layer. - + Handles all user-related operations. Inherits from BaseController, which provides logging and other generic methods. - - Testing: pass a mocked UserQueries object to the constructor. """ - def __init__(self, user_queries=user_queries) -> None: + def __init__(self, user_queries: UserQueries = user_queries) -> None: + """ + Initializes the UserController with a UserQueries object. + + :param UserQueries user_queries: object for querying user data, defaults to user_queries + """ super().__init__() self.db: UserQueries = user_queries def get_users(self) -> list[User]: - """Retrieves all users from the database and returns them as a list of User objects. + """ + Retrieves all users and returns them as a list. - Raises: - HTTPException: If any error occurs during the retrieval process (status code 500) - - Returns: - list[User]: A list of User objects which are suitable for a response body + :raises HTTPException: If any error occurs during the retrieval process (status code 500) + :return list[User]: A list of User objects suitable for a response body """ data = self.db.select_all_series() try: @@ -41,17 +40,13 @@ class UserController(BaseController): ) def get_user_by_id(self, user_id: int) -> User: - """Retrieves a single user from the database and returns it as a User object. + """ + Retrieves a single user from the database and returns it as a User object. - Args: - user_id (int): The ID of the user to retrieve - - Raises: - HTTPException: If the user is not found (status code 404) - HTTPException: If any error occurs during the retrieval process (status code 500) - - Returns: - User: A User object which is suitable for a response body + :param int user_id: The ID of the user to retrieve + :raises HTTPException: If the user is not found (status code 404) + :raises HTTPException: If any error occurs during the retrieval process (status code 500) + :return User: A User object which is suitable for a response body """ if (data := self.db.select_one_by_id(user_id)) is None: raise HTTPException( @@ -66,17 +61,13 @@ class UserController(BaseController): ) def get_user_by_email(self, email: str) -> User: - """Retrieves a single user from the database and returns it as a User object. + """ + Retrieves a single user from the database and returns it as a User object. - Args: - email (str): The email of the user to retrieve - - Raises: - HTTPException: If the user is not found (status code 404) - HTTPException: If any error occurs during the retrieval process (status code 500) - - Returns: - User: A User object which is suitable for a response body + :param str email: The email of the user to retrieve + :raises HTTPException: If the user is not found (status code 404) + :raises HTTPException: If any error occurs during the retrieval process (status code 500) + :return User: A User object which is suitable for a response body """ if (data := self.db.select_one_by_email(email)) is None: raise HTTPException( @@ -91,17 +82,13 @@ class UserController(BaseController): ) def get_user_by_sub(self, sub: str) -> User: - """Retrieves a single user from the database and returns it as a User object. + """ + Retrieves a single user from the database and returns it as a User object. - Args: - sub (str): The sub of the user to retrieve - - Raises: - HTTPException: If the user is not found (status code 404) - HTTPException: If any error occurs during the retrieval process (status code 500) - - Returns: - User: A User object which is suitable for a response body + :param str sub: The sub of the user to retrieve + :raises HTTPException: If the user is not found (status code 404) + :raises HTTPException: If any error occurs during the retrieval process (status code 500) + :return User: A User object which is suitable for a response body """ if (data := self.db.select_one_by_sub(sub)) is None: raise HTTPException( @@ -116,13 +103,11 @@ class UserController(BaseController): ) def create_user(self, token: HTTPAuthorizationCredentials) -> User: - """Updates a user's sub in the database and creates a new User object. + """ + Creates a new user in the database and returns the created User object. - Args: - token (HTTPAuthorizationCredentials): The token containing the user's email and sub - - Returns: - User: A User object which is suitable for a response body + :param HTTPAuthorizationCredentials token: The OAuth token + :return User: The created User object which is suitable for a response body """ email, sub = oauth_token.email_and_sub(token) user: User = self.get_user_by_email(email) diff --git a/server/app/models/event.py b/server/app/models/event.py index 7315ff7..b2a893e 100644 --- a/server/app/models/event.py +++ b/server/app/models/event.py @@ -6,10 +6,18 @@ from pydantic import BaseModel, HttpUrl class Poster(BaseModel): + """ + Represents a poster image file for an EventSeries object. + """ + file: UploadFile class NewEvent(BaseModel): + """ + Represents a new event object as received from the client. + """ + location: str time: datetime map_url: Optional[HttpUrl] = None @@ -17,16 +25,28 @@ class NewEvent(BaseModel): class Event(NewEvent): + """ + Represents an existing event object to be returned to the client. + """ + event_id: int class NewEventSeries(BaseModel): + """ + Represents a new event series object as received from the client. + """ + name: str description: str events: list[NewEvent] class EventSeries(NewEventSeries): + """ + Represents an existing event series object to be returned to the client. + """ + series_id: int events: list[Event] poster_id: Optional[str] = None diff --git a/server/app/routers/contact.py b/server/app/routers/contact.py index 4e5cd6b..10fae24 100644 --- a/server/app/routers/contact.py +++ b/server/app/routers/contact.py @@ -12,7 +12,6 @@ router = APIRouter( @router.post("/", status_code=status.HTTP_201_CREATED) async def post_message(contact: Contact): - """Sends an email to the site owner with the provided name, email, and message.""" subject = f"New message from {contact.name}" body = f"From: {contact.email}\n\n{contact.message}" send_email(subject, body) diff --git a/server/tests/controllers/test_main_controller.py b/server/tests/controllers/test_main_controller.py index 35e97c7..df2dd6f 100644 --- a/server/tests/controllers/test_main_controller.py +++ b/server/tests/controllers/test_main_controller.py @@ -1,14 +1,10 @@ from unittest.mock import MagicMock import pytest -from fastapi import HTTPException, status -from icecream import ic from app.controllers.controller import MainController -from app.models.event import Event, EventSeries, NewEventSeries -from app.models.group import Group +from app.models.event import EventSeries, NewEventSeries from app.models.musician import Musician -from app.models.user import User mock_user_controller = MagicMock() mock_musician_controller = MagicMock()