Skip to content

Commit 399ee2d

Browse files
authored
Merge pull request #98 from mongodb/development
Merge development into main
2 parents 0118793 + c8a4357 commit 399ee2d

33 files changed

Lines changed: 505 additions & 524 deletions

mflix/README-JAVA-SPRING.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Edit the `.env` file and set your MongoDB connection string:
5858

5959
```env
6060
# MongoDB Connection
61-
MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority
61+
MONGODB_URI="mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority"
6262
6363
# Voyage AI Configuration (optional - required for Vector Search)
6464
VOYAGE_API_KEY=your_voyage_api_key

mflix/README-NODE-EXPRESS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ Edit the `.env` file and set your MongoDB connection string:
5858
```env
5959
# MongoDB Connection
6060
# Replace with your MongoDB Atlas connection string or local MongoDB URI
61-
MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority
61+
MONGODB_URI="mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority"
6262
6363
# Voyage AI Configuration
6464
# API key for Voyage AI embedding model (required for Vector Search)

mflix/README-PYTHON-FASTAPI.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ Edit the `.env` file and set your MongoDB connection string:
6161

6262
```env
6363
# MongoDB Connection
64-
MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority
64+
MONGODB_URI="mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority"
6565
6666
# Voyage AI Configuration (optional - required for Vector Search)
6767
VOYAGE_API_KEY=your_voyage_api_key

mflix/server/java-spring/.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# MongoDB Connection
22
# Replace with your MongoDB Atlas connection string or local MongoDB URI
3-
MONGODB_URI=mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority
3+
MONGODB_URI="mongodb+srv://<username>:<password>@<cluster>.mongodb.net/sample_mflix?retryWrites=true&w=majority"
44

55
# OPTIONAL: Voyage AI Configuration (required for Vector Search)
66
# Get your API key from https://www.voyageai.com/

mflix/server/java-spring/pom.xml

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<parent>
99
<groupId>org.springframework.boot</groupId>
1010
<artifactId>spring-boot-starter-parent</artifactId>
11-
<version>3.5.7</version>
11+
<version>3.5.13</version>
1212
<relativePath/>
1313
</parent>
1414

@@ -58,7 +58,7 @@
5858
<dependency>
5959
<groupId>org.projectlombok</groupId>
6060
<artifactId>lombok</artifactId>
61-
<optional>true</optional>
61+
<scope>provided</scope>
6262
</dependency>
6363

6464
<!-- Force version for Commons Lang used by SpringDoc (CVE-2025-48924) -->
@@ -88,6 +88,20 @@
8888
<scope>test</scope>
8989
</dependency>
9090

91+
<!-- Testcontainers JUnit Jupiter integration -->
92+
<dependency>
93+
<groupId>org.testcontainers</groupId>
94+
<artifactId>junit-jupiter</artifactId>
95+
<scope>test</scope>
96+
</dependency>
97+
98+
<!-- MongoDB Atlas Local container for Testcontainers -->
99+
<dependency>
100+
<groupId>org.testcontainers</groupId>
101+
<artifactId>mongodb</artifactId>
102+
<scope>test</scope>
103+
</dependency>
104+
91105
<!-- Jackson Databind for JSON serialization/deserialization -->
92106
<dependency>
93107
<groupId>com.fasterxml.jackson.core</groupId>
@@ -118,11 +132,18 @@
118132
</configuration>
119133
</plugin>
120134

121-
<!-- Maven Compiler Plugin - Suppress annotation processing warnings -->
135+
<!-- Maven Compiler Plugin - Process Lombok and suppress annotation processing warnings -->
122136
<plugin>
123137
<groupId>org.apache.maven.plugins</groupId>
124138
<artifactId>maven-compiler-plugin</artifactId>
125139
<configuration>
140+
<annotationProcessorPaths>
141+
<path>
142+
<groupId>org.projectlombok</groupId>
143+
<artifactId>lombok</artifactId>
144+
<version>${lombok.version}</version>
145+
</path>
146+
</annotationProcessorPaths>
126147
<compilerArgs>
127148
<arg>-Xlint:-options</arg>
128149
</compilerArgs>

mflix/server/java-spring/src/main/java/com/mongodb/samplemflix/config/MongoConfig.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.springframework.context.annotation.Bean;
1010
import org.springframework.context.annotation.Configuration;
1111
import org.springframework.data.mongodb.config.AbstractMongoClientConfiguration;
12+
import org.springframework.data.mongodb.core.convert.MongoCustomConversions;
1213
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
1314
import org.springframework.lang.NonNull;
1415

@@ -119,4 +120,9 @@ public MongoDatabase mongoDatabase() {
119120

120121
return client.getDatabase(databaseName);
121122
}
123+
124+
@Bean
125+
public MongoCustomConversions customConversions() {
126+
return MongoCustomConversions.create(MongoCustomConversions.MongoConverterConfigurationAdapter::useNativeDriverJavaTimeCodecs);
127+
}
122128
}

mflix/server/java-spring/src/main/java/com/mongodb/samplemflix/controller/MovieControllerImpl.java

Lines changed: 6 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
import io.swagger.v3.oas.annotations.Parameter;
1919
import io.swagger.v3.oas.annotations.tags.Tag;
2020
import jakarta.validation.Valid;
21-
import java.time.Instant;
2221
import java.util.List;
2322
import java.util.Map;
2423
import org.bson.Document;
@@ -103,10 +102,8 @@ public ResponseEntity<SuccessResponse<List<Movie>>> getAllMovies(
103102
String message = "Found " + movies.size() + " movies";
104103

105104
SuccessResponse<List<Movie>> response = SuccessResponse.<List<Movie>>builder()
106-
.success(true)
107105
.message(message)
108106
.data(movies)
109-
.timestamp(Instant.now().toString())
110107
.build();
111108

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

124121
SuccessResponse<List<String>> response = SuccessResponse.<List<String>>builder()
125-
.success(true)
126122
.message("Found " + genres.size() + " distinct genres")
127123
.data(genres)
128-
.timestamp(Instant.now().toString())
129124
.build();
130125

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

144139
SuccessResponse<Movie> response = SuccessResponse.<Movie>builder()
145-
.success(true)
146140
.message("Movie retrieved successfully")
147141
.data(movie)
148-
.timestamp(Instant.now().toString())
149142
.build();
150143

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

164157
SuccessResponse<Movie> response = SuccessResponse.<Movie>builder()
165-
.success(true)
166-
.message("Movie '" + request.getTitle() + "' created successfully")
158+
.message("Movie '" + request.title() + "' created successfully")
167159
.data(movie)
168-
.timestamp(Instant.now().toString())
169160
.build();
170161

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

184175
SuccessResponse<BatchInsertResponse> response = SuccessResponse.<BatchInsertResponse>builder()
185-
.success(true)
186-
.message("Successfully created " + result.getInsertedCount() + " movies")
176+
.message("Successfully created " + result.insertedCount() + " movies")
187177
.data(result)
188-
.timestamp(Instant.now().toString())
189178
.build();
190179

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

212201
SuccessResponse<Movie> response = SuccessResponse.<Movie>builder()
213-
.success(true)
214202
.message("Movie updated successfully")
215203
.data(movie)
216-
.timestamp(Instant.now().toString())
217204
.build();
218205

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

237224
SuccessResponse<BatchUpdateResponse> response = SuccessResponse.<BatchUpdateResponse>builder()
238-
.success(true)
239-
.message("Update operation completed. Matched " + result.getMatchedCount() +
240-
" documents, modified " + result.getModifiedCount() + " documents.")
225+
.message("Update operation completed. Matched " + result.matchedCount() +
226+
" documents, modified " + result.modifiedCount() + " documents.")
241227
.data(result)
242-
.timestamp(Instant.now().toString())
243228
.build();
244229

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

259244
SuccessResponse<Movie> response = SuccessResponse.<Movie>builder()
260-
.success(true)
261245
.message("Movie found and deleted successfully")
262246
.data(movie)
263-
.timestamp(Instant.now().toString())
264247
.build();
265248

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

279262
SuccessResponse<DeleteResponse> response = SuccessResponse.<DeleteResponse>builder()
280-
.success(true)
281263
.message("Movie deleted successfully")
282264
.data(result)
283-
.timestamp(Instant.now().toString())
284265
.build();
285266

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

303284
SuccessResponse<DeleteResponse> response = SuccessResponse.<DeleteResponse>builder()
304-
.success(true)
305-
.message("Delete operation completed. Removed " + result.getDeletedCount() + " documents.")
285+
.message("Delete operation completed. Removed " + result.deletedCount() + " documents.")
306286
.data(result)
307-
.timestamp(Instant.now().toString())
308287
.build();
309288

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

329308
// Calculate total comments across all movies
330309
int totalComments = results.stream()
331-
.mapToInt(result -> result.getTotalComments() != null ? result.getTotalComments() : 0)
310+
.mapToInt(result -> result.totalComments() != null ? result.totalComments() : 0)
332311
.sum();
333312

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

339318
SuccessResponse<List<MovieWithCommentsResult>> response =
340319
SuccessResponse.<List<MovieWithCommentsResult>>builder()
341-
.success(true)
342320
.message(message)
343321
.data(results)
344-
.timestamp(Instant.now().toString())
345322
.build();
346323

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

360337
SuccessResponse<List<MoviesByYearResult>> response =
361338
SuccessResponse.<List<MoviesByYearResult>>builder()
362-
.success(true)
363339
.message(String.format("Aggregated statistics for %d years", results.size()))
364340
.data(results)
365-
.timestamp(Instant.now().toString())
366341
.build();
367342

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

383358
SuccessResponse<List<DirectorStatisticsResult>> response =
384359
SuccessResponse.<List<DirectorStatisticsResult>>builder()
385-
.success(true)
386360
.message(String.format("Found %d directors with most movies", results.size()))
387361
.data(results)
388-
.timestamp(Instant.now().toString())
389362
.build();
390363

391364
return ResponseEntity.ok(response);
@@ -440,10 +413,8 @@ public ResponseEntity<SuccessResponse<SearchMoviesResponse>> searchMovies(
440413
.build();
441414

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

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

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

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

491460
SuccessResponse<List<Movie>> response = SuccessResponse.<List<Movie>>builder()
492-
.success(true)
493461
.message(String.format("Found %d similar movies", movies.size()))
494462
.data(movies)
495-
.timestamp(Instant.now().toString())
496463
.build();
497464

498465
return ResponseEntity.ok(response);

mflix/server/java-spring/src/main/java/com/mongodb/samplemflix/exception/GlobalExceptionHandler.java

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ public ResponseEntity<ErrorResponse> handleResourceNotFoundException(
2929
logger.error("Resource not found: {}", ex.getMessage());
3030

3131
ErrorResponse errorResponse = ErrorResponse.builder()
32-
.success(false)
3332
.message(ex.getMessage())
3433
.error(ErrorResponse.ErrorDetails.builder()
3534
.message(ex.getMessage())
@@ -47,7 +46,6 @@ public ResponseEntity<ErrorResponse> handleValidationException(
4746
logger.error("Validation error: {}", ex.getMessage());
4847

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

6967
ErrorResponse errorResponse = ErrorResponse.builder()
70-
.success(false)
7168
.message(message)
7269
.error(ErrorResponse.ErrorDetails.builder()
7370
.message(message)
@@ -85,7 +82,6 @@ public ResponseEntity<ErrorResponse> handleServiceUnavailableException(
8582
logger.error("Service unavailable: {}", ex.getMessage());
8683

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

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

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

142136
ErrorResponse errorResponse = ErrorResponse.builder()
143-
.success(false)
144137
.message("Database operation failed")
145138
.error(ErrorResponse.ErrorDetails.builder()
146139
.message(ex.getMessage())
@@ -168,7 +161,6 @@ public ResponseEntity<ErrorResponse> handleMongoWriteException(
168161
}
169162

170163
ErrorResponse errorResponse = ErrorResponse.builder()
171-
.success(false)
172164
.message(message)
173165
.error(ErrorResponse.ErrorDetails.builder()
174166
.message(message)
@@ -187,7 +179,6 @@ public ResponseEntity<ErrorResponse> handleGenericException(
187179
logger.error("Unexpected error occurred", ex);
188180

189181
ErrorResponse errorResponse = ErrorResponse.builder()
190-
.success(false)
191182
.message(ex.getMessage() != null ? ex.getMessage() : "Internal server error")
192183
.error(ErrorResponse.ErrorDetails.builder()
193184
.message(ex.getMessage() != null ? ex.getMessage() : "Internal server error")

0 commit comments

Comments
 (0)