From dad0c461a5e34d22107da7d05eb36d57f5061c47 Mon Sep 17 00:00:00 2001 From: Cory Bullinger Date: Wed, 11 Feb 2026 16:41:12 -0500 Subject: [PATCH 01/11] fix: bump langchain-core and pillow for security fixes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - langchain-core: 1.2.9 → 1.2.11 (CVE-2026-26013 fix per Dependabot alert #25) - pillow: 12.1.0 → 12.1.1 (CVE-2026-25990 fix per Dependabot alert #26) --- mflix/server/python-fastapi/requirements.in | 2 ++ mflix/server/python-fastapi/requirements.txt | 14 +++++++++----- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/mflix/server/python-fastapi/requirements.in b/mflix/server/python-fastapi/requirements.in index c50a667..baf2bf5 100644 --- a/mflix/server/python-fastapi/requirements.in +++ b/mflix/server/python-fastapi/requirements.in @@ -64,3 +64,5 @@ rich-toolkit~=0.15.1 # Extensions for the 'rich' library filelock>=3.20.3 # Transitive dep via huggingface-hub aiohttp>=3.13.3 # Transitive dep via voyageai orjson>=3.11.7 # Transitive dep via langsmith (CVE fix) +langchain-core>=1.2.11 # Transitive dep via langchain-text-splitters (CVE-2026-26013 fix) +pillow>=12.1.1 # Transitive dep via voyageai (CVE-2026-25990 fix) diff --git a/mflix/server/python-fastapi/requirements.txt b/mflix/server/python-fastapi/requirements.txt index 29e4311..9082a76 100644 --- a/mflix/server/python-fastapi/requirements.txt +++ b/mflix/server/python-fastapi/requirements.txt @@ -2,7 +2,7 @@ # This file is autogenerated by pip-compile with Python 3.13 # by the following command: # -# pip-compile requirements.in +# pip-compile --output-file=requirements.txt requirements.in # aiohappyeyeballs==2.6.1 # via aiohttp @@ -99,8 +99,10 @@ jsonpatch==1.33 # via langchain-core jsonpointer==3.0.0 # via jsonpatch -langchain-core==1.2.9 - # via langchain-text-splitters +langchain-core==1.2.11 + # via + # -r requirements.in + # langchain-text-splitters langchain-text-splitters==1.1.0 # via voyageai langsmith==0.6.9 @@ -125,8 +127,10 @@ packaging==26.0 # langchain-core # langsmith # pytest -pillow==12.1.0 - # via voyageai +pillow==12.1.1 + # via + # -r requirements.in + # voyageai pluggy==1.6.0 # via pytest propcache==0.4.1 From 445c0a96745ce1d190851615bd38e969619306bd Mon Sep 17 00:00:00 2001 From: "Taylor M." Date: Thu, 26 Feb 2026 18:40:07 -0500 Subject: [PATCH 02/11] DOCSP-56401-[PYTHON]-Add-OpenAPI-Response-Documentation-to-FastAPI-Endpoints (#88) * init commit * - addressed pr feedback - addressed differences in python and express backend responses * updating tests to match new response formats --- .../python-fastapi/src/routers/movies.py | 473 ++++++++++++------ .../python-fastapi/src/utils/response_docs.py | 322 ++++++++++++ .../test_movie_routes_integration.py | 9 +- .../python-fastapi/tests/test_movie_routes.py | 267 ++++++---- 4 files changed, 797 insertions(+), 274 deletions(-) create mode 100644 mflix/server/python-fastapi/src/utils/response_docs.py diff --git a/mflix/server/python-fastapi/src/routers/movies.py b/mflix/server/python-fastapi/src/routers/movies.py index 1da9b6a..50170eb 100644 --- a/mflix/server/python-fastapi/src/routers/movies.py +++ b/mflix/server/python-fastapi/src/routers/movies.py @@ -1,10 +1,18 @@ -from fastapi import APIRouter, Query, Path, Body, HTTPException +from fastapi import APIRouter, Query, Path, Body from fastapi.responses import JSONResponse from src.database.mongo_client import get_collection, voyage_ai_available from src.models.models import VectorSearchResult, CreateMovieRequest, Movie, SuccessResponse, UpdateMovieRequest, SearchMoviesResponse from typing import Any, List, Optional from src.utils.successResponse import create_success_response from src.utils.errorResponse import create_error_response +from src.utils.response_docs import ( + VECTOR_SEARCH_RESPONSES, + OBJECTID_VALIDATION_RESPONSES, + SEARCH_ENDPOINT_RESPONSES, + DATABASE_OPERATION_RESPONSES, + CRUD_OPERATION_RESPONSES, + CRUD_WITH_OBJECTID_RESPONSES +) from src.utils.exceptions import VoyageAuthError, VoyageAPIError from bson import ObjectId, errors import re @@ -114,7 +122,8 @@ "/search", response_model=SuccessResponse[SearchMoviesResponse], status_code = 200, - summary="Search movies using MongoDB Search." + summary="Search movies using MongoDB Search.", + responses=SEARCH_ENDPOINT_RESPONSES ) async def search_movies( plot: Optional[str] = None, @@ -133,9 +142,12 @@ async def search_movies( valid_operators = {"must", "should", "mustNot", "filter"} if search_operator not in valid_operators: - raise HTTPException( - status_code = 400, - detail=f"Invalid search operator '{search_operator}'. The search operator must be one of {valid_operators}." + return JSONResponse( + status_code=400, + content=create_error_response( + message=f"Invalid search operator '{search_operator}'. The search operator must be one of {valid_operators}.", + code="INVALID_SEARCH_OPERATOR" + ) ) # Build the search_phrases list based on which fields were provided by the user. @@ -207,9 +219,12 @@ async def search_movies( }) if not search_phrases: - raise HTTPException( - status_code = 400, - detail="At least one search parameter must be provided." + return JSONResponse( + status_code=400, + content=create_error_response( + message="At least one search parameter must be provided.", + code="MISSING_SEARCH_PARAMS" + ) ) # Build the aggregation pipeline for MongoDB Search. @@ -262,9 +277,12 @@ async def search_movies( try: results = await execute_aggregation(aggregation_pipeline) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"An error occurred while performing the search: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"An error occurred while performing the search: {str(e)}", + code="SEARCH_ERROR" + ) ) @@ -324,7 +342,11 @@ async def search_movies( outputDimension = 2048 #Set to 2048 to match the dimensions of the collection's embeddings # Vector Search Endpoint -@router.get("/vector-search", response_model=SuccessResponse[List[VectorSearchResult]]) +@router.get( + "/vector-search", + response_model=SuccessResponse[List[VectorSearchResult]], + responses=VECTOR_SEARCH_RESPONSES +) async def vector_search_movies( q: str = Query(..., description="Search query to find similar movies by plot"), limit: int = Query(default=10, ge=1, le=50, description="Number of results to return") @@ -342,7 +364,7 @@ async def vector_search_movies( # Check if Voyage AI API key is configured if not voyage_ai_available(): return JSONResponse( - status_code=400, + status_code=503, content=create_error_response( message="Vector search unavailable: VOYAGE_API_KEY not configured. Please add your API key to the .env file", code="SERVICE_UNAVAILABLE" @@ -426,9 +448,12 @@ async def vector_search_movies( print(f"Vector search error: {str(e)}") # Handle generic errors - raise HTTPException( + return JSONResponse( status_code=500, - detail=f"Error performing vector search: {str(e)}" + content=create_error_response( + message=f"Error performing vector search: {str(e)}", + code="VECTOR_SEARCH_ERROR" + ) ) """ @@ -441,10 +466,13 @@ async def vector_search_movies( SuccessResponse[List[str]]: A response object containing the list of unique genres, sorted alphabetically. """ -@router.get("/genres", - response_model=SuccessResponse[List[str]], - status_code=200, - summary="Retrieve all distinct genres from the movies collection.") +@router.get( + "/genres", + response_model=SuccessResponse[List[str]], + status_code=200, + summary="Retrieve all distinct genres from the movies collection.", + responses=DATABASE_OPERATION_RESPONSES +) async def get_distinct_genres(): movies_collection = get_collection("movies") @@ -453,9 +481,12 @@ async def get_distinct_genres(): # MongoDB automatically flattens array fields when using distinct() genres = await movies_collection.distinct("genres") except Exception as e: - raise HTTPException( + return JSONResponse( status_code=500, - detail=f"Database error occurred: {str(e)}" + content=create_error_response( + message=f"Database error occurred: {str(e)}", + code="DATABASE_ERROR" + ) ) # Filter out null/empty values and sort alphabetically @@ -475,34 +506,46 @@ async def get_distinct_genres(): SuccessResponse[Movie]: A response object containing the movie data. """ -@router.get("/{id}", - response_model=SuccessResponse[Movie], - status_code = 200, - summary="Retrieve a single movie by its ID.") +@router.get( + "/{id}", + response_model=SuccessResponse[Movie], + status_code = 200, + summary="Retrieve a single movie by its ID.", + responses=OBJECTID_VALIDATION_RESPONSES +) async def get_movie_by_id(id: str): # Validate ObjectId format try: object_id = ObjectId(id) except errors.InvalidId: - raise HTTPException( - status_code = 400, - detail=f"The provided ID '{id}' is not a valid ObjectId" + return JSONResponse( + status_code=400, + content=create_error_response( + message=f"The provided ID '{id}' is not a valid ObjectId", + code="INVALID_OBJECT_ID" + ) ) movies_collection = get_collection("movies") try: movie = await movies_collection.find_one({"_id": object_id}) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"Database error occurred: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"Database error occurred: {str(e)}", + code="DATABASE_ERROR" + ) ) if movie is None: - raise HTTPException( - status_code = 404, - detail=f"No movie found with ID: {id}" + return JSONResponse( + status_code=404, + content=create_error_response( + message=f"No movie found with ID: {id}", + code="MOVIE_NOT_FOUND" + ) ) movie["_id"] = str(movie["_id"]) # Convert ObjectId to string @@ -529,10 +572,13 @@ async def get_movie_by_id(id: str): SuccessResponse[List[Movie]]: A response object containing the list of movies and metadata. """ -@router.get("/", - response_model=SuccessResponse[List[Movie]], - status_code = 200, - summary="Retrieve a list of movies with optional filtering, sorting, and pagination.") +@router.get( + "/", + response_model=SuccessResponse[List[Movie]], + status_code = 200, + summary="Retrieve a list of movies with optional filtering, sorting, and pagination.", + responses=DATABASE_OPERATION_RESPONSES +) # Validate the query parameters using FastAPI's Query functionality. async def get_all_movies( q:str = Query(default=None), @@ -574,9 +620,12 @@ async def get_all_movies( try: result = movies_collection.find(filter_dict).sort(sort).skip(skip).limit(limit) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"An error occurred while fetching movies. {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"An error occurred while fetching movies. {str(e)}", + code="DATABASE_ERROR" + ) ) movies = [] @@ -608,10 +657,13 @@ async def get_all_movies( SuccessResponse[Movie]: A response object containing the created movie data. """ -@router.post("/", - response_model=SuccessResponse[Movie], - status_code = 201, - summary="Creates a new movie in the database.") +@router.post( + "/", + response_model=SuccessResponse[Movie], + status_code = 201, + summary="Creates a new movie in the database.", + responses=CRUD_OPERATION_RESPONSES +) async def create_movie(movie: CreateMovieRequest): # Pydantic automatically validates the structure movie_data = movie.model_dump(by_alias=True, exclude_none=True) @@ -620,31 +672,43 @@ async def create_movie(movie: CreateMovieRequest): try: result = await movies_collection.insert_one(movie_data) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"Database error occurred: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"Database error occurred: {str(e)}", + code="DATABASE_ERROR" + ) ) # Verify that the document was created before querying it if not result.acknowledged: - raise HTTPException( - status_code = 500, - detail="Failed to create movie: The database did not acknowledge the insert operation" + return JSONResponse( + status_code=500, + content=create_error_response( + message="Failed to create movie: The database did not acknowledge the insert operation", + code="DATABASE_ERROR" + ) ) try: # Retrieve the created document to return complete data created_movie = await movies_collection.find_one({"_id": result.inserted_id}) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"Database error occurred: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"Database error occurred: {str(e)}", + code="DATABASE_ERROR" + ) ) if created_movie is None: - raise HTTPException( - status_code = 500, - detail="Movie was created but could not be retrieved for verification" + return JSONResponse( + status_code=500, + content=create_error_response( + message="Movie was created but could not be retrieved for verification", + code="DATABASE_ERROR" + ) ) created_movie["_id"] = str(created_movie["_id"]) # Convert ObjectId to string @@ -678,19 +742,23 @@ async def create_movie(movie: CreateMovieRequest): """ @router.post( - "/batch", - response_model=SuccessResponse[dict], - status_code = 201, - summary = "Create multiple movies in a single request." - ) + "/batch", + response_model=SuccessResponse[dict], + status_code = 201, + summary = "Create multiple movies in a single request.", + responses=CRUD_OPERATION_RESPONSES +) async def create_movies_batch(movies: List[CreateMovieRequest]) ->SuccessResponse[dict]: movies_collection = get_collection("movies") #Verify that the movies list is not empty if not movies: - raise HTTPException( - status_code = 400, - detail="Request body must be a non-empty list of movies." + return JSONResponse( + status_code=400, + content=create_error_response( + message="Request body must be a non-empty list of movies.", + code="EMPTY_REQUEST" + ) ) movies_dicts = [] @@ -710,9 +778,12 @@ async def create_movies_batch(movies: List[CreateMovieRequest]) ->SuccessRespons f"Successfully created {len(result.inserted_ids)} movies." ) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"Database error occurred: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"Database error occurred: {str(e)}", + code="DATABASE_ERROR" + ) ) """ @@ -730,10 +801,12 @@ async def create_movies_batch(movies: List[CreateMovieRequest]) ->SuccessRespons SuccessResponse: The updated movie document, the number of fields modified and a success message. """ @router.patch( - "/{id}", - response_model=SuccessResponse[Movie], - status_code = 200, - summary="Update a single movie by its ID.") + "/{id}", + response_model=SuccessResponse[Movie], + status_code = 200, + summary="Update a single movie by its ID.", + responses=CRUD_WITH_OBJECTID_RESPONSES +) async def update_movie( movie_data: UpdateMovieRequest, movie_id: str = Path(..., alias="id") @@ -745,18 +818,24 @@ async def update_movie( try: movie_id = ObjectId(movie_id) except Exception : - raise HTTPException( - status_code = 400, - detail=f"Invalid movie_id format: {movie_id}" + return JSONResponse( + status_code=400, + content=create_error_response( + message=f"Invalid movie_id format: {movie_id}", + code="INVALID_OBJECT_ID" + ) ) update_dict = movie_data.model_dump(exclude_unset=True, exclude_none=True) # Validate that the dict is not empty if not update_dict: - raise HTTPException( - status_code = 400, - detail="No valid fields provided for update." + return JSONResponse( + status_code=400, + content=create_error_response( + message="No valid fields provided for update.", + code="NO_UPDATE_DATA" + ) ) try: @@ -765,15 +844,21 @@ async def update_movie( {"$set":update_dict} ) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"An error occurred while updating the movie: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"An error occurred while updating the movie: {str(e)}", + code="DATABASE_ERROR" + ) ) if result.matched_count == 0: - raise HTTPException( - status_code = 404, - detail=f"No movie with that _id was found: {movie_id}" + return JSONResponse( + status_code=404, + content=create_error_response( + message=f"No movie with that _id was found: {movie_id}", + code="MOVIE_NOT_FOUND" + ) ) updatedMovie = await movies_collection.find_one({"_id": movie_id}) @@ -793,11 +878,13 @@ async def update_movie( SuccessResponse: A response object containing the number of matched and modified movies and a success message. """ -@router.patch("/", - response_model=SuccessResponse[dict], - status_code = 200, - summary="Batch update movies matching the given filter." - ) +@router.patch( + "/", + response_model=SuccessResponse[dict], + status_code = 200, + summary="Batch update movies matching the given filter.", + responses=CRUD_OPERATION_RESPONSES +) async def update_movies_batch( request_body: dict = Body(...) ) -> SuccessResponse[dict]: @@ -808,9 +895,12 @@ async def update_movies_batch( update_data = request_body.get("update", {}) if not filter_data or not update_data: - raise HTTPException( - status_code = 400, - detail="Both filter and update objects are required" + return JSONResponse( + status_code=400, + content=create_error_response( + message="Both filter and update objects are required", + code="MISSING_FILTER" + ) ) # Convert string IDs to ObjectIds if _id filter is present @@ -820,17 +910,23 @@ async def update_movies_batch( try: filter_data["_id"]["$in"] = [ObjectId(id_str) for id_str in filter_data["_id"]["$in"]] except Exception: - raise HTTPException( - status_code = 400, - detail="Invalid ObjectId format in filter", + return JSONResponse( + status_code=400, + content=create_error_response( + message="Invalid ObjectId format in filter", + code="INVALID_OBJECT_ID" + ) ) try: result = await movies_collection.update_many(filter_data, {"$set": update_data}) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"An error occurred while updating movies: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"An error occurred while updating movies: {str(e)}", + code="DATABASE_ERROR" + ) ) return create_success_response({ @@ -849,17 +945,23 @@ async def update_movies_batch( SuccessResponse[dict]: A response object containing deletion details. """ -@router.delete("/{id}", - response_model=SuccessResponse[dict], - status_code = 200, - summary="Delete a single movie by its ID.") +@router.delete( + "/{id}", + response_model=SuccessResponse[dict], + status_code = 200, + summary="Delete a single movie by its ID.", + responses=OBJECTID_VALIDATION_RESPONSES +) async def delete_movie_by_id(id: str): try: object_id = ObjectId(id) except errors.InvalidId: - raise HTTPException( - status_code = 400, - detail=f"Invalid movie ID format: The provided ID '{id}' is not a valid ObjectId" + return JSONResponse( + status_code=400, + content=create_error_response( + message=f"Invalid movie ID format: The provided ID '{id}' is not a valid ObjectId", + code="INVALID_OBJECT_ID" + ) ) movies_collection = get_collection("movies") @@ -867,15 +969,21 @@ async def delete_movie_by_id(id: str): # Use deleteOne() to remove a single document result = await movies_collection.delete_one({"_id": object_id}) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"Database error occurred: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"Database error occurred: {str(e)}", + code="DATABASE_ERROR" + ) ) if result.deleted_count == 0: - raise HTTPException( - status_code = 404, - detail=f"No movie found with ID: {id}" + return JSONResponse( + status_code=404, + content=create_error_response( + message=f"No movie found with ID: {id}", + code="MOVIE_NOT_FOUND" + ) ) return create_success_response( @@ -896,10 +1004,11 @@ async def delete_movie_by_id(id: str): """ @router.delete( - "/", - response_model=SuccessResponse[dict], - status_code = 200, - summary="Delete multiple movies matching the given filter." + "/", + response_model=SuccessResponse[dict], + status_code = 200, + summary="Delete multiple movies matching the given filter.", + responses=CRUD_OPERATION_RESPONSES ) async def delete_movies_batch(request_body: dict = Body(...)) -> SuccessResponse[dict]: @@ -909,9 +1018,12 @@ async def delete_movies_batch(request_body: dict = Body(...)) -> SuccessResponse filter_data = request_body.get("filter", {}) if not filter_data: - raise HTTPException( - status_code = 400, - detail="Filter object is required and cannot be empty." + return JSONResponse( + status_code=400, + content=create_error_response( + message="Filter object is required and cannot be empty.", + code="MISSING_FILTER" + ) ) # Convert string IDs to ObjectIds if _id filter is present @@ -921,17 +1033,23 @@ async def delete_movies_batch(request_body: dict = Body(...)) -> SuccessResponse try: filter_data["_id"]["$in"] = [ObjectId(id_str) for id_str in filter_data["_id"]["$in"]] except Exception: - raise HTTPException( - status_code = 400, - detail="Invalid ObjectId format in filter." + return JSONResponse( + status_code=400, + content=create_error_response( + message="Invalid ObjectId format in filter.", + code="INVALID_OBJECT_ID" + ) ) try: result = await movies_collection.delete_many(filter_data) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"An error occurred while deleting movies: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"An error occurred while deleting movies: {str(e)}", + code="DATABASE_ERROR" + ) ) return create_success_response( @@ -949,17 +1067,23 @@ async def delete_movies_batch(request_body: dict = Body(...)) -> SuccessResponse SuccessResponse[Movie]: A response object containing the deleted movie data. """ -@router.delete("/{id}/find-and-delete", - response_model=SuccessResponse[Movie], - status_code = 200, - summary="Find and delete a movie in a single operation.") +@router.delete( + "/{id}/find-and-delete", + response_model=SuccessResponse[Movie], + status_code = 200, + summary="Find and delete a movie in a single operation.", + responses=OBJECTID_VALIDATION_RESPONSES +) async def find_and_delete_movie(id: str): try: object_id = ObjectId(id) except errors.InvalidId: - raise HTTPException( - status_code = 400, - detail=f"Invalid movie ID format: The provided ID '{id}' is not a valid ObjectId" + return JSONResponse( + status_code=400, + content=create_error_response( + message=f"Invalid movie ID format: The provided ID '{id}' is not a valid ObjectId", + code="INVALID_OBJECT_ID" + ) ) movies_collection = get_collection("movies") @@ -969,15 +1093,21 @@ async def find_and_delete_movie(id: str): try: deleted_movie = await movies_collection.find_one_and_delete({"_id": object_id}) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"Database error occurred: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"Database error occurred: {str(e)}", + code="DATABASE_ERROR" + ) ) if deleted_movie is None: - raise HTTPException( - status_code = 404, - detail=f"No movie found with ID: {id}" + return JSONResponse( + status_code=404, + content=create_error_response( + message=f"No movie found with ID: {id}", + code="MOVIE_NOT_FOUND" + ) ) deleted_movie["_id"] = str(deleted_movie["_id"]) # Convert ObjectId to string @@ -994,10 +1124,13 @@ async def find_and_delete_movie(id: str): SuccessResponse[List[dict]]: A response object containing movies with their most recent comments. """ -@router.get("/aggregations/reportingByComments", - response_model=SuccessResponse[List[dict]], - status_code = 200, - summary="Aggregate movies with their most recent comments.") +@router.get( + "/aggregations/reportingByComments", + response_model=SuccessResponse[List[dict]], + status_code = 200, + summary="Aggregate movies with their most recent comments.", + responses=DATABASE_OPERATION_RESPONSES +) async def aggregate_movies_recent_commented( limit: int = Query(default=10, ge=1, le=50), movie_id: str = Query(default=None) @@ -1027,9 +1160,12 @@ async def aggregate_movies_recent_commented( object_id = ObjectId(movie_id) pipeline[0]["$match"]["_id"] = object_id except Exception: - raise HTTPException( - status_code = 400, - detail="The provided movie_id is not a valid ObjectId" + return JSONResponse( + status_code=400, + content=create_error_response( + message="The provided movie_id is not a valid ObjectId", + code="INVALID_OBJECT_ID" + ) ) # Add remaining pipeline stages @@ -1116,9 +1252,12 @@ async def aggregate_movies_recent_commented( try: results = await execute_aggregation(pipeline) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"Database error occurred during aggregation: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"Database error occurred during aggregation: {str(e)}", + code="DATABASE_ERROR" + ) ) # Convert ObjectId to string for response @@ -1142,10 +1281,13 @@ async def aggregate_movies_recent_commented( SuccessResponse[List[dict]]: A response object containing yearly movie statistics. """ -@router.get("/aggregations/reportingByYear", - response_model=SuccessResponse[List[dict]], - status_code = 200, - summary="Aggregate movies by year with average rating and movie count.") +@router.get( + "/aggregations/reportingByYear", + response_model=SuccessResponse[List[dict]], + status_code = 200, + summary="Aggregate movies by year with average rating and movie count.", + responses=DATABASE_OPERATION_RESPONSES +) async def aggregate_movies_by_year(): # Define aggregation pipeline to group movies by year with statistics # This pipeline demonstrates grouping, statistical calculations, and data cleaning @@ -1246,9 +1388,12 @@ async def aggregate_movies_by_year(): try: results = await execute_aggregation(pipeline) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"Database error occurred during aggregation: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"Database error occurred during aggregation: {str(e)}", + code="DATABASE_ERROR" + ) ) return create_success_response( @@ -1266,10 +1411,13 @@ async def aggregate_movies_by_year(): SuccessResponse[List[dict]]: A response object containing director statistics. """ -@router.get("/aggregations/reportingByDirectors", - response_model=SuccessResponse[List[dict]], - status_code = 200, - summary="Aggregate directors with the most movies and their statistics.") +@router.get( + "/aggregations/reportingByDirectors", + response_model=SuccessResponse[List[dict]], + status_code = 200, + summary="Aggregate directors with the most movies and their statistics.", + responses=DATABASE_OPERATION_RESPONSES +) async def aggregate_directors_most_movies( limit: int = Query(default=20, ge=1, le=100) ): @@ -1344,9 +1492,12 @@ async def aggregate_directors_most_movies( try: results = await execute_aggregation(pipeline) except Exception as e: - raise HTTPException( - status_code = 500, - detail=f"Database error occurred during aggregation: {str(e)}" + return JSONResponse( + status_code=500, + content=create_error_response( + message=f"Database error occurred during aggregation: {str(e)}", + code="DATABASE_ERROR" + ) ) return create_success_response( diff --git a/mflix/server/python-fastapi/src/utils/response_docs.py b/mflix/server/python-fastapi/src/utils/response_docs.py new file mode 100644 index 0000000..7ce5135 --- /dev/null +++ b/mflix/server/python-fastapi/src/utils/response_docs.py @@ -0,0 +1,322 @@ +""" +OpenAPI Response Documentation Helpers + +This module provides reusable response documentation for FastAPI endpoints +to maintain consistent OpenAPI documentation across all movie API endpoints. +Uses the standardized error format from create_error_response() to match Express backend. +""" + + +# Helper schema for standardized error responses (matches create_error_response format) +ERROR_RESPONSE_SCHEMA = { + "type": "object", + "properties": { + "success": {"type": "boolean"}, + "message": {"type": "string"}, + "error": { + "type": "object", + "properties": { + "message": {"type": "string"}, + "code": {"type": "string"}, + "details": {"type": "string"} + } + }, + "timestamp": {"type": "string"} + } +} + + +# 400 Bad Request Responses +ERROR_400_INVALID_OBJECTID = { + "description": "Bad Request - Invalid ObjectId format", + "content": { + "application/json": { + "schema": { + **ERROR_RESPONSE_SCHEMA, + "example": { + "success": False, + "message": "The provided ID 'invalid_id' is not a valid ObjectId", + "error": { + "message": "The provided ID 'invalid_id' is not a valid ObjectId", + "code": "INVALID_OBJECT_ID", + "details": None + }, + "timestamp": "2024-01-01T12:00:00.000Z" + } + } + } + } +} + +ERROR_400_VALIDATION = { + "description": "Bad Request - Request validation failed", + "content": { + "application/json": { + "schema": { + **ERROR_RESPONSE_SCHEMA, + "example": { + "success": False, + "message": "No valid fields provided for update.", + "error": { + "message": "No valid fields provided for update.", + "code": "NO_UPDATE_DATA", + "details": None + }, + "timestamp": "2024-01-01T12:00:00.000Z" + } + } + } + } +} + +# Combined 400 response for search endpoints (covers both invalid operator and missing params) +ERROR_400_SEARCH_ERRORS = { + "description": "Bad Request - Invalid search operator or missing search parameters", + "content": { + "application/json": { + "schema": ERROR_RESPONSE_SCHEMA, + "examples": { + "invalid_operator": { + "summary": "Invalid search operator", + "value": { + "success": False, + "message": "Invalid search operator 'invalid'. The search operator must be one of {'must', 'should', 'mustNot', 'filter'}.", + "error": { + "message": "Invalid search operator 'invalid'. The search operator must be one of {'must', 'should', 'mustNot', 'filter'}.", + "code": "INVALID_SEARCH_OPERATOR", + "details": None + }, + "timestamp": "2024-01-01T12:00:00.000Z" + } + }, + "missing_params": { + "summary": "Missing search parameters", + "value": { + "success": False, + "message": "At least one search parameter must be provided.", + "error": { + "message": "At least one search parameter must be provided.", + "code": "MISSING_SEARCH_PARAMS", + "details": None + }, + "timestamp": "2024-01-01T12:00:00.000Z" + } + } + } + } + } +} + +# 401 Unauthorized Responses +ERROR_401_VOYAGE_AUTH = { + "description": "Unauthorized - Invalid Voyage AI API key", + "content": { + "application/json": { + "schema": { + **ERROR_RESPONSE_SCHEMA, + "example": { + "success": False, + "message": "Invalid Voyage AI API key", + "error": { + "message": "Invalid Voyage AI API key", + "code": "VOYAGE_AUTH_ERROR", + "details": "Please verify your VOYAGE_API_KEY is correct in the .env file" + }, + "timestamp": "2024-01-01T12:00:00.000Z" + } + } + } + } +} + +# 404 Not Found Responses +ERROR_404_MOVIE_NOT_FOUND = { + "description": "Not Found - Movie not found", + "content": { + "application/json": { + "schema": { + **ERROR_RESPONSE_SCHEMA, + "example": { + "success": False, + "message": "No movie found with ID: 507f1f77bcf86cd799439011", + "error": { + "message": "No movie found with ID: 507f1f77bcf86cd799439011", + "code": "MOVIE_NOT_FOUND", + "details": None + }, + "timestamp": "2024-01-01T12:00:00.000Z" + } + } + } + } +} + +# 422 Unprocessable Entity - FastAPI's auto-generated validation errors +FASTAPI_422_VALIDATION_ERROR = { + "description": "Unprocessable Entity - Validation error", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "detail": { + "type": "array", + "items": { + "type": "object", + "properties": { + "loc": {"type": "array"}, + "msg": {"type": "string"}, + "type": {"type": "string"} + } + } + } + }, + "example": { + "detail": [ + { + "loc": ["body", "title"], + "msg": "field required", + "type": "value_error.missing" + } + ] + } + } + } + } +} + +# 500 Internal Server Error Responses +ERROR_500_DATABASE = { + "description": "Internal Server Error - Database operation failed", + "content": { + "application/json": { + "schema": { + **ERROR_RESPONSE_SCHEMA, + "example": { + "success": False, + "message": "Database error occurred: Connection timeout", + "error": { + "message": "Database error occurred: Connection timeout", + "code": "DATABASE_ERROR", + "details": None + }, + "timestamp": "2024-01-01T12:00:00.000Z" + } + } + } + } +} + +ERROR_500_SEARCH = { + "description": "Internal Server Error - Search operation failed", + "content": { + "application/json": { + "schema": { + **ERROR_RESPONSE_SCHEMA, + "example": { + "success": False, + "message": "An error occurred while performing the search: Index not found", + "error": { + "message": "An error occurred while performing the search: Index not found", + "code": "SEARCH_ERROR", + "details": None + }, + "timestamp": "2024-01-01T12:00:00.000Z" + } + } + } + } +} + +ERROR_500_VECTOR_SEARCH = { + "description": "Internal Server Error - Vector search operation failed", + "content": { + "application/json": { + "schema": { + **ERROR_RESPONSE_SCHEMA, + "example": { + "success": False, + "message": "Error performing vector search: Embedding generation failed", + "error": { + "message": "Error performing vector search: Embedding generation failed", + "code": "VECTOR_SEARCH_ERROR", + "details": None + }, + "timestamp": "2024-01-01T12:00:00.000Z" + } + } + } + } +} + +# 503 Service Unavailable Responses +ERROR_503_VOYAGE = { + "description": "Service Unavailable - Vector search service unavailable", + "content": { + "application/json": { + "schema": ERROR_RESPONSE_SCHEMA, + "examples": { + "api_key_not_configured": { + "summary": "Voyage API key not configured", + "value": { + "success": False, + "message": "Vector search unavailable: VOYAGE_API_KEY not configured. Please add your API key to the .env file", + "error": { + "message": "Vector search unavailable: VOYAGE_API_KEY not configured. Please add your API key to the .env file", + "code": "SERVICE_UNAVAILABLE", + "details": None + }, + "timestamp": "2024-01-01T12:00:00.000Z" + } + }, + "voyage_api_error": { + "summary": "Voyage AI API error", + "value": { + "success": False, + "message": "Vector search service unavailable", + "error": { + "message": "Vector search service unavailable", + "code": "VOYAGE_API_ERROR", + "details": "Voyage AI service temporarily unavailable" + }, + "timestamp": "2024-01-01T12:00:00.000Z" + } + } + } + } + } +} + + +# Common response combinations for different endpoint types +OBJECTID_VALIDATION_RESPONSES = { + 400: ERROR_400_INVALID_OBJECTID, + 404: ERROR_404_MOVIE_NOT_FOUND, + 500: ERROR_500_DATABASE +} + +SEARCH_ENDPOINT_RESPONSES = { + 400: ERROR_400_SEARCH_ERRORS, + 500: ERROR_500_SEARCH +} + +VECTOR_SEARCH_RESPONSES = { + 401: ERROR_401_VOYAGE_AUTH, + 500: ERROR_500_VECTOR_SEARCH, + 503: ERROR_503_VOYAGE +} + +DATABASE_OPERATION_RESPONSES = { + 500: ERROR_500_DATABASE +} + +CRUD_OPERATION_RESPONSES = { + 400: ERROR_400_VALIDATION, + 422: FASTAPI_422_VALIDATION_ERROR, + 500: ERROR_500_DATABASE +} + +CRUD_WITH_OBJECTID_RESPONSES = { + **OBJECTID_VALIDATION_RESPONSES, + 422: FASTAPI_422_VALIDATION_ERROR +} \ No newline at end of file diff --git a/mflix/server/python-fastapi/tests/integration/test_movie_routes_integration.py b/mflix/server/python-fastapi/tests/integration/test_movie_routes_integration.py index c9c9730..30cc6c6 100644 --- a/mflix/server/python-fastapi/tests/integration/test_movie_routes_integration.py +++ b/mflix/server/python-fastapi/tests/integration/test_movie_routes_integration.py @@ -126,8 +126,8 @@ async def test_delete_movie(self, client, test_movie_data): get_response = await client.get(f"/api/movies/{movie_id}") assert get_response.status_code == 404 error_data = get_response.json() - assert "detail" in error_data - assert "no movie found" in error_data["detail"].lower() + assert error_data["success"] is False + assert error_data["error"]["code"] == "MOVIE_NOT_FOUND" # No cleanup needed - movie already deleted @@ -276,13 +276,12 @@ async def test_batch_delete_movies(self, client, multiple_test_movies): assert delete_data["data"]["deletedCount"] == 3 # Verify all movies were deleted - # Note: The API returns 200 with INTERNAL_SERVER_ERROR code, not 404 for movie_id in multiple_test_movies: get_response = await client.get(f"/api/movies/{movie_id}") assert get_response.status_code == 404 error_data = get_response.json() - assert "detail" in error_data - assert "no movie found" in error_data["detail"].lower() + assert error_data["success"] is False + assert error_data["error"]["code"] == "MOVIE_NOT_FOUND" # Note: Fixture cleanup will try to delete but movies are already gone # The fixture should handle this gracefully diff --git a/mflix/server/python-fastapi/tests/test_movie_routes.py b/mflix/server/python-fastapi/tests/test_movie_routes.py index 8bb335d..b9f12f8 100644 --- a/mflix/server/python-fastapi/tests/test_movie_routes.py +++ b/mflix/server/python-fastapi/tests/test_movie_routes.py @@ -6,10 +6,11 @@ an actual database connection or server instance. """ +import json import pytest from unittest.mock import AsyncMock, MagicMock, patch from bson import ObjectId -from fastapi import HTTPException +from fastapi.responses import JSONResponse from src.models.models import CreateMovieRequest, UpdateMovieRequest from src.utils.exceptions import VoyageAuthError, VoyageAPIError @@ -59,23 +60,27 @@ async def test_get_movie_by_id_not_found(self, mock_get_collection): # Import and call the route handler from src.routers.movies import get_movie_by_id - with pytest.raises(HTTPException) as e: - await get_movie_by_id(TEST_MOVIE_ID) + response = await get_movie_by_id(TEST_MOVIE_ID) # Assertions - assert e.value.status_code == 404 - assert "no movie found" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 404 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "MOVIE_NOT_FOUND" async def test_get_movie_by_id_invalid_id(self): """Should return error when invalid ObjectId format is provided.""" # Import and call the route handler from src.routers.movies import get_movie_by_id - with pytest.raises(HTTPException) as e: - await get_movie_by_id(INVALID_MOVIE_ID) + response = await get_movie_by_id(INVALID_MOVIE_ID) # Assertions - assert e.value.status_code == 400 - assert " not a valid" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 400 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "INVALID_OBJECT_ID" @patch('src.routers.movies.get_collection') async def test_get_movie_by_id_database_error(self, mock_get_collection): @@ -87,12 +92,14 @@ async def test_get_movie_by_id_database_error(self, mock_get_collection): # Import and call the route handler from src.routers.movies import get_movie_by_id - with pytest.raises(HTTPException) as e: - await get_movie_by_id(TEST_MOVIE_ID) + response = await get_movie_by_id(TEST_MOVIE_ID) # Assertions - assert e.value.status_code == 500 - assert "error" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 500 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "DATABASE_ERROR" @pytest.mark.unit @@ -145,12 +152,14 @@ async def test_create_movie_database_error(self, mock_get_collection): # Create request from src.routers.movies import create_movie movie_request = CreateMovieRequest(title="New Movie") - with pytest.raises(HTTPException) as e: - await create_movie(movie_request) + response = await create_movie(movie_request) # Assertions - assert e.value.status_code == 500 - assert "error" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 500 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "DATABASE_ERROR" @pytest.mark.unit @@ -201,12 +210,14 @@ async def test_update_movie_not_found(self, mock_get_collection): from src.routers.movies import update_movie update_request = UpdateMovieRequest(title="Updated Movie") - with pytest.raises(HTTPException) as e: - await update_movie(update_request, TEST_MOVIE_ID) - - #Assertions - assert e.value.status_code == 404 - assert "no movie" in str(e.value.detail.lower()) + response = await update_movie(update_request, TEST_MOVIE_ID) + + # Assertions + assert isinstance(response, JSONResponse) + assert response.status_code == 404 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "MOVIE_NOT_FOUND" async def test_update_movie_invalid_id(self): """Should return error when invalid ObjectId format is provided.""" @@ -214,12 +225,14 @@ async def test_update_movie_invalid_id(self): from src.routers.movies import update_movie update_request = UpdateMovieRequest(title="Updated Movie") - with pytest.raises(HTTPException) as e: - await update_movie(update_request, INVALID_MOVIE_ID) + response = await update_movie(update_request, INVALID_MOVIE_ID) - # Assertions - assert e.value.status_code == 400 - assert "invalid" in str(e.value.detail.lower()) + # Assertions + assert isinstance(response, JSONResponse) + assert response.status_code == 400 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "INVALID_OBJECT_ID" @pytest.mark.unit @@ -258,23 +271,27 @@ async def test_delete_movie_not_found(self, mock_get_collection): # Call the route handler from src.routers.movies import delete_movie_by_id - with pytest.raises(HTTPException) as e: - await delete_movie_by_id(TEST_MOVIE_ID) + response = await delete_movie_by_id(TEST_MOVIE_ID) - # Assertions - assert e.value.status_code == 404 - assert "no movie" in str(e.value.detail.lower()) + # Assertions + assert isinstance(response, JSONResponse) + assert response.status_code == 404 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "MOVIE_NOT_FOUND" async def test_delete_movie_invalid_id(self): """Should return error when invalid ObjectId format is provided.""" # Call the route handler from src.routers.movies import delete_movie_by_id - with pytest.raises(HTTPException) as e: - await delete_movie_by_id(INVALID_MOVIE_ID) + response = await delete_movie_by_id(INVALID_MOVIE_ID) # Assertions - assert e.value.status_code == 400 - assert "invalid movie id" in str(e.value.detail.lower()) + assert isinstance(response, JSONResponse) + assert response.status_code == 400 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "INVALID_OBJECT_ID" @patch('src.routers.movies.get_collection') async def test_delete_movie_database_error(self, mock_get_collection): @@ -286,12 +303,14 @@ async def test_delete_movie_database_error(self, mock_get_collection): # Call the route handler from src.routers.movies import delete_movie_by_id - with pytest.raises(HTTPException) as e: - await delete_movie_by_id(TEST_MOVIE_ID) + response = await delete_movie_by_id(TEST_MOVIE_ID) - # Assertions - assert e.value.status_code == 500 - assert "error" in str(e.value.detail.lower()) + # Assertions + assert isinstance(response, JSONResponse) + assert response.status_code == 500 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "DATABASE_ERROR" @pytest.mark.unit @pytest.mark.asyncio @@ -394,12 +413,14 @@ async def test_get_all_movies_database_error(self, mock_get_collection): # Call the route handler from src.routers.movies import get_all_movies - with pytest.raises(HTTPException) as e: - await get_all_movies() + response = await get_all_movies() # Assertions - assert e.value.status_code == 500 - assert "error" in str(e.value.detail.lower()) + assert isinstance(response, JSONResponse) + assert response.status_code == 500 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "DATABASE_ERROR" @pytest.mark.unit @@ -441,12 +462,14 @@ async def test_create_movies_batch_empty_list(self, mock_get_collection): # Create request with empty list from src.routers.movies import create_movies_batch - with pytest.raises(HTTPException) as e: - await create_movies_batch([]) + response = await create_movies_batch([]) # Assertions - assert e.value.status_code == 400 - assert "empty" in str(e.value.detail.lower()) + assert isinstance(response, JSONResponse) + assert response.status_code == 400 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "EMPTY_REQUEST" @patch('src.routers.movies.get_collection') async def test_delete_movies_batch_success(self, mock_get_collection): @@ -476,12 +499,14 @@ async def test_delete_movies_batch_missing_filter(self, mock_get_collection): # Create request without filter from src.routers.movies import delete_movies_batch request_body = {} - with pytest.raises(HTTPException) as e: - await delete_movies_batch(request_body) + response = await delete_movies_batch(request_body) # Assertions - assert e.value.status_code == 400 - assert "filter" in e.value.detail.lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 400 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "MISSING_FILTER" @@ -523,23 +548,27 @@ async def test_find_and_delete_not_found(self, mock_get_collection): # Call the route handler from src.routers.movies import find_and_delete_movie - with pytest.raises(HTTPException) as e: - await find_and_delete_movie(TEST_MOVIE_ID) + response = await find_and_delete_movie(TEST_MOVIE_ID) # Assertions - assert e.value.status_code == 404 - assert "no movie" in str(e.value.detail.lower()) + assert isinstance(response, JSONResponse) + assert response.status_code == 404 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "MOVIE_NOT_FOUND" async def test_find_and_delete_invalid_id(self): """Should return error when invalid ObjectId format is provided.""" # Call the route handler from src.routers.movies import find_and_delete_movie - with pytest.raises(HTTPException) as e: - await find_and_delete_movie(INVALID_MOVIE_ID) + response = await find_and_delete_movie(INVALID_MOVIE_ID) # Assertions - assert e.value.status_code == 400 - assert "invalid" in str(e.value.detail.lower()) + assert isinstance(response, JSONResponse) + assert response.status_code == 400 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "INVALID_OBJECT_ID" @pytest.mark.unit @@ -580,12 +609,14 @@ async def test_update_movies_batch_missing_filter(self, mock_get_collection): # Create request without filter from src.routers.movies import update_movies_batch request_body = {"update": {"$set": {"rated": "PG-13"}}} - with pytest.raises(HTTPException) as e: - await update_movies_batch(request_body) + response = await update_movies_batch(request_body) # Assertions - assert e.value.status_code == 400 - assert "filter" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 400 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "MISSING_FILTER" @patch('src.routers.movies.get_collection') async def test_update_movies_batch_missing_update(self, mock_get_collection): @@ -595,12 +626,14 @@ async def test_update_movies_batch_missing_update(self, mock_get_collection): # Create request without update from src.routers.movies import update_movies_batch request_body = {"filter": {"year": 2020}} - with pytest.raises(HTTPException) as e: - await update_movies_batch(request_body) + response = await update_movies_batch(request_body) - # Assertions - assert e.value.status_code == 400 - assert "update" in str(e.value.detail).lower() + # Assertions - code returns MISSING_FILTER for both missing filter and missing update + assert isinstance(response, JSONResponse) + assert response.status_code == 400 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "MISSING_FILTER" @patch('src.routers.movies.get_collection') async def test_update_movies_batch_no_matches(self, mock_get_collection): @@ -700,22 +733,26 @@ async def test_search_movies_with_pagination(self, mock_execute_aggregation): async def test_search_movies_no_parameters(self): """Should return error when no search parameters provided.""" from src.routers.movies import search_movies - with pytest.raises(HTTPException) as e: - await search_movies(search_operator="must") + response = await search_movies(search_operator="must") # Assertions - assert e.value.status_code == 400 - assert "one search parameter" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 400 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "MISSING_SEARCH_PARAMS" async def test_search_movies_invalid_operator(self): """Should return error for invalid search operator.""" from src.routers.movies import search_movies - with pytest.raises(HTTPException) as e: - await search_movies(plot="test", search_operator="invalid") + response = await search_movies(plot="test", search_operator="invalid") # Assertions - assert e.value.status_code == 400 - assert "invalid search operator" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 400 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "INVALID_SEARCH_OPERATOR" @patch('src.routers.movies.execute_aggregation') async def test_search_movies_database_error(self, mock_execute_aggregation): @@ -725,12 +762,14 @@ async def test_search_movies_database_error(self, mock_execute_aggregation): # Call the route handler from src.routers.movies import search_movies - with pytest.raises(HTTPException) as e: - await search_movies(plot="test", search_operator="must") + response = await search_movies(plot="test", search_operator="must") # Assertions - assert e.value.status_code == 500 - assert "error" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 500 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "SEARCH_ERROR" @patch('src.routers.movies.execute_aggregation') async def test_search_movies_empty_results(self, mock_execute_aggregation): @@ -770,7 +809,7 @@ async def test_vector_search_unavailable(self, mock_voyage_available): # Assertions assert isinstance(response, JSONResponse) - assert response.status_code == 400 + assert response.status_code == 503 # Parse the response body import json @@ -827,12 +866,14 @@ async def test_vector_search_embedding_error(self, mock_get_embedding, mock_voya # Call the route handler from src.routers.movies import vector_search_movies - with pytest.raises(HTTPException) as e: - await vector_search_movies(q="action movie") + response = await vector_search_movies(q="action movie") # Assertions - assert e.value.status_code == 500 - assert "error" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 500 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "VECTOR_SEARCH_ERROR" @patch('src.routers.movies.voyage_ai_available') @patch('src.routers.movies.voyageai.Client') @@ -926,12 +967,14 @@ async def test_aggregate_movies_by_movie_id(self, mock_execute_aggregation): async def test_aggregate_movies_invalid_movie_id(self): """Should return error for invalid movie ID format.""" from src.routers.movies import aggregate_movies_recent_commented - with pytest.raises(HTTPException) as e: - await aggregate_movies_recent_commented(movie_id="invalid_id") + response = await aggregate_movies_recent_commented(movie_id="invalid_id") # Assertions - assert e.value.status_code == 400 - assert "movie_id is not" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 400 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "INVALID_OBJECT_ID" @patch('src.routers.movies.execute_aggregation') async def test_aggregate_movies_database_error(self, mock_execute_aggregation): @@ -941,12 +984,14 @@ async def test_aggregate_movies_database_error(self, mock_execute_aggregation): # Call the route handler from src.routers.movies import aggregate_movies_recent_commented - with pytest.raises(HTTPException) as e: - await aggregate_movies_recent_commented(limit=10, movie_id=None) + response = await aggregate_movies_recent_commented(limit=10, movie_id=None) # Assertions - assert e.value.status_code == 500 - assert "error" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 500 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "DATABASE_ERROR" @patch('src.routers.movies.execute_aggregation') async def test_aggregate_movies_empty_results(self, mock_execute_aggregation): @@ -997,12 +1042,14 @@ async def test_aggregate_movies_by_year_database_error(self, mock_execute_aggreg # Call the route handler from src.routers.movies import aggregate_movies_by_year - with pytest.raises(HTTPException) as e: - await aggregate_movies_by_year() + response = await aggregate_movies_by_year() # Assertions - assert e.value.status_code == 500 - assert "error" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 500 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "DATABASE_ERROR" @patch('src.routers.movies.execute_aggregation') async def test_aggregate_movies_by_year_empty_results(self, mock_execute_aggregation): @@ -1071,12 +1118,14 @@ async def test_aggregate_directors_database_error(self, mock_execute_aggregation # Call the route handler from src.routers.movies import aggregate_directors_most_movies - with pytest.raises(HTTPException) as e: - await aggregate_directors_most_movies() + response = await aggregate_directors_most_movies() # Assertions - assert e.value.status_code == 500 - assert "error" in str(e.value.detail).lower() + assert isinstance(response, JSONResponse) + assert response.status_code == 500 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "DATABASE_ERROR" @patch('src.routers.movies.execute_aggregation') async def test_aggregate_directors_empty_results(self, mock_execute_aggregation): @@ -1164,9 +1213,11 @@ async def test_get_distinct_genres_database_error(self, mock_get_collection): # Call the route handler from src.routers.movies import get_distinct_genres - with pytest.raises(HTTPException) as exc_info: - await get_distinct_genres() + response = await get_distinct_genres() # Assertions - assert exc_info.value.status_code == 500 - assert "Database error" in str(exc_info.value.detail) + assert isinstance(response, JSONResponse) + assert response.status_code == 500 + body = json.loads(response.body.decode()) + assert body["success"] is False + assert body["error"]["code"] == "DATABASE_ERROR" From 6f773cbbc46d99b88810f5487b320e302c93ce7c Mon Sep 17 00:00:00 2001 From: Kyle Rollins <115574589+krollins-mdb@users.noreply.github.com> Date: Tue, 3 Mar 2026 10:37:00 -0600 Subject: [PATCH 03/11] Change references to "JavaScript" sample app to "Node" sample app --- .copier/config.yaml | 2 +- .github/ISSUE_TEMPLATE.md | 11 +++++++++-- README.md | 12 ++++++------ ...-JAVASCRIPT-EXPRESS.md => README-NODE-EXPRESS.md} | 8 ++++++-- 4 files changed, 22 insertions(+), 11 deletions(-) rename mflix/{README-JAVASCRIPT-EXPRESS.md => README-NODE-EXPRESS.md} (98%) diff --git a/.copier/config.yaml b/.copier/config.yaml index d9f108f..15b83b4 100644 --- a/.copier/config.yaml +++ b/.copier/config.yaml @@ -67,7 +67,7 @@ workflows: transformations: - move: { from: "mflix/client", to: "client" } - move: { from: "mflix/server/js-express", to: "server" } - - copy: { from: "mflix/README-JAVASCRIPT-EXPRESS.md", to: "README.md" } + - copy: { from: "mflix/README-NODE-EXPRESS.md", to: "README.md" } - copy: { from: "mflix/.gitignore-js", to: ".gitignore" } commit_strategy: pr_title: "Update MFlix application from docs-sample-apps" diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index bf7249c..d98787f 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -5,30 +5,38 @@ ## Sample App Information **Which sample app are you using?** + + - [ ] Java (Spring Boot) -- [ ] JavaScript (Express.js) +- [ ] Node (Express.js) - [ ] Python (FastAPI) ## Environment Details **MongoDB Database Version:** + **MongoDB Driver Version:** + **Deployment Type:** + + - [ ] Local MongoDB instance - [ ] MongoDB Atlas (cloud) - [ ] Docker container - [ ] Other (please specify): **Operating System:** + **Runtime Version:** + ## Steps to Reproduce @@ -73,4 +81,3 @@ Before submitting this issue, please confirm: - [ ] I have verified my MongoDB connection string is correct - [ ] I have installed all required dependencies - [ ] I have searched existing issues to avoid duplicates - diff --git a/README.md b/README.md index 506405f..1f44dc9 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ The sample app provides a Next.js frontend in the `client` directory, with the choice of three backend stacks in the `server` directory: - Java: Spring Boot -- JavaScript: Express.js +- Node.js: Express.js - Python: FastAPI ``` @@ -34,7 +34,7 @@ choice of three backend stacks in the `server` directory: This repository serves as the source for the following artifact repositories: - Java: [mongodb/sample-app-java-mflix](https://github.com/mongodb/sample-app-java-mflix) -- JavaScript: [mongodb/sample-app-nodejs-mflix](https://github.com/mongodb/sample-app-nodejs-mflix) +- Node.js: [mongodb/sample-app-nodejs-mflix](https://github.com/mongodb/sample-app-nodejs-mflix) - Python: [mongodb/sample-app-python-mflix](https://github.com/mongodb/sample-app-python-mflix) ## Development @@ -46,9 +46,9 @@ to a target repository for each sample app. For configuration details, refer to ### Branching Model For development, work from the `development` branch. Make incremental PRs -containing new features and bug fixes to `development`, *not* `main`. +containing new features and bug fixes to `development`, _not_ `main`. -When all development work is complete, *then* create a release PR from +When all development work is complete, _then_ create a release PR from `development` to `main`. Upon merging to `main,` the copier tool runs automatically. It creates a new PR in the target repository, which must be tested and merged manually. @@ -91,7 +91,7 @@ To test and verify the PR, navigate to the target repository - see - [ ] Run the tests - [ ] Run the application and verify that it functions as expected. - [ ] Review the `deprecated_examples.json` file for any files that need to be - deleted. If files are deleted: + deleted. If files are deleted: - [ ] Add a commit to the copier PR to delete the files from the target repository. - [ ] Merge the PR. @@ -100,7 +100,7 @@ To test and verify the PR, navigate to the target repository - see If you are a developer having issues with the sample app, feel free to open an issue in this repository. Please include the following information: -- [ ] The sample app you are using (Java, JavaScript, or Python) +- [ ] The sample app you are using (Java, Node.js, or Python) - [ ] The version of the MongoDB database you are using - [ ] The version of the MongoDB driver you are using - [ ] What type of deployment you're using (local, Atlas, etc.) diff --git a/mflix/README-JAVASCRIPT-EXPRESS.md b/mflix/README-NODE-EXPRESS.md similarity index 98% rename from mflix/README-JAVASCRIPT-EXPRESS.md rename to mflix/README-NODE-EXPRESS.md index f3c531c..4afd2ca 100644 --- a/mflix/README-JAVASCRIPT-EXPRESS.md +++ b/mflix/README-NODE-EXPRESS.md @@ -1,4 +1,4 @@ -# JavaScript Express.js MongoDB Sample MFlix Application +# Node Express.js MongoDB Sample MFlix Application This is a full-stack movie browsing application built with Express.js and Next.js, demonstrating MongoDB operations using the `sample_mflix` dataset. The application showcases CRUD operations, aggregations, and MongoDB Search using the native MongoDB Node.js driver. @@ -89,7 +89,6 @@ From the `server` directory, run: npm run dev ``` - Or for production mode, run: ```bash @@ -98,6 +97,7 @@ npm start ``` The server will start on `http://localhost:3001`. You can verify it's running by visiting: + - API root: http://localhost:3001/ - API documentation (Swagger UI): http://localhost:3001/api-docs @@ -126,6 +126,7 @@ The Next.js application will start on `http://localhost:3000`. ### 5. Access the Application Open your browser and navigate to: + - **Frontend:** http://localhost:3000 - **Backend API:** http://localhost:3001 - **API Documentation:** http://localhost:3001/api-docs @@ -147,6 +148,7 @@ Open your browser and navigate to: ### Backend Development The Express.js backend uses: + - **Express.js 5** for REST API - **MongoDB Node.js Driver** for database operations - **TypeScript** for type safety @@ -170,6 +172,7 @@ npm run test:coverage ### Frontend Development The Next.js frontend uses: + - **React 19** with TypeScript - **Next.js 16** with App Router - **Turbopack** for fast development builds @@ -196,6 +199,7 @@ npm start # Starts production server ``` The production build: + - Minifies and optimizes JavaScript and CSS - Optimizes images and assets - Generates static pages where possible From 24b6e7509366b7e30d8d07cfb514acdee88df0fd Mon Sep 17 00:00:00 2001 From: Cory Bullinger Date: Mon, 9 Feb 2026 11:22:01 -0500 Subject: [PATCH 04/11] Add verification scripts and unify envs across all three backends --- mflix/README-JAVA-SPRING.md | 25 +- mflix/README-NODE-EXPRESS.md | 12 +- mflix/README-PYTHON-FASTAPI.md | 28 +- mflix/check-requirements-java.sh | 438 ++++++++++++++++++ mflix/check-requirements-js.sh | 408 ++++++++++++++++ mflix/check-requirements-python.sh | 400 ++++++++++++++++ mflix/server/java-spring/.env.example | 20 +- .../src/main/resources/application.properties | 4 +- mflix/server/js-express/.env.example | 19 +- mflix/server/js-express/src/app.ts | 7 +- mflix/server/python-fastapi/.env.example | 14 +- .../src/database/mongo_client.py | 7 +- 12 files changed, 1326 insertions(+), 56 deletions(-) create mode 100755 mflix/check-requirements-java.sh create mode 100755 mflix/check-requirements-js.sh create mode 100755 mflix/check-requirements-python.sh diff --git a/mflix/README-JAVA-SPRING.md b/mflix/README-JAVA-SPRING.md index 245afc5..8905cab 100644 --- a/mflix/README-JAVA-SPRING.md +++ b/mflix/README-JAVA-SPRING.md @@ -28,6 +28,16 @@ The `sample_mflix` dataset contains movies released up to **2016**. Searching fo - **Voyage AI API key** (For MongoDB Vector Search) - [Get a Voyage AI API key](https://www.voyageai.com/) +## Verify Requirements + +Before getting started, you can run the verification script to check if you have all the necessary requirements: + +```bash +./check-requirements-java.sh +``` + +This script checks for required tools (Java, Maven, Node.js), validates your environment configuration, and verifies dependencies. Run with `--help` for more options. + ## Getting Started ### 1. Configure the Backend @@ -48,28 +58,19 @@ Edit the `.env` file and set your MongoDB connection string: ```env # MongoDB Connection -# Replace with your MongoDB Atlas connection string or local MongoDB URI MONGODB_URI=mongodb+srv://:@.mongodb.net/sample_mflix?retryWrites=true&w=majority -# Voyage AI Configuration -# API key for Voyage AI embedding model (required for Vector Search) +# Voyage AI Configuration (optional - required for Vector Search) VOYAGE_API_KEY=your_voyage_api_key # Server Configuration -# Port on which the Spring Boot application will run PORT=3001 # CORS Configuration -# Allowed origin for cross-origin requests (frontend URL) -# For multiple origins, separate with commas -CORS_ORIGIN=http://localhost:3000 - -# Optional: Enable MongoDB Search tests -# Uncomment the following line to enable Search tests -# ENABLE_SEARCH_TESTS=true +CORS_ORIGINS=http://localhost:3000 ``` -**Note:** Replace `username`, `password`, and `cluster` with your +**Note:** Replace ``, ``, and `` with your actual MongoDB Atlas credentials. Replace `your_voyage_api_key` with your key. diff --git a/mflix/README-NODE-EXPRESS.md b/mflix/README-NODE-EXPRESS.md index 4afd2ca..273f0e5 100644 --- a/mflix/README-NODE-EXPRESS.md +++ b/mflix/README-NODE-EXPRESS.md @@ -27,6 +27,16 @@ The `sample_mflix` dataset contains movies released up to **2016**. Searching fo - **Voyage AI API key** (For MongoDB Vector Search) - [Get a Voyage AI API key](https://www.voyageai.com/) +## Verify Requirements + +Before getting started, you can run the verification script to check if you have all the necessary requirements: + +```bash +./check-requirements-js.sh +``` + +This script checks for required tools (Node.js, npm), validates your environment configuration, and verifies dependencies. Run with `--help` for more options. + ## Getting Started ### 1. Configure the Backend @@ -61,7 +71,7 @@ NODE_ENV=development # CORS Configuration # Allowed origin for cross-origin requests (frontend URL) # For multiple origins, separate with commas -CORS_ORIGIN=http://localhost:3000 +CORS_ORIGINS=http://localhost:3000 # Optional: Enable MongoDB Search tests # Uncomment the following line to enable Search tests diff --git a/mflix/README-PYTHON-FASTAPI.md b/mflix/README-PYTHON-FASTAPI.md index 3d79f3b..4b3e49f 100644 --- a/mflix/README-PYTHON-FASTAPI.md +++ b/mflix/README-PYTHON-FASTAPI.md @@ -31,6 +31,16 @@ The `sample_mflix` dataset contains movies released up to **2016**. Searching fo - **Voyage AI API key** (For MongoDB Vector Search) - [Get a Voyage AI API key](https://www.voyageai.com/) +## Verify Requirements + +Before getting started, you can run the verification script to check if you have all the necessary requirements: + +```bash +./check-requirements-python.sh +``` + +This script checks for required tools (Python, pip, Node.js), validates your environment configuration, and verifies dependencies. Run with `--help` for more options. + ## Getting Started ### 1. Configure the Backend @@ -50,21 +60,21 @@ cp .env.example .env Edit the `.env` file and set your MongoDB connection string: ```env -# MongoDB Configuration -MONGO_URI=mongodb+srv://:@.mongodb.net/sample_mflix?retryWrites=true&w=majority -MONGO_DB=sample_mflix +# MongoDB Connection +MONGODB_URI=mongodb+srv://:@.mongodb.net/sample_mflix?retryWrites=true&w=majority -# Voyage AI Configuration -# API key for Voyage AI embedding model (required for Vector Search) +# Voyage AI Configuration (optional - required for Vector Search) VOYAGE_API_KEY=your_voyage_api_key +# Server Configuration +PORT=3001 + # CORS Configuration -# Comma-separated list of allowed origins for CORS -CORS_ORIGINS=http://localhost:3000,http://localhost:3001 +CORS_ORIGINS=http://localhost:3000 ``` -**Note:** Replace `username`, `password`, and `cluster` with your actual MongoDB Atlas -credentials. +**Note:** Replace ``, ``, and `` with your actual MongoDB Atlas +credentials. Replace `your_voyage_api_key` with your key. Make a virtual environment: diff --git a/mflix/check-requirements-java.sh b/mflix/check-requirements-java.sh new file mode 100755 index 0000000..e857edf --- /dev/null +++ b/mflix/check-requirements-java.sh @@ -0,0 +1,438 @@ +#!/bin/bash + +# ============================================================================= +# Java/Spring Boot Sample App - Requirements Verification Script +# ============================================================================= +# This script verifies that all requirements are met to run the Java/Spring Boot +# backend sample application. It checks for required tools, dependencies, and +# environment configuration. +# +# Usage: +# ./check-requirements-java.sh # Check all requirements +# ./check-requirements-java.sh --setup # Check and auto-setup missing items +# ============================================================================= + +set -e + +# Get script directory (works even if script is sourced) +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +# Configuration - adjusted for artifact repo structure +SERVER_DIR="server" +CLIENT_DIR="client" +JAVA_MIN_VERSION="21" +NODE_MIN_VERSION="18" + +# Setup mode flag +SETUP_MODE=false +if [[ "$1" == "--setup" ]]; then + SETUP_MODE=true +fi + +# Counters +CHECKS_PASSED=0 +CHECKS_FAILED=0 +CHECKS_WARNED=0 + +# Colors +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# ============================================================================= +# Helper Functions +# ============================================================================= + +print_header() { + echo "" + echo -e "${BLUE}======================================${NC}" + echo -e "${BLUE} $1${NC}" + echo -e "${BLUE}======================================${NC}" +} + +print_subheader() { + echo "" + echo -e "${BLUE}--- $1 ---${NC}" +} + +check_pass() { + echo -e "${GREEN}✓${NC} $1" + CHECKS_PASSED=$((CHECKS_PASSED + 1)) +} + +check_fail() { + echo -e "${RED}✗${NC} $1" + CHECKS_FAILED=$((CHECKS_FAILED + 1)) +} + +check_warn() { + echo -e "${YELLOW}!${NC} $1" + CHECKS_WARNED=$((CHECKS_WARNED + 1)) +} + +check_info() { + echo -e " ${BLUE}→${NC} $1" +} + +version_gte() { + # Returns 0 (true) if $1 >= $2 + [ "$1" -ge "$2" ] 2>/dev/null +} + +command_exists() { + command -v "$1" &>/dev/null +} + +# ============================================================================= +# Common Requirements +# ============================================================================= + +check_common_requirements() { + print_subheader "Common Requirements" + + # Check Git + if command_exists git; then + GIT_VERSION=$(git --version | grep -oE '[0-9]+\.[0-9]+' | head -1) + check_pass "Git installed (v$GIT_VERSION)" + else + check_fail "Git not installed" + check_info "Install Git from https://git-scm.com/" + fi + + # Check curl + if command_exists curl; then + check_pass "curl installed" + else + check_warn "curl not installed (optional, but useful for testing)" + fi +} + +# ============================================================================= +# Java/Spring Boot Requirements +# ============================================================================= + +check_java_requirements() { + print_subheader "Java/Spring Boot Backend" + + local server_dir="$SCRIPT_DIR/$SERVER_DIR" + + # Check Java + if command_exists java; then + # Get Java version - handle different version formats + JAVA_VERSION_OUTPUT=$(java -version 2>&1 | head -n1) + JAVA_VERSION=$(echo "$JAVA_VERSION_OUTPUT" | sed -E 's/.*version "([0-9]+).*/\1/') + + if [ -n "$JAVA_VERSION" ] && version_gte "$JAVA_VERSION" "$JAVA_MIN_VERSION"; then + check_pass "Java installed: version $JAVA_VERSION (>= $JAVA_MIN_VERSION required)" + else + check_fail "Java version $JAVA_VERSION is too old (>= $JAVA_MIN_VERSION required)" + check_info "Install Java $JAVA_MIN_VERSION+ from:" + check_info " - Eclipse Temurin: https://adoptium.net/" + check_info " - Oracle JDK: https://www.oracle.com/java/technologies/downloads/" + check_info " - Or use SDKMAN: https://sdkman.io/" + fi + else + check_fail "Java not installed" + check_info "Install Java $JAVA_MIN_VERSION+ from:" + check_info " - Eclipse Temurin: https://adoptium.net/" + check_info " - Oracle JDK: https://www.oracle.com/java/technologies/downloads/" + check_info " - Or use SDKMAN: https://sdkman.io/" + return + fi + + # Check JAVA_HOME + if [ -n "$JAVA_HOME" ]; then + if [ -d "$JAVA_HOME" ]; then + check_pass "JAVA_HOME is set: $JAVA_HOME" + else + check_warn "JAVA_HOME is set but directory doesn't exist: $JAVA_HOME" + fi + else + check_warn "JAVA_HOME is not set (may cause issues with some tools)" + check_info "Set JAVA_HOME to your Java installation directory" + fi + + + # Check Maven wrapper + if [ -f "$server_dir/mvnw" ]; then + check_pass "Maven wrapper (mvnw) found" + + # Check if mvnw is executable + if [ -x "$server_dir/mvnw" ]; then + check_pass "Maven wrapper is executable" + else + check_warn "Maven wrapper is not executable" + if [ "$SETUP_MODE" = true ]; then + chmod +x "$server_dir/mvnw" + check_pass "Made Maven wrapper executable" + else + check_info "Run: chmod +x $server_dir/mvnw" + fi + fi + + # Try to get Maven version + MAVEN_VERSION=$(cd "$server_dir" && ./mvnw --version 2>/dev/null | grep "Apache Maven" | awk '{print $3}') + if [ -n "$MAVEN_VERSION" ]; then + check_pass "Maven version: $MAVEN_VERSION" + fi + else + check_fail "Maven wrapper (mvnw) not found in $server_dir" + check_info "The Maven wrapper should be included in the repository" + fi + + # Check if Maven dependencies are downloaded + if [ -d "$server_dir/target" ]; then + check_pass "Maven target directory exists (dependencies likely downloaded)" + else + check_warn "Maven target directory not found" + if [ "$SETUP_MODE" = true ]; then + check_info "Downloading Maven dependencies..." + if (cd "$server_dir" && ./mvnw dependency:resolve -q); then + check_pass "Maven dependencies downloaded successfully" + else + check_fail "Failed to download Maven dependencies" + fi + else + check_info "Run: cd $server_dir && ./mvnw dependency:resolve" + fi + fi + + # Check if project compiles + if [ -d "$server_dir/target/classes" ]; then + check_pass "Project appears to be compiled" + else + check_warn "Project not compiled yet" + if [ "$SETUP_MODE" = true ]; then + check_info "Compiling project..." + if (cd "$server_dir" && ./mvnw compile -q); then + check_pass "Project compiled successfully" + else + check_fail "Failed to compile project" + fi + else + check_info "Run: cd $server_dir && ./mvnw compile" + fi + fi +} + +# ============================================================================= +# Environment Configuration +# ============================================================================= + +check_env_configuration() { + print_subheader "Environment Configuration" + + local server_dir="$SCRIPT_DIR/$SERVER_DIR" + local env_file="$server_dir/.env" + local example_file="$server_dir/.env.example" + + if [ -f "$env_file" ]; then + check_pass ".env file exists" + + # Check MongoDB URI + if grep -q "^MONGODB_URI=" "$env_file" 2>/dev/null; then + if grep -qE "^MONGODB_URI=.*<.*>" "$env_file" 2>/dev/null; then + check_warn "MONGODB_URI appears to be a placeholder - update with your connection string" + elif grep -qE "^MONGODB_URI=.+" "$env_file" 2>/dev/null; then + check_pass "MONGODB_URI is configured" + else + check_warn "MONGODB_URI is empty" + fi + else + check_fail "MONGODB_URI not found in .env" + fi + + # Check Voyage AI key (optional) + if grep -q "^VOYAGE_API_KEY=" "$env_file" 2>/dev/null; then + if grep -qE "^VOYAGE_API_KEY=your" "$env_file" 2>/dev/null || \ + grep -qE "^VOYAGE_API_KEY=$" "$env_file" 2>/dev/null; then + check_info "VOYAGE_API_KEY not configured (optional - needed for vector search)" + else + check_pass "VOYAGE_API_KEY is configured" + fi + else + check_info "VOYAGE_API_KEY not found (optional - needed for vector search)" + fi + + # Check CORS_ORIGINS + if grep -q "^CORS_ORIGINS=" "$env_file" 2>/dev/null; then + check_pass "CORS_ORIGINS is configured" + else + check_info "CORS_ORIGINS not found (will use default: http://localhost:3000)" + fi + + # Check PORT + if grep -q "^PORT=" "$env_file" 2>/dev/null; then + check_pass "PORT is configured" + else + check_info "PORT not found (will use default)" + fi + else + check_warn ".env file not found" + if [ -f "$example_file" ]; then + check_info ".env.example exists - use it as a template" + if [ "$SETUP_MODE" = true ]; then + cp "$example_file" "$env_file" + check_pass "Created .env from .env.example" + check_warn "Please edit $env_file with your actual values" + else + check_info "Run: cp $example_file $env_file" + check_info "Then edit .env with your actual values" + fi + else + check_fail "No .env.example found to use as template" + fi + fi +} + + +# ============================================================================= +# Frontend Requirements +# ============================================================================= + +check_frontend_requirements() { + print_header "Frontend Requirements (Next.js)" + + local client_dir="$SCRIPT_DIR/$CLIENT_DIR" + + # Check Node.js (required for frontend) + print_subheader "Node.js" + if command_exists node; then + NODE_VERSION=$(node --version 2>/dev/null | sed 's/v//') + NODE_MAJOR=$(echo "$NODE_VERSION" | cut -d. -f1) + + if [ "$NODE_MAJOR" -ge "$NODE_MIN_VERSION" ] 2>/dev/null; then + check_pass "Node.js installed: v$NODE_VERSION (required: >= $NODE_MIN_VERSION)" + else + check_fail "Node.js version $NODE_VERSION is too old (required: >= $NODE_MIN_VERSION)" + check_info "Install Node.js $NODE_MIN_VERSION+ from: https://nodejs.org/" + fi + else + check_fail "Node.js not installed" + check_info "Install Node.js $NODE_MIN_VERSION+ from: https://nodejs.org/" + return + fi + + # Check npm + print_subheader "npm" + if command_exists npm; then + NPM_VERSION=$(npm --version 2>/dev/null) + check_pass "npm installed: v$NPM_VERSION" + else + check_fail "npm not installed" + check_info "npm should be installed with Node.js" + return + fi + + # Check client dependencies + print_subheader "Frontend Dependencies" + if [ -d "$client_dir/node_modules" ]; then + check_pass "Frontend dependencies installed" + + # Check for Next.js + if [ -d "$client_dir/node_modules/next" ]; then + check_pass "Next.js is installed" + else + check_warn "Next.js not found in dependencies" + fi + + # Check for React + if [ -d "$client_dir/node_modules/react" ]; then + check_pass "React is installed" + else + check_warn "React not found in dependencies" + fi + else + check_warn "Frontend dependencies not installed" + if [ "$SETUP_MODE" = true ]; then + check_info "Installing frontend dependencies..." + if (cd "$client_dir" && npm install); then + check_pass "Frontend dependencies installed successfully" + else + check_fail "Failed to install frontend dependencies" + fi + else + check_info "Run: cd $client_dir && npm install" + fi + fi +} + +# ============================================================================= +# Summary +# ============================================================================= + +print_summary() { + echo "" + echo "=============================================================================" + echo " SUMMARY" + echo "=============================================================================" + echo "" + + if [ "$CHECKS_FAILED" -eq 0 ]; then + echo -e "${GREEN}✓ All checks passed!${NC}" + else + echo -e "${RED}✗ Some checks failed${NC}" + fi + + echo "" + echo -e " ${GREEN}Passed:${NC} $CHECKS_PASSED" + echo -e " ${RED}Failed:${NC} $CHECKS_FAILED" + echo -e " ${YELLOW}Warnings:${NC} $CHECKS_WARNED" + echo "" + + if [ "$CHECKS_FAILED" -gt 0 ]; then + echo "Review the failed checks above and follow the instructions to resolve them." + echo "Run with --setup flag to automatically fix some issues: ./check-requirements-java.sh --setup" + echo "" + fi +} + +# ============================================================================= +# Main Execution +# ============================================================================= + +main() { + echo "=============================================================================" + echo " Java/Spring Boot Backend - Requirements Verification" + echo "=============================================================================" + echo "" + + check_common_requirements + check_java_requirements + check_env_configuration + check_frontend_requirements + print_summary + + # Exit with error code if any checks failed + if [ "$CHECKS_FAILED" -gt 0 ]; then + exit 1 + fi +} + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --setup) + SETUP_MODE=true + shift + ;; + --help|-h) + echo "Usage: ./check-requirements-java.sh [OPTIONS]" + echo "" + echo "Options:" + echo " --setup Automatically set up missing requirements where possible" + echo " --help Show this help message" + echo "" + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + +main \ No newline at end of file diff --git a/mflix/check-requirements-js.sh b/mflix/check-requirements-js.sh new file mode 100755 index 0000000..4fea4c6 --- /dev/null +++ b/mflix/check-requirements-js.sh @@ -0,0 +1,408 @@ +#!/bin/bash +# +# Requirements Verification Script for mflix Sample Application +# JavaScript/Express Backend (Node.js + Express + MongoDB) +# +# This script checks if you have all the necessary requirements to run the +# mflix sample application with the JavaScript/Express backend. +# +# Usage: +# ./check-requirements-js.sh # Check all requirements +# ./check-requirements-js.sh --setup # Check and auto-setup missing items +# ./check-requirements-js.sh --help # Show help message +# + +set -e + +# ============================================================================= +# Configuration +# ============================================================================= + +# Server directory (in artifact repo, server/js-express becomes just server) +SERVER_DIR="server" + +# ============================================================================= +# Colors for output +# ============================================================================= + +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# ============================================================================= +# Counters +# ============================================================================= + +CHECKS_PASSED=0 +CHECKS_FAILED=0 +CHECKS_WARNED=0 + +# ============================================================================= +# Helper Functions +# ============================================================================= + +check_pass() { + echo -e "${GREEN}✓${NC} $1" + CHECKS_PASSED=$((CHECKS_PASSED + 1)) +} + +check_fail() { + echo -e "${RED}✗${NC} $1" + CHECKS_FAILED=$((CHECKS_FAILED + 1)) +} + +check_warn() { + echo -e "${YELLOW}!${NC} $1" + CHECKS_WARNED=$((CHECKS_WARNED + 1)) +} + +check_info() { + echo -e "${BLUE}ℹ${NC} $1" +} + +print_section() { + echo "" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BLUE} $1${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +} + +# ============================================================================= +# Check Common Requirements +# ============================================================================= + +check_common_requirements() { + print_section "Common Requirements" + + # Check Git + if command -v git &> /dev/null; then + local git_version=$(git --version | cut -d' ' -f3) + check_pass "Git installed (version $git_version)" + else + check_fail "Git not installed" + check_info "Install Git: https://git-scm.com/downloads" + fi + + # Check curl (useful for API testing) + if command -v curl &> /dev/null; then + check_pass "curl installed" + else + check_warn "curl not installed (optional, useful for API testing)" + fi +} + +# ============================================================================= +# Check Node.js/Express Requirements +# ============================================================================= + +check_node_requirements() { + print_section "Node.js/Express Backend Requirements" + + # Check Node.js + if command -v node &> /dev/null; then + local node_version=$(node --version | sed 's/v//') + local node_major=$(echo "$node_version" | cut -d. -f1) + if [ "$node_major" -ge 18 ]; then + check_pass "Node.js installed (version $node_version)" + else + check_fail "Node.js version $node_version is below minimum required (18+)" + check_info "Install Node.js 18+: https://nodejs.org/" + fi + else + check_fail "Node.js not installed" + check_info "Install Node.js 18+: https://nodejs.org/" + return + fi + + # Check npm + if command -v npm &> /dev/null; then + local npm_version=$(npm --version) + check_pass "npm installed (version $npm_version)" + else + check_fail "npm not installed" + check_info "npm should come with Node.js installation" + return + fi + + # Check server directory + if [ ! -d "$SERVER_DIR" ]; then + check_fail "Server directory not found: $SERVER_DIR" + return + fi + + # Check package.json + if [ -f "$SERVER_DIR/package.json" ]; then + check_pass "package.json found" + else + check_fail "package.json not found in $SERVER_DIR" + return + fi + + # Check node_modules + if [ -d "$SERVER_DIR/node_modules" ]; then + check_pass "node_modules directory exists" + + # Check key dependencies + if [ -d "$SERVER_DIR/node_modules/express" ]; then + check_pass "Express.js dependency installed" + else + check_fail "Express.js dependency not installed" + fi + + if [ -d "$SERVER_DIR/node_modules/mongodb" ]; then + check_pass "MongoDB driver dependency installed" + else + check_fail "MongoDB driver dependency not installed" + fi + + # Check TypeScript build + if [ -d "$SERVER_DIR/dist" ]; then + check_pass "TypeScript build output exists (dist directory)" + else + check_warn "TypeScript build output not found (dist directory)" + check_info "Run 'npm run build' in $SERVER_DIR to build" + fi + else + check_fail "node_modules directory not found" + if [ "$SETUP_MODE" = true ]; then + check_info "Attempting to install dependencies..." + if (cd "$SERVER_DIR" && npm install); then + check_pass "Dependencies installed successfully" + else + check_fail "Failed to install dependencies" + check_info "Run 'npm install' manually in $SERVER_DIR" + fi + else + check_info "Run 'npm install' in $SERVER_DIR or use --setup flag" + fi + fi +} + +# ============================================================================= +# Check Environment Configuration +# ============================================================================= + +check_env_configuration() { + print_section "Environment Configuration" + + local env_file="$SERVER_DIR/.env" + local env_example="$SERVER_DIR/.env.example" + + # Check .env file + if [ -f "$env_file" ]; then + check_pass ".env file exists" + + # Check MONGODB_URI + if grep -q "^MONGODB_URI=" "$env_file" 2>/dev/null; then + local mongo_uri=$(grep "^MONGODB_URI=" "$env_file" | cut -d'=' -f2-) + if [ -n "$mongo_uri" ] && [ "$mongo_uri" != "mongodb+srv://:@.mongodb.net/" ]; then + check_pass "MONGODB_URI is configured" + else + check_fail "MONGODB_URI is not configured (still has placeholder value)" + check_info "Update MONGODB_URI in $env_file with your MongoDB connection string" + fi + else + check_fail "MONGODB_URI not found in .env" + check_info "Add MONGODB_URI= to $env_file" + fi + + # Check VOYAGE_API_KEY (optional) + if grep -q "^VOYAGE_API_KEY=" "$env_file" 2>/dev/null; then + local voyage_key=$(grep "^VOYAGE_API_KEY=" "$env_file" | cut -d'=' -f2-) + if [ -n "$voyage_key" ] && [ "$voyage_key" != "" ]; then + check_pass "VOYAGE_API_KEY is configured" + else + check_warn "VOYAGE_API_KEY has placeholder value (optional for vector search)" + fi + else + check_info "VOYAGE_API_KEY not set (optional, needed for vector search features)" + fi + + # Check CORS_ORIGINS (optional) + if grep -q "^CORS_ORIGINS=" "$env_file" 2>/dev/null; then + check_pass "CORS_ORIGINS is configured" + else + check_info "CORS_ORIGINS not set (will use default: http://localhost:3000)" + fi + + # Check PORT (optional) + if grep -q "^PORT=" "$env_file" 2>/dev/null; then + local port=$(grep "^PORT=" "$env_file" | cut -d'=' -f2-) + check_pass "PORT is configured ($port)" + else + check_info "PORT not set (will use default: 3001)" + fi + + # Check LOG_LEVEL (optional) + if grep -q "^LOG_LEVEL=" "$env_file" 2>/dev/null; then + check_pass "LOG_LEVEL is configured" + else + check_info "LOG_LEVEL not set (will use default)" + fi + else + check_fail ".env file not found" + if [ -f "$env_example" ]; then + if [ "$SETUP_MODE" = true ]; then + check_info "Attempting to create .env from .env.example..." + if cp "$env_example" "$env_file"; then + check_pass ".env file created from .env.example" + check_warn "Please update the placeholder values in $env_file" + else + check_fail "Failed to create .env file" + fi + else + check_info "Copy .env.example to .env: cp $env_example $env_file" + check_info "Or use --setup flag to create automatically" + fi + else + check_info "Create a .env file with required configuration" + check_info "Required: MONGODB_URI" + check_info "Optional: VOYAGE_API_KEY, CORS_ORIGINS, PORT, LOG_LEVEL" + fi + fi +} + +# ============================================================================= +# Check Frontend Requirements +# ============================================================================= + +check_frontend_requirements() { + print_section "Frontend Requirements (Next.js)" + + local client_dir="client" + + # Check client directory + if [ ! -d "$client_dir" ]; then + check_warn "Client directory not found: $client_dir" + check_info "Frontend may be in a separate repository" + return + fi + + # Check package.json + if [ -f "$client_dir/package.json" ]; then + check_pass "Frontend package.json found" + else + check_fail "Frontend package.json not found" + return + fi + + # Check node_modules + if [ -d "$client_dir/node_modules" ]; then + check_pass "Frontend node_modules exists" + + # Check Next.js + if [ -d "$client_dir/node_modules/next" ]; then + check_pass "Next.js dependency installed" + else + check_fail "Next.js dependency not installed" + fi + + # Check React + if [ -d "$client_dir/node_modules/react" ]; then + check_pass "React dependency installed" + else + check_fail "React dependency not installed" + fi + else + check_fail "Frontend node_modules not found" + if [ "$SETUP_MODE" = true ]; then + check_info "Attempting to install frontend dependencies..." + if (cd "$client_dir" && npm install); then + check_pass "Frontend dependencies installed successfully" + else + check_fail "Failed to install frontend dependencies" + check_info "Run 'npm install' manually in $client_dir" + fi + else + check_info "Run 'npm install' in $client_dir or use --setup flag" + fi + fi +} + +# ============================================================================= +# Print Summary +# ============================================================================= + +print_summary() { + echo "" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BLUE} Summary${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo "" + echo -e " ${GREEN}Passed:${NC} $CHECKS_PASSED" + echo -e " ${RED}Failed:${NC} $CHECKS_FAILED" + echo -e " ${YELLOW}Warnings:${NC} $CHECKS_WARNED" + echo "" + + if [ $CHECKS_FAILED -eq 0 ]; then + echo -e "${GREEN}All required checks passed!${NC}" + if [ $CHECKS_WARNED -gt 0 ]; then + echo -e "${YELLOW}There are some warnings to review.${NC}" + fi + else + echo -e "${RED}Some checks failed. Please address the issues above.${NC}" + if [ "$SETUP_MODE" != true ]; then + echo -e "${BLUE}Tip: Run with --setup flag to auto-fix some issues${NC}" + fi + fi + echo "" +} + +# ============================================================================= +# Main Execution +# ============================================================================= + +SETUP_MODE=false + +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --setup) + SETUP_MODE=true + shift + ;; + --help|-h) + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --setup Attempt to automatically set up missing requirements" + echo " --help Show this help message" + echo "" + echo "This script checks if you have all the necessary requirements" + echo "to run the mflix sample application with the JavaScript/Express backend." + exit 0 + ;; + *) + echo "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +done + +echo "" +echo -e "${BLUE}╔══════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ mflix Sample Application - Requirements Check ║${NC}" +echo -e "${BLUE}║ JavaScript/Express Backend ║${NC}" +echo -e "${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}" + +if [ "$SETUP_MODE" = true ]; then + echo -e "${YELLOW}Running in setup mode - will attempt to fix issues${NC}" +fi + +# Run all checks +check_common_requirements +check_node_requirements +check_env_configuration +check_frontend_requirements + +# Print summary +print_summary + +# Exit with appropriate code +if [ $CHECKS_FAILED -gt 0 ]; then + exit 1 +fi +exit 0 diff --git a/mflix/check-requirements-python.sh b/mflix/check-requirements-python.sh new file mode 100755 index 0000000..05007e7 --- /dev/null +++ b/mflix/check-requirements-python.sh @@ -0,0 +1,400 @@ +#!/bin/bash + +# ============================================================================= +# Requirements Verification Script for mflix Sample Application +# Python/FastAPI Backend +# ============================================================================= +# This script checks that all necessary requirements are installed to run +# the mflix sample application with the Python/FastAPI backend. +# +# Usage: ./check-requirements-python.sh [options] +# --setup Attempt to set up missing requirements +# --help Show this help message +# ============================================================================= + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Counters +CHECKS_PASSED=0 +CHECKS_FAILED=0 +CHECKS_WARNED=0 + +# Options +SETUP_MODE=false + +# Configuration +PYTHON_MIN_VERSION="3.11" +SERVER_DIR="server" +CLIENT_DIR="client" + +# ============================================================================= +# Helper Functions +# ============================================================================= + +print_header() { + echo "" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BLUE} $1${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +} + +print_subheader() { + echo "" + echo -e "${YELLOW}▸ $1${NC}" +} + +check_pass() { + echo -e " ${GREEN}✓${NC} $1" + CHECKS_PASSED=$((CHECKS_PASSED + 1)) +} + +check_fail() { + echo -e " ${RED}✗${NC} $1" + CHECKS_FAILED=$((CHECKS_FAILED + 1)) +} + +check_warn() { + echo -e " ${YELLOW}⚠${NC} $1" + CHECKS_WARNED=$((CHECKS_WARNED + 1)) +} + +check_info() { + echo -e " ${BLUE}ℹ${NC} $1" +} + +version_gte() { + [ "$(printf '%s\n' "$2" "$1" | sort -V | head -n1)" = "$2" ] +} + +command_exists() { + command -v "$1" >/dev/null 2>&1 +} + +# ============================================================================= +# Parse Arguments +# ============================================================================= + +show_help() { + echo "Usage: ./check-requirements-python.sh [options]" + echo "" + echo "Options:" + echo " --setup Attempt to set up missing requirements" + echo " --help Show this help message" + echo "" + echo "Examples:" + echo " ./check-requirements-python.sh # Check all requirements" + echo " ./check-requirements-python.sh --setup # Check and set up missing items" +} + +while [[ $# -gt 0 ]]; do + case $1 in + --setup) + SETUP_MODE=true + shift + ;; + --help) + show_help + exit 0 + ;; + *) + echo "Unknown option: $1" + show_help + exit 1 + ;; + esac +done + +# ============================================================================= +# Get Script Directory +# ============================================================================= + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +print_header "mflix Sample Application - Python/FastAPI Requirements Check" +echo "" +echo "Setup mode: $SETUP_MODE" +echo "Working directory: $SCRIPT_DIR" + +# ============================================================================= +# Common Requirements +# ============================================================================= + +check_common_requirements() { + print_subheader "Common Requirements" + + # Check Git + if command_exists git; then + local git_version + git_version=$(git --version | awk '{print $3}') + check_pass "Git installed (version $git_version)" + else + check_fail "Git not installed" + check_info "Install Git: https://git-scm.com/downloads" + fi + + # Check curl + if command_exists curl; then + check_pass "curl installed" + else + check_fail "curl not installed" + check_info "Install curl using your package manager" + fi +} + +# ============================================================================= +# Python Backend Requirements +# ============================================================================= + +check_python_requirements() { + print_subheader "Python/FastAPI Backend Requirements" + + # Check Python version + if command_exists python3; then + PYTHON_VERSION=$(python3 --version 2>&1 | grep -oE '[0-9]+\.[0-9]+' | head -1) + if version_gte "$PYTHON_VERSION" "$PYTHON_MIN_VERSION"; then + check_pass "Python $PYTHON_VERSION installed (>= $PYTHON_MIN_VERSION required)" + else + check_fail "Python $PYTHON_VERSION installed but >= $PYTHON_MIN_VERSION required" + check_info "Install Python $PYTHON_MIN_VERSION+ from https://www.python.org/downloads/" + fi + else + check_fail "Python 3 not installed" + check_info "Install Python $PYTHON_MIN_VERSION+ from https://www.python.org/downloads/" + fi + + # Check pip + if command_exists pip3 || python3 -m pip --version &>/dev/null; then + check_pass "pip installed" + else + check_fail "pip not installed" + check_info "Install pip: python3 -m ensurepip --upgrade" + fi + + # Check virtual environment + local venv_dir="$SCRIPT_DIR/$SERVER_DIR/.venv" + if [[ -d "$venv_dir" ]]; then + check_pass "Python virtual environment exists at $SERVER_DIR/.venv" + + # Check if venv is activated or can be used + if [[ -f "$venv_dir/bin/activate" ]]; then + check_pass "Virtual environment activation script exists" + else + check_warn "Virtual environment activation script missing" + fi + else + check_warn "Python virtual environment not found at $SERVER_DIR/.venv" + if [[ "$SETUP_MODE" == true ]]; then + check_info "Creating virtual environment..." + if python3 -m venv "$venv_dir"; then + check_pass "Virtual environment created" + else + check_fail "Failed to create virtual environment" + fi + else + check_info "Create with: cd $SERVER_DIR && python3 -m venv .venv" + fi + fi + + # Check Python dependencies + local requirements_file="$SCRIPT_DIR/$SERVER_DIR/requirements.txt" + if [[ -f "$requirements_file" ]]; then + check_pass "requirements.txt found" + + # Check if key dependencies are installed + if [[ -d "$venv_dir" ]]; then + local pip_cmd="$venv_dir/bin/pip" + if [[ -f "$pip_cmd" ]]; then + # Check FastAPI + if "$pip_cmd" show fastapi &>/dev/null; then + check_pass "FastAPI installed in virtual environment" + else + check_warn "FastAPI not installed in virtual environment" + if [[ "$SETUP_MODE" == true ]]; then + check_info "Installing dependencies..." + if "$pip_cmd" install -r "$requirements_file" &>/dev/null; then + check_pass "Dependencies installed" + else + check_fail "Failed to install dependencies" + fi + else + check_info "Install with: source $SERVER_DIR/.venv/bin/activate && pip install -r $SERVER_DIR/requirements.txt" + fi + fi + + # Check PyMongo + if "$pip_cmd" show pymongo &>/dev/null; then + check_pass "PyMongo installed in virtual environment" + else + check_warn "PyMongo not installed in virtual environment" + fi + fi + fi + else + check_fail "requirements.txt not found at $SERVER_DIR/requirements.txt" + fi +} + +# ============================================================================= +# Environment Configuration +# ============================================================================= + +check_env_configuration() { + print_subheader "Environment Configuration" + + local env_file="$SCRIPT_DIR/$SERVER_DIR/.env" + local env_example="$SCRIPT_DIR/$SERVER_DIR/.env.example" + + # Check .env file exists + if [[ -f "$env_file" ]]; then + check_pass ".env file exists at $SERVER_DIR/.env" + else + check_warn ".env file not found at $SERVER_DIR/.env" + if [[ "$SETUP_MODE" == true ]] && [[ -f "$env_example" ]]; then + check_info "Copying .env.example to .env..." + if cp "$env_example" "$env_file"; then + check_pass ".env file created from .env.example" + check_info "Please update the values in $SERVER_DIR/.env" + else + check_fail "Failed to create .env file" + fi + else + check_info "Copy the example: cp $SERVER_DIR/.env.example $SERVER_DIR/.env" + fi + return + fi + + # Check required environment variables + if grep -q "^MONGODB_URI=" "$env_file" 2>/dev/null; then + local mongo_uri=$(grep "^MONGODB_URI=" "$env_file" | cut -d'=' -f2-) + if [[ -n "$mongo_uri" && "$mongo_uri" != "mongodb+srv://:@.mongodb.net/" ]]; then + check_pass "MONGODB_URI is configured" + else + check_fail "MONGODB_URI is not configured (still has placeholder value)" + check_info "Update MONGODB_URI in $SERVER_DIR/.env with your MongoDB connection string" + fi + else + check_fail "MONGODB_URI not found in .env" + check_info "Add MONGODB_URI to $SERVER_DIR/.env" + fi + + # Check optional environment variables + if grep -q "^VOYAGE_API_KEY=" "$env_file" 2>/dev/null; then + local voyage_key=$(grep "^VOYAGE_API_KEY=" "$env_file" | cut -d'=' -f2-) + if [[ -n "$voyage_key" && "$voyage_key" != "" ]]; then + check_pass "VOYAGE_API_KEY is configured" + else + check_info "VOYAGE_API_KEY not configured (optional - needed for vector search)" + fi + else + check_info "VOYAGE_API_KEY not set (optional - needed for vector search)" + fi + + if grep -q "^CORS_ORIGINS=" "$env_file" 2>/dev/null; then + check_pass "CORS_ORIGINS is configured" + else + check_info "CORS_ORIGINS not set (will use default: http://localhost:3000)" + fi + + if grep -q "^PORT=" "$env_file" 2>/dev/null; then + check_pass "PORT is configured" + else + check_info "PORT not set (will use default: 8000)" + fi +} + + + +# ============================================================================= +# Frontend Requirements (Next.js) +# ============================================================================= + +check_frontend_requirements() { + print_subheader "Frontend Requirements (Next.js)" + + # Check Node.js version + if command_exists node; then + NODE_VERSION=$(node --version 2>&1 | grep -oE '[0-9]+' | head -1) + if version_gte "$NODE_VERSION" "$NODE_MIN_VERSION"; then + check_pass "Node.js v$NODE_VERSION installed (>= v$NODE_MIN_VERSION required)" + else + check_fail "Node.js v$NODE_VERSION installed but >= v$NODE_MIN_VERSION required" + fi + else + check_fail "Node.js not installed" + check_info "Install Node.js $NODE_MIN_VERSION+ from https://nodejs.org/" + fi + + # Check npm + if command_exists npm; then + check_pass "npm installed" + else + check_fail "npm not installed" + fi + + # Check client dependencies + local client_dir="$SCRIPT_DIR/$CLIENT_DIR" + if [[ -d "$client_dir" ]]; then + if [[ -d "$client_dir/node_modules" ]]; then + check_pass "Client dependencies installed" + else + check_warn "Client dependencies not installed" + if [[ "$SETUP_MODE" == true ]]; then + check_info "Installing client dependencies..." + if (cd "$client_dir" && npm install &>/dev/null); then + check_pass "Client dependencies installed" + else + check_fail "Failed to install client dependencies" + fi + else + check_info "Install with: cd $CLIENT_DIR && npm install" + fi + fi + fi +} + +# ============================================================================= +# Summary +# ============================================================================= + +print_summary() { + echo "" + print_header "Summary" + echo -e "${GREEN}Passed:${NC} $CHECKS_PASSED" + echo -e "${RED}Failed:${NC} $CHECKS_FAILED" + echo -e "${YELLOW}Warnings:${NC} $CHECKS_WARNED" + echo "" + + if [[ $CHECKS_FAILED -gt 0 ]]; then + echo -e "${RED}Some checks failed. Please address the issues above.${NC}" + exit 1 + elif [[ $CHECKS_WARNED -gt 0 ]]; then + echo -e "${YELLOW}All critical checks passed, but there are warnings to review.${NC}" + exit 0 + else + echo -e "${GREEN}All checks passed! You're ready to run the application.${NC}" + exit 0 + fi +} + +# ============================================================================= +# Main Execution +# ============================================================================= + +main() { + print_header "Python/FastAPI Sample App - Requirements Check" + + check_common_requirements + check_python_requirements + check_env_configuration + check_frontend_requirements + + print_summary +} + +main diff --git a/mflix/server/java-spring/.env.example b/mflix/server/java-spring/.env.example index 049e6e9..465f95d 100644 --- a/mflix/server/java-spring/.env.example +++ b/mflix/server/java-spring/.env.example @@ -2,21 +2,19 @@ # Replace with your MongoDB Atlas connection string or local MongoDB URI MONGODB_URI=mongodb+srv://:@.mongodb.net/sample_mflix?retryWrites=true&w=majority -# Optional: Voyage AI Configuration -# API key for Voyage AI embedding model (required for Vector Search) +# Voyage AI Configuration (optional - required for Vector Search) # Get your API key from https://www.voyageai.com/ -# Uncomment the following line to enable vector search -# VOYAGE_API_KEY=your-api-key +VOYAGE_API_KEY=your_voyage_api_key # Server Configuration -# Port on which the Spring Boot application will run PORT=3001 # CORS Configuration -# Allowed origin for cross-origin requests (frontend URL) -# For multiple origins, separate with commas -CORS_ORIGIN=http://localhost:3000 +# Comma-separated list of allowed origins for cross-origin requests +CORS_ORIGINS=http://localhost:3000 -# Optional: Enable MongoDB Search tests -# Uncomment the following line to enable Search tests -# ENABLE_SEARCH_TESTS=true \ No newline at end of file +# Logging Configuration +# Log level: TRACE, DEBUG, INFO, WARN, ERROR (default: INFO) +LOG_LEVEL=INFO +# Optional: Path to log file (if not set, logs only to console) +# LOG_FILE=app.log \ No newline at end of file diff --git a/mflix/server/java-spring/src/main/resources/application.properties b/mflix/server/java-spring/src/main/resources/application.properties index ffe611f..47bd6d0 100644 --- a/mflix/server/java-spring/src/main/resources/application.properties +++ b/mflix/server/java-spring/src/main/resources/application.properties @@ -8,8 +8,8 @@ spring.data.mongodb.database=sample_mflix server.port=${PORT:3001} # CORS Configuration -# Allowed origins for cross-origin requests (typically the frontend URL) -cors.allowed.origins=${CORS_ORIGIN:http://localhost:3000} +# Comma-separated list of allowed origins for cross-origin requests +cors.allowed.origins=${CORS_ORIGINS:http://localhost:3000} # Voyage AI Configuration # API key for Voyage AI embedding model (required for vector search) diff --git a/mflix/server/js-express/.env.example b/mflix/server/js-express/.env.example index c870ee0..71ef3ab 100644 --- a/mflix/server/js-express/.env.example +++ b/mflix/server/js-express/.env.example @@ -2,24 +2,19 @@ # Replace with your MongoDB Atlas connection string or local MongoDB URI MONGODB_URI=mongodb+srv://:@.mongodb.net/sample_mflix?retryWrites=true&w=majority -# Voyage AI Configuration -# API key for Voyage AI embedding model (required for Vector Search) +# Voyage AI Configuration (optional - required for Vector Search) +# Get your API key from https://www.voyageai.com/ VOYAGE_API_KEY=your_voyage_api_key # Server Configuration PORT=3001 NODE_ENV=development +# CORS Configuration +# Comma-separated list of allowed origins for cross-origin requests +CORS_ORIGINS=http://localhost:3000 + # Logging Configuration # Available levels: error, warn, info, http, debug # Default: debug (development), info (production), error (test) -LOG_LEVEL=debug - -# CORS Configuration -# Allowed origin for cross-origin requests (frontend URL) -# For multiple origins, separate with commas -CORS_ORIGIN=http://localhost:3000 - -# Optional: Enable MongoDB Search tests -# Uncomment the following line to enable Search tests -# ENABLE_SEARCH_TESTS=true \ No newline at end of file +LOG_LEVEL=debug \ No newline at end of file diff --git a/mflix/server/js-express/src/app.ts b/mflix/server/js-express/src/app.ts index 6e3d389..d6f2e99 100644 --- a/mflix/server/js-express/src/app.ts +++ b/mflix/server/js-express/src/app.ts @@ -32,10 +32,15 @@ const PORT = process.env.PORT || 3001; * CORS Configuration * Allows the frontend to communicate with this Express backend * In production, this should be configured to only allow specific origins + * Supports multiple origins via comma-separated CORS_ORIGINS environment variable */ +const corsOrigins = (process.env.CORS_ORIGINS || "http://localhost:3000") + .split(",") + .map((origin) => origin.trim()); + app.use( cors({ - origin: process.env.CORS_ORIGIN || "http://localhost:3000", + origin: corsOrigins.length === 1 ? corsOrigins[0] : corsOrigins, credentials: true, }) ); diff --git a/mflix/server/python-fastapi/.env.example b/mflix/server/python-fastapi/.env.example index 6dc1d0d..74db84c 100644 --- a/mflix/server/python-fastapi/.env.example +++ b/mflix/server/python-fastapi/.env.example @@ -1,15 +1,17 @@ # MongoDB Connection # Replace with your MongoDB Atlas connection string or local MongoDB URI -MONGO_URI="mongodb+srv://:@.mongodb.net/sample_mflix?retryWrites=true&w=majority" -MONGO_DB="sample_mflix" +MONGODB_URI=mongodb+srv://:@.mongodb.net/sample_mflix?retryWrites=true&w=majority -# Voyage AI Configuration -# API key for Voyage AI embedding model (required for Vector Search) +# Voyage AI Configuration (optional - required for Vector Search) +# Get your API key from https://www.voyageai.com/ VOYAGE_API_KEY=your_voyage_api_key +# Server Configuration +PORT=3001 + # CORS Configuration -# Comma-separated list of allowed origins for CORS -CORS_ORIGINS="http://localhost:3000,http://localhost:3001" +# Comma-separated list of allowed origins for cross-origin requests +CORS_ORIGINS=http://localhost:3000 # Logging Configuration # Log level: DEBUG, INFO, WARNING, ERROR, CRITICAL (default: INFO) diff --git a/mflix/server/python-fastapi/src/database/mongo_client.py b/mflix/server/python-fastapi/src/database/mongo_client.py index 37ab816..b5bdf35 100644 --- a/mflix/server/python-fastapi/src/database/mongo_client.py +++ b/mflix/server/python-fastapi/src/database/mongo_client.py @@ -5,11 +5,14 @@ load_dotenv() -client = AsyncMongoClient(os.getenv("MONGO_URI"), +# Database name is hardcoded to sample_mflix for consistency across all backends +DATABASE_NAME = "sample_mflix" + +client = AsyncMongoClient(os.getenv("MONGODB_URI"), # Set application name appname="sample-app-python-mflix") -db = client[os.getenv("MONGO_DB")] +db = client[DATABASE_NAME] voyage_api_key = os.getenv("VOYAGE_API_KEY") if voyage_api_key: From eb5e27e7881e3578fd4d2c897e680aced6d63a75 Mon Sep 17 00:00:00 2001 From: Cory Bullinger Date: Wed, 11 Feb 2026 16:16:30 -0500 Subject: [PATCH 05/11] Remove comment --- mflix/server/python-fastapi/src/database/mongo_client.py | 1 - 1 file changed, 1 deletion(-) diff --git a/mflix/server/python-fastapi/src/database/mongo_client.py b/mflix/server/python-fastapi/src/database/mongo_client.py index b5bdf35..ac8e2d3 100644 --- a/mflix/server/python-fastapi/src/database/mongo_client.py +++ b/mflix/server/python-fastapi/src/database/mongo_client.py @@ -5,7 +5,6 @@ load_dotenv() -# Database name is hardcoded to sample_mflix for consistency across all backends DATABASE_NAME = "sample_mflix" client = AsyncMongoClient(os.getenv("MONGODB_URI"), From ec5f8566035fcce395891fd55c398b796466ad5e Mon Sep 17 00:00:00 2001 From: Cory Bullinger Date: Wed, 11 Feb 2026 16:25:57 -0500 Subject: [PATCH 06/11] Update copier workflow to include new check-requirements.sh scripts --- .copier/config.yaml | 3 +++ mflix/README-JAVA-SPRING.md | 2 +- mflix/README-NODE-EXPRESS.md | 2 +- mflix/README-PYTHON-FASTAPI.md | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.copier/config.yaml b/.copier/config.yaml index 15b83b4..7b8d177 100644 --- a/.copier/config.yaml +++ b/.copier/config.yaml @@ -43,6 +43,7 @@ workflows: - move: { from: "mflix/client", to: "client" } - move: { from: "mflix/server/java-spring", to: "server" } - copy: { from: "mflix/README-JAVA-SPRING.md", to: "README.md" } + - copy: { from: "mflix/check-requirements-java.sh", to: "check-requirements.sh" } - copy: { from: "mflix/.gitignore-java", to: ".gitignore" } commit_strategy: pr_title: "Update MFlix application from docs-sample-apps" @@ -68,6 +69,7 @@ workflows: - move: { from: "mflix/client", to: "client" } - move: { from: "mflix/server/js-express", to: "server" } - copy: { from: "mflix/README-NODE-EXPRESS.md", to: "README.md" } + - copy: { from: "mflix/check-requirements-js.sh", to: "check-requirements.sh" } - copy: { from: "mflix/.gitignore-js", to: ".gitignore" } commit_strategy: pr_title: "Update MFlix application from docs-sample-apps" @@ -93,6 +95,7 @@ workflows: - move: { from: "mflix/client", to: "client" } - move: { from: "mflix/server/python-fastapi", to: "server" } - copy: { from: "mflix/README-PYTHON-FASTAPI.md", to: "README.md" } + - copy: { from: "mflix/check-requirements-python.sh", to: "check-requirements.sh" } - copy: { from: "mflix/.gitignore-python", to: ".gitignore" } commit_strategy: pr_title: "Update MFlix application from docs-sample-apps" diff --git a/mflix/README-JAVA-SPRING.md b/mflix/README-JAVA-SPRING.md index 8905cab..a888664 100644 --- a/mflix/README-JAVA-SPRING.md +++ b/mflix/README-JAVA-SPRING.md @@ -33,7 +33,7 @@ The `sample_mflix` dataset contains movies released up to **2016**. Searching fo Before getting started, you can run the verification script to check if you have all the necessary requirements: ```bash -./check-requirements-java.sh +./check-requirements.sh ``` This script checks for required tools (Java, Maven, Node.js), validates your environment configuration, and verifies dependencies. Run with `--help` for more options. diff --git a/mflix/README-NODE-EXPRESS.md b/mflix/README-NODE-EXPRESS.md index 273f0e5..f86f7a1 100644 --- a/mflix/README-NODE-EXPRESS.md +++ b/mflix/README-NODE-EXPRESS.md @@ -32,7 +32,7 @@ The `sample_mflix` dataset contains movies released up to **2016**. Searching fo Before getting started, you can run the verification script to check if you have all the necessary requirements: ```bash -./check-requirements-js.sh +./check-requirements.sh ``` This script checks for required tools (Node.js, npm), validates your environment configuration, and verifies dependencies. Run with `--help` for more options. diff --git a/mflix/README-PYTHON-FASTAPI.md b/mflix/README-PYTHON-FASTAPI.md index 4b3e49f..fdcf10d 100644 --- a/mflix/README-PYTHON-FASTAPI.md +++ b/mflix/README-PYTHON-FASTAPI.md @@ -36,7 +36,7 @@ The `sample_mflix` dataset contains movies released up to **2016**. Searching fo Before getting started, you can run the verification script to check if you have all the necessary requirements: ```bash -./check-requirements-python.sh +./check-requirements.sh ``` This script checks for required tools (Python, pip, Node.js), validates your environment configuration, and verifies dependencies. Run with `--help` for more options. From dda7d6c605f9cb001f22850c6ad4c03f04236f94 Mon Sep 17 00:00:00 2001 From: Cory Bullinger Date: Thu, 12 Feb 2026 13:22:48 -0500 Subject: [PATCH 07/11] genericize script names --- mflix/check-requirements-java.sh | 8 ++++---- mflix/check-requirements-js.sh | 6 +++--- mflix/check-requirements-python.sh | 8 ++++---- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/mflix/check-requirements-java.sh b/mflix/check-requirements-java.sh index e857edf..07a2b5e 100755 --- a/mflix/check-requirements-java.sh +++ b/mflix/check-requirements-java.sh @@ -8,8 +8,8 @@ # environment configuration. # # Usage: -# ./check-requirements-java.sh # Check all requirements -# ./check-requirements-java.sh --setup # Check and auto-setup missing items +# ./check-requirements.sh # Check all requirements +# ./check-requirements.sh --setup # Check and auto-setup missing items # ============================================================================= set -e @@ -384,7 +384,7 @@ print_summary() { if [ "$CHECKS_FAILED" -gt 0 ]; then echo "Review the failed checks above and follow the instructions to resolve them." - echo "Run with --setup flag to automatically fix some issues: ./check-requirements-java.sh --setup" + echo "Run with --setup flag to automatically fix some issues: ./check-requirements.sh --setup" echo "" fi } @@ -419,7 +419,7 @@ while [[ $# -gt 0 ]]; do shift ;; --help|-h) - echo "Usage: ./check-requirements-java.sh [OPTIONS]" + echo "Usage: ./check-requirements.sh [OPTIONS]" echo "" echo "Options:" echo " --setup Automatically set up missing requirements where possible" diff --git a/mflix/check-requirements-js.sh b/mflix/check-requirements-js.sh index 4fea4c6..e6ee17c 100755 --- a/mflix/check-requirements-js.sh +++ b/mflix/check-requirements-js.sh @@ -7,9 +7,9 @@ # mflix sample application with the JavaScript/Express backend. # # Usage: -# ./check-requirements-js.sh # Check all requirements -# ./check-requirements-js.sh --setup # Check and auto-setup missing items -# ./check-requirements-js.sh --help # Show help message +# ./check-requirements.sh # Check all requirements +# ./check-requirements.sh --setup # Check and auto-setup missing items +# ./check-requirements.sh --help # Show help message # set -e diff --git a/mflix/check-requirements-python.sh b/mflix/check-requirements-python.sh index 05007e7..e761350 100755 --- a/mflix/check-requirements-python.sh +++ b/mflix/check-requirements-python.sh @@ -7,7 +7,7 @@ # This script checks that all necessary requirements are installed to run # the mflix sample application with the Python/FastAPI backend. # -# Usage: ./check-requirements-python.sh [options] +# Usage: ./check-requirements.sh [options] # --setup Attempt to set up missing requirements # --help Show this help message # ============================================================================= @@ -80,15 +80,15 @@ command_exists() { # ============================================================================= show_help() { - echo "Usage: ./check-requirements-python.sh [options]" + echo "Usage: ./check-requirements.sh [options]" echo "" echo "Options:" echo " --setup Attempt to set up missing requirements" echo " --help Show this help message" echo "" echo "Examples:" - echo " ./check-requirements-python.sh # Check all requirements" - echo " ./check-requirements-python.sh --setup # Check and set up missing items" + echo " ./check-requirements.sh # Check all requirements" + echo " ./check-requirements.sh --setup # Check and set up missing items" } while [[ $# -gt 0 ]]; do From e1b763e289ff9526a43257fe5f77d156265895c1 Mon Sep 17 00:00:00 2001 From: Cory Bullinger Date: Thu, 12 Feb 2026 13:24:49 -0500 Subject: [PATCH 08/11] Comment out the voyage api keys in env.examples --- mflix/server/java-spring/.env.example | 4 ++-- mflix/server/js-express/.env.example | 4 ++-- mflix/server/python-fastapi/.env.example | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mflix/server/java-spring/.env.example b/mflix/server/java-spring/.env.example index 465f95d..96c3555 100644 --- a/mflix/server/java-spring/.env.example +++ b/mflix/server/java-spring/.env.example @@ -2,9 +2,9 @@ # Replace with your MongoDB Atlas connection string or local MongoDB URI MONGODB_URI=mongodb+srv://:@.mongodb.net/sample_mflix?retryWrites=true&w=majority -# Voyage AI Configuration (optional - required for Vector Search) +# OPTIONAL: Voyage AI Configuration (required for Vector Search) # Get your API key from https://www.voyageai.com/ -VOYAGE_API_KEY=your_voyage_api_key +# VOYAGE_API_KEY=your_voyage_api_key # Server Configuration PORT=3001 diff --git a/mflix/server/js-express/.env.example b/mflix/server/js-express/.env.example index 71ef3ab..8b264f6 100644 --- a/mflix/server/js-express/.env.example +++ b/mflix/server/js-express/.env.example @@ -2,9 +2,9 @@ # Replace with your MongoDB Atlas connection string or local MongoDB URI MONGODB_URI=mongodb+srv://:@.mongodb.net/sample_mflix?retryWrites=true&w=majority -# Voyage AI Configuration (optional - required for Vector Search) +# OPTIONAL: Voyage AI Configuration (required for Vector Search) # Get your API key from https://www.voyageai.com/ -VOYAGE_API_KEY=your_voyage_api_key +# VOYAGE_API_KEY=your_voyage_api_key # Server Configuration PORT=3001 diff --git a/mflix/server/python-fastapi/.env.example b/mflix/server/python-fastapi/.env.example index 74db84c..f0d77b2 100644 --- a/mflix/server/python-fastapi/.env.example +++ b/mflix/server/python-fastapi/.env.example @@ -2,9 +2,9 @@ # Replace with your MongoDB Atlas connection string or local MongoDB URI MONGODB_URI=mongodb+srv://:@.mongodb.net/sample_mflix?retryWrites=true&w=majority -# Voyage AI Configuration (optional - required for Vector Search) +# OPTIONAL: Voyage AI Configuration (required for Vector Search) # Get your API key from https://www.voyageai.com/ -VOYAGE_API_KEY=your_voyage_api_key +# VOYAGE_API_KEY=your_voyage_api_key # Server Configuration PORT=3001 From 109f2755d08e28a398b36f92daed9cb8225eb152 Mon Sep 17 00:00:00 2001 From: Cory Bullinger Date: Thu, 12 Feb 2026 13:47:31 -0500 Subject: [PATCH 09/11] update scripts to be consistent across backends --- mflix/check-requirements-java.sh | 372 +++++++++++++------------- mflix/check-requirements-js.sh | 299 +++++++++++---------- mflix/check-requirements-python.sh | 403 ++++++++++++++++------------- 3 files changed, 562 insertions(+), 512 deletions(-) diff --git a/mflix/check-requirements-java.sh b/mflix/check-requirements-java.sh index 07a2b5e..97dedb7 100755 --- a/mflix/check-requirements-java.sh +++ b/mflix/check-requirements-java.sh @@ -1,150 +1,135 @@ #!/bin/bash - # ============================================================================= -# Java/Spring Boot Sample App - Requirements Verification Script +# Requirements Verification Script for mflix Sample Application +# Java/Spring Boot Backend # ============================================================================= -# This script verifies that all requirements are met to run the Java/Spring Boot -# backend sample application. It checks for required tools, dependencies, and -# environment configuration. +# +# This script checks that all necessary requirements are installed to run +# the mflix sample application with the Java/Spring Boot backend. # # Usage: -# ./check-requirements.sh # Check all requirements -# ./check-requirements.sh --setup # Check and auto-setup missing items +# ./check-requirements-java.sh # Check all requirements +# ./check-requirements-java.sh --setup # Check and auto-setup missing items +# ./check-requirements-java.sh --help # Show help message +# # ============================================================================= +# Exit on error (but handle arithmetic expressions carefully) set -e -# Get script directory (works even if script is sourced) -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# ============================================================================= +# Configuration +# ============================================================================= -# Configuration - adjusted for artifact repo structure SERVER_DIR="server" CLIENT_DIR="client" JAVA_MIN_VERSION="21" NODE_MIN_VERSION="18" -# Setup mode flag -SETUP_MODE=false -if [[ "$1" == "--setup" ]]; then - SETUP_MODE=true -fi - -# Counters -CHECKS_PASSED=0 -CHECKS_FAILED=0 -CHECKS_WARNED=0 - +# ============================================================================= # Colors +# ============================================================================= + RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color +# ============================================================================= +# Counters +# ============================================================================= + +CHECKS_PASSED=0 +CHECKS_FAILED=0 +CHECKS_WARNED=0 + # ============================================================================= # Helper Functions # ============================================================================= print_header() { echo "" - echo -e "${BLUE}======================================${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${BLUE} $1${NC}" - echo -e "${BLUE}======================================${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" } -print_subheader() { +print_section() { echo "" - echo -e "${BLUE}--- $1 ---${NC}" + echo -e "${YELLOW}▸ $1${NC}" } check_pass() { - echo -e "${GREEN}✓${NC} $1" + echo -e " ${GREEN}✓${NC} $1" CHECKS_PASSED=$((CHECKS_PASSED + 1)) } check_fail() { - echo -e "${RED}✗${NC} $1" + echo -e " ${RED}✗${NC} $1" CHECKS_FAILED=$((CHECKS_FAILED + 1)) } check_warn() { - echo -e "${YELLOW}!${NC} $1" + echo -e " ${YELLOW}⚠${NC} $1" CHECKS_WARNED=$((CHECKS_WARNED + 1)) } check_info() { - echo -e " ${BLUE}→${NC} $1" -} - -version_gte() { - # Returns 0 (true) if $1 >= $2 - [ "$1" -ge "$2" ] 2>/dev/null + echo -e " ${BLUE}→${NC} $1" } command_exists() { command -v "$1" &>/dev/null } -# ============================================================================= -# Common Requirements -# ============================================================================= +version_gte() { + # Returns 0 (true) if $1 >= $2 (numeric comparison) + [[ "$1" -ge "$2" ]] 2>/dev/null +} -check_common_requirements() { - print_subheader "Common Requirements" +show_help() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --setup Attempt to automatically set up missing requirements" + echo " --help Show this help message" + echo "" + echo "This script checks that all necessary requirements are installed" + echo "to run the mflix sample application with the Java/Spring Boot backend." + exit 0 +} - # Check Git - if command_exists git; then - GIT_VERSION=$(git --version | grep -oE '[0-9]+\.[0-9]+' | head -1) - check_pass "Git installed (v$GIT_VERSION)" - else - check_fail "Git not installed" - check_info "Install Git from https://git-scm.com/" - fi - # Check curl - if command_exists curl; then - check_pass "curl installed" - else - check_warn "curl not installed (optional, but useful for testing)" - fi -} # ============================================================================= -# Java/Spring Boot Requirements +# Check Java/Spring Boot Backend Requirements # ============================================================================= -check_java_requirements() { - print_subheader "Java/Spring Boot Backend" +check_backend_requirements() { + print_section "Java/Spring Boot Backend Requirements" local server_dir="$SCRIPT_DIR/$SERVER_DIR" - # Check Java + # Check Java version if command_exists java; then - # Get Java version - handle different version formats - JAVA_VERSION_OUTPUT=$(java -version 2>&1 | head -n1) - JAVA_VERSION=$(echo "$JAVA_VERSION_OUTPUT" | sed -E 's/.*version "([0-9]+).*/\1/') - - if [ -n "$JAVA_VERSION" ] && version_gte "$JAVA_VERSION" "$JAVA_MIN_VERSION"; then - check_pass "Java installed: version $JAVA_VERSION (>= $JAVA_MIN_VERSION required)" + local java_version + java_version=$(java -version 2>&1 | head -n1 | sed -E 's/.*version "([0-9]+).*/\1/') + if [[ -n "$java_version" ]] && version_gte "$java_version" "$JAVA_MIN_VERSION"; then + check_pass "Java $java_version installed (>= $JAVA_MIN_VERSION required)" else - check_fail "Java version $JAVA_VERSION is too old (>= $JAVA_MIN_VERSION required)" - check_info "Install Java $JAVA_MIN_VERSION+ from:" - check_info " - Eclipse Temurin: https://adoptium.net/" - check_info " - Oracle JDK: https://www.oracle.com/java/technologies/downloads/" - check_info " - Or use SDKMAN: https://sdkman.io/" + check_fail "Java $java_version installed but >= $JAVA_MIN_VERSION required" + check_info "Install Java $JAVA_MIN_VERSION+ from https://adoptium.net/" fi else check_fail "Java not installed" - check_info "Install Java $JAVA_MIN_VERSION+ from:" - check_info " - Eclipse Temurin: https://adoptium.net/" - check_info " - Oracle JDK: https://www.oracle.com/java/technologies/downloads/" - check_info " - Or use SDKMAN: https://sdkman.io/" + check_info "Install Java $JAVA_MIN_VERSION+ from https://adoptium.net/" return fi # Check JAVA_HOME - if [ -n "$JAVA_HOME" ]; then - if [ -d "$JAVA_HOME" ]; then + if [[ -n "$JAVA_HOME" ]]; then + if [[ -d "$JAVA_HOME" ]]; then check_pass "JAVA_HOME is set: $JAVA_HOME" else check_warn "JAVA_HOME is set but directory doesn't exist: $JAVA_HOME" @@ -154,40 +139,40 @@ check_java_requirements() { check_info "Set JAVA_HOME to your Java installation directory" fi - # Check Maven wrapper - if [ -f "$server_dir/mvnw" ]; then + if [[ -f "$server_dir/mvnw" ]]; then check_pass "Maven wrapper (mvnw) found" # Check if mvnw is executable - if [ -x "$server_dir/mvnw" ]; then + if [[ -x "$server_dir/mvnw" ]]; then check_pass "Maven wrapper is executable" else check_warn "Maven wrapper is not executable" - if [ "$SETUP_MODE" = true ]; then + if [[ "$SETUP_MODE" == true ]]; then chmod +x "$server_dir/mvnw" check_pass "Made Maven wrapper executable" else - check_info "Run: chmod +x $server_dir/mvnw" + check_info "Run: chmod +x $SERVER_DIR/mvnw" fi fi # Try to get Maven version - MAVEN_VERSION=$(cd "$server_dir" && ./mvnw --version 2>/dev/null | grep "Apache Maven" | awk '{print $3}') - if [ -n "$MAVEN_VERSION" ]; then - check_pass "Maven version: $MAVEN_VERSION" + local maven_version + maven_version=$(cd "$server_dir" && ./mvnw --version 2>/dev/null | grep "Apache Maven" | awk '{print $3}') + if [[ -n "$maven_version" ]]; then + check_pass "Maven version: $maven_version" fi else - check_fail "Maven wrapper (mvnw) not found in $server_dir" + check_fail "Maven wrapper (mvnw) not found in $SERVER_DIR" check_info "The Maven wrapper should be included in the repository" fi # Check if Maven dependencies are downloaded - if [ -d "$server_dir/target" ]; then + if [[ -d "$server_dir/target" ]]; then check_pass "Maven target directory exists (dependencies likely downloaded)" else check_warn "Maven target directory not found" - if [ "$SETUP_MODE" = true ]; then + if [[ "$SETUP_MODE" == true ]]; then check_info "Downloading Maven dependencies..." if (cd "$server_dir" && ./mvnw dependency:resolve -q); then check_pass "Maven dependencies downloaded successfully" @@ -195,16 +180,16 @@ check_java_requirements() { check_fail "Failed to download Maven dependencies" fi else - check_info "Run: cd $server_dir && ./mvnw dependency:resolve" + check_info "Run: cd $SERVER_DIR && ./mvnw dependency:resolve" fi fi # Check if project compiles - if [ -d "$server_dir/target/classes" ]; then + if [[ -d "$server_dir/target/classes" ]]; then check_pass "Project appears to be compiled" else check_warn "Project not compiled yet" - if [ "$SETUP_MODE" = true ]; then + if [[ "$SETUP_MODE" == true ]]; then check_info "Compiling project..." if (cd "$server_dir" && ./mvnw compile -q); then check_pass "Project compiled successfully" @@ -212,74 +197,80 @@ check_java_requirements() { check_fail "Failed to compile project" fi else - check_info "Run: cd $server_dir && ./mvnw compile" + check_info "Run: cd $SERVER_DIR && ./mvnw compile" fi fi } # ============================================================================= -# Environment Configuration +# Check Environment Configuration # ============================================================================= check_env_configuration() { - print_subheader "Environment Configuration" + print_section "Environment Configuration" local server_dir="$SCRIPT_DIR/$SERVER_DIR" local env_file="$server_dir/.env" - local example_file="$server_dir/.env.example" + local env_example="$server_dir/.env.example" - if [ -f "$env_file" ]; then + # Check .env file + if [[ -f "$env_file" ]]; then check_pass ".env file exists" - # Check MongoDB URI + # Check MONGODB_URI if grep -q "^MONGODB_URI=" "$env_file" 2>/dev/null; then - if grep -qE "^MONGODB_URI=.*<.*>" "$env_file" 2>/dev/null; then - check_warn "MONGODB_URI appears to be a placeholder - update with your connection string" - elif grep -qE "^MONGODB_URI=.+" "$env_file" 2>/dev/null; then + local mongo_uri + mongo_uri=$(grep "^MONGODB_URI=" "$env_file" | cut -d'=' -f2-) + if [[ -n "$mongo_uri" ]] && [[ "$mongo_uri" != *"<"*">"* ]]; then check_pass "MONGODB_URI is configured" else - check_warn "MONGODB_URI is empty" + check_fail "MONGODB_URI is not configured (still has placeholder value)" + check_info "Update MONGODB_URI in $SERVER_DIR/.env with your MongoDB connection string" fi else check_fail "MONGODB_URI not found in .env" + check_info "Add MONGODB_URI to $SERVER_DIR/.env" fi - # Check Voyage AI key (optional) + # Check VOYAGE_API_KEY (optional) if grep -q "^VOYAGE_API_KEY=" "$env_file" 2>/dev/null; then - if grep -qE "^VOYAGE_API_KEY=your" "$env_file" 2>/dev/null || \ - grep -qE "^VOYAGE_API_KEY=$" "$env_file" 2>/dev/null; then - check_info "VOYAGE_API_KEY not configured (optional - needed for vector search)" - else + local voyage_key + voyage_key=$(grep "^VOYAGE_API_KEY=" "$env_file" | cut -d'=' -f2-) + if [[ -n "$voyage_key" ]] && [[ "$voyage_key" != "your_voyage_api_key" ]]; then check_pass "VOYAGE_API_KEY is configured" + else + check_info "VOYAGE_API_KEY not configured (optional - needed for vector search)" fi else - check_info "VOYAGE_API_KEY not found (optional - needed for vector search)" + check_info "VOYAGE_API_KEY not set (optional - needed for vector search)" fi - # Check CORS_ORIGINS + # Check CORS_ORIGINS (optional) if grep -q "^CORS_ORIGINS=" "$env_file" 2>/dev/null; then check_pass "CORS_ORIGINS is configured" else - check_info "CORS_ORIGINS not found (will use default: http://localhost:3000)" + check_info "CORS_ORIGINS not set (will use default: http://localhost:3000)" fi - # Check PORT + # Check PORT (optional) if grep -q "^PORT=" "$env_file" 2>/dev/null; then check_pass "PORT is configured" else - check_info "PORT not found (will use default)" + check_info "PORT not set (will use default: 3001)" fi else check_warn ".env file not found" - if [ -f "$example_file" ]; then - check_info ".env.example exists - use it as a template" - if [ "$SETUP_MODE" = true ]; then - cp "$example_file" "$env_file" - check_pass "Created .env from .env.example" - check_warn "Please edit $env_file with your actual values" + if [[ -f "$env_example" ]]; then + if [[ "$SETUP_MODE" == true ]]; then + check_info "Creating .env from .env.example..." + if cp "$env_example" "$env_file"; then + check_pass ".env file created from .env.example" + check_warn "Please update the placeholder values in $SERVER_DIR/.env" + else + check_fail "Failed to create .env file" + fi else - check_info "Run: cp $example_file $env_file" - check_info "Then edit .env with your actual values" + check_info "Copy .env.example to .env: cp $SERVER_DIR/.env.example $SERVER_DIR/.env" fi else check_fail "No .env.example found to use as template" @@ -287,131 +278,121 @@ check_env_configuration() { fi } - # ============================================================================= -# Frontend Requirements +# Check Frontend Requirements # ============================================================================= check_frontend_requirements() { - print_header "Frontend Requirements (Next.js)" + print_section "Frontend Requirements (Next.js)" local client_dir="$SCRIPT_DIR/$CLIENT_DIR" - # Check Node.js (required for frontend) - print_subheader "Node.js" + # Check Node.js if command_exists node; then - NODE_VERSION=$(node --version 2>/dev/null | sed 's/v//') - NODE_MAJOR=$(echo "$NODE_VERSION" | cut -d. -f1) - - if [ "$NODE_MAJOR" -ge "$NODE_MIN_VERSION" ] 2>/dev/null; then - check_pass "Node.js installed: v$NODE_VERSION (required: >= $NODE_MIN_VERSION)" + local node_version + node_version=$(node --version | sed 's/v//') + local node_major + node_major=$(echo "$node_version" | cut -d. -f1) + if [[ "$node_major" -ge "$NODE_MIN_VERSION" ]]; then + check_pass "Node.js installed (version $node_version, >= $NODE_MIN_VERSION required)" else - check_fail "Node.js version $NODE_VERSION is too old (required: >= $NODE_MIN_VERSION)" - check_info "Install Node.js $NODE_MIN_VERSION+ from: https://nodejs.org/" + check_fail "Node.js version $node_version is below minimum required ($NODE_MIN_VERSION+)" + check_info "Install Node.js $NODE_MIN_VERSION+: https://nodejs.org/" fi else check_fail "Node.js not installed" - check_info "Install Node.js $NODE_MIN_VERSION+ from: https://nodejs.org/" + check_info "Install Node.js $NODE_MIN_VERSION+: https://nodejs.org/" return fi # Check npm - print_subheader "npm" if command_exists npm; then - NPM_VERSION=$(npm --version 2>/dev/null) - check_pass "npm installed: v$NPM_VERSION" + local npm_version + npm_version=$(npm --version) + check_pass "npm installed (version $npm_version)" else check_fail "npm not installed" - check_info "npm should be installed with Node.js" + check_info "npm should come with Node.js installation" + return + fi + + # Check client directory + if [[ ! -d "$client_dir" ]]; then + check_warn "Client directory not found: $CLIENT_DIR" + check_info "Frontend may be in a separate repository" return fi # Check client dependencies - print_subheader "Frontend Dependencies" - if [ -d "$client_dir/node_modules" ]; then + if [[ -d "$client_dir/node_modules" ]]; then check_pass "Frontend dependencies installed" - # Check for Next.js - if [ -d "$client_dir/node_modules/next" ]; then - check_pass "Next.js is installed" + # Check Next.js + if [[ -d "$client_dir/node_modules/next" ]]; then + check_pass "Next.js dependency installed" else check_warn "Next.js not found in dependencies" fi - # Check for React - if [ -d "$client_dir/node_modules/react" ]; then - check_pass "React is installed" + # Check React + if [[ -d "$client_dir/node_modules/react" ]]; then + check_pass "React dependency installed" else check_warn "React not found in dependencies" fi else check_warn "Frontend dependencies not installed" - if [ "$SETUP_MODE" = true ]; then + if [[ "$SETUP_MODE" == true ]]; then check_info "Installing frontend dependencies..." - if (cd "$client_dir" && npm install); then + if (cd "$client_dir" && npm install &>/dev/null); then check_pass "Frontend dependencies installed successfully" else check_fail "Failed to install frontend dependencies" fi else - check_info "Run: cd $client_dir && npm install" + check_info "Run: cd $CLIENT_DIR && npm install" fi fi } # ============================================================================= -# Summary +# Print Summary # ============================================================================= print_summary() { - echo "" - echo "=============================================================================" - echo " SUMMARY" - echo "=============================================================================" - echo "" - - if [ "$CHECKS_FAILED" -eq 0 ]; then - echo -e "${GREEN}✓ All checks passed!${NC}" - else - echo -e "${RED}✗ Some checks failed${NC}" - fi - + print_header "Summary" echo "" echo -e " ${GREEN}Passed:${NC} $CHECKS_PASSED" echo -e " ${RED}Failed:${NC} $CHECKS_FAILED" echo -e " ${YELLOW}Warnings:${NC} $CHECKS_WARNED" echo "" - if [ "$CHECKS_FAILED" -gt 0 ]; then - echo "Review the failed checks above and follow the instructions to resolve them." - echo "Run with --setup flag to automatically fix some issues: ./check-requirements.sh --setup" - echo "" + if [[ $CHECKS_FAILED -eq 0 ]]; then + echo -e "${GREEN}All required checks passed!${NC}" + if [[ $CHECKS_WARNED -gt 0 ]]; then + echo -e "${YELLOW}There are some warnings to review.${NC}" + fi + else + echo -e "${RED}Some checks failed. Please address the issues above.${NC}" + if [[ "$SETUP_MODE" != true ]]; then + echo -e "${BLUE}Tip: Run with --setup flag to auto-fix some issues${NC}" + fi fi + echo "" } # ============================================================================= # Main Execution # ============================================================================= -main() { - echo "=============================================================================" - echo " Java/Spring Boot Backend - Requirements Verification" - echo "=============================================================================" - echo "" - - check_common_requirements - check_java_requirements - check_env_configuration - check_frontend_requirements - print_summary +# Get script directory and change to it +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" - # Exit with error code if any checks failed - if [ "$CHECKS_FAILED" -gt 0 ]; then - exit 1 - fi -} +# Default options +SETUP_MODE=false -# Parse command line arguments +# Parse arguments while [[ $# -gt 0 ]]; do case $1 in --setup) @@ -419,13 +400,7 @@ while [[ $# -gt 0 ]]; do shift ;; --help|-h) - echo "Usage: ./check-requirements.sh [OPTIONS]" - echo "" - echo "Options:" - echo " --setup Automatically set up missing requirements where possible" - echo " --help Show this help message" - echo "" - exit 0 + show_help ;; *) echo "Unknown option: $1" @@ -435,4 +410,27 @@ while [[ $# -gt 0 ]]; do esac done -main \ No newline at end of file +# Print banner +echo "" +echo -e "${BLUE}╔══════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ mflix Sample Application - Requirements Check ║${NC}" +echo -e "${BLUE}║ Java/Spring Boot Backend ║${NC}" +echo -e "${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}" + +if [[ "$SETUP_MODE" == true ]]; then + echo -e "${YELLOW}Running in setup mode - will attempt to fix issues${NC}" +fi + +# Run all checks +check_backend_requirements +check_env_configuration +check_frontend_requirements + +# Print summary +print_summary + +# Exit with appropriate code +if [[ $CHECKS_FAILED -gt 0 ]]; then + exit 1 +fi +exit 0 \ No newline at end of file diff --git a/mflix/check-requirements-js.sh b/mflix/check-requirements-js.sh index e6ee17c..a588fe0 100755 --- a/mflix/check-requirements-js.sh +++ b/mflix/check-requirements-js.sh @@ -1,28 +1,32 @@ #!/bin/bash -# +# ============================================================================= # Requirements Verification Script for mflix Sample Application -# JavaScript/Express Backend (Node.js + Express + MongoDB) +# JavaScript/Express Backend +# ============================================================================= # -# This script checks if you have all the necessary requirements to run the -# mflix sample application with the JavaScript/Express backend. +# This script checks that all necessary requirements are installed to run +# the mflix sample application with the JavaScript/Express backend. # # Usage: -# ./check-requirements.sh # Check all requirements -# ./check-requirements.sh --setup # Check and auto-setup missing items -# ./check-requirements.sh --help # Show help message +# ./check-requirements-js.sh # Check all requirements +# ./check-requirements-js.sh --setup # Check and auto-setup missing items +# ./check-requirements-js.sh --help # Show help message # +# ============================================================================= +# Exit on error (but handle arithmetic expressions carefully) set -e # ============================================================================= # Configuration # ============================================================================= -# Server directory (in artifact repo, server/js-express becomes just server) SERVER_DIR="server" +CLIENT_DIR="client" +NODE_MIN_VERSION="18" # ============================================================================= -# Colors for output +# Colors # ============================================================================= RED='\033[0;31m' @@ -43,82 +47,91 @@ CHECKS_WARNED=0 # Helper Functions # ============================================================================= +print_header() { + echo "" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BLUE} $1${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +} + +print_section() { + echo "" + echo -e "${YELLOW}▸ $1${NC}" +} + check_pass() { - echo -e "${GREEN}✓${NC} $1" + echo -e " ${GREEN}✓${NC} $1" CHECKS_PASSED=$((CHECKS_PASSED + 1)) } check_fail() { - echo -e "${RED}✗${NC} $1" + echo -e " ${RED}✗${NC} $1" CHECKS_FAILED=$((CHECKS_FAILED + 1)) } check_warn() { - echo -e "${YELLOW}!${NC} $1" + echo -e " ${YELLOW}⚠${NC} $1" CHECKS_WARNED=$((CHECKS_WARNED + 1)) } check_info() { - echo -e "${BLUE}ℹ${NC} $1" + echo -e " ${BLUE}→${NC} $1" } -print_section() { - echo "" - echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${BLUE} $1${NC}" - echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" +command_exists() { + command -v "$1" &>/dev/null } -# ============================================================================= -# Check Common Requirements -# ============================================================================= +version_gte() { + # Returns 0 (true) if $1 >= $2 (numeric comparison) + [[ "$1" -ge "$2" ]] 2>/dev/null +} -check_common_requirements() { - print_section "Common Requirements" +show_help() { + echo "Usage: $0 [OPTIONS]" + echo "" + echo "Options:" + echo " --setup Attempt to automatically set up missing requirements" + echo " --help Show this help message" + echo "" + echo "This script checks that all necessary requirements are installed" + echo "to run the mflix sample application with the JavaScript/Express backend." + exit 0 +} - # Check Git - if command -v git &> /dev/null; then - local git_version=$(git --version | cut -d' ' -f3) - check_pass "Git installed (version $git_version)" - else - check_fail "Git not installed" - check_info "Install Git: https://git-scm.com/downloads" - fi - # Check curl (useful for API testing) - if command -v curl &> /dev/null; then - check_pass "curl installed" - else - check_warn "curl not installed (optional, useful for API testing)" - fi -} # ============================================================================= -# Check Node.js/Express Requirements +# Check JavaScript/Express Backend Requirements # ============================================================================= -check_node_requirements() { - print_section "Node.js/Express Backend Requirements" +check_backend_requirements() { + print_section "JavaScript/Express Backend Requirements" - # Check Node.js - if command -v node &> /dev/null; then - local node_version=$(node --version | sed 's/v//') - local node_major=$(echo "$node_version" | cut -d. -f1) - if [ "$node_major" -ge 18 ]; then - check_pass "Node.js installed (version $node_version)" + local server_dir="$SCRIPT_DIR/$SERVER_DIR" + + # Check Node.js version + if command_exists node; then + local node_version + node_version=$(node --version | sed 's/v//') + local node_major + node_major=$(echo "$node_version" | cut -d. -f1) + if [[ "$node_major" -ge "$NODE_MIN_VERSION" ]]; then + check_pass "Node.js $node_version installed (>= $NODE_MIN_VERSION required)" else - check_fail "Node.js version $node_version is below minimum required (18+)" - check_info "Install Node.js 18+: https://nodejs.org/" + check_fail "Node.js $node_version installed but >= $NODE_MIN_VERSION required" + check_info "Install Node.js $NODE_MIN_VERSION+ from https://nodejs.org/" fi else check_fail "Node.js not installed" - check_info "Install Node.js 18+: https://nodejs.org/" + check_info "Install Node.js $NODE_MIN_VERSION+ from https://nodejs.org/" return fi # Check npm - if command -v npm &> /dev/null; then - local npm_version=$(npm --version) + if command_exists npm; then + local npm_version + npm_version=$(npm --version) check_pass "npm installed (version $npm_version)" else check_fail "npm not installed" @@ -127,13 +140,13 @@ check_node_requirements() { fi # Check server directory - if [ ! -d "$SERVER_DIR" ]; then + if [[ ! -d "$server_dir" ]]; then check_fail "Server directory not found: $SERVER_DIR" return fi # Check package.json - if [ -f "$SERVER_DIR/package.json" ]; then + if [[ -f "$server_dir/package.json" ]]; then check_pass "package.json found" else check_fail "package.json not found in $SERVER_DIR" @@ -141,41 +154,41 @@ check_node_requirements() { fi # Check node_modules - if [ -d "$SERVER_DIR/node_modules" ]; then + if [[ -d "$server_dir/node_modules" ]]; then check_pass "node_modules directory exists" - # Check key dependencies - if [ -d "$SERVER_DIR/node_modules/express" ]; then + # Check Express.js + if [[ -d "$server_dir/node_modules/express" ]]; then check_pass "Express.js dependency installed" else check_fail "Express.js dependency not installed" fi - if [ -d "$SERVER_DIR/node_modules/mongodb" ]; then + # Check MongoDB driver + if [[ -d "$server_dir/node_modules/mongodb" ]]; then check_pass "MongoDB driver dependency installed" else check_fail "MongoDB driver dependency not installed" fi # Check TypeScript build - if [ -d "$SERVER_DIR/dist" ]; then + if [[ -d "$server_dir/dist" ]]; then check_pass "TypeScript build output exists (dist directory)" else check_warn "TypeScript build output not found (dist directory)" - check_info "Run 'npm run build' in $SERVER_DIR to build" + check_info "Run: cd $SERVER_DIR && npm run build" fi else - check_fail "node_modules directory not found" - if [ "$SETUP_MODE" = true ]; then - check_info "Attempting to install dependencies..." - if (cd "$SERVER_DIR" && npm install); then + check_warn "node_modules directory not found" + if [[ "$SETUP_MODE" == true ]]; then + check_info "Installing dependencies..." + if (cd "$server_dir" && npm install &>/dev/null); then check_pass "Dependencies installed successfully" else check_fail "Failed to install dependencies" - check_info "Run 'npm install' manually in $SERVER_DIR" fi else - check_info "Run 'npm install' in $SERVER_DIR or use --setup flag" + check_info "Run: cd $SERVER_DIR && npm install" fi fi } @@ -187,37 +200,40 @@ check_node_requirements() { check_env_configuration() { print_section "Environment Configuration" - local env_file="$SERVER_DIR/.env" - local env_example="$SERVER_DIR/.env.example" + local server_dir="$SCRIPT_DIR/$SERVER_DIR" + local env_file="$server_dir/.env" + local env_example="$server_dir/.env.example" # Check .env file - if [ -f "$env_file" ]; then + if [[ -f "$env_file" ]]; then check_pass ".env file exists" # Check MONGODB_URI if grep -q "^MONGODB_URI=" "$env_file" 2>/dev/null; then - local mongo_uri=$(grep "^MONGODB_URI=" "$env_file" | cut -d'=' -f2-) - if [ -n "$mongo_uri" ] && [ "$mongo_uri" != "mongodb+srv://:@.mongodb.net/" ]; then + local mongo_uri + mongo_uri=$(grep "^MONGODB_URI=" "$env_file" | cut -d'=' -f2-) + if [[ -n "$mongo_uri" ]] && [[ "$mongo_uri" != *"<"*">"* ]]; then check_pass "MONGODB_URI is configured" else check_fail "MONGODB_URI is not configured (still has placeholder value)" - check_info "Update MONGODB_URI in $env_file with your MongoDB connection string" + check_info "Update MONGODB_URI in $SERVER_DIR/.env with your MongoDB connection string" fi else check_fail "MONGODB_URI not found in .env" - check_info "Add MONGODB_URI= to $env_file" + check_info "Add MONGODB_URI to $SERVER_DIR/.env" fi # Check VOYAGE_API_KEY (optional) if grep -q "^VOYAGE_API_KEY=" "$env_file" 2>/dev/null; then - local voyage_key=$(grep "^VOYAGE_API_KEY=" "$env_file" | cut -d'=' -f2-) - if [ -n "$voyage_key" ] && [ "$voyage_key" != "" ]; then + local voyage_key + voyage_key=$(grep "^VOYAGE_API_KEY=" "$env_file" | cut -d'=' -f2-) + if [[ -n "$voyage_key" ]] && [[ "$voyage_key" != "your_voyage_api_key" ]]; then check_pass "VOYAGE_API_KEY is configured" else - check_warn "VOYAGE_API_KEY has placeholder value (optional for vector search)" + check_info "VOYAGE_API_KEY not configured (optional - needed for vector search)" fi else - check_info "VOYAGE_API_KEY not set (optional, needed for vector search features)" + check_info "VOYAGE_API_KEY not set (optional - needed for vector search)" fi # Check CORS_ORIGINS (optional) @@ -229,37 +245,26 @@ check_env_configuration() { # Check PORT (optional) if grep -q "^PORT=" "$env_file" 2>/dev/null; then - local port=$(grep "^PORT=" "$env_file" | cut -d'=' -f2-) - check_pass "PORT is configured ($port)" + check_pass "PORT is configured" else check_info "PORT not set (will use default: 3001)" fi - - # Check LOG_LEVEL (optional) - if grep -q "^LOG_LEVEL=" "$env_file" 2>/dev/null; then - check_pass "LOG_LEVEL is configured" - else - check_info "LOG_LEVEL not set (will use default)" - fi else - check_fail ".env file not found" - if [ -f "$env_example" ]; then - if [ "$SETUP_MODE" = true ]; then - check_info "Attempting to create .env from .env.example..." + check_warn ".env file not found" + if [[ -f "$env_example" ]]; then + if [[ "$SETUP_MODE" == true ]]; then + check_info "Creating .env from .env.example..." if cp "$env_example" "$env_file"; then check_pass ".env file created from .env.example" - check_warn "Please update the placeholder values in $env_file" + check_warn "Please update the placeholder values in $SERVER_DIR/.env" else check_fail "Failed to create .env file" fi else - check_info "Copy .env.example to .env: cp $env_example $env_file" - check_info "Or use --setup flag to create automatically" + check_info "Copy .env.example to .env: cp $SERVER_DIR/.env.example $SERVER_DIR/.env" fi else - check_info "Create a .env file with required configuration" - check_info "Required: MONGODB_URI" - check_info "Optional: VOYAGE_API_KEY, CORS_ORIGINS, PORT, LOG_LEVEL" + check_fail "No .env.example found to use as template" fi fi } @@ -271,52 +276,72 @@ check_env_configuration() { check_frontend_requirements() { print_section "Frontend Requirements (Next.js)" - local client_dir="client" + local client_dir="$SCRIPT_DIR/$CLIENT_DIR" - # Check client directory - if [ ! -d "$client_dir" ]; then - check_warn "Client directory not found: $client_dir" - check_info "Frontend may be in a separate repository" + # Check Node.js + if command_exists node; then + local node_version + node_version=$(node --version | sed 's/v//') + local node_major + node_major=$(echo "$node_version" | cut -d. -f1) + if [[ "$node_major" -ge "$NODE_MIN_VERSION" ]]; then + check_pass "Node.js installed (version $node_version, >= $NODE_MIN_VERSION required)" + else + check_fail "Node.js version $node_version is below minimum required ($NODE_MIN_VERSION+)" + check_info "Install Node.js $NODE_MIN_VERSION+: https://nodejs.org/" + fi + else + check_fail "Node.js not installed" + check_info "Install Node.js $NODE_MIN_VERSION+: https://nodejs.org/" return fi - # Check package.json - if [ -f "$client_dir/package.json" ]; then - check_pass "Frontend package.json found" + # Check npm + if command_exists npm; then + local npm_version + npm_version=$(npm --version) + check_pass "npm installed (version $npm_version)" else - check_fail "Frontend package.json not found" + check_fail "npm not installed" + check_info "npm should come with Node.js installation" return fi - # Check node_modules - if [ -d "$client_dir/node_modules" ]; then - check_pass "Frontend node_modules exists" + # Check client directory + if [[ ! -d "$client_dir" ]]; then + check_warn "Client directory not found: $CLIENT_DIR" + check_info "Frontend may be in a separate repository" + return + fi + + # Check client dependencies + if [[ -d "$client_dir/node_modules" ]]; then + check_pass "Frontend dependencies installed" # Check Next.js - if [ -d "$client_dir/node_modules/next" ]; then + if [[ -d "$client_dir/node_modules/next" ]]; then check_pass "Next.js dependency installed" else - check_fail "Next.js dependency not installed" + check_warn "Next.js not found in dependencies" fi # Check React - if [ -d "$client_dir/node_modules/react" ]; then + if [[ -d "$client_dir/node_modules/react" ]]; then check_pass "React dependency installed" else - check_fail "React dependency not installed" + check_warn "React not found in dependencies" fi else - check_fail "Frontend node_modules not found" - if [ "$SETUP_MODE" = true ]; then - check_info "Attempting to install frontend dependencies..." - if (cd "$client_dir" && npm install); then + check_warn "Frontend dependencies not installed" + if [[ "$SETUP_MODE" == true ]]; then + check_info "Installing frontend dependencies..." + if (cd "$client_dir" && npm install &>/dev/null); then check_pass "Frontend dependencies installed successfully" else check_fail "Failed to install frontend dependencies" - check_info "Run 'npm install' manually in $client_dir" fi else - check_info "Run 'npm install' in $client_dir or use --setup flag" + check_info "Run: cd $CLIENT_DIR && npm install" fi fi } @@ -326,24 +351,21 @@ check_frontend_requirements() { # ============================================================================= print_summary() { + print_header "Summary" echo "" - echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo -e "${BLUE} Summary${NC}" - echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" - echo "" - echo -e " ${GREEN}Passed:${NC} $CHECKS_PASSED" - echo -e " ${RED}Failed:${NC} $CHECKS_FAILED" + echo -e " ${GREEN}Passed:${NC} $CHECKS_PASSED" + echo -e " ${RED}Failed:${NC} $CHECKS_FAILED" echo -e " ${YELLOW}Warnings:${NC} $CHECKS_WARNED" echo "" - if [ $CHECKS_FAILED -eq 0 ]; then + if [[ $CHECKS_FAILED -eq 0 ]]; then echo -e "${GREEN}All required checks passed!${NC}" - if [ $CHECKS_WARNED -gt 0 ]; then + if [[ $CHECKS_WARNED -gt 0 ]]; then echo -e "${YELLOW}There are some warnings to review.${NC}" fi else echo -e "${RED}Some checks failed. Please address the issues above.${NC}" - if [ "$SETUP_MODE" != true ]; then + if [[ "$SETUP_MODE" != true ]]; then echo -e "${BLUE}Tip: Run with --setup flag to auto-fix some issues${NC}" fi fi @@ -354,6 +376,11 @@ print_summary() { # Main Execution # ============================================================================= +# Get script directory and change to it +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" + +# Default options SETUP_MODE=false # Parse arguments @@ -364,15 +391,7 @@ while [[ $# -gt 0 ]]; do shift ;; --help|-h) - echo "Usage: $0 [OPTIONS]" - echo "" - echo "Options:" - echo " --setup Attempt to automatically set up missing requirements" - echo " --help Show this help message" - echo "" - echo "This script checks if you have all the necessary requirements" - echo "to run the mflix sample application with the JavaScript/Express backend." - exit 0 + show_help ;; *) echo "Unknown option: $1" @@ -382,19 +401,19 @@ while [[ $# -gt 0 ]]; do esac done +# Print banner echo "" echo -e "${BLUE}╔══════════════════════════════════════════════════════════════╗${NC}" echo -e "${BLUE}║ mflix Sample Application - Requirements Check ║${NC}" echo -e "${BLUE}║ JavaScript/Express Backend ║${NC}" echo -e "${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}" -if [ "$SETUP_MODE" = true ]; then +if [[ "$SETUP_MODE" == true ]]; then echo -e "${YELLOW}Running in setup mode - will attempt to fix issues${NC}" fi # Run all checks -check_common_requirements -check_node_requirements +check_backend_requirements check_env_configuration check_frontend_requirements @@ -402,7 +421,7 @@ check_frontend_requirements print_summary # Exit with appropriate code -if [ $CHECKS_FAILED -gt 0 ]; then +if [[ $CHECKS_FAILED -gt 0 ]]; then exit 1 fi exit 0 diff --git a/mflix/check-requirements-python.sh b/mflix/check-requirements-python.sh index e761350..ea1249c 100755 --- a/mflix/check-requirements-python.sh +++ b/mflix/check-requirements-python.sh @@ -1,49 +1,61 @@ #!/bin/bash - # ============================================================================= # Requirements Verification Script for mflix Sample Application # Python/FastAPI Backend # ============================================================================= +# # This script checks that all necessary requirements are installed to run # the mflix sample application with the Python/FastAPI backend. # -# Usage: ./check-requirements.sh [options] -# --setup Attempt to set up missing requirements -# --help Show this help message +# Usage: +# ./check-requirements-python.sh # Check all requirements +# ./check-requirements-python.sh --setup # Check and auto-setup missing items +# ./check-requirements-python.sh --help # Show help message +# +# ============================================================================= + +# Exit on error (but handle arithmetic expressions carefully) +set -e + +# ============================================================================= +# Configuration +# ============================================================================= + +SERVER_DIR="server" +CLIENT_DIR="client" +PYTHON_MIN_VERSION="3.11" +NODE_MIN_VERSION="18" + +# ============================================================================= +# Colors # ============================================================================= -# Colors for output RED='\033[0;31m' GREEN='\033[0;32m' YELLOW='\033[1;33m' BLUE='\033[0;34m' NC='\033[0m' # No Color +# ============================================================================= # Counters +# ============================================================================= + CHECKS_PASSED=0 CHECKS_FAILED=0 CHECKS_WARNED=0 -# Options -SETUP_MODE=false - -# Configuration -PYTHON_MIN_VERSION="3.11" -SERVER_DIR="server" -CLIENT_DIR="client" - # ============================================================================= # Helper Functions # ============================================================================= print_header() { echo "" - echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" echo -e "${BLUE} $1${NC}" - echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" + echo -e "${BLUE}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}" } -print_subheader() { +print_section() { echo "" echo -e "${YELLOW}▸ $1${NC}" } @@ -64,108 +76,55 @@ check_warn() { } check_info() { - echo -e " ${BLUE}ℹ${NC} $1" -} - -version_gte() { - [ "$(printf '%s\n' "$2" "$1" | sort -V | head -n1)" = "$2" ] + echo -e " ${BLUE}→${NC} $1" } command_exists() { - command -v "$1" >/dev/null 2>&1 + command -v "$1" &>/dev/null } -# ============================================================================= -# Parse Arguments -# ============================================================================= +version_gte() { + # Returns 0 (true) if $1 >= $2 using version sorting + [ "$(printf '%s\n' "$2" "$1" | sort -V | head -n1)" = "$2" ] +} show_help() { - echo "Usage: ./check-requirements.sh [options]" + echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" - echo " --setup Attempt to set up missing requirements" + echo " --setup Attempt to automatically set up missing requirements" echo " --help Show this help message" echo "" - echo "Examples:" - echo " ./check-requirements.sh # Check all requirements" - echo " ./check-requirements.sh --setup # Check and set up missing items" + echo "This script checks that all necessary requirements are installed" + echo "to run the mflix sample application with the Python/FastAPI backend." + exit 0 } -while [[ $# -gt 0 ]]; do - case $1 in - --setup) - SETUP_MODE=true - shift - ;; - --help) - show_help - exit 0 - ;; - *) - echo "Unknown option: $1" - show_help - exit 1 - ;; - esac -done - -# ============================================================================= -# Get Script Directory -# ============================================================================= -SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -cd "$SCRIPT_DIR" - -print_header "mflix Sample Application - Python/FastAPI Requirements Check" -echo "" -echo "Setup mode: $SETUP_MODE" -echo "Working directory: $SCRIPT_DIR" # ============================================================================= -# Common Requirements +# Check Python/FastAPI Backend Requirements # ============================================================================= -check_common_requirements() { - print_subheader "Common Requirements" +check_backend_requirements() { + print_section "Python/FastAPI Backend Requirements" - # Check Git - if command_exists git; then - local git_version - git_version=$(git --version | awk '{print $3}') - check_pass "Git installed (version $git_version)" - else - check_fail "Git not installed" - check_info "Install Git: https://git-scm.com/downloads" - fi - - # Check curl - if command_exists curl; then - check_pass "curl installed" - else - check_fail "curl not installed" - check_info "Install curl using your package manager" - fi -} - -# ============================================================================= -# Python Backend Requirements -# ============================================================================= - -check_python_requirements() { - print_subheader "Python/FastAPI Backend Requirements" + local server_dir="$SCRIPT_DIR/$SERVER_DIR" # Check Python version if command_exists python3; then - PYTHON_VERSION=$(python3 --version 2>&1 | grep -oE '[0-9]+\.[0-9]+' | head -1) - if version_gte "$PYTHON_VERSION" "$PYTHON_MIN_VERSION"; then - check_pass "Python $PYTHON_VERSION installed (>= $PYTHON_MIN_VERSION required)" + local python_version + python_version=$(python3 --version 2>&1 | grep -oE '[0-9]+\.[0-9]+' | head -1) + if version_gte "$python_version" "$PYTHON_MIN_VERSION"; then + check_pass "Python $python_version installed (>= $PYTHON_MIN_VERSION required)" else - check_fail "Python $PYTHON_VERSION installed but >= $PYTHON_MIN_VERSION required" + check_fail "Python $python_version installed but >= $PYTHON_MIN_VERSION required" check_info "Install Python $PYTHON_MIN_VERSION+ from https://www.python.org/downloads/" fi else check_fail "Python 3 not installed" check_info "Install Python $PYTHON_MIN_VERSION+ from https://www.python.org/downloads/" + return fi # Check pip @@ -177,11 +136,11 @@ check_python_requirements() { fi # Check virtual environment - local venv_dir="$SCRIPT_DIR/$SERVER_DIR/.venv" + local venv_dir="$server_dir/.venv" if [[ -d "$venv_dir" ]]; then check_pass "Python virtual environment exists at $SERVER_DIR/.venv" - # Check if venv is activated or can be used + # Check if venv activation script exists if [[ -f "$venv_dir/bin/activate" ]]; then check_pass "Virtual environment activation script exists" else @@ -202,7 +161,7 @@ check_python_requirements() { fi # Check Python dependencies - local requirements_file="$SCRIPT_DIR/$SERVER_DIR/requirements.txt" + local requirements_file="$server_dir/requirements.txt" if [[ -f "$requirements_file" ]]; then check_pass "requirements.txt found" @@ -241,160 +200,234 @@ check_python_requirements() { } # ============================================================================= -# Environment Configuration +# Check Environment Configuration # ============================================================================= check_env_configuration() { - print_subheader "Environment Configuration" + print_section "Environment Configuration" - local env_file="$SCRIPT_DIR/$SERVER_DIR/.env" - local env_example="$SCRIPT_DIR/$SERVER_DIR/.env.example" + local server_dir="$SCRIPT_DIR/$SERVER_DIR" + local env_file="$server_dir/.env" + local env_example="$server_dir/.env.example" - # Check .env file exists + # Check .env file if [[ -f "$env_file" ]]; then - check_pass ".env file exists at $SERVER_DIR/.env" - else - check_warn ".env file not found at $SERVER_DIR/.env" - if [[ "$SETUP_MODE" == true ]] && [[ -f "$env_example" ]]; then - check_info "Copying .env.example to .env..." - if cp "$env_example" "$env_file"; then - check_pass ".env file created from .env.example" - check_info "Please update the values in $SERVER_DIR/.env" + check_pass ".env file exists" + + # Check MONGODB_URI + if grep -q "^MONGODB_URI=" "$env_file" 2>/dev/null; then + local mongo_uri + mongo_uri=$(grep "^MONGODB_URI=" "$env_file" | cut -d'=' -f2-) + if [[ -n "$mongo_uri" ]] && [[ "$mongo_uri" != *"<"*">"* ]]; then + check_pass "MONGODB_URI is configured" else - check_fail "Failed to create .env file" + check_fail "MONGODB_URI is not configured (still has placeholder value)" + check_info "Update MONGODB_URI in $SERVER_DIR/.env with your MongoDB connection string" fi else - check_info "Copy the example: cp $SERVER_DIR/.env.example $SERVER_DIR/.env" + check_fail "MONGODB_URI not found in .env" + check_info "Add MONGODB_URI to $SERVER_DIR/.env" fi - return - fi - # Check required environment variables - if grep -q "^MONGODB_URI=" "$env_file" 2>/dev/null; then - local mongo_uri=$(grep "^MONGODB_URI=" "$env_file" | cut -d'=' -f2-) - if [[ -n "$mongo_uri" && "$mongo_uri" != "mongodb+srv://:@.mongodb.net/" ]]; then - check_pass "MONGODB_URI is configured" + # Check VOYAGE_API_KEY (optional) + if grep -q "^VOYAGE_API_KEY=" "$env_file" 2>/dev/null; then + local voyage_key + voyage_key=$(grep "^VOYAGE_API_KEY=" "$env_file" | cut -d'=' -f2-) + if [[ -n "$voyage_key" ]] && [[ "$voyage_key" != "your_voyage_api_key" ]]; then + check_pass "VOYAGE_API_KEY is configured" + else + check_info "VOYAGE_API_KEY not configured (optional - needed for vector search)" + fi else - check_fail "MONGODB_URI is not configured (still has placeholder value)" - check_info "Update MONGODB_URI in $SERVER_DIR/.env with your MongoDB connection string" + check_info "VOYAGE_API_KEY not set (optional - needed for vector search)" fi - else - check_fail "MONGODB_URI not found in .env" - check_info "Add MONGODB_URI to $SERVER_DIR/.env" - fi - # Check optional environment variables - if grep -q "^VOYAGE_API_KEY=" "$env_file" 2>/dev/null; then - local voyage_key=$(grep "^VOYAGE_API_KEY=" "$env_file" | cut -d'=' -f2-) - if [[ -n "$voyage_key" && "$voyage_key" != "" ]]; then - check_pass "VOYAGE_API_KEY is configured" + # Check CORS_ORIGINS (optional) + if grep -q "^CORS_ORIGINS=" "$env_file" 2>/dev/null; then + check_pass "CORS_ORIGINS is configured" else - check_info "VOYAGE_API_KEY not configured (optional - needed for vector search)" + check_info "CORS_ORIGINS not set (will use default: http://localhost:3000)" fi - else - check_info "VOYAGE_API_KEY not set (optional - needed for vector search)" - fi - if grep -q "^CORS_ORIGINS=" "$env_file" 2>/dev/null; then - check_pass "CORS_ORIGINS is configured" - else - check_info "CORS_ORIGINS not set (will use default: http://localhost:3000)" - fi - - if grep -q "^PORT=" "$env_file" 2>/dev/null; then - check_pass "PORT is configured" + # Check PORT (optional) + if grep -q "^PORT=" "$env_file" 2>/dev/null; then + check_pass "PORT is configured" + else + check_info "PORT not set (will use default: 3001)" + fi else - check_info "PORT not set (will use default: 8000)" + check_warn ".env file not found" + if [[ -f "$env_example" ]]; then + if [[ "$SETUP_MODE" == true ]]; then + check_info "Creating .env from .env.example..." + if cp "$env_example" "$env_file"; then + check_pass ".env file created from .env.example" + check_warn "Please update the placeholder values in $SERVER_DIR/.env" + else + check_fail "Failed to create .env file" + fi + else + check_info "Copy .env.example to .env: cp $SERVER_DIR/.env.example $SERVER_DIR/.env" + fi + else + check_fail "No .env.example found to use as template" + fi fi } - - # ============================================================================= -# Frontend Requirements (Next.js) +# Check Frontend Requirements # ============================================================================= check_frontend_requirements() { - print_subheader "Frontend Requirements (Next.js)" + print_section "Frontend Requirements (Next.js)" - # Check Node.js version + local client_dir="$SCRIPT_DIR/$CLIENT_DIR" + + # Check Node.js if command_exists node; then - NODE_VERSION=$(node --version 2>&1 | grep -oE '[0-9]+' | head -1) - if version_gte "$NODE_VERSION" "$NODE_MIN_VERSION"; then - check_pass "Node.js v$NODE_VERSION installed (>= v$NODE_MIN_VERSION required)" + local node_version + node_version=$(node --version | sed 's/v//') + local node_major + node_major=$(echo "$node_version" | cut -d. -f1) + if [[ "$node_major" -ge "$NODE_MIN_VERSION" ]]; then + check_pass "Node.js installed (version $node_version, >= $NODE_MIN_VERSION required)" else - check_fail "Node.js v$NODE_VERSION installed but >= v$NODE_MIN_VERSION required" + check_fail "Node.js version $node_version is below minimum required ($NODE_MIN_VERSION+)" + check_info "Install Node.js $NODE_MIN_VERSION+: https://nodejs.org/" fi else check_fail "Node.js not installed" - check_info "Install Node.js $NODE_MIN_VERSION+ from https://nodejs.org/" + check_info "Install Node.js $NODE_MIN_VERSION+: https://nodejs.org/" + return fi # Check npm if command_exists npm; then - check_pass "npm installed" + local npm_version + npm_version=$(npm --version) + check_pass "npm installed (version $npm_version)" else check_fail "npm not installed" + check_info "npm should come with Node.js installation" + return + fi + + # Check client directory + if [[ ! -d "$client_dir" ]]; then + check_warn "Client directory not found: $CLIENT_DIR" + check_info "Frontend may be in a separate repository" + return fi # Check client dependencies - local client_dir="$SCRIPT_DIR/$CLIENT_DIR" - if [[ -d "$client_dir" ]]; then - if [[ -d "$client_dir/node_modules" ]]; then - check_pass "Client dependencies installed" + if [[ -d "$client_dir/node_modules" ]]; then + check_pass "Frontend dependencies installed" + + # Check Next.js + if [[ -d "$client_dir/node_modules/next" ]]; then + check_pass "Next.js dependency installed" else - check_warn "Client dependencies not installed" - if [[ "$SETUP_MODE" == true ]]; then - check_info "Installing client dependencies..." - if (cd "$client_dir" && npm install &>/dev/null); then - check_pass "Client dependencies installed" - else - check_fail "Failed to install client dependencies" - fi + check_warn "Next.js not found in dependencies" + fi + + # Check React + if [[ -d "$client_dir/node_modules/react" ]]; then + check_pass "React dependency installed" + else + check_warn "React not found in dependencies" + fi + else + check_warn "Frontend dependencies not installed" + if [[ "$SETUP_MODE" == true ]]; then + check_info "Installing frontend dependencies..." + if (cd "$client_dir" && npm install &>/dev/null); then + check_pass "Frontend dependencies installed successfully" else - check_info "Install with: cd $CLIENT_DIR && npm install" + check_fail "Failed to install frontend dependencies" fi + else + check_info "Run: cd $CLIENT_DIR && npm install" fi fi } # ============================================================================= -# Summary +# Print Summary # ============================================================================= print_summary() { - echo "" print_header "Summary" - echo -e "${GREEN}Passed:${NC} $CHECKS_PASSED" - echo -e "${RED}Failed:${NC} $CHECKS_FAILED" - echo -e "${YELLOW}Warnings:${NC} $CHECKS_WARNED" + echo "" + echo -e " ${GREEN}Passed:${NC} $CHECKS_PASSED" + echo -e " ${RED}Failed:${NC} $CHECKS_FAILED" + echo -e " ${YELLOW}Warnings:${NC} $CHECKS_WARNED" echo "" - if [[ $CHECKS_FAILED -gt 0 ]]; then - echo -e "${RED}Some checks failed. Please address the issues above.${NC}" - exit 1 - elif [[ $CHECKS_WARNED -gt 0 ]]; then - echo -e "${YELLOW}All critical checks passed, but there are warnings to review.${NC}" - exit 0 + if [[ $CHECKS_FAILED -eq 0 ]]; then + echo -e "${GREEN}All required checks passed!${NC}" + if [[ $CHECKS_WARNED -gt 0 ]]; then + echo -e "${YELLOW}There are some warnings to review.${NC}" + fi else - echo -e "${GREEN}All checks passed! You're ready to run the application.${NC}" - exit 0 + echo -e "${RED}Some checks failed. Please address the issues above.${NC}" + if [[ "$SETUP_MODE" != true ]]; then + echo -e "${BLUE}Tip: Run with --setup flag to auto-fix some issues${NC}" + fi fi + echo "" } # ============================================================================= # Main Execution # ============================================================================= -main() { - print_header "Python/FastAPI Sample App - Requirements Check" +# Get script directory and change to it +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +cd "$SCRIPT_DIR" - check_common_requirements - check_python_requirements - check_env_configuration - check_frontend_requirements +# Default options +SETUP_MODE=false - print_summary -} +# Parse arguments +while [[ $# -gt 0 ]]; do + case $1 in + --setup) + SETUP_MODE=true + shift + ;; + --help|-h) + show_help + ;; + *) + echo "Unknown option: $1" + echo "Use --help for usage information" + exit 1 + ;; + esac +done -main +# Print banner +echo "" +echo -e "${BLUE}╔══════════════════════════════════════════════════════════════╗${NC}" +echo -e "${BLUE}║ mflix Sample Application - Requirements Check ║${NC}" +echo -e "${BLUE}║ Python/FastAPI Backend ║${NC}" +echo -e "${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}" + +if [[ "$SETUP_MODE" == true ]]; then + echo -e "${YELLOW}Running in setup mode - will attempt to fix issues${NC}" +fi + +# Run all checks +check_backend_requirements +check_env_configuration +check_frontend_requirements + +# Print summary +print_summary + +# Exit with appropriate code +if [[ $CHECKS_FAILED -gt 0 ]]; then + exit 1 +fi +exit 0 From 6f5a8b86a3be666e3ac02e1774e61fdae2a94c03 Mon Sep 17 00:00:00 2001 From: Cory Bullinger Date: Tue, 24 Feb 2026 12:29:23 -0500 Subject: [PATCH 10/11] Break out -pre script --- mflix/README-JAVA-SPRING.md | 16 ++++++-- mflix/README-NODE-EXPRESS.md | 16 ++++++-- mflix/README-PYTHON-FASTAPI.md | 16 ++++++-- mflix/check-requirements-java.sh | 59 ++++++++++++++++++++++++---- mflix/check-requirements-js.sh | 63 ++++++++++++++++++++++++++---- mflix/check-requirements-python.sh | 59 ++++++++++++++++++++++++---- 6 files changed, 199 insertions(+), 30 deletions(-) diff --git a/mflix/README-JAVA-SPRING.md b/mflix/README-JAVA-SPRING.md index a888664..d561d10 100644 --- a/mflix/README-JAVA-SPRING.md +++ b/mflix/README-JAVA-SPRING.md @@ -30,13 +30,13 @@ The `sample_mflix` dataset contains movies released up to **2016**. Searching fo ## Verify Requirements -Before getting started, you can run the verification script to check if you have all the necessary requirements: +Before getting started, run the verification script to check if you have the required runtime: ```bash -./check-requirements.sh +./check-requirements-java.sh --pre ``` -This script checks for required tools (Java, Maven, Node.js), validates your environment configuration, and verifies dependencies. Run with `--help` for more options. +This checks that Java and JAVA_HOME are configured correctly. Run with `--help` for more options. ## Getting Started @@ -191,6 +191,16 @@ cd client npm run lint ``` +## Verify Setup + +After completing the setup, run the full verification to ensure everything is configured correctly: + +```bash +./check-requirements-java.sh +``` + +This checks your Java environment, Maven dependencies, `.env` configuration, and frontend setup. + ## Issues If you have problems running the sample app, please check the following: diff --git a/mflix/README-NODE-EXPRESS.md b/mflix/README-NODE-EXPRESS.md index f86f7a1..d7c013a 100644 --- a/mflix/README-NODE-EXPRESS.md +++ b/mflix/README-NODE-EXPRESS.md @@ -29,13 +29,13 @@ The `sample_mflix` dataset contains movies released up to **2016**. Searching fo ## Verify Requirements -Before getting started, you can run the verification script to check if you have all the necessary requirements: +Before getting started, run the verification script to check if you have the required runtime: ```bash -./check-requirements.sh +./check-requirements-js.sh --pre ``` -This script checks for required tools (Node.js, npm), validates your environment configuration, and verifies dependencies. Run with `--help` for more options. +This checks that Node.js and npm are installed with the correct versions. Run with `--help` for more options. ## Getting Started @@ -224,6 +224,16 @@ cd client npm run lint ``` +## Verify Setup + +After completing the setup, run the full verification to ensure everything is configured correctly: + +```bash +./check-requirements-js.sh +``` + +This checks your Node.js environment, npm dependencies, `.env` configuration, and frontend setup. + ## Issues If you have problems running the sample app, please check the following: diff --git a/mflix/README-PYTHON-FASTAPI.md b/mflix/README-PYTHON-FASTAPI.md index fdcf10d..38168be 100644 --- a/mflix/README-PYTHON-FASTAPI.md +++ b/mflix/README-PYTHON-FASTAPI.md @@ -33,13 +33,13 @@ The `sample_mflix` dataset contains movies released up to **2016**. Searching fo ## Verify Requirements -Before getting started, you can run the verification script to check if you have all the necessary requirements: +Before getting started, run the verification script to check if you have the required runtime: ```bash -./check-requirements.sh +./check-requirements-python.sh --pre ``` -This script checks for required tools (Python, pip, Node.js), validates your environment configuration, and verifies dependencies. Run with `--help` for more options. +This checks that Python and pip are installed with the correct versions. Run with `--help` for more options. ## Getting Started @@ -206,6 +206,16 @@ cd client npm run lint ``` +## Verify Setup + +After completing the setup, run the full verification to ensure everything is configured correctly: + +```bash +./check-requirements-python.sh +``` + +This checks your Python environment, dependencies, `.env` configuration, and frontend setup. + ## Issues If you have problems running the sample app, please check the following: diff --git a/mflix/check-requirements-java.sh b/mflix/check-requirements-java.sh index 97dedb7..001079a 100755 --- a/mflix/check-requirements-java.sh +++ b/mflix/check-requirements-java.sh @@ -8,7 +8,8 @@ # the mflix sample application with the Java/Spring Boot backend. # # Usage: -# ./check-requirements-java.sh # Check all requirements +# ./check-requirements-java.sh # Check all requirements (post-setup) +# ./check-requirements-java.sh --pre # Check only runtime requirements (pre-setup) # ./check-requirements-java.sh --setup # Check and auto-setup missing items # ./check-requirements-java.sh --help # Show help message # @@ -92,18 +93,51 @@ show_help() { echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" + echo " --pre Check only runtime requirements (use before setup)" echo " --setup Attempt to automatically set up missing requirements" echo " --help Show this help message" echo "" echo "This script checks that all necessary requirements are installed" echo "to run the mflix sample application with the Java/Spring Boot backend." + echo "" + echo "Use --pre before starting setup to verify you have the required runtime." + echo "Use without flags after completing setup to verify everything is ready." exit 0 } +# ============================================================================= +# Check Runtime Requirements (Pre-Setup) +# ============================================================================= + +check_runtime_requirements() { + print_section "Runtime Requirements" + + # Check Java version + if command_exists java; then + local java_version + java_version=$(java -version 2>&1 | head -1 | grep -oE '"[0-9]+' | tr -d '"') + if version_gte "$java_version" "$JAVA_MIN_VERSION"; then + check_pass "Java $java_version installed (>= $JAVA_MIN_VERSION required)" + else + check_fail "Java $java_version installed but >= $JAVA_MIN_VERSION required" + check_info "Install Java $JAVA_MIN_VERSION+: https://adoptium.net/" + fi + else + check_fail "Java not installed" + check_info "Install Java $JAVA_MIN_VERSION+: https://adoptium.net/" + fi + # Check JAVA_HOME + if [[ -n "$JAVA_HOME" ]]; then + check_pass "JAVA_HOME is set: $JAVA_HOME" + else + check_warn "JAVA_HOME is not set" + check_info "Set JAVA_HOME to your Java installation directory" + fi +} # ============================================================================= -# Check Java/Spring Boot Backend Requirements +# Check Java/Spring Boot Backend Requirements (Full) # ============================================================================= check_backend_requirements() { @@ -391,10 +425,15 @@ cd "$SCRIPT_DIR" # Default options SETUP_MODE=false +PRE_CHECK_MODE=false # Parse arguments while [[ $# -gt 0 ]]; do case $1 in + --pre) + PRE_CHECK_MODE=true + shift + ;; --setup) SETUP_MODE=true shift @@ -417,14 +456,20 @@ echo -e "${BLUE}║ mflix Sample Application - Requirements Check echo -e "${BLUE}║ Java/Spring Boot Backend ║${NC}" echo -e "${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}" -if [[ "$SETUP_MODE" == true ]]; then +if [[ "$PRE_CHECK_MODE" == true ]]; then + echo -e "${YELLOW}Pre-setup check - verifying runtime requirements only${NC}" +elif [[ "$SETUP_MODE" == true ]]; then echo -e "${YELLOW}Running in setup mode - will attempt to fix issues${NC}" fi -# Run all checks -check_backend_requirements -check_env_configuration -check_frontend_requirements +# Run checks based on mode +if [[ "$PRE_CHECK_MODE" == true ]]; then + check_runtime_requirements +else + check_backend_requirements + check_env_configuration + check_frontend_requirements +fi # Print summary print_summary diff --git a/mflix/check-requirements-js.sh b/mflix/check-requirements-js.sh index a588fe0..20d7f3a 100755 --- a/mflix/check-requirements-js.sh +++ b/mflix/check-requirements-js.sh @@ -8,7 +8,8 @@ # the mflix sample application with the JavaScript/Express backend. # # Usage: -# ./check-requirements-js.sh # Check all requirements +# ./check-requirements-js.sh # Check all requirements (post-setup) +# ./check-requirements-js.sh --pre # Check only runtime requirements (pre-setup) # ./check-requirements-js.sh --setup # Check and auto-setup missing items # ./check-requirements-js.sh --help # Show help message # @@ -91,18 +92,55 @@ show_help() { echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" + echo " --pre Check only runtime requirements (use before setup)" echo " --setup Attempt to automatically set up missing requirements" echo " --help Show this help message" echo "" echo "This script checks that all necessary requirements are installed" echo "to run the mflix sample application with the JavaScript/Express backend." + echo "" + echo "Use --pre before starting setup to verify you have the required runtime." + echo "Use without flags after completing setup to verify everything is ready." exit 0 } +# ============================================================================= +# Check Runtime Requirements (Pre-Setup) +# ============================================================================= +check_runtime_requirements() { + print_section "Runtime Requirements" + + # Check Node.js version + if command_exists node; then + local node_version + node_version=$(node --version | sed 's/v//') + local node_major + node_major=$(echo "$node_version" | cut -d. -f1) + if version_gte "$node_major" "$NODE_MIN_VERSION"; then + check_pass "Node.js $node_version installed (>= $NODE_MIN_VERSION required)" + else + check_fail "Node.js $node_version installed but >= $NODE_MIN_VERSION required" + check_info "Install Node.js $NODE_MIN_VERSION+: https://nodejs.org/" + fi + else + check_fail "Node.js not installed" + check_info "Install Node.js $NODE_MIN_VERSION+: https://nodejs.org/" + fi + + # Check npm + if command_exists npm; then + local npm_version + npm_version=$(npm --version) + check_pass "npm installed (version $npm_version)" + else + check_fail "npm not installed" + check_info "npm should come with Node.js installation" + fi +} # ============================================================================= -# Check JavaScript/Express Backend Requirements +# Check JavaScript/Express Backend Requirements (Full) # ============================================================================= check_backend_requirements() { @@ -382,10 +420,15 @@ cd "$SCRIPT_DIR" # Default options SETUP_MODE=false +PRE_CHECK_MODE=false # Parse arguments while [[ $# -gt 0 ]]; do case $1 in + --pre) + PRE_CHECK_MODE=true + shift + ;; --setup) SETUP_MODE=true shift @@ -408,14 +451,20 @@ echo -e "${BLUE}║ mflix Sample Application - Requirements Check echo -e "${BLUE}║ JavaScript/Express Backend ║${NC}" echo -e "${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}" -if [[ "$SETUP_MODE" == true ]]; then +if [[ "$PRE_CHECK_MODE" == true ]]; then + echo -e "${YELLOW}Pre-setup check - verifying runtime requirements only${NC}" +elif [[ "$SETUP_MODE" == true ]]; then echo -e "${YELLOW}Running in setup mode - will attempt to fix issues${NC}" fi -# Run all checks -check_backend_requirements -check_env_configuration -check_frontend_requirements +# Run checks based on mode +if [[ "$PRE_CHECK_MODE" == true ]]; then + check_runtime_requirements +else + check_backend_requirements + check_env_configuration + check_frontend_requirements +fi # Print summary print_summary diff --git a/mflix/check-requirements-python.sh b/mflix/check-requirements-python.sh index ea1249c..fd4d7f1 100755 --- a/mflix/check-requirements-python.sh +++ b/mflix/check-requirements-python.sh @@ -8,7 +8,8 @@ # the mflix sample application with the Python/FastAPI backend. # # Usage: -# ./check-requirements-python.sh # Check all requirements +# ./check-requirements-python.sh # Check all requirements (post-setup) +# ./check-requirements-python.sh --pre # Check only runtime requirements (pre-setup) # ./check-requirements-python.sh --setup # Check and auto-setup missing items # ./check-requirements-python.sh --help # Show help message # @@ -92,18 +93,51 @@ show_help() { echo "Usage: $0 [OPTIONS]" echo "" echo "Options:" + echo " --pre Check only runtime requirements (use before setup)" echo " --setup Attempt to automatically set up missing requirements" echo " --help Show this help message" echo "" echo "This script checks that all necessary requirements are installed" echo "to run the mflix sample application with the Python/FastAPI backend." + echo "" + echo "Use --pre before starting setup to verify you have the required runtime." + echo "Use without flags after completing setup to verify everything is ready." exit 0 } +# ============================================================================= +# Check Runtime Requirements (Pre-Setup) +# ============================================================================= + +check_runtime_requirements() { + print_section "Runtime Requirements" + + # Check Python version + if command_exists python3; then + local python_version + python_version=$(python3 --version 2>&1 | grep -oE '[0-9]+\.[0-9]+' | head -1) + if version_gte "$python_version" "$PYTHON_MIN_VERSION"; then + check_pass "Python $python_version installed (>= $PYTHON_MIN_VERSION required)" + else + check_fail "Python $python_version installed but >= $PYTHON_MIN_VERSION required" + check_info "Install Python $PYTHON_MIN_VERSION+ from https://www.python.org/downloads/" + fi + else + check_fail "Python 3 not installed" + check_info "Install Python $PYTHON_MIN_VERSION+ from https://www.python.org/downloads/" + fi + # Check pip + if command_exists pip3 || python3 -m pip --version &>/dev/null; then + check_pass "pip installed" + else + check_fail "pip not installed" + check_info "Install pip: python3 -m ensurepip --upgrade" + fi +} # ============================================================================= -# Check Python/FastAPI Backend Requirements +# Check Python/FastAPI Backend Requirements (Full) # ============================================================================= check_backend_requirements() { @@ -388,10 +422,15 @@ cd "$SCRIPT_DIR" # Default options SETUP_MODE=false +PRE_CHECK_MODE=false # Parse arguments while [[ $# -gt 0 ]]; do case $1 in + --pre) + PRE_CHECK_MODE=true + shift + ;; --setup) SETUP_MODE=true shift @@ -414,14 +453,20 @@ echo -e "${BLUE}║ mflix Sample Application - Requirements Check echo -e "${BLUE}║ Python/FastAPI Backend ║${NC}" echo -e "${BLUE}╚══════════════════════════════════════════════════════════════╝${NC}" -if [[ "$SETUP_MODE" == true ]]; then +if [[ "$PRE_CHECK_MODE" == true ]]; then + echo -e "${YELLOW}Pre-setup check - verifying runtime requirements only${NC}" +elif [[ "$SETUP_MODE" == true ]]; then echo -e "${YELLOW}Running in setup mode - will attempt to fix issues${NC}" fi -# Run all checks -check_backend_requirements -check_env_configuration -check_frontend_requirements +# Run checks based on mode +if [[ "$PRE_CHECK_MODE" == true ]]; then + check_runtime_requirements +else + check_backend_requirements + check_env_configuration + check_frontend_requirements +fi # Print summary print_summary From 43d80f5be579f931fd4df2f586a804699c832dae Mon Sep 17 00:00:00 2001 From: Cory Bullinger Date: Fri, 6 Mar 2026 07:55:00 -0500 Subject: [PATCH 11/11] Update workflow to use the updated env vars --- .github/workflows/run-python-tests.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/run-python-tests.yml b/.github/workflows/run-python-tests.yml index 4a24fe6..4568fa9 100644 --- a/.github/workflows/run-python-tests.yml +++ b/.github/workflows/run-python-tests.yml @@ -107,15 +107,13 @@ jobs: working-directory: mflix/server/python-fastapi run: pytest -m unit --verbose --tb=short --junit-xml=test-results-unit.xml env: - MONGO_URI: mongodb://localhost:27017 - MONGO_DB: sample_mflix + MONGODB_URI: mongodb://localhost:27017 - name: Run integration tests working-directory: mflix/server/python-fastapi run: pytest -m integration --verbose --tb=short --junit-xml=test-results-integration.xml env: - MONGO_URI: mongodb://localhost:27017/?directConnection=true - MONGO_DB: sample_mflix + MONGODB_URI: mongodb://localhost:27017/?directConnection=true - name: Upload test results uses: actions/upload-artifact@v4