Skip to content

Commit a094465

Browse files
kubazuchmigooxGitQueenZofiamslup
authored
Release v0.4 (#74)
* Roles and permissions integration with Spring Security (#37) - Roles and permissions are integrated with spring security -- you can use `@PreAuthorize` with `hasRole`, `hasAuhtority`, etc. on our endpoints now 🎉. - The POST `/api/products` endpoint has been secured🔒. - Removed unecessary `level` property from the `roles_permissions` ❌. - Added `RoleService`, which provides interface for creating roles, granting users etc., it also provides the default roles and injects them in `AuthService.register()` 🔑. - Roles and permissions are included in the jwt access token (as claims) response -- `JwtService.generateToken` relies on `JwtUserClaims` object that may be created with `JwtClaimsService` 😺. - Created `RoleServiceTest` 👩‍🔬. - Provided additional tests to `AuthService` (testing the default roles injection), and `JwtService` (testing the new claims mechanism) 👩‍🔬. - Added basic roles and permissions to the liquibase migration script 📝. - Added mocked users -- one with `USER` role one with `MODERATOR` role into the liquibase migration script. It should make the testing more convenient, but we need to change it to the production only using liquibase contexts in the future 📝. * Products paging (#44) * Products paging * Tests updated (paging) * Prepare for CD (#53) * Update application.properties * Git queen zofia/31 (#51) * Products paging * Tests updated (paging) * Liking products doesn't work :( * Liking products works!! * style. * Update src/main/java/pl/edu/pw/mini/ingreedio/api/service/ProductService.java Co-authored-by: Jakub Żuchowski <[email protected]> * Update src/main/java/pl/edu/pw/mini/ingreedio/api/service/ProductService.java Co-authored-by: Jakub Żuchowski <[email protected]> * Update src/main/java/pl/edu/pw/mini/ingreedio/api/service/ProductService.java Co-authored-by: Jakub Żuchowski <[email protected]> * Update src/main/java/pl/edu/pw/mini/ingreedio/api/service/UserService.java Co-authored-by: Jakub Żuchowski <[email protected]> * Update src/main/java/pl/edu/pw/mini/ingreedio/api/service/UserService.java Co-authored-by: Jakub Żuchowski <[email protected]> --------- Co-authored-by: Jakub Żuchowski <[email protected]> * Git queen zofia/32 (#52) * Liking ingredients (beginning) * Liking ingredients, adding allergens * changelog added * Update UserService.java * Product volume should be of type `String` (#59) * Update User.java * Update Product.java * Update FullProductDto.java * Update ProductServiceTest.java * Prepare CD workflow * Update test.yml * Backend CD (#61) * Create test.yml * Update test.yml * Update test.yml * Update test.yml * Update test.yml * Update docker.yml * Update test.yml * Update test.yml * Update test.yml * Update test.yml * Update test.yml * Update test.yml * Update test.yml * Update test.yml * Rename test.yml to deploy.yml * Git queen zofia/55 (#57) * Liking ingredients (beginning) * Liking ingredients, adding allergens * changelog added * imports --------- Co-authored-by: Jakub Żuchowski <[email protected]> * Git queen zofia/54 (#56) * Products paging * Tests updated (paging) * Liking products doesn't work :( * Liking products works!! * style. * Update src/main/java/pl/edu/pw/mini/ingreedio/api/service/ProductService.java Co-authored-by: Jakub Żuchowski <[email protected]> * Update src/main/java/pl/edu/pw/mini/ingreedio/api/service/ProductService.java Co-authored-by: Jakub Żuchowski <[email protected]> * Update src/main/java/pl/edu/pw/mini/ingreedio/api/service/ProductService.java Co-authored-by: Jakub Żuchowski <[email protected]> * Update src/main/java/pl/edu/pw/mini/ingreedio/api/service/UserService.java Co-authored-by: Jakub Żuchowski <[email protected]> * Update src/main/java/pl/edu/pw/mini/ingreedio/api/service/UserService.java Co-authored-by: Jakub Żuchowski <[email protected]> * Like products tests * imports --------- Co-authored-by: Jakub Żuchowski <[email protected]> * simple ingredient search (#58) * simple ingredient search * Update IngredientService.java --------- Co-authored-by: Jakub Żuchowski <[email protected]> * Filtering, searching and sorting products (#70) * Release v0.3 (#40) * Update docker.yml * Update docker.yml * Update docker.yml * MongoDB setup (#16) * MongoDB! * Dockerized tests * Bug: wrong mongo port * Username naming changed (#18) * Update docker-compose.yml * Products fetching (#20) * Products fetching * Update Application.run.xml * style corrected * tests corrected * checkstyle * Import the ingredients names to postgres db (#21) * HelloWorldController hotfix * Add roles and permissions tables and entities (#22) * Added roles sql schema * Added entites classes --------- Co-authored-by: Jakub Żuchowski <[email protected]> * Filtering of products (#23) * Permitted POST requests to /api/products * Filtering products by fields * Checkstyle is persecuting me * Some tests, filtering by ingredients with array * Builder for criteria utilized, Checkstyle corrections * Endpoint moving, small fixes. (#24) Closes #19 * Removed searching (by keywords) from filtering, corrected tests (#26) * Filtering products bugfixes (#28) * New tests, attempt to solve bugs (failed :c) * search + fixed tests * Revert "Merge branch 'develop' into mslup/hotfix/3" This reverts commit eb035d8, reversing changes made to 943168d. * Checkstyle corrections --------- Co-authored-by: GitQueenZofia <[email protected]> Co-authored-by: GitQueenZofia <[email protected]> * Reviewdog setup (#38) * Endpoint moving, small fixes. Closes #19 * Reviewdog setup * Update reviewdog.yml * Version bump * Update gradle.yml --------- Co-authored-by: Marcin Słupczyński <[email protected]> Co-authored-by: GitQueenZofia <[email protected]> Co-authored-by: Michał Okurowski <[email protected]> Co-authored-by: GitQueenZofia <[email protected]> * Added 2 stages of searching * Moved keywords creation logic * Fixed paging * matchScore works * Styling fix * Added TODO * Added brand, provider and category to criteria * Fixed tests * Fixed styling issues * Added suppress warnings to tests * Added suppress warnings to tests 2 * Checkstyle * Update Application.run.xml * Fixed case sensitivity issues * Fixed ProductsCriteriaService * Added case sensitivity test * Changed ingredients List to Set * Upgrade Optional stream Co-authored-by: Jakub Żuchowski <[email protected]> * Comment typo fix Co-authored-by: Jakub Żuchowski <[email protected]> * Changed ProductCriteria to record * Moved query to resource * Json should be .json :) * typo * change the format also in test im dumbo and i forgor --------- Co-authored-by: Jakub Żuchowski <[email protected]> Co-authored-by: Marcin Słupczyński <[email protected]> Co-authored-by: GitQueenZofia <[email protected]> Co-authored-by: GitQueenZofia <[email protected]> * Version bump --------- Co-authored-by: Michał Okurowski <[email protected]> Co-authored-by: GitQueenZofia <[email protected]> Co-authored-by: Marcin Słupczyński <[email protected]> Co-authored-by: GitQueenZofia <[email protected]>
1 parent 5d02eb8 commit a094465

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2227
-526
lines changed

.github/workflows/deploy.yml

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
on:
2+
workflow_run:
3+
workflows: ["Build and Prepare Docker Image"]
4+
types:
5+
- completed
6+
workflow_dispatch:
7+
8+
name: Deploy to Cloud Run
9+
10+
env:
11+
PROJECT_ID: able-balm-421213
12+
GAR_LOCATION: europe-central2
13+
SERVICE: ingreedio-api
14+
REGION: europe-west1
15+
REGISTRY: ghcr.io
16+
IMAGE_NAME: java-dzgs/ingreedio-api
17+
18+
jobs:
19+
deploy:
20+
permissions:
21+
contents: 'read'
22+
id-token: 'write'
23+
packages: 'write'
24+
25+
runs-on: ubuntu-latest
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@v2
29+
30+
- name: Docker auth for the GitHub Container registry
31+
uses: docker/[email protected]
32+
with:
33+
registry: ${{ env.REGISTRY }}
34+
username: ${{ github.actor }}
35+
password: ${{ secrets.GITHUB_TOKEN }}
36+
37+
- name: Pull latest develop Backend image
38+
run: |-
39+
docker pull ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop
40+
docker tag ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:develop ${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/docker-dev/ingreedio-api
41+
42+
- name: Google auth
43+
id: auth
44+
uses: 'google-github-actions/auth@v2'
45+
with:
46+
workload_identity_provider: '${{ secrets.WIF_PROVIDER }}'
47+
service_account: '${{ secrets.WIF_SERVICE_ACCOUNT }}'
48+
49+
- name: Set up Cloud SDK
50+
uses: 'google-github-actions/setup-gcloud@v1'
51+
with:
52+
project_id: '${{ env.PROJECT_ID }}'
53+
54+
- name: Docker auth for Cloud
55+
run: |-
56+
gcloud auth configure-docker ${{ env.GAR_LOCATION }}-docker.pkg.dev
57+
58+
- name: Docker push image to Cloud
59+
run: |-
60+
docker push ${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/docker-dev/ingreedio-api
61+
62+
- name: Deploy image to Cloud Run
63+
id: deploy
64+
uses: google-github-actions/deploy-cloudrun@v2
65+
with:
66+
service: ${{ env.SERVICE }}
67+
region: ${{ env.REGION }}
68+
image: ${{ env.GAR_LOCATION }}-docker.pkg.dev/${{ env.PROJECT_ID }}/docker-dev/ingreedio-api:latest

.github/workflows/docker.yml

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,6 @@ jobs:
1717

1818
steps:
1919
- uses: actions/checkout@v2
20-
# Checkout your repository content into GitHub Actions runner
21-
22-
# - name: Docker Compose
23-
# run: docker compose build
24-
# # Builds the Docker image
25-
26-
- name: Log in to registry
27-
run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u $ --password-stdin
2820

2921
- name: Log in to the Container registry
3022
uses: docker/[email protected]
@@ -46,22 +38,3 @@ jobs:
4638
push: true
4739
tags: ${{ steps.meta.outputs.tags }}
4840
labels: ${{ steps.meta.outputs.labels }}
49-
50-
# - name: Save Docker image
51-
# run: docker save ingreedio-api-app -o api-image.tar
52-
# # Saves the Docker image as a tarball
53-
54-
# - name: Upload Docker image as an artifact
55-
# uses: actions/upload-artifact@v3
56-
# with:
57-
# name: api-docker-image
58-
# path: api-image.tar
59-
# retention-days: 7
60-
# - uses: "marvinpinto/action-automatic-releases@latest"
61-
# with:
62-
# repo_token: "${{ secrets.GITHUB_TOKEN }}"
63-
# automatic_release_tag: "latest"
64-
# prerelease: true
65-
# title: "Automatic build"
66-
# files: |
67-
# api-image.tar

build.gradle

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ plugins {
55
}
66

77
group = 'pl.edu.pw.mini.ingreedio'
8-
version = 'v0.3'
8+
version = 'v0.4'
99

1010
java {
1111
sourceCompatibility = '21'
@@ -35,9 +35,11 @@ dependencies {
3535
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
3636
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'
3737

38-
38+
implementation 'com.google.cloud.sql:postgres-socket-factory:1.18.0'
3939
testImplementation 'org.springframework.boot:spring-boot-starter-test'
4040
testImplementation 'org.testcontainers:testcontainers'
41+
42+
testImplementation 'org.springframework.security:spring-security-test'
4143
}
4244

4345
tasks.named('test') {

src/main/java/pl/edu/pw/mini/ingreedio/api/config/OpenApi30Configuration.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,6 @@
1414
scheme = "bearer"
1515
)
1616
@OpenAPIDefinition(
17-
info = @Info(title = "InGreed.io", version = "v0.3")
17+
info = @Info(title = "InGreed.io", version = "v0.4")
1818
)
1919
public class OpenApi30Configuration {}

src/main/java/pl/edu/pw/mini/ingreedio/api/config/SecurityConfig.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import org.springframework.context.annotation.Configuration;
66
import org.springframework.http.HttpMethod;
77
import org.springframework.security.authentication.AuthenticationProvider;
8+
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
89
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
910
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
1011
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -15,10 +16,9 @@
1516

1617
@Configuration
1718
@EnableWebSecurity
18-
//@EnableMethodSecurity
19+
@EnableMethodSecurity
1920
@RequiredArgsConstructor
2021
public class SecurityConfig {
21-
2222
private final JwtAuthFilter jwtAuthFilter;
2323
private final AuthenticationProvider authenticationProvider;
2424

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package pl.edu.pw.mini.ingreedio.api.controller;
2+
3+
import com.mongodb.event.CommandSucceededEvent;
4+
import io.swagger.v3.oas.annotations.Operation;
5+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
6+
import io.swagger.v3.oas.annotations.tags.Tag;
7+
import java.util.List;
8+
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.ResponseEntity;
10+
import org.springframework.web.bind.annotation.DeleteMapping;
11+
import org.springframework.web.bind.annotation.GetMapping;
12+
import org.springframework.web.bind.annotation.PathVariable;
13+
import org.springframework.web.bind.annotation.PostMapping;
14+
import org.springframework.web.bind.annotation.RequestMapping;
15+
import org.springframework.web.bind.annotation.RequestParam;
16+
import org.springframework.web.bind.annotation.RestController;
17+
import pl.edu.pw.mini.ingreedio.api.dto.IngredientDto;
18+
import pl.edu.pw.mini.ingreedio.api.dto.ProductDto;
19+
import pl.edu.pw.mini.ingreedio.api.model.Ingredient;
20+
import pl.edu.pw.mini.ingreedio.api.service.IngredientService;
21+
22+
@RestController
23+
@RequestMapping("/api/ingredients")
24+
@RequiredArgsConstructor
25+
@Tag(name = "Ingredients" /*, description = "..."*/)
26+
public class IngredientController {
27+
private final IngredientService ingredientService;
28+
29+
@Operation(summary = "Search ingredients",
30+
description = "Search ingredients",
31+
security = {@SecurityRequirement(name = "Bearer Authentication")})
32+
@GetMapping
33+
public ResponseEntity<List<IngredientDto>> getIngredients(
34+
@RequestParam int count,
35+
@RequestParam(required = false) String query) {
36+
String name = query != null ? query : "";
37+
List<IngredientDto> matchingIngredients = ingredientService.getIngredients(name);
38+
if (matchingIngredients.size() > count) {
39+
matchingIngredients = matchingIngredients.subList(0, count);
40+
}
41+
return ResponseEntity.ok(matchingIngredients);
42+
}
43+
44+
@Operation(summary = "Get liked ingredients",
45+
description = "Get liked ingredients",
46+
security = {@SecurityRequirement(name = "Bearer Authentication")})
47+
@GetMapping("/liked")
48+
public ResponseEntity<List<IngredientDto>> getLikedIngredients() {
49+
List<IngredientDto> likedIngredients = ingredientService.getLikedIngredients();
50+
return ResponseEntity.ok(likedIngredients);
51+
}
52+
53+
@Operation(summary = "Get allergens",
54+
description = "Get allergens",
55+
security = {@SecurityRequirement(name = "Bearer Authentication")})
56+
@GetMapping("/allergens")
57+
public ResponseEntity<List<IngredientDto>> getAllergens() {
58+
List<IngredientDto> allergens = ingredientService.getAllergens();
59+
return ResponseEntity.ok(allergens);
60+
}
61+
62+
@Operation(summary = "Like an ingredient",
63+
description = "Like an ingredient",
64+
security = {@SecurityRequirement(name = "Bearer Authentication")})
65+
@PostMapping("/{id}/likes")
66+
public ResponseEntity<Void> likeIngredient(@PathVariable Long id) {
67+
boolean likeSucceeded = ingredientService.likeIngredient(id);
68+
if (likeSucceeded) {
69+
return ResponseEntity.ok().build();
70+
}
71+
return ResponseEntity.notFound().build();
72+
}
73+
74+
@Operation(summary = "Unlike ingredient",
75+
description = "Unlike ingredient",
76+
security = {@SecurityRequirement(name = "Bearer Authentication")})
77+
@DeleteMapping("/{id}/likes")
78+
public ResponseEntity<Void> unlikeIngredient(@PathVariable Long id) {
79+
boolean unlikeSucceeded = ingredientService.unlikeIngredient(id);
80+
if (unlikeSucceeded) {
81+
return ResponseEntity.ok().build();
82+
}
83+
return ResponseEntity.notFound().build();
84+
}
85+
86+
@Operation(summary = "Add allergen",
87+
description = "Add allergen",
88+
security = {@SecurityRequirement(name = "Bearer Authentication")})
89+
@PostMapping("/{id}/allergens")
90+
public ResponseEntity<Void> addAllergen(@PathVariable Long id) {
91+
boolean addAllergenSucceeded = ingredientService.addAllergen(id);
92+
if (addAllergenSucceeded) {
93+
return ResponseEntity.ok().build();
94+
}
95+
return ResponseEntity.notFound().build();
96+
}
97+
98+
@Operation(summary = "Remove allergen",
99+
description = "Remove allergen",
100+
security = {@SecurityRequirement(name = "Bearer Authentication")})
101+
@DeleteMapping("/{id}/allergens")
102+
public ResponseEntity<Void> removeAllergen(@PathVariable Long id) {
103+
boolean removeAllergenSucceeded = ingredientService.removeAllergen(id);
104+
if (removeAllergenSucceeded) {
105+
return ResponseEntity.ok().build();
106+
}
107+
return ResponseEntity.notFound().build();
108+
}
109+
}

src/main/java/pl/edu/pw/mini/ingreedio/api/controller/ProductController.java

Lines changed: 62 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@
33
import io.swagger.v3.oas.annotations.Operation;
44
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
55
import io.swagger.v3.oas.annotations.tags.Tag;
6+
import java.util.HashSet;
67
import java.util.List;
78
import java.util.Optional;
9+
import java.util.Set;
810
import lombok.RequiredArgsConstructor;
911
import org.springframework.http.HttpStatus;
1012
import org.springframework.http.ResponseEntity;
13+
import org.springframework.security.access.prepost.PreAuthorize;
14+
import org.springframework.web.bind.annotation.DeleteMapping;
1115
import org.springframework.web.bind.annotation.GetMapping;
1216
import org.springframework.web.bind.annotation.PathVariable;
1317
import org.springframework.web.bind.annotation.PostMapping;
@@ -16,44 +20,54 @@
1620
import org.springframework.web.bind.annotation.RequestParam;
1721
import org.springframework.web.bind.annotation.ResponseBody;
1822
import org.springframework.web.bind.annotation.RestController;
19-
import pl.edu.pw.mini.ingreedio.api.criteria.ProductFilterCriteria;
2023
import pl.edu.pw.mini.ingreedio.api.dto.FullProductDto;
21-
import pl.edu.pw.mini.ingreedio.api.dto.ProductDto;
24+
import pl.edu.pw.mini.ingreedio.api.dto.ProductListResponseDto;
2225
import pl.edu.pw.mini.ingreedio.api.model.Product;
26+
import pl.edu.pw.mini.ingreedio.api.service.PaginationService;
2327
import pl.edu.pw.mini.ingreedio.api.service.ProductService;
28+
import pl.edu.pw.mini.ingreedio.api.service.ProductsCriteriaService;
2429

2530
@RestController
2631
@RequestMapping("/api/products")
2732
@RequiredArgsConstructor
2833
@Tag(name = "Products" /*, description = "..."*/)
2934
public class ProductController {
3035
private final ProductService productService;
36+
private final PaginationService paginationService;
37+
private final ProductsCriteriaService productsCriteriaService;
3138

32-
@Operation(summary = "Get matching products", description = "Get matching products")
39+
@Operation(summary = "Get matching products",
40+
description = "Get matching products",
41+
security = {@SecurityRequirement(name = "Bearer Authentication")})
3342
@GetMapping
34-
public ResponseEntity<List<ProductDto>> getProducts(
35-
@RequestParam Optional<String> name,
36-
@RequestParam Optional<String> provider,
37-
@RequestParam Optional<String> brand,
38-
@RequestParam Optional<Integer> volumeFrom,
39-
@RequestParam Optional<Integer> volumeTo,
40-
@RequestParam Optional<String[]> ingredients) {
41-
List<ProductDto> products = productService.getProductsMatching(
42-
ProductFilterCriteria.builder()
43-
.name(name.orElse(null))
44-
.provider(provider.orElse(null))
45-
.brand(brand.orElse(null))
46-
.volumeFrom(volumeFrom.orElse(null))
47-
.volumeTo(volumeTo.orElse(null))
48-
.ingredients(ingredients.orElse(null))
49-
.build()
43+
public ResponseEntity<ProductListResponseDto> getProducts(
44+
@RequestParam("page-number") Optional<Integer> pageNumber,
45+
@RequestParam("ingredients-exclude") Optional<Set<Long>> ingredientsToExclude,
46+
@RequestParam("ingredients-include") Optional<Set<Long>> ingredientsToInclude,
47+
@RequestParam("min-rating") Optional<Integer> minRating,
48+
@RequestParam("phrase") Optional<String> phrase,
49+
@RequestParam("sort-by") Optional<List<String>> sortBy,
50+
@RequestParam("liked") Optional<Boolean> liked) {
51+
52+
ProductListResponseDto products = productService.getProductsMatchingCriteria(
53+
productsCriteriaService.getProductsCriteria(
54+
ingredientsToExclude,
55+
ingredientsToInclude,
56+
minRating,
57+
phrase,
58+
sortBy,
59+
liked
60+
// TODO: provider, brand, category
61+
),
62+
paginationService.getPageRequest(pageNumber)
5063
);
5164

5265
return ResponseEntity.ok(products);
5366
}
5467

5568
@Operation(summary = "Get info of a specific product",
56-
description = "Get info of a specific product")
69+
description = "Get info of a specific product",
70+
security = {@SecurityRequirement(name = "Bearer Authentication")})
5771
@GetMapping("/{id}")
5872
public ResponseEntity<FullProductDto> getProductById(@PathVariable Long id) {
5973
return productService.getProductById(id).map(ResponseEntity::ok)
@@ -63,11 +77,39 @@ public ResponseEntity<FullProductDto> getProductById(@PathVariable Long id) {
6377
@Operation(summary = "Add a product to the database",
6478
description = "Add a product to the database",
6579
security = {@SecurityRequirement(name = "Bearer Authentication")})
80+
@PreAuthorize("hasAuthority('ADD_PRODUCT')")
6681
@PostMapping
6782
@ResponseBody
6883
public ResponseEntity<Product> addProduct(@RequestBody Product product) {
6984
Product savedProduct = productService.addProduct(product);
7085
return new ResponseEntity<>(savedProduct, HttpStatus.CREATED);
7186
}
7287

88+
@Operation(summary = "",
89+
description = "",
90+
security = {@SecurityRequirement(name = "Bearer Authentication")})
91+
@PostMapping("/{id}/likes")
92+
@ResponseBody
93+
public ResponseEntity<Void> likeProduct(@PathVariable Long id) {
94+
boolean likeSucceeded = productService.likeProduct(id);
95+
if (likeSucceeded) {
96+
return ResponseEntity.ok().build();
97+
} else {
98+
return ResponseEntity.notFound().build();
99+
}
100+
}
101+
102+
@Operation(summary = "",
103+
description = "",
104+
security = {@SecurityRequirement(name = "Bearer Authentication")})
105+
@DeleteMapping("/{id}/likes")
106+
@ResponseBody
107+
public ResponseEntity<Void> unlikeProduct(@PathVariable Long id) {
108+
boolean unlikeSucceeded = productService.unlikeProduct(id);
109+
if (unlikeSucceeded) {
110+
return ResponseEntity.ok().build();
111+
} else {
112+
return ResponseEntity.notFound().build();
113+
}
114+
}
73115
}

0 commit comments

Comments
 (0)