Skip to content

Commit 0b2c2ac

Browse files
Merge branch 'main' into BENCH-195-update-request-models-name-ak-java
2 parents 861aa1b + b4810c3 commit 0b2c2ac

File tree

19 files changed

+481
-52
lines changed

19 files changed

+481
-52
lines changed

.github/workflows/build_integration_and_code_analysis.yml

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ on:
1313
jobs:
1414
Sonar-Cloud:
1515
name: Static Code Analysis - Sonar Cloud
16+
outputs:
17+
job-status: ${{ job.status }}
1618
runs-on: ubuntu-latest
1719
steps:
1820
- uses: actions/checkout@v3
@@ -44,6 +46,8 @@ jobs:
4446

4547
Build-Test:
4648
name: Integration Test
49+
outputs:
50+
job-status: ${{ job.status }}
4751
runs-on: ubuntu-latest
4852
permissions:
4953
checks: write
@@ -72,9 +76,31 @@ jobs:
7276

7377
- name: Publish test coverage results
7478
id: jacoco_reporter
75-
uses: PavanMudigonda/jacoco-reporter@v4.6
79+
uses: PavanMudigonda/jacoco-reporter@v4.7
7680
with:
7781
coverage_results_path: 'target/site/jacoco/jacoco.xml'
7882
coverage_report_title: 'Test coverage results'
7983
coverage_report_name: 'Test coverage results'
80-
github_token: ${{ secrets.GITHUB_TOKEN }}
84+
github_token: ${{ secrets.GITHUB_TOKEN }}
85+
86+
Notify-Slack:
87+
if: ${{ always() }}
88+
needs: [ Build-Test, Sonar-Cloud ]
89+
runs-on: ubuntu-latest
90+
name: Send Slack Notification
91+
steps:
92+
- name: Send GitHub trigger payload to Slack Workflow Builder
93+
id: slack
94+
uses: slackapi/[email protected]
95+
with:
96+
payload: |
97+
{
98+
"PR_URL": "${{ github.event_name == 'push' && github.event.repository.html_url || github.event.pull_request.html_url }}",
99+
"Sonar_Status": "${{ needs.Sonar-Cloud.outputs.job-status }}",
100+
"Test_Status": "${{ needs.Build-Test.outputs.job-status }}",
101+
"Title": "${{ github.event_name == 'push' && github.event.commits[0].message || github.event.pull_request.title }}",
102+
"Type": "${{ github.event_name == 'push' && 'Merged' || github.event.action }}",
103+
"Author": "${{ github.actor }}"
104+
}
105+
env:
106+
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}

pom.xml

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@
6464
<dependency>
6565
<groupId>org.springdoc</groupId>
6666
<artifactId>springdoc-openapi-ui</artifactId>
67-
<version>1.6.12</version>
67+
<version>1.6.13</version>
6868
</dependency>
6969

7070
<dependency>
@@ -83,7 +83,7 @@
8383
<dependency>
8484
<groupId>org.springdoc</groupId>
8585
<artifactId>springdoc-openapi-security</artifactId>
86-
<version>1.6.12</version>
86+
<version>1.6.13</version>
8787
</dependency>
8888

8989
<dependency>
@@ -92,6 +92,27 @@
9292
<version>1.33</version>
9393
<scope>compile</scope>
9494
</dependency>
95+
96+
<dependency>
97+
<groupId>org.testcontainers</groupId>
98+
<artifactId>testcontainers</artifactId>
99+
<version>1.17.5</version>
100+
<scope>test</scope>
101+
</dependency>
102+
103+
<dependency>
104+
<groupId>org.testcontainers</groupId>
105+
<artifactId>junit-jupiter</artifactId>
106+
<version>1.17.5</version>
107+
<scope>test</scope>
108+
</dependency>
109+
110+
<dependency>
111+
<groupId>org.testcontainers</groupId>
112+
<artifactId>mysql</artifactId>
113+
<version>1.17.5</version>
114+
<scope>test</scope>
115+
</dependency>
95116
</dependencies>
96117

97118
<build>

src/main/java/com/answerdigital/answerking/config/SecurityConfig.java

Lines changed: 13 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
66
import org.springframework.security.config.http.SessionCreationPolicy;
77
import org.springframework.security.core.userdetails.User;
8-
import org.springframework.security.core.userdetails.UserDetails;
98
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
109
import org.springframework.security.web.SecurityFilterChain;
1110

11+
import java.util.List;
12+
1213
@EnableWebSecurity
1314
public class SecurityConfig {
1415
private static final String[] PERMITTED_PATTERNS = {
@@ -44,26 +45,17 @@ public SecurityFilterChain securityFilterChain(final HttpSecurity http) throws E
4445

4546
@Bean
4647
public InMemoryUserDetailsManager userDetailsManager() {
47-
final UserDetails paul = User.withUsername("paul")
48-
.password(COMMON_PASSWORD)
49-
.authorities(COMMON_ROLE)
50-
.build();
51-
52-
final UserDetails john = User.withUsername("john")
53-
.password(COMMON_PASSWORD)
54-
.authorities(COMMON_ROLE)
55-
.build();
56-
57-
final UserDetails ringo = User.withUsername("ringo")
58-
.password(COMMON_PASSWORD)
59-
.authorities(COMMON_ROLE)
60-
.build();
6148

62-
final UserDetails george = User.withUsername("george")
63-
.password(COMMON_PASSWORD)
64-
.authorities(COMMON_ROLE)
65-
.build();
66-
67-
return new InMemoryUserDetailsManager(paul, john, ringo, george);
49+
return new InMemoryUserDetailsManager(
50+
List.of("paul", "john", "ringo", "george")
51+
.stream()
52+
.map(user -> {
53+
return User.withUsername(user)
54+
.password(COMMON_PASSWORD)
55+
.authorities(COMMON_ROLE)
56+
.build();
57+
}).toList()
58+
);
6859
}
60+
6961
}

src/main/java/com/answerdigital/answerking/config/SwaggerConfig.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,11 @@ public GroupedOpenApi actuatorApi() {
3030
@Bean
3131
public OpenAPI springShopOpenAPI() {
3232
return new OpenAPI()
33-
.info(new Info().title("AnswerKing API")
34-
.description("Answer King application")
35-
.version("v0.0.1")
36-
.license(new License().name("Apache 2.0").url("http://springdoc.org")));
33+
.info(new Info()
34+
.title("AnswerKing API")
35+
.description("Answer King application")
36+
.version("v1")
37+
.license(new License().name("Apache 2.0").url("http://springdoc.org")));
3738
}
3839

3940
}

src/main/java/com/answerdigital/answerking/controller/CategoryController.java

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package com.answerdigital.answerking.controller;
22

3+
import com.answerdigital.answerking.exception.util.ErrorResponse;
34
import com.answerdigital.answerking.model.Category;
45
import com.answerdigital.answerking.request.RequestModelsCategory;
6+
import com.answerdigital.answerking.response.CategoryResponse;
57
import com.answerdigital.answerking.service.CategoryService;
68
import io.swagger.v3.oas.annotations.Operation;
79
import io.swagger.v3.oas.annotations.media.Content;
@@ -39,45 +41,89 @@ public CategoryController(final CategoryService categoryService) {
3941
this.categoryService = categoryService;
4042
}
4143

44+
@Operation(summary = "Create a new category.")
45+
@ApiResponses(value = {
46+
@ApiResponse(responseCode = "201", description = "When the category has been created.",
47+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = Category.class)) }),
48+
@ApiResponse(responseCode = "400", description = "When invalid parameters are provided.",
49+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class)) })
50+
})
4251
@PostMapping
43-
public ResponseEntity<Category> addCategory(@Valid @RequestBody final RequestModelsCategory categoryRequest) {
52+
public ResponseEntity<CategoryResponse> addCategory(@Valid @RequestBody final RequestModelsCategory categoryRequest) {
4453
return new ResponseEntity<>(categoryService.addCategory(categoryRequest), HttpStatus.CREATED);
4554
}
4655

4756
@Operation(summary = "Get all categories.")
4857
@ApiResponses(value = {
49-
@ApiResponse(responseCode = "200", description = "Found the the list of categories",
58+
@ApiResponse(responseCode = "200", description = "Found the list of categories.",
5059
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = Category.class)) })
5160
})
5261
@GetMapping
53-
public ResponseEntity<Collection<Category>> getAllCategories() {
54-
final Set<Category> categories = categoryService.findAll();
62+
public ResponseEntity<Collection<CategoryResponse>> getAllCategories() {
63+
final Set<CategoryResponse> categories = categoryService.findAll();
5564
return new ResponseEntity<>(categories, categories.isEmpty() ? HttpStatus.NO_CONTENT : HttpStatus.OK);
5665
}
5766

67+
@Operation(summary = "Get a single category.")
68+
@ApiResponses(value = {
69+
@ApiResponse(responseCode = "200", description = "When the category with the provided id has been found.",
70+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = Category.class)) }),
71+
@ApiResponse(responseCode = "404", description = "When the category with the given id does not exist.",
72+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class)) })
73+
})
5874
@GetMapping("/{categoryId}")
5975
public ResponseEntity<Category> getCategoryById(@PathVariable @NotNull final Long categoryId) {
6076
return new ResponseEntity<>(categoryService.findById(categoryId), HttpStatus.OK);
6177
}
6278

79+
@Operation(summary = "Add product to a category.")
80+
@ApiResponses(value = {
81+
@ApiResponse(responseCode = "200", description = "Add product to a category.",
82+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = Category.class)) })
83+
})
6384
@PutMapping("/{categoryId}/addproduct/{productId}")
6485
public ResponseEntity<Category> addProductToCategory(@PathVariable @NotNull final Long categoryId,
6586
@PathVariable @NotNull final Long productId) {
6687
return new ResponseEntity<>(categoryService.addProductToCategory(categoryId, productId), HttpStatus.OK);
6788
}
6889

90+
@Operation(summary = "Remove product from a category.")
91+
@ApiResponses(value = {
92+
@ApiResponse(responseCode = "200", description = "Remove product from a category.",
93+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = Category.class)) })
94+
})
6995
@PutMapping("/{categoryId}/removeproduct/{productId}")
7096
public ResponseEntity<Category> removeProductFromCategory(@PathVariable @NotNull final Long categoryId,
7197
@PathVariable @NotNull final Long productId) {
7298
return new ResponseEntity<>(categoryService.removeProductFromCategory(categoryId, productId), HttpStatus.OK);
7399
}
74100

101+
@Operation(summary = "Update an existing category.")
102+
@ApiResponses(value = {
103+
@ApiResponse(responseCode = "200", description = "When the category has been updated.",
104+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = Category.class)) }),
105+
@ApiResponse(responseCode = "400", description = "When invalid parameters are provided.",
106+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class)) }),
107+
@ApiResponse(responseCode = "404", description = "When the category with the given id does not exist.",
108+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class)) })
109+
})
75110
@PutMapping("/{categoryId}")
76111
public ResponseEntity<Category> updateCategory(@Valid @RequestBody final RequestModelsCategory updateCategoryRequest,
77112
@PathVariable @NotNull final Long categoryId) {
78113
return new ResponseEntity<>(categoryService.updateCategory(updateCategoryRequest, categoryId), HttpStatus.OK);
79114
}
80115

116+
@Operation(summary = "Retire an existing category.")
117+
@ApiResponses(value = {
118+
@ApiResponse(responseCode = "200", description = "When the category has been retired.",
119+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = Category.class)) }),
120+
@ApiResponse(responseCode = "400", description = "When invalid parameters are provided.",
121+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class)) }),
122+
@ApiResponse(responseCode = "404", description = "When the category with the given id does not exist.",
123+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class)) }),
124+
@ApiResponse(responseCode = "410", description = "When the category with the given id is already retired.",
125+
content = { @Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class)) })
126+
})
81127
@DeleteMapping("/{categoryId}")
82128
public ResponseEntity<Category> retireCategory(@PathVariable @NotNull final Long categoryId) {
83129
return new ResponseEntity<>(categoryService.retireCategory(categoryId), HttpStatus.OK);

src/main/java/com/answerdigital/answerking/mapper/CategoryMapper.java

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,35 @@
22

33
import com.answerdigital.answerking.model.Category;
44
import com.answerdigital.answerking.request.RequestModelsCategory;
5+
import com.answerdigital.answerking.response.CategoryResponse;
56
import org.mapstruct.Mapper;
7+
import org.mapstruct.Mapping;
68
import org.mapstruct.MappingTarget;
9+
import java.time.ZonedDateTime;
10+
import java.time.format.DateTimeFormatter;
11+
import java.time.temporal.ChronoUnit;
12+
import java.time.ZoneOffset;
13+
import java.util.stream.Collectors;
714

8-
@Mapper(componentModel = "spring")
15+
@Mapper(componentModel = "spring",
16+
imports = {DateTimeFormatter.class, ZoneOffset.class, ZonedDateTime.class, ChronoUnit.class, Collectors.class})
917
public interface CategoryMapper {
18+
Category updateRequestToCategory(@MappingTarget Category category, RequestModelsCategory updateCategoryRequest);
19+
20+
@Mapping(target = "createdOn", expression = "java( ZonedDateTime.now(ZoneOffset.UTC)" +
21+
".truncatedTo( ChronoUnit.SECONDS )" +
22+
".format( DateTimeFormatter.ofPattern( \"yyyy-MM-dd HH:mm:ss\" ) ) )")
23+
@Mapping(target = "lastUpdated", expression = "java( ZonedDateTime.now(ZoneOffset.UTC)" +
24+
".truncatedTo( ChronoUnit.SECONDS )" +
25+
".format( DateTimeFormatter.ofPattern( \"yyyy-MM-dd HH:mm:ss\" ) ) )")
1026
Category addRequestToCategory(RequestModelsCategory addCategoryRequest);
1127

28+
@Mapping(target = "lastUpdated", expression = "java( ZonedDateTime.now(ZoneOffset.UTC)" +
29+
".truncatedTo( ChronoUnit.SECONDS )" +
30+
".format( DateTimeFormatter.ofPattern( \"yyyy-MM-dd HH:mm:ss\" ) ) )")
1231
Category updateRequestToCategory(@MappingTarget Category category, RequestModelsCategory updateCategoryRequest);
32+
33+
@Mapping(target = "productIds",
34+
expression = "java(category.getProducts().stream().map(product -> product.getId()).collect(Collectors.toList()) )")
35+
CategoryResponse convertCategoryEntityToCategoryResponse(Category category);
1336
}

src/main/java/com/answerdigital/answerking/model/Category.java

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import com.answerdigital.answerking.request.RequestModelsCategory;
44
import com.fasterxml.jackson.annotation.JsonIgnore;
5+
import com.fasterxml.jackson.annotation.JsonProperty;
56
import lombok.AllArgsConstructor;
67
import lombok.Builder;
78
import lombok.Getter;
@@ -19,6 +20,10 @@
1920
import javax.persistence.ManyToMany;
2021
import javax.validation.constraints.NotBlank;
2122
import javax.validation.constraints.Pattern;
23+
import java.time.ZoneOffset;
24+
import java.time.ZonedDateTime;
25+
import java.time.format.DateTimeFormatter;
26+
import java.time.temporal.ChronoUnit;
2227
import java.util.HashSet;
2328
import java.util.Objects;
2429
import java.util.Set;
@@ -44,9 +49,15 @@ public class Category {
4449
message = "Category description can only contain letters, numbers, spaces and !?-.,' punctuation")
4550
private String description;
4651

52+
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
53+
private String createdOn;
54+
55+
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
56+
private String lastUpdated;
57+
4758
private boolean retired;
4859

49-
@ManyToMany(fetch = FetchType.LAZY)
60+
@ManyToMany(fetch = FetchType.EAGER)
5061
@JoinTable(
5162
name = "product_category",
5263
joinColumns = {@JoinColumn(name = "category_id")},
@@ -59,11 +70,14 @@ public Category(final RequestModelsCategory categoryRequest) {
5970
this.description = categoryRequest.description();
6071
this.retired = false;
6172
}
62-
6373
public Category(final String name, final String description) {
6474
this.name = name;
6575
this.description = description;
6676
this.retired = false;
77+
this.createdOn = ZonedDateTime.now(ZoneOffset.UTC)
78+
.truncatedTo( ChronoUnit.SECONDS )
79+
.format( DateTimeFormatter.ofPattern( "yyyy-MM-dd HH:mm::ss" ) );
80+
this.lastUpdated = this.createdOn;
6781
}
6882

6983
public void addProduct(final Product product) {

src/main/java/com/answerdigital/answerking/model/Product.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,6 @@ public Product(final RequestModelsProduct productRequest){
6666
this.price = productRequest.price();
6767
this.retired = false;
6868
}
69-
7069
public Product(final String name, final String description, final BigDecimal price, final boolean isRetired) {
7170
this.name = name;
7271
this.description = description;

0 commit comments

Comments
 (0)