Skip to content

Commit c232e68

Browse files
committed
Improviment in logic
1 parent e1492f4 commit c232e68

File tree

18 files changed

+727
-630
lines changed

18 files changed

+727
-630
lines changed

examples/spring-boot-demo/implementation/README-DEV.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,4 +31,5 @@ jwebserver -p 8005 -d "$(pwd)/target/site/"
3131

3232
./mvnw clean spring-boot:run -Dspring-boot.run.profiles=local
3333
curl "http://localhost:8080/api/v1/films?startsWith=A"
34+
open http://localhost:8080/api/v1/swagger-ui.html
3435
```

examples/spring-boot-demo/implementation/src/main/java/info/jab/ms/DemoApplication.java renamed to examples/spring-boot-demo/implementation/src/main/java/info/jab/ms/MainApplication.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
import org.springframework.boot.autoconfigure.SpringBootApplication;
55

66
@SpringBootApplication
7-
public class DemoApplication {
7+
public class MainApplication {
88

99
public static void main(String[] args) {
10-
SpringApplication.run(DemoApplication.class, args);
10+
SpringApplication.run(MainApplication.class, args);
1111
}
1212

1313
}

examples/spring-boot-demo/implementation/src/main/java/info/jab/ms/config/OpenApiConfig.java

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
/**
1414
* OpenAPI Configuration for Film Query Service
1515
*
16-
* Task 12.1: Complete OpenAPI documentation with proper descriptions ✅
17-
*
1816
* This configuration provides comprehensive API documentation including:
1917
* - API information, version, and description
2018
* - Contact information and license
@@ -70,13 +68,7 @@ public OpenAPI filmQueryOpenAPI() {
7068
.servers(List.of(
7169
new Server()
7270
.url("http://localhost:8080")
73-
.description("Local development server"),
74-
new Server()
75-
.url("https://api-staging.example.com")
76-
.description("Staging environment"),
77-
new Server()
78-
.url("https://api.example.com")
79-
.description("Production environment")))
71+
.description("Local development server")))
8072
.tags(List.of(
8173
new Tag()
8274
.name("Films")

examples/spring-boot-demo/implementation/src/main/java/info/jab/ms/controller/FilmController.java

Lines changed: 20 additions & 446 deletions
Large diffs are not rendered by default.
Lines changed: 317 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,317 @@
1+
package info.jab.ms.controller;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
5+
import io.swagger.v3.oas.annotations.media.Content;
6+
import io.swagger.v3.oas.annotations.media.ExampleObject;
7+
import io.swagger.v3.oas.annotations.media.Schema;
8+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
9+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
10+
import io.swagger.v3.oas.annotations.tags.Tag;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.web.bind.annotation.GetMapping;
13+
import org.springframework.web.bind.annotation.RequestParam;
14+
15+
/**
16+
* FilmControllerApi - API Contract for Film Query Operations
17+
*
18+
* This interface defines the REST API contract for querying films from the Sakila database.
19+
* It contains all the OpenAPI/Swagger documentation and defines the API endpoints for
20+
* retrieving films that start with specific letters.
21+
*
22+
* API Endpoints:
23+
* - GET /api/v1/films - Retrieve all films or filter by starting letter
24+
* - GET /api/v1/films?startsWith=A - Retrieve films starting with letter "A"
25+
*
26+
* This interface separates the API contract and documentation from the business logic
27+
* implementation, following the principle of separation of concerns.
28+
*/
29+
@Tag(name = "Films", description = "Film query operations")
30+
public interface FilmControllerApi {
31+
32+
/**
33+
* Retrieves films from the Sakila database, optionally filtered by starting letter.
34+
*
35+
* @param startsWith Optional parameter to filter films by starting letter (single character A-Z)
36+
* @param request HttpServletRequest for building error responses
37+
* @return JSON response containing films array, count, and filter information or error response
38+
*/
39+
@Operation(
40+
summary = "Query films by starting letter",
41+
description = """
42+
### Query Films from Sakila Database
43+
44+
Retrieves films from the PostgreSQL Sakila database that start with the specified letter.
45+
The search is case-insensitive and returns complete film information including ID and title.
46+
47+
### Query Behavior
48+
- **Without parameter**: Returns all films in the database (1000 films)
49+
- **With startsWith parameter**: Returns films starting with the specified letter
50+
- **Case handling**: Search is case-insensitive (both 'A' and 'a' work identically)
51+
- **Ordering**: Results are ordered by film ID for consistent pagination
52+
53+
### Performance Characteristics
54+
- **Response time**: Guaranteed under 2 seconds for all queries
55+
- **Database optimization**: Uses indexed queries for optimal performance
56+
- **Result caching**: Frequently accessed results may be cached
57+
- **Connection pooling**: Efficient database connection management
58+
59+
### Expected Result Counts
60+
Based on the complete Sakila database:
61+
- **Letter "A"**: 46 films (e.g., "ACADEMY DINOSAUR", "AIRPORT POLLOCK")
62+
- **Letter "B"**: 37 films (e.g., "BADMAN DAWN", "BATMAN BEGINS")
63+
- **Letter "C"**: 57 films (e.g., "CABIN FLASH", "CALIFORNIA BIRDS")
64+
- **Letter "D"**: 41 films
65+
- **Letter "E"**: 20 films
66+
- **Letter "F"**: 58 films
67+
- **Letter "G"**: 42 films
68+
- **Letter "H"**: 49 films
69+
70+
### Usage Examples
71+
```
72+
GET /api/v1/films?startsWith=A # Returns 46 films starting with 'A'
73+
GET /api/v1/films?startsWith=a # Same result (case-insensitive)
74+
GET /api/v1/films # Returns all films
75+
```
76+
""",
77+
operationId = "getFilmsByStartingLetter",
78+
tags = {"Films"}
79+
)
80+
@ApiResponses(value = {
81+
@ApiResponse(
82+
responseCode = "200",
83+
description = """
84+
Successfully retrieved films matching the query criteria.
85+
Response includes films array, total count, and applied filter parameters.
86+
""",
87+
content = @Content(
88+
mediaType = "application/json",
89+
schema = @Schema(implementation = info.jab.ms.controller.FilmDTO.class),
90+
examples = {
91+
@ExampleObject(
92+
name = "Films starting with A",
93+
summary = "46 films starting with letter 'A' (most common scenario)",
94+
description = "Returns all films from the Sakila database that start with letter 'A'. This is the most frequently requested query with 46 matching films.",
95+
value = """
96+
{
97+
"films": [
98+
{
99+
"film_id": 1,
100+
"title": "ACADEMY DINOSAUR"
101+
},
102+
{
103+
"film_id": 8,
104+
"title": "AIRPORT POLLOCK"
105+
},
106+
{
107+
"film_id": 10,
108+
"title": "ALADDIN CALENDAR"
109+
},
110+
{
111+
"film_id": 13,
112+
"title": "ALI FOREVER"
113+
},
114+
{
115+
"film_id": 15,
116+
"title": "ALIEN CENTER"
117+
}
118+
],
119+
"count": 46,
120+
"filter": {
121+
"startsWith": "A"
122+
}
123+
}
124+
"""
125+
),
126+
@ExampleObject(
127+
name = "Films starting with B",
128+
summary = "37 films starting with letter 'B'",
129+
description = "Example response showing films starting with 'B'. Note the case-insensitive filter behavior.",
130+
value = """
131+
{
132+
"films": [
133+
{
134+
"film_id": 16,
135+
"title": "ALLEY EVOLUTION"
136+
},
137+
{
138+
"film_id": 23,
139+
"title": "ANACONDA CONFESSIONS"
140+
},
141+
{
142+
"film_id": 25,
143+
"title": "ANGELS LIFE"
144+
}
145+
],
146+
"count": 37,
147+
"filter": {
148+
"startsWith": "B"
149+
}
150+
}
151+
"""
152+
),
153+
@ExampleObject(
154+
name = "All films (no filter)",
155+
summary = "All films when no startsWith parameter is provided",
156+
description = "Returns all films from the database when no filter is applied. Shows the complete dataset structure.",
157+
value = """
158+
{
159+
"films": [
160+
{
161+
"film_id": 1,
162+
"title": "ACADEMY DINOSAUR"
163+
},
164+
{
165+
"film_id": 2,
166+
"title": "ACE GOLDFINGER"
167+
},
168+
{
169+
"film_id": 3,
170+
"title": "ADAPTATION HOLES"
171+
}
172+
],
173+
"count": 1000,
174+
"filter": {}
175+
}
176+
"""
177+
),
178+
@ExampleObject(
179+
name = "Empty result set",
180+
summary = "No films found for a specific letter",
181+
description = "Example response when no films match the filter criteria. Some letters might have no films in the database.",
182+
value = """
183+
{
184+
"films": [],
185+
"count": 0,
186+
"filter": {
187+
"startsWith": "Q"
188+
}
189+
}
190+
"""
191+
),
192+
@ExampleObject(
193+
name = "Case-insensitive filter",
194+
summary = "Lowercase parameter produces same results",
195+
description = "Demonstrates case-insensitive behavior - lowercase 'a' returns the same results as uppercase 'A'.",
196+
value = """
197+
{
198+
"films": [
199+
{
200+
"film_id": 1,
201+
"title": "ACADEMY DINOSAUR"
202+
},
203+
{
204+
"film_id": 8,
205+
"title": "AIRPORT POLLOCK"
206+
}
207+
],
208+
"count": 46,
209+
"filter": {
210+
"startsWith": "a"
211+
}
212+
}
213+
"""
214+
)
215+
}
216+
)
217+
),
218+
@ApiResponse(
219+
responseCode = "400",
220+
description = """
221+
Bad Request - Invalid query parameter provided.
222+
The startsWith parameter must be a single letter (A-Z or a-z).
223+
Returns empty response body.
224+
"""
225+
),
226+
@ApiResponse(
227+
responseCode = "500",
228+
description = """
229+
Internal Server Error - An unexpected error occurred while processing the request.
230+
This could be due to database connectivity issues or other system problems.
231+
""",
232+
content = @Content(
233+
mediaType = "application/json",
234+
schema = @Schema(implementation = org.springframework.http.ProblemDetail.class),
235+
examples = {
236+
@ExampleObject(
237+
name = "Database connection error",
238+
summary = "Database connectivity issues",
239+
description = "Response when the service cannot connect to the PostgreSQL database",
240+
value = """
241+
{
242+
"title": "Database Connection Error",
243+
"status": 500,
244+
"detail": "Unable to connect to the database. Please try again later.",
245+
"instance": "/api/v1/films",
246+
"timestamp": "2024-01-15T10:30:00Z"
247+
}
248+
"""
249+
),
250+
@ExampleObject(
251+
name = "Query timeout error",
252+
summary = "Database query timeout",
253+
description = "Response when a database query takes longer than the configured timeout",
254+
value = """
255+
{
256+
"title": "Query Timeout",
257+
"status": 500,
258+
"detail": "The database query timed out. Please try again with a smaller dataset.",
259+
"instance": "/api/v1/films",
260+
"timestamp": "2024-01-15T10:30:00Z"
261+
}
262+
"""
263+
),
264+
@ExampleObject(
265+
name = "Generic server error",
266+
summary = "General internal server error",
267+
description = "Response for unexpected server errors not covered by specific error types",
268+
value = """
269+
{
270+
"title": "Internal Server Error",
271+
"status": 500,
272+
"detail": "An unexpected error occurred while processing your request",
273+
"instance": "/api/v1/films",
274+
"timestamp": "2024-01-15T10:30:00Z"
275+
}
276+
"""
277+
)
278+
}
279+
)
280+
)
281+
})
282+
@GetMapping("/films")
283+
ResponseEntity<FilmDTO> getFilms(
284+
@Parameter(
285+
name = "startsWith",
286+
description = """
287+
Filter films by their starting letter.
288+
289+
**Requirements:**
290+
- Must be a single letter (A-Z or a-z)
291+
- Case-insensitive (both 'A' and 'a' return the same results)
292+
- Cannot be empty, numeric, or special characters
293+
- Cannot be multiple characters
294+
295+
**Examples:**
296+
- `A` or `a` → Returns 46 films starting with 'A'
297+
- `B` or `b` → Returns 37 films starting with 'B'
298+
- `Z` or `z` → Returns films starting with 'Z'
299+
300+
**Invalid values:**
301+
- `AB` (multiple characters)
302+
- `1` (numeric)
303+
- `@` (special character)
304+
- ` ` (empty/whitespace)
305+
""",
306+
example = "A",
307+
required = false,
308+
schema = @Schema(
309+
type = "string",
310+
pattern = "^[A-Za-z]$",
311+
minLength = 1,
312+
maxLength = 1,
313+
description = "Single letter (A-Z, case-insensitive)"
314+
)
315+
)
316+
@RequestParam(required = false) String startsWith);
317+
}

examples/spring-boot-demo/implementation/src/main/java/info/jab/ms/dto/FilmDTO.java renamed to examples/spring-boot-demo/implementation/src/main/java/info/jab/ms/controller/FilmDTO.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package info.jab.ms.dto;
1+
package info.jab.ms.controller;
22

33
import com.fasterxml.jackson.annotation.JsonProperty;
44
import io.swagger.v3.oas.annotations.media.Schema;
@@ -106,7 +106,7 @@ public record Film(
106106
* @param entity The Film entity to convert
107107
* @return Film record with mapped data
108108
*/
109-
public static Film fromEntity(info.jab.ms.entity.Film entity) {
109+
public static Film fromEntity(info.jab.ms.repository.Film entity) {
110110
return new Film(entity.filmId(), entity.title());
111111
}
112112

@@ -130,7 +130,7 @@ public Map<String, Object> toMap() {
130130
* @param filterMap Map containing filter parameters
131131
* @return FilmDTO instance with complete response structure
132132
*/
133-
public static FilmDTO fromEntities(List<info.jab.ms.entity.Film> entities, Map<String, Object> filterMap) {
133+
public static FilmDTO fromEntities(List<info.jab.ms.repository.Film> entities, Map<String, Object> filterMap) {
134134
List<Film> films = entities.stream()
135135
.map(Film::fromEntity)
136136
.toList();

0 commit comments

Comments
 (0)