Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
1170d0b
Merge pull request #92 from mongodb/main
cbullinger Mar 28, 2026
0f141a3
fix(python-fastapi): bump aiohttp to >=3.13.4 for CVE-2026-34525
cbullinger Apr 2, 2026
e313bb9
Merge pull request #93 from mongodb/security/fix-aiohttp-cve-2026-345…
cbullinger Apr 2, 2026
03633f2
Add lombok annotation processor to avoid `cannot find symbol` errors …
ozgliderpilot Apr 14, 2026
4ea43f5
Use double-quotes to escape `&` in the MONGODB_URI as otherwise it wi…
ozgliderpilot Apr 14, 2026
3099ad0
Replace `@Data` with records for DTOs
ozgliderpilot Apr 14, 2026
61aa003
Remove lombok `@Data` from MongoDB entity
ozgliderpilot Apr 14, 2026
d6cda6d
Clean up lombok and compiler plugin config in pom.xml
ozgliderpilot Apr 14, 2026
c871585
Exclude lombok from fat jar, quote MONGODB_URI in integration README
ozgliderpilot Apr 14, 2026
47328c6
Merge branch 'bugfix/spring-boot-run-failures' into bugfix/get-rid-of…
ozgliderpilot Apr 14, 2026
3519b60
Use builder defaults for response records and clean up annotations
ozgliderpilot Apr 15, 2026
3a06ceb
Add @ToString to Movie entity and align ApiResponse with record acces…
ozgliderpilot Apr 15, 2026
88090fd
Add protected constructors for better Spring Data performance
ozgliderpilot Apr 15, 2026
52e66cd
Add protected constructors to Movie nested classes and clean up recor…
ozgliderpilot Apr 15, 2026
5a8988c
Remove redundant parentheses in MoviesByYearResult rounding
ozgliderpilot Apr 15, 2026
69e56f5
Replace java.util.Date with Instant and LocalDate for MongoDB datetim…
ozgliderpilot Apr 15, 2026
b49a178
Merge pull request #95 from ozgliderpilot/bugfix/spring-boot-run-fail…
cbullinger Apr 15, 2026
21bbf34
Merge pull request #96 from ozgliderpilot/bugfix/get-rid-of-lombok-da…
cbullinger Apr 15, 2026
304a258
Test that BSON DateTime at midnight UTC is read as the correct LocalD…
ozgliderpilot Apr 16, 2026
686cb2c
Add integration test to verify LocalDate is not shifted when read and…
ozgliderpilot Apr 16, 2026
7e5d3b8
Bump spring boot version for related test containers version increase…
ozgliderpilot Apr 16, 2026
c8a4357
Merge pull request #97 from ozgliderpilot/refactor/java-datetime-alig…
cbullinger Apr 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion mflix/README-JAVA-SPRING.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ Edit the `.env` file and set your MongoDB connection string:

```env
# MongoDB Connection
MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority
MONGODB_URI="mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority"

# Voyage AI Configuration (optional - required for Vector Search)
VOYAGE_API_KEY=your_voyage_api_key
Expand Down
2 changes: 1 addition & 1 deletion mflix/README-NODE-EXPRESS.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ 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://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority
MONGODB_URI="mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority"

# Voyage AI Configuration
# API key for Voyage AI embedding model (required for Vector Search)
Expand Down
2 changes: 1 addition & 1 deletion mflix/README-PYTHON-FASTAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Edit the `.env` file and set your MongoDB connection string:

```env
# MongoDB Connection
MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority
MONGODB_URI="mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority"

# Voyage AI Configuration (optional - required for Vector Search)
VOYAGE_API_KEY=your_voyage_api_key
Expand Down
2 changes: 1 addition & 1 deletion mflix/server/java-spring/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# MongoDB Connection
# Replace with your MongoDB Atlas connection string or local MongoDB URI
MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority
MONGODB_URI="mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority"

# OPTIONAL: Voyage AI Configuration (required for Vector Search)
# Get your API key from https://www.voyageai.com/
Expand Down
27 changes: 24 additions & 3 deletions mflix/server/java-spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.7</version>
<version>3.5.13</version>
<relativePath/>
</parent>

Expand Down Expand Up @@ -58,7 +58,7 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
<scope>provided</scope>
</dependency>

<!-- Force version for Commons Lang used by SpringDoc (CVE-2025-48924) -->
Expand Down Expand Up @@ -88,6 +88,20 @@
<scope>test</scope>
</dependency>

<!-- Testcontainers JUnit Jupiter integration -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

<!-- MongoDB Atlas Local container for Testcontainers -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>mongodb</artifactId>
<scope>test</scope>
</dependency>

<!-- Jackson Databind for JSON serialization/deserialization -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
Expand Down Expand Up @@ -118,11 +132,18 @@
</configuration>
</plugin>

<!-- Maven Compiler Plugin - Suppress annotation processing warnings -->
<!-- Maven Compiler Plugin - Process Lombok and suppress annotation processing warnings -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<annotationProcessorPaths>
<path>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</path>
</annotationProcessorPaths>
<compilerArgs>
<arg>-Xlint:-options</arg>
</compilerArgs>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import org.springframework.lang.NonNull;

Expand Down Expand Up @@ -119,4 +120,9 @@ public MongoDatabase mongoDatabase() {

return client.getDatabase(databaseName);
}

@Bean
public MongoCustomConversions customConversions() {
return MongoCustomConversions.create(MongoCustomConversions.MongoConverterConfigurationAdapter::useNativeDriverJavaTimeCodecs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import org.bson.Document;
Expand Down Expand Up @@ -103,10 +102,8 @@ public ResponseEntity<SuccessResponse<List<Movie>>> getAllMovies(
String message = "Found " + movies.size() + " movies";

SuccessResponse<List<Movie>> response = SuccessResponse.<List<Movie>>builder()
.success(true)
.message(message)
.data(movies)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand All @@ -122,10 +119,8 @@ public ResponseEntity<SuccessResponse<List<String>>> getDistinctGenres() {
List<String> genres = movieService.getDistinctGenres();

SuccessResponse<List<String>> response = SuccessResponse.<List<String>>builder()
.success(true)
.message("Found " + genres.size() + " distinct genres")
.data(genres)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand All @@ -142,10 +137,8 @@ public ResponseEntity<SuccessResponse<Movie>> getMovieById(
Movie movie = movieService.getMovieById(id);

SuccessResponse<Movie> response = SuccessResponse.<Movie>builder()
.success(true)
.message("Movie retrieved successfully")
.data(movie)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand All @@ -162,10 +155,8 @@ public ResponseEntity<SuccessResponse<Movie>> createMovie(
Movie movie = movieService.createMovie(request);

SuccessResponse<Movie> response = SuccessResponse.<Movie>builder()
.success(true)
.message("Movie '" + request.getTitle() + "' created successfully")
.message("Movie '" + request.title() + "' created successfully")
.data(movie)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.status(HttpStatus.CREATED).body(response);
Expand All @@ -182,10 +173,8 @@ public ResponseEntity<SuccessResponse<BatchInsertResponse>> createMoviesBatch(
BatchInsertResponse result = movieService.createMoviesBatch(requests);

SuccessResponse<BatchInsertResponse> response = SuccessResponse.<BatchInsertResponse>builder()
.success(true)
.message("Successfully created " + result.getInsertedCount() + " movies")
.message("Successfully created " + result.insertedCount() + " movies")
.data(result)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.status(HttpStatus.CREATED).body(response);
Expand All @@ -210,10 +199,8 @@ public ResponseEntity<SuccessResponse<Movie>> updateMovie(
Movie movie = movieService.updateMovie(id, request);

SuccessResponse<Movie> response = SuccessResponse.<Movie>builder()
.success(true)
.message("Movie updated successfully")
.data(movie)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand All @@ -235,11 +222,9 @@ public ResponseEntity<SuccessResponse<BatchUpdateResponse>> updateMoviesBatch(
BatchUpdateResponse result = movieService.updateMoviesBatch(filter, update);

SuccessResponse<BatchUpdateResponse> response = SuccessResponse.<BatchUpdateResponse>builder()
.success(true)
.message("Update operation completed. Matched " + result.getMatchedCount() +
" documents, modified " + result.getModifiedCount() + " documents.")
.message("Update operation completed. Matched " + result.matchedCount() +
" documents, modified " + result.modifiedCount() + " documents.")
.data(result)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand All @@ -257,10 +242,8 @@ public ResponseEntity<SuccessResponse<Movie>> findAndDeleteMovie(
Movie movie = movieService.findAndDeleteMovie(id);

SuccessResponse<Movie> response = SuccessResponse.<Movie>builder()
.success(true)
.message("Movie found and deleted successfully")
.data(movie)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand All @@ -277,10 +260,8 @@ public ResponseEntity<SuccessResponse<DeleteResponse>> deleteMovie(
DeleteResponse result = movieService.deleteMovie(id);

SuccessResponse<DeleteResponse> response = SuccessResponse.<DeleteResponse>builder()
.success(true)
.message("Movie deleted successfully")
.data(result)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand All @@ -301,10 +282,8 @@ public ResponseEntity<SuccessResponse<DeleteResponse>> deleteMoviesBatch(
DeleteResponse result = movieService.deleteMoviesBatch(filter);

SuccessResponse<DeleteResponse> response = SuccessResponse.<DeleteResponse>builder()
.success(true)
.message("Delete operation completed. Removed " + result.getDeletedCount() + " documents.")
.message("Delete operation completed. Removed " + result.deletedCount() + " documents.")
.data(result)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand All @@ -328,7 +307,7 @@ public ResponseEntity<SuccessResponse<List<MovieWithCommentsResult>>> getMoviesW

// Calculate total comments across all movies
int totalComments = results.stream()
.mapToInt(result -> result.getTotalComments() != null ? result.getTotalComments() : 0)
.mapToInt(result -> result.totalComments() != null ? result.totalComments() : 0)
.sum();

String message = movieId != null
Expand All @@ -338,10 +317,8 @@ public ResponseEntity<SuccessResponse<List<MovieWithCommentsResult>>> getMoviesW

SuccessResponse<List<MovieWithCommentsResult>> response =
SuccessResponse.<List<MovieWithCommentsResult>>builder()
.success(true)
.message(message)
.data(results)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand All @@ -359,10 +336,8 @@ public ResponseEntity<SuccessResponse<List<MoviesByYearResult>>> getMoviesByYear

SuccessResponse<List<MoviesByYearResult>> response =
SuccessResponse.<List<MoviesByYearResult>>builder()
.success(true)
.message(String.format("Aggregated statistics for %d years", results.size()))
.data(results)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand All @@ -382,10 +357,8 @@ public ResponseEntity<SuccessResponse<List<DirectorStatisticsResult>>> getDirect

SuccessResponse<List<DirectorStatisticsResult>> response =
SuccessResponse.<List<DirectorStatisticsResult>>builder()
.success(true)
.message(String.format("Found %d directors with most movies", results.size()))
.data(results)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand Down Expand Up @@ -440,10 +413,8 @@ public ResponseEntity<SuccessResponse<SearchMoviesResponse>> searchMovies(
.build();

SuccessResponse<SearchMoviesResponse> response = SuccessResponse.<SearchMoviesResponse>builder()
.success(true)
.message(String.format("Found %d movies matching the search criteria", movies.size()))
.data(searchResponse)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand All @@ -465,10 +436,8 @@ public ResponseEntity<SuccessResponse<List<VectorSearchResult>>> vectorSearchMov
List<VectorSearchResult> results = movieService.vectorSearchMovies(q, limit);

SuccessResponse<List<VectorSearchResult>> response = SuccessResponse.<List<VectorSearchResult>>builder()
.success(true)
.message(String.format("Found %d similar movies for query: '%s'", results.size(), q))
.data(results)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand All @@ -489,10 +458,8 @@ public ResponseEntity<SuccessResponse<List<Movie>>> findSimilarMovies(
List<Movie> movies = movieService.findSimilarMovies(movieId, limit);

SuccessResponse<List<Movie>> response = SuccessResponse.<List<Movie>>builder()
.success(true)
.message(String.format("Found %d similar movies", movies.size()))
.data(movies)
.timestamp(Instant.now().toString())
.build();

return ResponseEntity.ok(response);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ public ResponseEntity<ErrorResponse> handleResourceNotFoundException(
logger.error("Resource not found: {}", ex.getMessage());

ErrorResponse errorResponse = ErrorResponse.builder()
.success(false)
.message(ex.getMessage())
.error(ErrorResponse.ErrorDetails.builder()
.message(ex.getMessage())
Expand All @@ -47,7 +46,6 @@ public ResponseEntity<ErrorResponse> handleValidationException(
logger.error("Validation error: {}", ex.getMessage());

ErrorResponse errorResponse = ErrorResponse.builder()
.success(false)
.message("Validation failed")
.error(ErrorResponse.ErrorDetails.builder()
.message(ex.getMessage())
Expand All @@ -67,7 +65,6 @@ public ResponseEntity<ErrorResponse> handleMissingServletRequestParameter(
String message = String.format("Required parameter '%s' is missing", ex.getParameterName());

ErrorResponse errorResponse = ErrorResponse.builder()
.success(false)
.message(message)
.error(ErrorResponse.ErrorDetails.builder()
.message(message)
Expand All @@ -85,7 +82,6 @@ public ResponseEntity<ErrorResponse> handleServiceUnavailableException(
logger.error("Service unavailable: {}", ex.getMessage());

ErrorResponse errorResponse = ErrorResponse.builder()
.success(false)
.message(ex.getMessage())
.error(ErrorResponse.ErrorDetails.builder()
.message(ex.getMessage())
Expand All @@ -103,7 +99,6 @@ public ResponseEntity<ErrorResponse> handleVoyageAuthException(
logger.error("Voyage AI authentication error: {}", ex.getMessage());

ErrorResponse errorResponse = ErrorResponse.builder()
.success(false)
.message(ex.getMessage())
.error(ErrorResponse.ErrorDetails.builder()
.message(ex.getMessage())
Expand All @@ -122,7 +117,6 @@ public ResponseEntity<ErrorResponse> handleVoyageAPIException(
logger.error("Voyage AI API error: {}", ex.getMessage());

ErrorResponse errorResponse = ErrorResponse.builder()
.success(false)
.message("Vector search service unavailable")
.error(ErrorResponse.ErrorDetails.builder()
.message(ex.getMessage())
Expand All @@ -140,7 +134,6 @@ public ResponseEntity<ErrorResponse> handleDatabaseOperationException(
logger.error("Database operation error: {}", ex.getMessage());

ErrorResponse errorResponse = ErrorResponse.builder()
.success(false)
.message("Database operation failed")
.error(ErrorResponse.ErrorDetails.builder()
.message(ex.getMessage())
Expand Down Expand Up @@ -168,7 +161,6 @@ public ResponseEntity<ErrorResponse> handleMongoWriteException(
}

ErrorResponse errorResponse = ErrorResponse.builder()
.success(false)
.message(message)
.error(ErrorResponse.ErrorDetails.builder()
.message(message)
Expand All @@ -187,7 +179,6 @@ public ResponseEntity<ErrorResponse> handleGenericException(
logger.error("Unexpected error occurred", ex);

ErrorResponse errorResponse = ErrorResponse.builder()
.success(false)
.message(ex.getMessage() != null ? ex.getMessage() : "Internal server error")
.error(ErrorResponse.ErrorDetails.builder()
.message(ex.getMessage() != null ? ex.getMessage() : "Internal server error")
Expand Down
Loading
Loading