From de6d8896cb31b5564fcbaaf18f63b14a8e440e8d Mon Sep 17 00:00:00 2001 From: dacharyc Date: Wed, 5 Nov 2025 15:54:02 -0500 Subject: [PATCH 1/3] Express: Add Swagger API docs --- server/express/package.json | 6 +- server/express/src/app.ts | 42 ++- server/express/src/config/swagger.ts | 354 ++++++++++++++++++++ server/express/src/routes/movies.ts | 462 +++++++++++++++++++++++++-- 4 files changed, 830 insertions(+), 34 deletions(-) create mode 100644 server/express/src/config/swagger.ts diff --git a/server/express/package.json b/server/express/package.json index c067a31..b776f78 100644 --- a/server/express/package.json +++ b/server/express/package.json @@ -21,13 +21,17 @@ "cors": "^2.8.5", "dotenv": "^17.2.3", "express": "^5.1.0", - "mongodb": "^6.20.0" + "mongodb": "^6.20.0", + "swagger-jsdoc": "^6.2.8", + "swagger-ui-express": "^5.0.1" }, "devDependencies": { "@types/cors": "^2.8.17", "@types/express": "^4.17.21", "@types/jest": "^29.5.14", "@types/node": "^20.10.5", + "@types/swagger-jsdoc": "^6.0.4", + "@types/swagger-ui-express": "^4.1.8", "jest": "^29.7.0", "ts-jest": "^29.1.1", "ts-node": "^10.9.2", diff --git a/server/express/src/app.ts b/server/express/src/app.ts index 571ffd4..1d4898f 100644 --- a/server/express/src/app.ts +++ b/server/express/src/app.ts @@ -9,6 +9,7 @@ import express from "express"; import cors from "cors"; import dotenv from "dotenv"; +import swaggerUi from "swagger-ui-express"; import { closeDatabaseConnection, connectToDatabase, @@ -16,6 +17,7 @@ import { } from "./config/database"; import { errorHandler } from "./utils/errorHandler"; import moviesRouter from "./routes/movies"; +import { swaggerSpec } from "./config/swagger"; // Load environment variables from .env file // This must be called before any other imports that use environment variables @@ -44,6 +46,12 @@ app.use( app.use(express.json({ limit: "10mb" })); app.use(express.urlencoded({ extended: true, limit: "10mb" })); +/** + * Swagger API Documentation + * Provides interactive API documentation at /api-docs + */ +app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerSpec)); + /** * API Routes * All movie-related CRUD operations are handled by the movies router @@ -53,6 +61,37 @@ app.use("/api/movies", moviesRouter); /** * Root Endpoint * Provides basic information about the API + * @swagger + * /: + * get: + * summary: Get API information + * description: Returns basic information about the API and available endpoints + * tags: [Info] + * responses: + * 200: + * description: API information + * content: + * application/json: + * schema: + * type: object + * properties: + * name: + * type: string + * example: MongoDB Sample MFlix API + * version: + * type: string + * example: 1.0.0 + * description: + * type: string + * endpoints: + * type: object + * properties: + * movies: + * type: string + * example: /api/movies + * documentation: + * type: string + * example: /api-docs */ app.get("/", (req, res) => { res.json({ @@ -62,6 +101,7 @@ app.get("/", (req, res) => { "Express.js backend demonstrating MongoDB operations with the sample_mflix dataset", endpoints: { movies: "/api/movies", + documentation: "/api-docs", }, }); }); @@ -94,7 +134,7 @@ async function startServer() { // Start the Express server app.listen(PORT, () => { console.log(`Server running on port ${PORT}`); - console.log(`API documentation available at http://localhost:${PORT}`); + console.log(`API documentation available at http://localhost:${PORT}/api-docs`); }); } catch (error) { console.error("Failed to start server:", error); diff --git a/server/express/src/config/swagger.ts b/server/express/src/config/swagger.ts new file mode 100644 index 0000000..3002da9 --- /dev/null +++ b/server/express/src/config/swagger.ts @@ -0,0 +1,354 @@ +/** + * Swagger/OpenAPI Configuration + * + * This module configures swagger-jsdoc to generate OpenAPI documentation + * from JSDoc comments in the codebase. + */ + +import swaggerJsdoc from "swagger-jsdoc"; + +const options: swaggerJsdoc.Options = { + definition: { + openapi: "3.0.0", + info: { + title: "MongoDB Sample MFlix API", + version: "1.0.0", + description: + "Express.js backend demonstrating MongoDB operations with the sample_mflix dataset. " + + "This API provides CRUD operations for movies, including text search, filtering, pagination, and batch operations.", + contact: { + name: "API Support", + }, + license: { + name: "Apache 2.0", + url: "https://www.apache.org/licenses/LICENSE-2.0.html", + }, + }, + servers: [ + { + url: "http://localhost:3001", + description: "Development server", + }, + ], + tags: [ + { + name: "Movies", + description: "Movie CRUD operations and queries", + }, + { + name: "Info", + description: "API information", + }, + ], + components: { + schemas: { + Movie: { + type: "object", + required: ["title"], + properties: { + _id: { + type: "string", + description: "MongoDB ObjectId", + example: "573a1390f29313caabcd4135", + }, + title: { + type: "string", + description: "Movie title", + example: "The Shawshank Redemption", + }, + year: { + type: "integer", + description: "Release year", + example: 1994, + }, + plot: { + type: "string", + description: "Short plot summary", + example: "Two imprisoned men bond over a number of years...", + }, + fullplot: { + type: "string", + description: "Full plot description", + }, + runtime: { + type: "integer", + description: "Runtime in minutes", + example: 142, + }, + poster: { + type: "string", + description: "URL to movie poster", + }, + genres: { + type: "array", + items: { + type: "string", + }, + description: "Movie genres", + example: ["Drama"], + }, + directors: { + type: "array", + items: { + type: "string", + }, + description: "Movie directors", + example: ["Frank Darabont"], + }, + writers: { + type: "array", + items: { + type: "string", + }, + description: "Movie writers", + }, + cast: { + type: "array", + items: { + type: "string", + }, + description: "Movie cast", + example: ["Tim Robbins", "Morgan Freeman"], + }, + countries: { + type: "array", + items: { + type: "string", + }, + description: "Countries of origin", + example: ["USA"], + }, + languages: { + type: "array", + items: { + type: "string", + }, + description: "Languages", + example: ["English"], + }, + rated: { + type: "string", + description: "MPAA rating", + example: "R", + }, + awards: { + type: "object", + properties: { + wins: { + type: "integer", + example: 21, + }, + nominations: { + type: "integer", + example: 42, + }, + text: { + type: "string", + example: "Nominated for 7 Oscars. Another 21 wins & 42 nominations.", + }, + }, + }, + imdb: { + type: "object", + properties: { + rating: { + type: "number", + format: "float", + example: 9.3, + }, + votes: { + type: "integer", + example: 2343110, + }, + id: { + type: "integer", + example: 111161, + }, + }, + }, + tomatoes: { + type: "object", + properties: { + viewer: { + type: "object", + properties: { + rating: { + type: "number", + format: "float", + }, + numReviews: { + type: "integer", + }, + meter: { + type: "integer", + }, + }, + }, + critic: { + type: "object", + properties: { + rating: { + type: "number", + format: "float", + }, + numReviews: { + type: "integer", + }, + meter: { + type: "integer", + }, + }, + }, + }, + }, + metacritic: { + type: "integer", + example: 80, + }, + type: { + type: "string", + example: "movie", + }, + }, + }, + CreateMovieRequest: { + type: "object", + required: ["title"], + properties: { + title: { + type: "string", + description: "Movie title (required)", + example: "My New Movie", + }, + year: { + type: "integer", + example: 2024, + }, + plot: { + type: "string", + example: "An exciting new film...", + }, + fullplot: { + type: "string", + }, + genres: { + type: "array", + items: { + type: "string", + }, + example: ["Action", "Adventure"], + }, + directors: { + type: "array", + items: { + type: "string", + }, + example: ["Jane Director"], + }, + writers: { + type: "array", + items: { + type: "string", + }, + }, + cast: { + type: "array", + items: { + type: "string", + }, + }, + countries: { + type: "array", + items: { + type: "string", + }, + }, + languages: { + type: "array", + items: { + type: "string", + }, + }, + rated: { + type: "string", + }, + runtime: { + type: "integer", + }, + poster: { + type: "string", + }, + }, + }, + UpdateMovieRequest: { + type: "object", + properties: { + title: { + type: "string", + }, + year: { + type: "integer", + }, + plot: { + type: "string", + }, + genres: { + type: "array", + items: { + type: "string", + }, + }, + }, + description: "All fields are optional for partial updates", + }, + SuccessResponse: { + type: "object", + properties: { + success: { + type: "boolean", + example: true, + }, + message: { + type: "string", + example: "Operation completed successfully", + }, + data: { + type: "object", + description: "Response data (varies by endpoint)", + }, + }, + }, + ErrorResponse: { + type: "object", + properties: { + success: { + type: "boolean", + example: false, + }, + error: { + type: "object", + properties: { + message: { + type: "string", + example: "An error occurred", + }, + code: { + type: "string", + example: "ERROR_CODE", + }, + details: { + type: "string", + }, + }, + }, + }, + }, + }, + }, + }, + // Path to the API routes files where JSDoc comments are located + apis: ["./src/routes/*.ts", "./src/app.ts"], +}; + +export const swaggerSpec = swaggerJsdoc(options); + diff --git a/server/express/src/routes/movies.ts b/server/express/src/routes/movies.ts index 5d6d6ac..a265095 100644 --- a/server/express/src/routes/movies.ts +++ b/server/express/src/routes/movies.ts @@ -22,10 +22,99 @@ import * as movieController from "../controllers/movieController"; const router = express.Router(); /** - * GET /api/movies - * - * Retrieves multiple movies with optional filtering, sorting, and pagination. - * Demonstrates the find() operation with various query options. + * @swagger + * /api/movies: + * get: + * summary: Get all movies + * description: Retrieves multiple movies with optional filtering, sorting, and pagination. Demonstrates the MongoDB find() operation. + * tags: [Movies] + * parameters: + * - in: query + * name: q + * schema: + * type: string + * description: Text search query (searches title, plot, fullplot) + * example: shawshank + * - in: query + * name: genre + * schema: + * type: string + * description: Filter by genre + * example: Drama + * - in: query + * name: year + * schema: + * type: integer + * description: Filter by year + * example: 1994 + * - in: query + * name: minRating + * schema: + * type: number + * description: Minimum IMDB rating + * example: 8.0 + * - in: query + * name: maxRating + * schema: + * type: number + * description: Maximum IMDB rating + * example: 10.0 + * - in: query + * name: limit + * schema: + * type: integer + * default: 20 + * maximum: 100 + * description: Number of results to return + * - in: query + * name: skip + * schema: + * type: integer + * default: 0 + * description: Number of documents to skip for pagination + * - in: query + * name: sortBy + * schema: + * type: string + * default: title + * description: Field to sort by + * example: year + * - in: query + * name: sortOrder + * schema: + * type: string + * enum: [asc, desc] + * default: asc + * description: Sort direction + * responses: + * 200: + * description: List of movies + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * movies: + * type: array + * items: + * $ref: '#/components/schemas/Movie' + * count: + * type: integer + * limit: + * type: integer + * skip: + * type: integer + * 400: + * description: Bad request + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' */ router.get("/", asyncHandler(movieController.getAllMovies)); @@ -74,46 +163,281 @@ router.get("/aggregations/reportingByDirectors", asyncHandler(movieController.ge * * Retrieves a single movie by its ObjectId. * Demonstrates the findOne() operation. + * @swagger + * /api/movies/{id}: + * get: + * summary: Get a movie by ID + * description: Retrieves a single movie by its MongoDB ObjectId. Demonstrates the findOne() operation. + * tags: [Movies] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: MongoDB ObjectId of the movie + * example: 573a1390f29313caabcd4135 + * responses: + * 200: + * description: Movie found + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/Movie' + * 400: + * description: Invalid ObjectId format + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * 404: + * description: Movie not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' */ router.get("/:id", asyncHandler(movieController.getMovieById)); /** - * POST /api/movies - * - * Creates a single new movie document. - * Demonstrates the insertOne() operation. + * @swagger + * /api/movies: + * post: + * summary: Create a new movie + * description: Creates a single new movie document. Demonstrates the MongoDB insertOne() operation. + * tags: [Movies] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/CreateMovieRequest' + * responses: + * 201: + * description: Movie created successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * insertedId: + * type: string + * description: MongoDB ObjectId of the created movie + * 400: + * description: Validation error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' */ router.post("/", asyncHandler(movieController.createMovie)); /** - * POST /api/movies/batch - * - * Creates multiple movie documents in a single operation. - * Demonstrates the insertMany() operation. + * @swagger + * /api/movies/batch: + * post: + * summary: Create multiple movies + * description: Creates multiple movie documents in a single operation. Demonstrates the MongoDB insertMany() operation. + * tags: [Movies] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: array + * items: + * $ref: '#/components/schemas/CreateMovieRequest' + * example: + * - title: "Movie One" + * year: 2024 + * genres: ["Action"] + * - title: "Movie Two" + * year: 2024 + * genres: ["Drama"] + * responses: + * 201: + * description: Movies created successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * insertedCount: + * type: integer + * insertedIds: + * type: object + * 400: + * description: Validation error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' */ router.post("/batch", asyncHandler(movieController.createMoviesBatch)); /** - * PATCH /api/movies/:id - * - * Updates a single movie document. - * Demonstrates the updateOne() operation. + * @swagger + * /api/movies/{id}: + * patch: + * summary: Update a movie + * description: Updates a single movie document by ID. Demonstrates the MongoDB updateOne() operation. + * tags: [Movies] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: MongoDB ObjectId of the movie + * example: 573a1390f29313caabcd4135 + * requestBody: + * required: true + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/UpdateMovieRequest' + * example: + * title: "Updated Movie Title" + * year: 2024 + * responses: + * 200: + * description: Movie updated successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * matchedCount: + * type: integer + * modifiedCount: + * type: integer + * 400: + * description: Invalid ObjectId or validation error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * 404: + * description: Movie not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' */ router.patch("/:id", asyncHandler(movieController.updateMovie)); /** - * PATCH /api/movies - * - * Updates multiple movies based on a filter. - * Demonstrates the updateMany() operation. + * @swagger + * /api/movies: + * patch: + * summary: Update multiple movies + * description: Updates multiple movies based on a filter. Demonstrates the MongoDB updateMany() operation. + * tags: [Movies] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - filter + * - update + * properties: + * filter: + * type: object + * description: MongoDB filter criteria + * example: + * year: 1994 + * update: + * type: object + * description: Fields to update + * example: + * rated: "PG-13" + * responses: + * 200: + * description: Movies updated successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * matchedCount: + * type: integer + * modifiedCount: + * type: integer + * 400: + * description: Validation error + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' */ router.patch("/", asyncHandler(movieController.updateMoviesBatch)); /** - * DELETE /api/movies/:id/find-and-delete - * - * Finds and deletes a movie in a single atomic operation. - * Demonstrates the findOneAndDelete() operation. + * @swagger + * /api/movies/{id}/find-and-delete: + * delete: + * summary: Find and delete a movie + * description: Finds and deletes a movie in a single atomic operation. Demonstrates the MongoDB findOneAndDelete() operation. + * tags: [Movies] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: MongoDB ObjectId of the movie + * example: 573a1390f29313caabcd4135 + * responses: + * 200: + * description: Movie found and deleted + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * $ref: '#/components/schemas/Movie' + * 400: + * description: Invalid ObjectId format + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * 404: + * description: Movie not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' */ router.delete( "/:id/find-and-delete", @@ -121,18 +445,92 @@ router.delete( ); /** - * DELETE /api/movies/:id - * - * Deletes a single movie document. - * Demonstrates the deleteOne() operation. + * @swagger + * /api/movies/{id}: + * delete: + * summary: Delete a movie + * description: Deletes a single movie document by ID. Demonstrates the MongoDB deleteOne() operation. + * tags: [Movies] + * parameters: + * - in: path + * name: id + * required: true + * schema: + * type: string + * description: MongoDB ObjectId of the movie + * example: 573a1390f29313caabcd4135 + * responses: + * 200: + * description: Movie deleted successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * deletedCount: + * type: integer + * 400: + * description: Invalid ObjectId format + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' + * 404: + * description: Movie not found + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' */ router.delete("/:id", asyncHandler(movieController.deleteMovie)); /** - * DELETE /api/movies - * - * Deletes multiple movies based on a filter. - * Demonstrates the deleteMany() operation. + * @swagger + * /api/movies: + * delete: + * summary: Delete multiple movies + * description: Deletes multiple movies based on a filter. Demonstrates the MongoDB deleteMany() operation. + * tags: [Movies] + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * required: + * - filter + * properties: + * filter: + * type: object + * description: MongoDB filter criteria (cannot be empty to prevent accidental deletion of all documents) + * example: + * year: 1990 + * responses: + * 200: + * description: Movies deleted successfully + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * deletedCount: + * type: integer + * 400: + * description: Validation error (missing or empty filter) + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' */ router.delete("/", asyncHandler(movieController.deleteMoviesBatch)); From 5610a758e51e238a33366060740d3ebe36b07fbb Mon Sep 17 00:00:00 2001 From: dacharyc Date: Thu, 6 Nov 2025 21:47:54 -0500 Subject: [PATCH 2/3] Add Swagger annotations for new routes --- server/express/src/routes/movies.ts | 299 ++++++++++++++++++++++++++-- 1 file changed, 279 insertions(+), 20 deletions(-) diff --git a/server/express/src/routes/movies.ts b/server/express/src/routes/movies.ts index a265095..a4dbbf9 100644 --- a/server/express/src/routes/movies.ts +++ b/server/express/src/routes/movies.ts @@ -119,42 +119,301 @@ const router = express.Router(); router.get("/", asyncHandler(movieController.getAllMovies)); /** - * GET /api/movies/search - * - * Search movies using MongoDB Search across multiple fields. - * Demonstrates MongoDB Atlas Search with compound queries and fuzzy matching. + * @swagger + * /api/movies/search: + * get: + * summary: Search movies using MongoDB Atlas Search + * description: Search movies using MongoDB Atlas Search across multiple fields with compound queries and fuzzy matching. Demonstrates MongoDB Atlas Search capabilities. + * tags: [Movies] + * parameters: + * - in: query + * name: plot + * schema: + * type: string + * description: Search in plot field using phrase matching + * example: detective solving mystery + * - in: query + * name: fullplot + * schema: + * type: string + * description: Search in fullplot field using phrase matching + * example: crime investigation + * - in: query + * name: directors + * schema: + * type: string + * description: Search for directors with fuzzy matching + * example: Christopher Nolan + * - in: query + * name: writers + * schema: + * type: string + * description: Search for writers with fuzzy matching + * example: Quentin Tarantino + * - in: query + * name: cast + * schema: + * type: string + * description: Search for cast members with fuzzy matching + * example: Tom Hanks + * - in: query + * name: searchOperator + * schema: + * type: string + * enum: [must, should, mustNot, filter] + * default: must + * description: Search operator for compound queries + * - in: query + * name: limit + * schema: + * type: integer + * default: 20 + * maximum: 100 + * description: Number of results to return + * - in: query + * name: skip + * schema: + * type: integer + * default: 0 + * description: Number of documents to skip for pagination + * responses: + * 200: + * description: Search results + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * type: object + * properties: + * movies: + * type: array + * items: + * $ref: '#/components/schemas/Movie' + * count: + * type: integer + * limit: + * type: integer + * skip: + * type: integer + * 400: + * description: Bad request - invalid search operator or no search parameters provided + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' */ router.get("/search", asyncHandler(movieController.searchMovies)); /** - * GET /api/movies/vector-search - * - * Search movies using MongoDB Vector Search for semantic similarity. - * Demonstrates vector search using embeddings to find similar plots. + * @swagger + * /api/movies/vector-search: + * get: + * summary: Search movies using MongoDB Vector Search + * description: Search movies using MongoDB Vector Search for semantic similarity. Demonstrates vector search using embeddings to find movies with similar plots. Requires VOYAGE_API_KEY to be configured. + * tags: [Movies] + * parameters: + * - in: query + * name: q + * required: true + * schema: + * type: string + * description: Search query for semantic similarity + * example: space exploration and alien encounters + * - in: query + * name: limit + * schema: + * type: integer + * default: 10 + * maximum: 50 + * description: Number of results to return + * responses: + * 200: + * description: Vector search results with similarity scores + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * allOf: + * - $ref: '#/components/schemas/Movie' + * - type: object + * properties: + * score: + * type: number + * description: Vector similarity score + * 400: + * description: Bad request - missing query parameter or VOYAGE_API_KEY not configured + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' */ router.get("/vector-search", asyncHandler(movieController.vectorSearchMovies)); /** - * GET /api/movies/aggregations/reportingByComments - * - * Aggregate movies with their most recent comments. - * Demonstrates MongoDB $lookup aggregation to join collections. + * @swagger + * /api/movies/aggregations/reportingByComments: + * get: + * summary: Get movies with their most recent comments + * description: Aggregate movies with their most recent comments using MongoDB $lookup aggregation. Demonstrates joining collections and sorting by nested fields. + * tags: [Movies] + * parameters: + * - in: query + * name: limit + * schema: + * type: integer + * default: 10 + * maximum: 50 + * description: Number of recent comments to include per movie + * - in: query + * name: movieId + * schema: + * type: string + * description: Optional MongoDB ObjectId to filter for a specific movie + * example: 573a1390f29313caabcd4135 + * responses: + * 200: + * description: Movies with their most recent comments + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * type: object + * properties: + * _id: + * type: string + * title: + * type: string + * year: + * type: integer + * genres: + * type: array + * items: + * type: string + * imdbRating: + * type: number + * recentComments: + * type: array + * items: + * type: object + * properties: + * _id: + * type: string + * userName: + * type: string + * userEmail: + * type: string + * text: + * type: string + * date: + * type: string + * format: date-time + * 400: + * description: Invalid movie ID format + * content: + * application/json: + * schema: + * $ref: '#/components/schemas/ErrorResponse' */ router.get("/aggregations/reportingByComments", asyncHandler(movieController.getMoviesWithMostRecentComments)); /** - * GET /api/movies/aggregations/reportingByYear - * - * Aggregate movies by year with statistics. - * Demonstrates MongoDB $group aggregation for statistical calculations. + * @swagger + * /api/movies/aggregations/reportingByYear: + * get: + * summary: Get movie statistics by year + * description: Aggregate movies by year with statistics including count, average rating, highest/lowest ratings, and total votes. Demonstrates MongoDB $group aggregation for statistical calculations. + * tags: [Movies] + * responses: + * 200: + * description: Movie statistics grouped by year + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * type: object + * properties: + * year: + * type: integer + * example: 1994 + * movieCount: + * type: integer + * example: 150 + * averageRating: + * type: number + * example: 7.25 + * highestRating: + * type: number + * example: 9.3 + * lowestRating: + * type: number + * example: 4.5 + * totalVotes: + * type: integer + * example: 1500000 */ router.get("/aggregations/reportingByYear", asyncHandler(movieController.getMoviesByYearWithStats)); /** - * GET /api/movies/aggregations/reportingByDirectors - * - * Aggregate directors with the most movies. - * Demonstrates MongoDB $unwind and $group for array aggregation. + * @swagger + * /api/movies/aggregations/reportingByDirectors: + * get: + * summary: Get directors with the most movies + * description: Aggregate directors with the most movies, including their movie count and average rating. Demonstrates MongoDB $unwind and $group for array aggregation. + * tags: [Movies] + * parameters: + * - in: query + * name: limit + * schema: + * type: integer + * default: 20 + * maximum: 100 + * description: Number of directors to return + * responses: + * 200: + * description: Directors with the most movies + * content: + * application/json: + * schema: + * allOf: + * - $ref: '#/components/schemas/SuccessResponse' + * - type: object + * properties: + * data: + * type: array + * items: + * type: object + * properties: + * director: + * type: string + * example: Steven Spielberg + * movieCount: + * type: integer + * example: 25 + * averageRating: + * type: number + * example: 7.85 */ router.get("/aggregations/reportingByDirectors", asyncHandler(movieController.getDirectorsWithMostMovies)); From aeca8fc74eeae912863dcbdbd11cbe2ccb72f343 Mon Sep 17 00:00:00 2001 From: dacharyc Date: Thu, 6 Nov 2025 21:51:47 -0500 Subject: [PATCH 3/3] Fix controller comments to match routes whose names have changed --- server/express/src/controllers/movieController.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/express/src/controllers/movieController.ts b/server/express/src/controllers/movieController.ts index 14269dc..d913a09 100644 --- a/server/express/src/controllers/movieController.ts +++ b/server/express/src/controllers/movieController.ts @@ -848,7 +848,7 @@ export async function vectorSearchMovies(req: Request, res: Response): Promise