Skip to content

bugfix/7682/eco-places#9150

Open
SaenkoVova wants to merge 12 commits intodevfrom
bugfix/7682/eco-places
Open

bugfix/7682/eco-places#9150
SaenkoVova wants to merge 12 commits intodevfrom
bugfix/7682/eco-places

Conversation

@SaenkoVova
Copy link
Contributor

@SaenkoVova SaenkoVova commented Sep 25, 2025

GreenCity PR

Issue Links 📋

Summary by CodeRabbit

  • New Features

    • JWT can be read from an accessToken cookie when Authorization header is absent.
    • Place create/update endpoints now accept multipart/form-data with optional images, require authenticated user id, and respond with 204 No Content.
  • Refactor

    • Payloads simplified to name/address/categoryId; discount-related fields and legacy propose flow removed.
    • Category representations now include an id; management UI removed discount controls and flattened address/category inputs.
  • Documentation

    • API docs updated for new request formats and responses.
  • Tests

    • Tests migrated to multipart flows and updated DTO shapes/constructors.

@SaenkoVova SaenkoVova self-assigned this Sep 25, 2025
@SaenkoVova SaenkoVova added the Bug label Sep 25, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 25, 2025

Walkthrough

Endpoints for creating and updating places were converted to multipart flows (JSON @RequestPart + optional images) returning 204 No Content; DTOs were flattened (address + categoryId), discount fields and related UI removed, several DTOs/mappers/tests deleted, service methods renamed/adjusted, tests updated, and JwtTool gained cookie-based token extraction.

Changes

Cohort / File(s) Summary
Controllers
core/src/main/java/greencity/controller/PlaceController.java, core/src/main/java/greencity/webcontroller/ManagementPlacesController.java
Removed proposePlace; save/update endpoints now consume multipart/form-data, accept JSON via @RequestPart + optional MultipartFile[], use @CurrentUserId, return 204 No Content, and call placeService.save / placeService.update. Mappings and Javadoc updated.
Frontend (templates & scripts)
core/src/main/resources/templates/core/management_places.html, core/src/main/resources/static/scripts/management/places/buttonsAJAX.js
Removed discounts UI and hidden geo inputs; category select simplified; client AJAX payloads flattened to name/address/categoryId; discount-related UI logic removed.
Public DTO API (service-api)
service-api/src/main/java/greencity/dto/place/AddPlaceDto.java, .../place/PlaceUpdateDto.java, .../category/CategoryDto.java, .../location/LocationAddressAndGeoForUpdateDto.java
AddPlaceDto: placeNamename, added address and categoryId, removed legacy fields. PlaceUpdateDto: replaced nested location/category with address and categoryId, removed discountValues. CategoryDto: added id. Removed LocationAddressAndGeoForUpdateDto.
Removed DTOs & Mappers
core/src/main/java/greencity/dto/place/PlaceWithUserDto.java, service-api/src/main/java/greencity/dto/place/PlaceAddDto.java, service/src/main/java/greencity/mapping/AddPlaceDtoMapper.java, service/src/main/java/greencity/mapping/ProposePlaceMapper.java
Deleted DTOs and mapping classes tied to prior propose/add flows; mapping logic and associated tests removed.
Service API & Impl
service-api/src/main/java/greencity/service/PlaceService.java, service/src/main/java/greencity/service/PlaceServiceImpl.java
Removed legacy save(PlaceAddDto, String) and updateFromUI; added void save(AddPlaceDto, Long userId, MultipartFile[] images) and void update(PlaceUpdateDto, MultipartFile[] images, Long userId); refactored internals (user/category lookup, location building, opening-hours mapping, image handling); getInfoForUpdatingById now builds DTO manually.
Security
service-api/src/main/java/greencity/security/jwt/JwtTool.java
Added fallback to extract accessToken from cookies when Authorization header is absent; preserves header flow and throws NoJwtException if token not found.
Controller & Webcontroller Tests
core/src/test/java/greencity/controller/PlaceControllerTest.java, core/src/test/java/greencity/webcontroller/ManagementPlacesControllerTest.java
Tests migrated to multipart requests (MockMultipartFile JSON part + optional images); payload shapes and expected statuses updated; service verifications changed to new save/update signatures; CategoryDto constructor usage updated.
Service Tests & Test Utils
service/src/test/java/greencity/service/PlaceServiceImplTest.java, service/src/test/java/greencity/service/NotificationServiceImplTest.java, service/src/test/java/greencity/ModelUtils.java
Tests adapted to DTO-centered save/update flows; ModelUtils now returns AddPlaceDto; CategoryDto creation updated to include id; mocks and assertions adjusted for new service behavior and new exception paths.
Removed Mapper Tests
service/src/test/java/greencity/mapping/AddPlaceDtoMapperTest.java, service/src/test/java/greencity/mapping/ProposePlaceMapperTest.java
Deleted tests for removed mapper classes.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant UI as Client/UI
  participant PC as PlaceController
  participant PS as PlaceService
  participant Repo as Repositories
  participant Geo as GeocodingProvider

  rect rgb(245,250,255)
    note right of UI: Multipart flow — JSON dto part + optional images
    UI->>PC: POST /v2/places/save (multipart: AddPlaceDto + images)
    UI->>PC: PUT /v2/places/update (multipart: PlaceUpdateDto + images)
  end

  PC->>PS: save(dto, userId, images) / update(dto, images, userId)
  PS->>Repo: findUserById(userId)
  PS->>Repo: findCategoryById(categoryId)
  PS->>Geo: geocode(address)
  alt geocode results found
    PS->>Repo: save(place with openingHours, photos, location)
    PS-->>PC: void
    PC-->>UI: 204 No Content
  else geocode empty / not found
    PS-->>PC: throws NotFound/NoGeocode
    PC-->>UI: 4xx error
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Notes for reviewer attention:

  • Controller-to-service signature changes (PlaceController -> PlaceService.save/update) and multipart handling.
  • DTO removals and field renames (impact on serialization/deserialization and clients).
  • ServiceImpl refactor: new helper methods (geocoding, opening-hours mapping, image handling) and transactional boundaries.
  • JwtTool cookie extraction logic and its tests.

Possibly related PRs

  • Bug/7682/fix management page #7975 — Modifies ManagementPlacesController save/update endpoints and internal service call targets; likely overlaps with this multipart/controller refactor.

Suggested reviewers

  • LazarenkoDmytro
  • Maryna-511750
  • holotsvan
  • softservedata

Poem

Multipart parcels glide in code tonight,
Discounts shelved, addresses set just right.
Controllers whisper "204" and pass,
Services stitch photos into saved-place mass.
Cookies hum tokens — tests wake, green and bright.

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The pull request title 'bugfix/7682/eco-places' uses a branch-naming convention rather than a descriptive commit message format. While it references an issue number and mentions 'eco-places,' the title lacks specificity about the actual changes being made. The raw_summary indicates extensive API refactoring (removal of DTOs, endpoint signature changes, discount handling removal, and service method consolidation), but the title does not convey the scope or nature of these changes clearly enough for reviewers scanning the history. Consider revising the title to be more descriptive of the primary change from a user or developer perspective, such as 'Refactor place creation/update endpoints to consolidate service methods and simplify DTOs' or 'Remove discount functionality and unify place management workflows.' This will help future maintainers understand the changeset's purpose at a glance without referencing the issue tracker.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch bugfix/7682/eco-places

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 18

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (6)
service/src/test/java/greencity/service/NotificationServiceImplTest.java (1)

91-94: Fix copy-paste bug: assigning category/id to testPlace1 instead of testPlace2

This causes duplicated data and can hide errors. Assign to testPlace2.

@@
-        Place testPlace2 = getPlace();
-        testPlace1.setCategory(getCategory());
-        testPlace1.setId(2L);
+        Place testPlace2 = getPlace();
+        testPlace2.setCategory(getCategory());
+        testPlace2.setId(2L);
@@
-        Place testPlace2 = getPlace();
-        testPlace1.setCategory(getCategory());
-        testPlace1.setId(2L);
+        Place testPlace2 = getPlace();
+        testPlace2.setCategory(getCategory());
+        testPlace2.setId(2L);
@@
-        Place testPlace2 = getPlace();
-        testPlace1.setCategory(getCategory());
-        testPlace1.setId(2L);
+        Place testPlace2 = getPlace();
+        testPlace2.setCategory(getCategory());
+        testPlace2.setId(2L);

Also applies to: 130-133, 169-172

core/src/main/resources/templates/core/management_places.html (2)

326-331: Address input: avoid ASCII-only pattern and fix error span id to match DTO

The regex blocks non-Latin addresses (i18n). Also, the error span id should match server field ‘address’ for error mapping.

-                            <input class="form-control" id="address" name="address" type="text"
-                                   pattern="^[A-Za-z0-9\s,.-]+$"
-                                   title="Please enter a valid address"
-                                   required>
-                            <span class="errorSpan" th:id="errorModalSavelocation"></span>
+                            <input class="form-control" id="address" name="address" type="text" required>
+                            <span class="errorSpan" th:id="errorModalSaveaddress"></span>

470-472: Remove hardcoded Google Maps API key from source

Leaking API keys in HTML is unsafe. Load via server-side config/env and restrict the key by domain/IP.

core/src/main/resources/static/scripts/management/places/buttonsAJAX.js (1)

221-225: Fix error span ids to match updated HTML and DTO field names

Ensures client-side validation displays in the right spans and aligns with server field names.

-        const requiredFields = [
-            { id: 'address', errorId: 'errorModalSavelocation', message: messages["greenCity.places.page.add.address"] },
-            { id: 'placeName', errorId: 'errorModalSavename', message: messages["greenCity.places.page.add.place.name"] },
-            { id: 'category', errorId: 'errorModalSavecategory', message: messages["greenCity.places.page.add.category"] },
-        ];
+        const requiredFields = [
+            { id: 'address',   errorId: 'errorModalSaveaddress',    message: messages["greenCity.places.page.add.address"] },
+            { id: 'placeName', errorId: 'errorModalSavename',       message: messages["greenCity.places.page.add.place.name"] },
+            { id: 'category',  errorId: 'errorModalSavecategoryId', message: messages["greenCity.places.page.add.category"] },
+        ];
service/src/test/java/greencity/ModelUtils.java (1)

3331-3334: Fix copy/paste bug: longitude assigned to latitude.

Second setter should be longitude, not latitude.

             .coordinates(AddressDto.builder()
-                .latitude(50.1234)
-                .latitude(30.1234)
+                .latitude(50.1234)
+                .longitude(30.1234)
                 .build())
service/src/main/java/greencity/service/PlaceServiceImpl.java (1)

519-533: Guard against null Photo on upload failure (NPE/IllegalArgument).

If upload throws, newPhoto stays null but is still passed to photoRepo.save(newPhoto), causing runtime failure.

Apply:

-                Photo savedPhoto = photoRepo.save(newPhoto);
-                newPhotos.add(savedPhoto);
+                if (newPhoto != null) {
+                    Photo savedPhoto = photoRepo.save(newPhoto);
+                    newPhotos.add(savedPhoto);
+                }
🧹 Nitpick comments (14)
service-api/src/main/java/greencity/dto/place/AddPlaceDto.java (2)

32-33: Ensure categoryId is positive

Guard against invalid IDs by requiring positive values.

-    @NotNull
-    private Long categoryId;
+    @NotNull
+    @jakarta.validation.constraints.Positive
+    private Long categoryId;

Add import if not present:

import jakarta.validation.constraints.Positive;

29-31: Consider a max length constraint on address

Add a safe upper bound (e.g., @Length(max = 255)) to prevent oversized input.

core/src/main/java/greencity/webcontroller/ManagementPlacesController.java (2)

91-121: Javadoc vs behavior: align documented responses with actual return type

Docs mention 204/void and various HTTP codes, but the method returns 200 with GenericResponseDto. Adjust Javadoc or response semantics for consistency.


144-175: Javadoc consistency for update endpoint

Same as save: documented statuses don’t match the 200 + GenericResponseDto behavior. Please align.

core/src/main/resources/templates/core/management_places.html (1)

403-406: Optional: allow multiple image uploads in the form

The update flow supports multiple files; align the create flow with multiple.

-                            <input type="file" class="form-control" name="imagePath" id="creationFile" placeholder="ferf">
+                            <input type="file" class="form-control" name="imagePath" id="creationFile" multiple>
core/src/main/resources/static/scripts/management/places/buttonsAJAX.js (1)

162-165: POST also append all selected files for ‘images’ (parity with PUT)

Supports multiple uploads consistently.

-        const file = document.getElementById("creationFile").files[0];
-        if (file) {
-            formData.append("images", file);
-        }
+        const fileInput = document.getElementById("creationFile");
+        Array.from(fileInput?.files || []).forEach((file) => {
+            formData.append("images", file);
+        });
core/src/test/java/greencity/controller/PlaceControllerTest.java (1)

469-496: Remove legacy commented-out block.

This large commented section adds noise and can confuse future maintenance.

-//        String json = """
-//            {
-//              "categoryName": "test",
-//              "locationName": "вулиця Під Дубом, 7Б, Львів, Львівська область, 79000",
-//              "openingHoursList": [
-//                {
-//                  "closeTime": "20:00",
-//                  "openTime": "08:00",
-//                  "weekDay": "MONDAY"
-//                }
-//              ],
-//              "placeName": "Форум Львів"
-//            }
-//            """;
-//
-//        ObjectMapper objectMapper = new ObjectMapper();
-//        objectMapper.findAndRegisterModules();
-//        AddPlaceDto addPlaceDtoRequest = objectMapper.readValue(json, AddPlaceDto.class);
-//        String jsonValue = objectMapper.writeValueAsString(addPlaceDtoRequest);
-//
-//        MockMultipartFile jsonFile = new MockMultipartFile("dto", "",
-//            "application/json", jsonValue.getBytes());
-//        mockMvc.perform(multipart(placeLink + "/v2/save")
-//            .file(jsonFile)
-//            .principal(principal)
-//            .accept(MediaType.APPLICATION_JSON)
-//            .contentType(MediaType.APPLICATION_JSON))
-//            .andExpect(status().isCreated());
core/src/test/java/greencity/webcontroller/ManagementPlacesControllerTest.java (1)

198-198: Delete obsolete commented verification.

Keeps the test suite concise and focused on current behavior.

-//        verify(placeService, never()).updateFromUI(any(PlaceUpdateDto.class), any(), any());
service-api/src/main/java/greencity/dto/place/PlaceUpdateDto.java (1)

31-35: Consider limiting address length.

Add a max-length constraint for address to prevent excessively large inputs (e.g., @Length with a project-standard max). If there’s a ServiceValidationConstants for address, reuse it.

service/src/test/java/greencity/ModelUtils.java (1)

2126-2136: Ensure test helper builds a valid AddPlaceDto.

Provide minimal required fields (name, address) to match new validations.

     public static AddPlaceDto getAddPlaceDto() {
         return AddPlaceDto.builder()
-            .categoryId(1L)
+            .name("Test Place")
+            .address("Kyiv")
+            .categoryId(1L)
 //            .placeName("test")
 //            .locationName("Test")
             .openingHoursList(Set.of(OpeningHoursDto.builder()
                 .openTime(LocalTime.now())
                 .closeTime(LocalTime.now())
                 .weekDay(DayOfWeek.MONDAY)
                 .build()))
             .build();
     }
service/src/test/java/greencity/service/PlaceServiceImplTest.java (1)

230-236: Generalize stubbing for coordinates to avoid brittle tests.

Match any coordinates instead of hardcoding values that may diverge from geocoder output.

-        when(locationService.existsByLatAndLng(50.45, 30.52)).thenReturn(false);
+        when(locationService.existsByLatAndLng(any(), any())).thenReturn(false);

Also remove the debug print inside the argThat block.

-        verify(placeRepo).save(argThat(place -> {
-            System.out.println(place.getLocation());
+        verify(placeRepo).save(argThat(place -> {
             return place.getName().equals("My Cafe") &&
                 place.getAuthor().equals(user) &&
                 place.getCategory().equals(category) &&
                 place.getOpeningHoursList().size() == 1;
         }));
core/src/main/java/greencity/controller/PlaceController.java (3)

96-106: Document 204 response in OpenAPI.

Method returns 204 NO_CONTENT but it's not listed in @ApiResponses. Add:

     @ApiResponses(value = {
+        @ApiResponse(responseCode = "204", description = HttpStatuses.NO_CONTENT),
         @ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST,

495-499: Add 204 to API responses for create.

Endpoint returns 204 NO_CONTENT; include it in docs:

 @ApiResponses(value = {
+    @ApiResponse(responseCode = "204", description = HttpStatuses.NO_CONTENT),
     @ApiResponse(responseCode = "400", description = HttpStatuses.BAD_REQUEST,

57-75: Align JavaDoc with actual behavior.

If you keep only multipart for this method (with JSON handled by an overload), update the doc to reflect that. If you add the JSON overload, mention both variants explicitly.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 92e43f8 and e5607af.

📒 Files selected for processing (22)
  • core/src/main/java/greencity/controller/PlaceController.java (4 hunks)
  • core/src/main/java/greencity/dto/place/PlaceWithUserDto.java (0 hunks)
  • core/src/main/java/greencity/webcontroller/ManagementPlacesController.java (3 hunks)
  • core/src/main/resources/static/scripts/management/places/buttonsAJAX.js (4 hunks)
  • core/src/main/resources/templates/core/management_places.html (2 hunks)
  • core/src/test/java/greencity/controller/PlaceControllerTest.java (3 hunks)
  • core/src/test/java/greencity/webcontroller/ManagementPlacesControllerTest.java (4 hunks)
  • service-api/src/main/java/greencity/dto/category/CategoryDto.java (1 hunks)
  • service-api/src/main/java/greencity/dto/location/LocationAddressAndGeoForUpdateDto.java (0 hunks)
  • service-api/src/main/java/greencity/dto/place/AddPlaceDto.java (2 hunks)
  • service-api/src/main/java/greencity/dto/place/PlaceAddDto.java (0 hunks)
  • service-api/src/main/java/greencity/dto/place/PlaceUpdateDto.java (1 hunks)
  • service-api/src/main/java/greencity/security/jwt/JwtTool.java (2 hunks)
  • service-api/src/main/java/greencity/service/PlaceService.java (2 hunks)
  • service/src/main/java/greencity/mapping/AddPlaceDtoMapper.java (0 hunks)
  • service/src/main/java/greencity/mapping/ProposePlaceMapper.java (0 hunks)
  • service/src/main/java/greencity/service/PlaceServiceImpl.java (2 hunks)
  • service/src/test/java/greencity/ModelUtils.java (3 hunks)
  • service/src/test/java/greencity/mapping/AddPlaceDtoMapperTest.java (0 hunks)
  • service/src/test/java/greencity/mapping/ProposePlaceMapperTest.java (0 hunks)
  • service/src/test/java/greencity/service/NotificationServiceImplTest.java (3 hunks)
  • service/src/test/java/greencity/service/PlaceServiceImplTest.java (3 hunks)
💤 Files with no reviewable changes (7)
  • service-api/src/main/java/greencity/dto/location/LocationAddressAndGeoForUpdateDto.java
  • service-api/src/main/java/greencity/dto/place/PlaceAddDto.java
  • service/src/main/java/greencity/mapping/AddPlaceDtoMapper.java
  • service/src/main/java/greencity/mapping/ProposePlaceMapper.java
  • service/src/test/java/greencity/mapping/AddPlaceDtoMapperTest.java
  • service/src/test/java/greencity/mapping/ProposePlaceMapperTest.java
  • core/src/main/java/greencity/dto/place/PlaceWithUserDto.java
🧰 Additional context used
🧬 Code graph analysis (4)
service/src/test/java/greencity/service/PlaceServiceImplTest.java (1)
service/src/test/java/greencity/ModelUtils.java (1)
  • ModelUtils (291-3705)
service-api/src/main/java/greencity/dto/place/PlaceUpdateDto.java (1)
core/src/main/resources/static/scripts/management/events/createEvent.js (1)
  • address (299-299)
service/src/main/java/greencity/service/PlaceServiceImpl.java (1)
core/src/main/resources/static/scripts/management/places/buttonsAJAX.js (6)
  • place (138-143)
  • place (149-155)
  • i (57-57)
  • openTime (239-239)
  • closeTime (240-240)
  • openingHours (271-271)
service-api/src/main/java/greencity/dto/place/AddPlaceDto.java (1)
core/src/main/resources/static/scripts/management/events/createEvent.js (1)
  • address (299-299)
🔇 Additional comments (7)
service-api/src/main/java/greencity/dto/category/CategoryDto.java (1)

17-17: LGTM: id field addition aligns with new API

Adding id to CategoryDto is consistent with the refactor and test updates.

service/src/test/java/greencity/service/NotificationServiceImplTest.java (1)

107-114: LGTM: CategoryDto construction updated to new signature

Using new CategoryDto(1L, ...) matches the added id field.

Also applies to: 146-153, 184-191

core/src/test/java/greencity/webcontroller/ManagementPlacesControllerTest.java (1)

77-77: LGTM: CategoryDto with id update.

Using CategoryDto(1L, ...) aligns with the new constructor and API shape.

service/src/test/java/greencity/ModelUtils.java (1)

3466-3469: LGTM: CategoryDto factory updated to new ctor.

Matches the DTO signature update.

service/src/main/java/greencity/service/PlaceServiceImpl.java (2)

321-340: LGTM: explicit DTO assembly for update info.

Clear, focused fields for update; good move away from blind mapping.


614-635: OpeningHours cascade configuration is sufficient. Place.openingHoursList is annotated with cascade={PERSIST, MERGE}, and old entries are explicitly deleted before setting new ones, so new OpeningHours entities will be persisted correctly without needing orphanRemoval.

core/src/main/java/greencity/controller/PlaceController.java (1)

500-507: Restrict save endpoint to multipart, add JSON overload, and enforce validation

  • Update saveEcoPlaceFromUi in PlaceController.java to consume only MediaType.MULTIPART_FORM_DATA_VALUE and annotate the @RequestPart AddPlaceDto with @Valid
  • Add a separate @PostMapping for application/json using @RequestBody and @Valid (no images)
  • Verify no existing callers post JSON to /place/v2/save using the provided script

Comment on lines 107 to 113
@PutMapping(value = "/update", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
@ResponseStatus(HttpStatus.NO_CONTENT)
public void updatePlace(@Parameter(required = true) @RequestPart PlaceUpdateDto dto,
@Parameter(hidden = true) @CurrentUserId Long userId,
@RequestPart(required = false) @Nullable MultipartFile[] images) {
placeService.update(dto, images, userId);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Fix multipart vs JSON handling and restore validation (@Valid).

@RequestPart requires multipart; with consumes including application/json this will fail for JSON clients. Also, @Valid on DTO was dropped.

  • Either restrict this method to multipart only and add an overload for JSON with @RequestBody, or drop application/json from consumes.
  • Add @Valid to the DTO.

Apply this diff here:

-    @PutMapping(value = "/update", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
+    @PutMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@@
-    public void updatePlace(@Parameter(required = true) @RequestPart PlaceUpdateDto dto,
+    public void updatePlace(@Parameter(required = true) @Valid @RequestPart PlaceUpdateDto dto,

Add this overload (outside the selected range) to support pure JSON:

@PutMapping(value = "/update", consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void updatePlaceJson(@Valid @RequestBody PlaceUpdateDto dto,
                            @Parameter(hidden = true) @CurrentUserId Long userId) {
    placeService.update(dto, null, userId);
}

Run this to find any app/json callers for /place/update:


🏁 Script executed:

#!/bin/bash
rg -nC3 -P '/place/(v2/)?update' --type js --type ts --type java --type html
rg -nC2 -P '(Content-Type|contentType)\s*[:=]\s*["\']application/json' --type js --type ts

Length of output: 2506


🏁 Script executed:

#!/usr/bin/env bash
set +e
# Search for any client calls to /place/update in code and templates
rg -nC3 "/place/update" --type js --type ts --type java --type html
# Search for JSON content-type usage in JS/TS clients
rg -nC2 -e "(Content-Type|contentType)\s*[:=]\s*['\"]application/json" --type js --type ts

Length of output: 24298


Separate multipart vs JSON update endpoints and restore validation
@RequestPart only binds multipart; drop application/json from consumes, add @Valid on the DTO, and add a JSON‐only overload with @RequestBody.

In core/src/main/java/greencity/controller/PlaceController.java:

-    @PutMapping(value = "/update", consumes = {MediaType.MULTIPART_FORM_DATA_VALUE, MediaType.APPLICATION_JSON_VALUE})
+    @PutMapping(value = "/update", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@@
-    public void updatePlace(@Parameter(required = true) @RequestPart PlaceUpdateDto dto,
+    public void updatePlace(@Parameter(required = true) @Valid @RequestPart PlaceUpdateDto dto,

Add below:

@PutMapping(value = "/update", consumes = MediaType.APPLICATION_JSON_VALUE)
@ResponseStatus(HttpStatus.NO_CONTENT)
public void updatePlaceJson(@Valid @RequestBody PlaceUpdateDto dto,
                            @CurrentUserId Long userId) {
    placeService.update(dto, null, userId);
}
🤖 Prompt for AI Agents
In core/src/main/java/greencity/controller/PlaceController.java around lines 107
to 113, the current @PutMapping declares consumes = {MULTIPART_FORM_DATA,
APPLICATION_JSON} while using @RequestPart (which only binds multipart) and the
DTO lacks validation; change the multipart handler to remove APPLICATION_JSON
from consumes and add @Valid to the @RequestPart PlaceUpdateDto parameter;
additionally add a new JSON-only overload mapped to the same "/update" with
consumes = MediaType.APPLICATION_JSON_VALUE that accepts @Valid @RequestBody
PlaceUpdateDto and the @CurrentUserId, and calls placeService.update(dto, null,
userId) so JSON requests are handled correctly and DTO validation is restored.

Comment on lines +351 to +355
"id": "1",
"name": "Тестове місце",
"address": "Смиків, південна 7",
"categoryId": "1",
"openingHoursList": [
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Avoid relying on Jackson string-to-number coercion in JSON payload.

Use numeric literals for id/categoryId to match DTO types and prevent fragile coercion.

-                "id": "1",
+                "id": 1,
...
-                "categoryId": "1",
+                "categoryId": 1,
🤖 Prompt for AI Agents
In core/src/test/java/greencity/controller/PlaceControllerTest.java around lines
351 to 355, the JSON test payload uses string values for numeric fields ("id":
"1", "categoryId": "1"); change these to numeric literals (id: 1, categoryId: 1)
so the JSON matches the DTO types and avoids relying on Jackson's
string-to-number coercion; update any other numeric fields in the payload
similarly and run the tests to ensure deserialization now uses proper numeric
types.

Comment on lines +439 to +444
"name": "Тестове місце",
"address": "Смиків, південна 7",
"categoryId": "1",
"openingHoursList": [
{
"weekDay": "MONDAY",
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use numeric value for categoryId.

DTO expects a Long; pass a number instead of a quoted string.

-                "categoryId": "1",
+                "categoryId": 1,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"name": "Тестове місце",
"address": "Смиків, південна 7",
"categoryId": "1",
"openingHoursList": [
{
"weekDay": "MONDAY",
"name": "Тестове місце",
"address": "Смиків, південна 7",
"categoryId": 1,
"openingHoursList": [
{
"weekDay": "MONDAY",
🤖 Prompt for AI Agents
In core/src/test/java/greencity/controller/PlaceControllerTest.java around lines
439 to 444, the test JSON uses a quoted string for categoryId ("1") but the DTO
expects a Long; update the test payload to use a numeric value (unquoted) for
categoryId (e.g., 1) so the deserializer maps it to a Long correctly.

Comment on lines +702 to +710
private Set<OpeningHours> mapOpeningHours(Set<OpeningHoursDto> dtos) {
return dtos.stream()
.map(i -> OpeningHours.builder()
.openTime(i.getOpenTime())
.closeTime(i.getCloseTime())
.weekDay(i.getWeekDay())
.build())
.collect(Collectors.toSet());
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Null-safety for opening hours mapping.

mapOpeningHours() NPEs when dtos is null. Treat null as empty.

-    private Set<OpeningHours> mapOpeningHours(Set<OpeningHoursDto> dtos) {
-        return dtos.stream()
+    private Set<OpeningHours> mapOpeningHours(Set<OpeningHoursDto> dtos) {
+        if (dtos == null || dtos.isEmpty()) {
+            return new HashSet<>();
+        }
+        return dtos.stream()
             .map(i -> OpeningHours.builder()
                 .openTime(i.getOpenTime())
                 .closeTime(i.getCloseTime())
                 .weekDay(i.getWeekDay())
                 .build())
             .collect(Collectors.toSet());
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private Set<OpeningHours> mapOpeningHours(Set<OpeningHoursDto> dtos) {
return dtos.stream()
.map(i -> OpeningHours.builder()
.openTime(i.getOpenTime())
.closeTime(i.getCloseTime())
.weekDay(i.getWeekDay())
.build())
.collect(Collectors.toSet());
}
private Set<OpeningHours> mapOpeningHours(Set<OpeningHoursDto> dtos) {
if (dtos == null || dtos.isEmpty()) {
return new HashSet<>();
}
return dtos.stream()
.map(i -> OpeningHours.builder()
.openTime(i.getOpenTime())
.closeTime(i.getCloseTime())
.weekDay(i.getWeekDay())
.build())
.collect(Collectors.toSet());
}
🤖 Prompt for AI Agents
In service/src/main/java/greencity/service/PlaceServiceImpl.java around lines
702 to 710, mapOpeningHours currently NPEs when dtos is null; modify it to treat
null as empty and avoid NPEs by returning Collections.emptySet() (or new
HashSet<>()) when dtos is null, and also defensively filter out any null
elements before mapping (e.g., dtos.stream().filter(Objects::nonNull) ...). Keep
the existing mapping logic otherwise.

Comment on lines +1141 to 1153
public static AddPlaceDto getPlaceAddDto() {
AddPlaceDto addPlaceDto = new AddPlaceDto();
addPlaceDto.setName("Test");
CategoryDto category = new CategoryDto();
category.setNameEn("category");
placeAddDto.setCategory(category);
placeAddDto.setLocation(getLocationAddressAndGeoDto());
addPlaceDto.setCategoryId(1L);
HashSet<OpeningHoursDto> openingHoursDtos = new HashSet<>();
openingHoursDtos.add(getOpeningHoursDto());
placeAddDto.setOpeningHoursList(openingHoursDtos);
addPlaceDto.setOpeningHoursList(openingHoursDtos);
HashSet<DiscountValueDto> discountValueDtos = new HashSet<>();
discountValueDtos.add(getDiscountValueDto());
placeAddDto.setDiscountValues(discountValueDtos);
return placeAddDto;
return addPlaceDto;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Complete AddPlaceDto and remove unused local variable.

  • Set address to reflect new API.
  • Drop the unused CategoryDto variable.
 public static AddPlaceDto getPlaceAddDto() {
-        AddPlaceDto addPlaceDto = new AddPlaceDto();
-        addPlaceDto.setName("Test");
-        CategoryDto category = new CategoryDto();
-        category.setNameEn("category");
-        addPlaceDto.setCategoryId(1L);
+        AddPlaceDto addPlaceDto = new AddPlaceDto();
+        addPlaceDto.setName("Test");
+        addPlaceDto.setAddress("Kyiv");
+        addPlaceDto.setCategoryId(1L);
         HashSet<OpeningHoursDto> openingHoursDtos = new HashSet<>();
         openingHoursDtos.add(getOpeningHoursDto());
         addPlaceDto.setOpeningHoursList(openingHoursDtos);
         HashSet<DiscountValueDto> discountValueDtos = new HashSet<>();
         discountValueDtos.add(getDiscountValueDto());
         return addPlaceDto;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static AddPlaceDto getPlaceAddDto() {
AddPlaceDto addPlaceDto = new AddPlaceDto();
addPlaceDto.setName("Test");
CategoryDto category = new CategoryDto();
category.setNameEn("category");
placeAddDto.setCategory(category);
placeAddDto.setLocation(getLocationAddressAndGeoDto());
addPlaceDto.setCategoryId(1L);
HashSet<OpeningHoursDto> openingHoursDtos = new HashSet<>();
openingHoursDtos.add(getOpeningHoursDto());
placeAddDto.setOpeningHoursList(openingHoursDtos);
addPlaceDto.setOpeningHoursList(openingHoursDtos);
HashSet<DiscountValueDto> discountValueDtos = new HashSet<>();
discountValueDtos.add(getDiscountValueDto());
placeAddDto.setDiscountValues(discountValueDtos);
return placeAddDto;
return addPlaceDto;
}
public static AddPlaceDto getPlaceAddDto() {
AddPlaceDto addPlaceDto = new AddPlaceDto();
addPlaceDto.setName("Test");
addPlaceDto.setAddress("Kyiv");
addPlaceDto.setCategoryId(1L);
HashSet<OpeningHoursDto> openingHoursDtos = new HashSet<>();
openingHoursDtos.add(getOpeningHoursDto());
addPlaceDto.setOpeningHoursList(openingHoursDtos);
HashSet<DiscountValueDto> discountValueDtos = new HashSet<>();
discountValueDtos.add(getDiscountValueDto());
return addPlaceDto;
}
🤖 Prompt for AI Agents
In service/src/test/java/greencity/ModelUtils.java around lines 1141 to 1153,
remove the unused local CategoryDto variable, set the AddPlaceDto address field
to the new API expected value (e.g., addPlaceDto.setAddress(...) with a test
AddressDto/String as required), and ensure the discountValueDtos collection is
actually assigned to the addPlaceDto (e.g., addPlaceDto.setDiscounts(...) or the
correct setter name). Keep the existing openingHoursList assignment and the
categoryId assignment as-is.

Comment on lines +1003 to +1011
void updateFromUIThrowsNotFoundExceptionForCategoryTest() {
PlaceUpdateDto dto = new PlaceUpdateDto();
dto.setId(1L);
dto.setName("Updated Place");
dto.setCategory(new CategoryDto("Test Category", "Test Category Ua", 1L));
dto.setLocation(new LocationAddressAndGeoForUpdateDto("New Address", 50.45, 30.52, "New Address Ua"));

MultipartFile[] images = new MultipartFile[] {
new MockMultipartFile("image1.jpg", "image1.jpg", "image/jpeg", new byte[0])
};
LocationVO locationVO = LocationVO.builder()
.id(1L)
.addressEn("New Address")
.lat(50.45)
.lng(30.52)
.addressUk("New Address Ua")
dto.setCategoryId(1L);

when(categoryService.findByName("Nonexistent Category"))
.thenThrow(new NotFoundException("Category not found"));
assertThrows(NotFoundException.class, () -> placeServiceImpl.update(dto, null, TestConst.USER_ID));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Broken test: stubs old API (findByName) but update(...) now uses categoryId.

This test won’t fail for missing category with the new flow. Stub CategoryRepo by id instead.

     void updateFromUIThrowsNotFoundExceptionForCategoryTest() {
         PlaceUpdateDto dto = new PlaceUpdateDto();
         dto.setId(1L);
         dto.setCategoryId(1L);
-
-        when(categoryService.findByName("Nonexistent Category"))
-            .thenThrow(new NotFoundException("Category not found"));
+        when(userRepo.findById(TestConst.USER_ID)).thenReturn(Optional.of(user));
+        when(placeRepo.findById(1L)).thenReturn(Optional.of(ModelUtils.getPlace()));
+        when(categoryRepo.findById(1L)).thenReturn(Optional.empty());
         assertThrows(NotFoundException.class, () -> placeServiceImpl.update(dto, null, TestConst.USER_ID));
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void updateFromUIThrowsNotFoundExceptionForCategoryTest() {
PlaceUpdateDto dto = new PlaceUpdateDto();
dto.setId(1L);
dto.setName("Updated Place");
dto.setCategory(new CategoryDto("Test Category", "Test Category Ua", 1L));
dto.setLocation(new LocationAddressAndGeoForUpdateDto("New Address", 50.45, 30.52, "New Address Ua"));
MultipartFile[] images = new MultipartFile[] {
new MockMultipartFile("image1.jpg", "image1.jpg", "image/jpeg", new byte[0])
};
LocationVO locationVO = LocationVO.builder()
.id(1L)
.addressEn("New Address")
.lat(50.45)
.lng(30.52)
.addressUk("New Address Ua")
dto.setCategoryId(1L);
when(categoryService.findByName("Nonexistent Category"))
.thenThrow(new NotFoundException("Category not found"));
assertThrows(NotFoundException.class, () -> placeServiceImpl.update(dto, null, TestConst.USER_ID));
}
void updateFromUIThrowsNotFoundExceptionForCategoryTest() {
PlaceUpdateDto dto = new PlaceUpdateDto();
dto.setId(1L);
dto.setCategoryId(1L);
when(userRepo.findById(TestConst.USER_ID)).thenReturn(Optional.of(user));
when(placeRepo.findById(1L)).thenReturn(Optional.of(ModelUtils.getPlace()));
when(categoryRepo.findById(1L)).thenReturn(Optional.empty());
assertThrows(NotFoundException.class, () -> placeServiceImpl.update(dto, null, TestConst.USER_ID));
}
🤖 Prompt for AI Agents
In service/src/test/java/greencity/service/PlaceServiceImplTest.java around
lines 1003 to 1011, the test currently stubs
categoryService.findByName("Nonexistent Category") but the production
update(...) path now resolves categories by ID; update the stub to mock the
ID-based lookup instead (e.g., when(categoryService.findById(1L)).thenThrow(new
NotFoundException("Category not found"))), keep dto.setCategoryId(1L) as-is, and
ensure the assertThrows still calls placeServiceImpl.update(dto, null,
TestConst.USER_ID).

Comment on lines +1014 to 1040
void update_successfullyUpdatesPlace() {
Place existingPlace = ModelUtils.getPlace();
// given
PlaceUpdateDto dto = PlaceUpdateDto.builder()
.id(existingPlace.getId())
.name("New Place")
.categoryId(category.getId())
.openingHoursList(Set.of(ModelUtils.getOpeningHoursDto()))
.build();
when(categoryService.findByName("Test Category")).thenReturn(categoryDtoResponse);
when(placeRepo.findById(1L)).thenReturn(Optional.of(genericEntity1));
when(userRepo.findById(user.getId())).thenReturn(Optional.of(user));
when(locationService.findById(1L)).thenReturn(locationVO);
List<GeocodingResult> geocodingResults = new ArrayList<>();
GeocodingResult ukrLang = new GeocodingResult();
ukrLang.formattedAddress = "New Address";
ukrLang.geometry = new Geometry();
ukrLang.geometry.location = new LatLng(50.45, 30.52);
GeocodingResult engLang = new GeocodingResult();
engLang.formattedAddress = "New Address Ua";
engLang.geometry = new Geometry();
engLang.geometry.location = new LatLng(50.45, 30.52);
geocodingResults.add(ukrLang);
geocodingResults.add(engLang);
when(googleApiService.getResultFromGeoCode("New Address")).thenReturn(geocodingResults);
PlaceVO result = placeServiceImpl.updateFromUI(dto, images, user.getId());

assertNotNull(result);
assertEquals("Updated Place", result.getName());
verify(placeRepo, atLeastOnce()).save(any(Place.class));
verify(photoRepo, times(1)).save(any(Photo.class));
verify(locationService, times(1)).update(anyLong(), any(LocationVO.class));

MultipartFile[] images = new MultipartFile[0];

GeocodingResult geo1 = ModelUtils.getGeocodingResult().getFirst();
GeocodingResult geo2 = ModelUtils.getGeocodingResult().getLast();

when(userRepo.findById(1L)).thenReturn(Optional.of(user));
when(placeRepo.findById(existingPlace.getId())).thenReturn(Optional.of(existingPlace));
when(categoryRepo.findById(category.getId())).thenReturn(Optional.of(category));
when(googleApiService.getResultFromGeoCode("New Place"))
.thenReturn(List.of(geo1, geo2));

placeService.update(dto, images, 1L);

verify(placeRepo).save(argThat(place -> place.getId().equals(place.getId()) &&
place.getName().equals(place.getName()) &&
place.getOpeningHoursList().size() == 1));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Fix assertion and geocoding target in update_successfullyUpdatesPlace.

  • Geocoding should use address, not name.
  • The verify lambda compares the same object to itself; assert against expected values.
         Place existingPlace = ModelUtils.getPlace();
         // given
         PlaceUpdateDto dto = PlaceUpdateDto.builder()
             .id(existingPlace.getId())
             .name("New Place")
             .categoryId(category.getId())
+            .address("Kyiv")
             .openingHoursList(Set.of(ModelUtils.getOpeningHoursDto()))
             .build();
 
         MultipartFile[] images = new MultipartFile[0];
 
         GeocodingResult geo1 = ModelUtils.getGeocodingResult().getFirst();
         GeocodingResult geo2 = ModelUtils.getGeocodingResult().getLast();
 
         when(userRepo.findById(1L)).thenReturn(Optional.of(user));
         when(placeRepo.findById(existingPlace.getId())).thenReturn(Optional.of(existingPlace));
         when(categoryRepo.findById(category.getId())).thenReturn(Optional.of(category));
-        when(googleApiService.getResultFromGeoCode("New Place"))
+        when(googleApiService.getResultFromGeoCode("Kyiv"))
             .thenReturn(List.of(geo1, geo2));
 
         placeService.update(dto, images, 1L);
 
-        verify(placeRepo).save(argThat(place -> place.getId().equals(place.getId()) &&
-            place.getName().equals(place.getName()) &&
-            place.getOpeningHoursList().size() == 1));
+        verify(placeRepo).save(argThat(p ->
+            p.getId().equals(existingPlace.getId()) &&
+            p.getName().equals("New Place") &&
+            p.getCategory().equals(category) &&
+            p.getOpeningHoursList().size() == 1));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void update_successfullyUpdatesPlace() {
Place existingPlace = ModelUtils.getPlace();
// given
PlaceUpdateDto dto = PlaceUpdateDto.builder()
.id(existingPlace.getId())
.name("New Place")
.categoryId(category.getId())
.openingHoursList(Set.of(ModelUtils.getOpeningHoursDto()))
.build();
when(categoryService.findByName("Test Category")).thenReturn(categoryDtoResponse);
when(placeRepo.findById(1L)).thenReturn(Optional.of(genericEntity1));
when(userRepo.findById(user.getId())).thenReturn(Optional.of(user));
when(locationService.findById(1L)).thenReturn(locationVO);
List<GeocodingResult> geocodingResults = new ArrayList<>();
GeocodingResult ukrLang = new GeocodingResult();
ukrLang.formattedAddress = "New Address";
ukrLang.geometry = new Geometry();
ukrLang.geometry.location = new LatLng(50.45, 30.52);
GeocodingResult engLang = new GeocodingResult();
engLang.formattedAddress = "New Address Ua";
engLang.geometry = new Geometry();
engLang.geometry.location = new LatLng(50.45, 30.52);
geocodingResults.add(ukrLang);
geocodingResults.add(engLang);
when(googleApiService.getResultFromGeoCode("New Address")).thenReturn(geocodingResults);
PlaceVO result = placeServiceImpl.updateFromUI(dto, images, user.getId());
assertNotNull(result);
assertEquals("Updated Place", result.getName());
verify(placeRepo, atLeastOnce()).save(any(Place.class));
verify(photoRepo, times(1)).save(any(Photo.class));
verify(locationService, times(1)).update(anyLong(), any(LocationVO.class));
MultipartFile[] images = new MultipartFile[0];
GeocodingResult geo1 = ModelUtils.getGeocodingResult().getFirst();
GeocodingResult geo2 = ModelUtils.getGeocodingResult().getLast();
when(userRepo.findById(1L)).thenReturn(Optional.of(user));
when(placeRepo.findById(existingPlace.getId())).thenReturn(Optional.of(existingPlace));
when(categoryRepo.findById(category.getId())).thenReturn(Optional.of(category));
when(googleApiService.getResultFromGeoCode("New Place"))
.thenReturn(List.of(geo1, geo2));
placeService.update(dto, images, 1L);
verify(placeRepo).save(argThat(place -> place.getId().equals(place.getId()) &&
place.getName().equals(place.getName()) &&
place.getOpeningHoursList().size() == 1));
}
void update_successfullyUpdatesPlace() {
Place existingPlace = ModelUtils.getPlace();
// given
PlaceUpdateDto dto = PlaceUpdateDto.builder()
.id(existingPlace.getId())
.name("New Place")
.categoryId(category.getId())
.address("Kyiv")
.openingHoursList(Set.of(ModelUtils.getOpeningHoursDto()))
.build();
MultipartFile[] images = new MultipartFile[0];
GeocodingResult geo1 = ModelUtils.getGeocodingResult().getFirst();
GeocodingResult geo2 = ModelUtils.getGeocodingResult().getLast();
when(userRepo.findById(1L)).thenReturn(Optional.of(user));
when(placeRepo.findById(existingPlace.getId())).thenReturn(Optional.of(existingPlace));
when(categoryRepo.findById(category.getId())).thenReturn(Optional.of(category));
when(googleApiService.getResultFromGeoCode("Kyiv"))
.thenReturn(List.of(geo1, geo2));
placeService.update(dto, images, 1L);
verify(placeRepo).save(argThat(p ->
p.getId().equals(existingPlace.getId()) &&
p.getName().equals("New Place") &&
p.getCategory().equals(category) &&
p.getOpeningHoursList().size() == 1));
}
🤖 Prompt for AI Agents
In service/src/test/java/greencity/service/PlaceServiceImplTest.java around
lines 1014-1040, the test incorrectly calls
googleApiService.getResultFromGeoCode with the place name and the verify lambda
compares the same place instance to itself; change the geocoding call to use the
DTO's address (e.g., dto.getAddress()) instead of "New Place", and update the
verify assertion to compare saved Place fields to expected values (e.g.,
place.getId().equals(dto.getId()), place.getName().equals("New Place"), and
place.getOpeningHoursList().size() == 1) so the test verifies actual changes
rather than self-comparisons.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
service/src/test/java/greencity/ModelUtils.java (1)

2138-2146: Builder-based DTO is now underfilled (no name/address).

The builder variant returns an AddPlaceDto with only categoryId and opening hours. The upstream refactor removed placeName/locationName but replaced them with mandatory name and address. Leaving both unset yields DTOs that fail controller/service validation and breaks any tests that call this helper.

Please set name and address (and any other new required primitives) on the builder.

     public static AddPlaceDto getAddPlaceDto() {
         return AddPlaceDto.builder()
             .categoryId(1L)
-//            .placeName("test")
-//            .locationName("Test")
+            .name("Test")
+            .address("Kyiv")
             .openingHoursList(Set.of(OpeningHoursDto.builder()
                 .openTime(LocalTime.now())
                 .closeTime(LocalTime.now())
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e5607af and 3357921.

📒 Files selected for processing (3)
  • service/src/main/java/greencity/service/PlaceServiceImpl.java (3 hunks)
  • service/src/test/java/greencity/ModelUtils.java (3 hunks)
  • service/src/test/java/greencity/service/PlaceServiceImplTest.java (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-01-31T08:55:30.651Z
Learnt from: urio999
PR: ita-social-projects/GreenCity#8082
File: service/src/main/java/greencity/mapping/events/EventResponseDtoMapper.java:0-0
Timestamp: 2025-01-31T08:55:30.651Z
Learning: In the EventResponseDtoMapper, null checks for address should use Optional or null-safe operators instead of assertions to ensure runtime safety, as assertions can be disabled with the -da flag.

Applied to files:

  • service/src/main/java/greencity/service/PlaceServiceImpl.java
🧬 Code graph analysis (2)
service/src/test/java/greencity/service/PlaceServiceImplTest.java (1)
service/src/test/java/greencity/ModelUtils.java (1)
  • ModelUtils (301-3764)
service/src/main/java/greencity/service/PlaceServiceImpl.java (1)
core/src/main/resources/static/scripts/management/places/buttonsAJAX.js (6)
  • place (138-143)
  • place (149-155)
  • i (57-57)
  • openTime (239-239)
  • closeTime (240-240)
  • openingHours (271-271)
🪛 GitHub Actions: CI/CD GreenCity
service/src/main/java/greencity/service/PlaceServiceImpl.java

[error] 568-572: Cannot find symbol: class SearchCriteria at line 568 and class PlaceSearchSpecification at line 572.


[error] Compilation failed due to missing classes SearchCriteria and PlaceSearchSpecification.

🔇 Additional comments (7)
service/src/main/java/greencity/service/PlaceServiceImpl.java (3)

624-633: Geocode using the address, not the name

Passing the place name into buildLocation sends the wrong signal to the geocoder and can easily produce mismatched coordinates (or fail entirely for generic names). The DTO already carries the address—please use it here.

-        Location location = buildLocation(dto.getName());
+        Location location = buildLocation(dto.getAddress());

697-704: Guard against short geocoding responses

geocodingResults.get(1) throws whenever Google only returns one match (quite common for precise addresses). Add a safe fallback (and ideally fetch per locale) so the update/save flow doesn’t 500 on valid input.

-        GeocodingResult ukrLang = geocodingResults.getFirst();
-        GeocodingResult engLang = geocodingResults.get(1);
+        GeocodingResult ukrLang = geocodingResults.getFirst();
+        GeocodingResult engLang =
+            geocodingResults.size() > 1 ? geocodingResults.get(1) : ukrLang;

708-716: Handle null opening-hours payloads

mapOpeningHours currently NPEs when the client omits openingHoursList. Treat null/empty as “no hours” so create/update remains resilient.

-    private Set<OpeningHours> mapOpeningHours(Set<OpeningHoursDto> dtos) {
-        return dtos.stream()
+    private Set<OpeningHours> mapOpeningHours(Set<OpeningHoursDto> dtos) {
+        if (CollectionUtils.isEmpty(dtos)) {
+            return new HashSet<>();
+        }
+        return dtos.stream()
             .map(i -> OpeningHours.builder()
                 .openTime(i.getOpenTime())
                 .closeTime(i.getCloseTime())
                 .weekDay(i.getWeekDay())
                 .build())
             .collect(Collectors.toSet());
     }
service/src/test/java/greencity/service/PlaceServiceImplTest.java (3)

1003-1011: Stub category lookup the same way production code does

update(...) now resolves categories via categoryRepo.findById, so this test never hits your stub and instead fails on the missing user. Please align the mocks so you actually assert the “category not found” path.

-        when(categoryService.findByName("Nonexistent Category"))
-            .thenThrow(new NotFoundException("Category not found"));
+        when(userRepo.findById(TestConst.USER_ID)).thenReturn(Optional.of(user));
+        when(placeRepo.findById(1L)).thenReturn(Optional.of(ModelUtils.getPlace()));
+        when(categoryRepo.findById(1L)).thenReturn(Optional.empty());

1014-1039: Bring the happy-path update test in line with the new contract

The service should geocode the address—not the name—and the current verification compares the object to itself, so the assertion never fails. Set the DTO address, stub geocoding with that string, and assert the persisted fields against expected values.

         PlaceUpdateDto dto = PlaceUpdateDto.builder()
             .id(existingPlace.getId())
             .name("New Place")
+            .address("Kyiv")
             .categoryId(category.getId())
             .openingHoursList(Set.of(ModelUtils.getOpeningHoursDto()))
             .build();
@@
-        when(googleApiService.getResultFromGeoCode("New Place"))
+        when(googleApiService.getResultFromGeoCode("Kyiv"))
             .thenReturn(List.of(geo1, geo2));
@@
-        verify(placeRepo).save(argThat(place -> place.getId().equals(place.getId()) &&
-            place.getName().equals(place.getName()) &&
-            place.getOpeningHoursList().size() == 1));
+        verify(placeRepo).save(argThat(place ->
+            place.getId().equals(existingPlace.getId())
+                && place.getName().equals("New Place")
+                && place.getCategory().equals(category)
+                && place.getOpeningHoursList().size() == 1));

1078-1093: Match the geocoding-empty test to the service behaviour

Once the service geocodes the address, this test needs to set and stub the address as well; otherwise it never reaches the empty-geocode branch you’re trying to cover.

         PlaceUpdateDto dto = PlaceUpdateDto.builder()
             .id(existingPlace.getId())
             .categoryId(category.getId())
-            .name("Bad Place")
+            .name("Bad Place")
+            .address("Bad Place")
             .build();
service/src/test/java/greencity/ModelUtils.java (1)

3478-3481: Ignore the typeId reference—CategoryDto’s fourth constructor argument is parentCategoryId, and the existing fixture value (1L) already matches that API.

Likely an incorrect or invalid review comment.

Comment on lines +1151 to +1162
public static AddPlaceDto getPlaceAddDto() {
AddPlaceDto addPlaceDto = new AddPlaceDto();
addPlaceDto.setName("Test");
CategoryDto category = new CategoryDto();
category.setNameEn("category");
placeAddDto.setCategory(category);
placeAddDto.setLocation(getLocationAddressAndGeoDto());
addPlaceDto.setCategoryId(1L);
HashSet<OpeningHoursDto> openingHoursDtos = new HashSet<>();
openingHoursDtos.add(getOpeningHoursDto());
placeAddDto.setOpeningHoursList(openingHoursDtos);
addPlaceDto.setOpeningHoursList(openingHoursDtos);
HashSet<DiscountValueDto> discountValueDtos = new HashSet<>();
discountValueDtos.add(getDiscountValueDto());
placeAddDto.setDiscountValues(discountValueDtos);
return placeAddDto;
return addPlaceDto;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing address assignment and stale discount handling in getPlaceAddDto().

The API now expects AddPlaceDto to carry a plain address string and no longer accepts discount payloads. This helper neither sets address nor attaches the opening hours / other fields that downstream validation still requires, and it keeps constructing discountValueDtos that are never written anywhere. Tests that rely on this factory will keep producing DTOs rejected by the updated service flow.

Please populate the new address field (e.g., "Kyiv" to mirror other fixtures), drop the obsolete DiscountValueDto set entirely, and remove the unused CategoryDto variable.

     public static AddPlaceDto getPlaceAddDto() {
         AddPlaceDto addPlaceDto = new AddPlaceDto();
         addPlaceDto.setName("Test");
-        CategoryDto category = new CategoryDto();
-        category.setNameEn("category");
         addPlaceDto.setCategoryId(1L);
+        addPlaceDto.setAddress("Kyiv");
         HashSet<OpeningHoursDto> openingHoursDtos = new HashSet<>();
         openingHoursDtos.add(getOpeningHoursDto());
         addPlaceDto.setOpeningHoursList(openingHoursDtos);
-        HashSet<DiscountValueDto> discountValueDtos = new HashSet<>();
-        discountValueDtos.add(getDiscountValueDto());
         return addPlaceDto;
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public static AddPlaceDto getPlaceAddDto() {
AddPlaceDto addPlaceDto = new AddPlaceDto();
addPlaceDto.setName("Test");
CategoryDto category = new CategoryDto();
category.setNameEn("category");
placeAddDto.setCategory(category);
placeAddDto.setLocation(getLocationAddressAndGeoDto());
addPlaceDto.setCategoryId(1L);
HashSet<OpeningHoursDto> openingHoursDtos = new HashSet<>();
openingHoursDtos.add(getOpeningHoursDto());
placeAddDto.setOpeningHoursList(openingHoursDtos);
addPlaceDto.setOpeningHoursList(openingHoursDtos);
HashSet<DiscountValueDto> discountValueDtos = new HashSet<>();
discountValueDtos.add(getDiscountValueDto());
placeAddDto.setDiscountValues(discountValueDtos);
return placeAddDto;
return addPlaceDto;
public static AddPlaceDto getPlaceAddDto() {
AddPlaceDto addPlaceDto = new AddPlaceDto();
addPlaceDto.setName("Test");
addPlaceDto.setCategoryId(1L);
addPlaceDto.setAddress("Kyiv");
HashSet<OpeningHoursDto> openingHoursDtos = new HashSet<>();
openingHoursDtos.add(getOpeningHoursDto());
addPlaceDto.setOpeningHoursList(openingHoursDtos);
return addPlaceDto;
}
🤖 Prompt for AI Agents
In service/src/test/java/greencity/ModelUtils.java around lines 1151 to 1162,
the test factory getPlaceAddDto() must be updated to match the API changes:
remove the unused CategoryDto local variable, stop constructing or attaching any
DiscountValueDto collection, and set the new plain address field on AddPlaceDto
(use "Kyiv" to match other fixtures); keep the openingHoursList assignment as-is
and ensure addPlaceDto is returned with the address populated and no discount
payloads.

@SaenkoVova SaenkoVova linked an issue Sep 25, 2025 that may be closed by this pull request
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
core/src/main/resources/templates/core/management_places.html (2)

469-471: Remove Google Maps loader or guard initMap; missing #map causes runtime error

There is no element with id="map" in this template, yet the Google Maps API loads with callback=initMap. This will throw when constructing the map and when trying to write to removed latitude/longitude inputs.

Apply this diff to remove the loader (preferred if map picking is no longer used):

-<script defer
-        src="https://maps.googleapis.com/maps/api/js?key=AIzaSyB3xs7Kczo46LFcQRFKPMdrE0lU4qsR_S4&callback=initMap">
-</script>

If map is still needed elsewhere, add a null‑guard inside initMap in JS and reintroduce the #map container and lat/lng inputs.


325-331: Fix address error span id and relax validation for i18n

  • Align the error span id with backend field name “address” to display server‑side validation.
  • Current pattern blocks non‑Latin characters and common address symbols; remove it to avoid rejecting valid addresses.

Apply this diff:

-                            <input class="form-control" id="address" name="address" type="text"
-                                   pattern="^[A-Za-z0-9\s,.-]+$"
-                                   title="Please enter a valid address"
-                                   required>
-                            <span class="errorSpan" th:id="errorModalSavelocation"></span>
+                            <input class="form-control" id="address" name="address" type="text" required>
+                            <span class="errorSpan" th:id="errorModalSaveaddress"></span>
core/src/main/resources/static/scripts/management/places/buttonsAJAX.js (3)

181-216: Surface server validation errors from 4xx responses (field-level mapping)

With endpoints returning 204 on success and 400 on validation failures, errors won’t appear in the UI. Parse xhr.responseText and map any errors[] to spans using getErrorSpanId.

Apply this diff:

             error: function (xhr, status, error) {
-                console.error('XHR Status: ' + xhr.status);
-                console.error('Error: ' + error);
-                console.error('Response Text: ' + xhr.responseText);
-
-                let errorMessage = `Error status: ${xhr.status} - ${error}`;
-                if (xhr.responseText) {
-                    try {
-                        const response = JSON.parse(xhr.responseText);
-                        errorMessage += `\nMessage: ${response.message || 'Unknown error'}`;
-                    } catch (e) {
-                        errorMessage += `\nMessage: ${xhr.responseText}`;
-                    }
-                }
-
-                alert(errorMessage);
+                // Try to display field errors first
+                try {
+                    const resp = xhr.responseText ? JSON.parse(xhr.responseText) : null;
+                    if (resp && Array.isArray(resp.errors) && resp.errors.length) {
+                        clearAllErrorsSpan();
+                        resp.errors.forEach(function (el) {
+                            $(document.getElementById(getErrorSpanId(el.fieldName))).text(el.fieldError).show();
+                        });
+                        return;
+                    }
+                } catch (e) { /* fall through to alert */ }
+                // Fallback alert
+                const base = `Error status: ${xhr.status} - ${error}`;
+                const msg = xhr.responseText ? `${base}\nMessage: ${xhr.responseText}` : base;
+                alert(msg);
             }

221-225: Align client-side validation spans with backend field names

Update error targets to match server fields “address” and “categoryId”.

Apply this diff:

-        const requiredFields = [
-            { id: 'address', errorId: 'errorModalSavelocation', message: messages["greenCity.places.page.add.address"] },
-            { id: 'placeName', errorId: 'errorModalSavename', message: messages["greenCity.places.page.add.place.name"] },
-            { id: 'category', errorId: 'errorModalSavecategory', message: messages["greenCity.places.page.add.category"] },
-        ];
+        const requiredFields = [
+            { id: 'address', errorId: 'errorModalSaveaddress', message: messages["greenCity.places.page.add.address"] },
+            { id: 'placeName', errorId: 'errorModalSavename', message: messages["greenCity.places.page.add.place.name"] },
+            { id: 'category', errorId: 'errorModalSavecategoryId', message: messages["greenCity.places.page.add.category"] },
+        ];

15-30: Guard initMap when map UI is absent; avoid null dereferences

Page has no #map or lat/lng inputs after the refactor. Add a null guard to prevent runtime errors.

Apply this diff:

 function initMap() {
-    const mapCenter = {lat: 49.842957, lng: 24.031111};
-    map = new google.maps.Map(document.getElementById("map"), {
+    const mapEl = document.getElementById("map");
+    if (!mapEl) { return; }
+    const mapCenter = {lat: 49.842957, lng: 24.031111};
+    map = new google.maps.Map(mapEl, {
         zoom: 12,
         center: mapCenter
     });
@@
-        addMarker(event.latLng);
-        document.getElementById('latitude').value = event.latLng.lat();
-        document.getElementById('longitude').value = event.latLng.lng();
+        addMarker(event.latLng);
+        const latEl = document.getElementById('latitude');
+        const lngEl = document.getElementById('longitude');
+        if (latEl && lngEl) {
+            latEl.value = event.latLng.lat();
+            lngEl.value = event.latLng.lng();
+        }
     });
🧹 Nitpick comments (10)
service-api/src/main/java/greencity/security/jwt/JwtTool.java (1)

127-133: Avoid redundant getCookies() invocation

We still call request.getCookies() twice—once to assign cookies and again inside the Optional. Reuse the array you already fetched to keep the code tidy and avoid extra servlet calls.

-            Cookie[] cookies = request.getCookies();
-
-            String accessToken = Optional.ofNullable(request.getCookies()).stream().flatMap(Arrays::stream)
+            Cookie[] cookies = request.getCookies();
+            String accessToken = Optional.ofNullable(cookies).stream().flatMap(Arrays::stream)
                 .filter(c -> c.getName().equals("accessToken"))
                 .findFirst()
                 .map(Cookie::getValue).orElse(null);
service-api/src/test/java/greencity/security/jwt/JwtToolTest.java (2)

164-180: Prefer using HttpHeaders.AUTHORIZATION for clarity.

Re-using the Spring constant keeps these tests consistent with the rest of the class and avoids string literals for header names.

-        when(webRequest.getHeader("Authorization")).thenReturn(null);
+        when(webRequest.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(null);

182-197: Consider covering the empty-cookie array case.

An additional test where getCookies() returns an empty array (instead of null) would harden the behaviour and guard against regressions if the servlet container supplies a non-null-but-empty array.

core/src/main/resources/templates/core/management_places.html (2)

403-405: Optional: Rename file input to ‘images’ for consistency

The JS appends files under “images”. Renaming the input’s name reduces duplicate/unexpected parts on the request.

Apply this diff:

-                            <input type="file" class="form-control" name="imagePath" id="creationFile" placeholder="ferf">
+                            <input type="file" class="form-control" name="images" id="creationFile" placeholder="ferf">

320-322: Remove dead lat/lng/addressUa remnants or restore end‑to‑end flow

These hidden fields were removed, but related JS (initMap) still attempts to set them, causing errors if map is present. Align the stack by removing map code or restoring the inputs.

core/src/main/resources/static/scripts/management/places/buttonsAJAX.js (3)

336-341: Fix category reset in modal

#empty-category doesn’t exist. Reset the select to its first option.

Apply this diff:

     function clearEditModal() {
         $('input[name=day]').prop('checked', false);
         $('#addPlaceModal').find('input').not('input[name=status]').not('#submitAddBtn').val('');
-        $('#empty-category').prop("selected", true);
+        $('#category').prop('selectedIndex', 0);
         deleteMarkers();
     }

391-405: Remove unused page param mutation

params.page isn’t defined; this block is dead code.

Apply this diff:

-    if (params.page !== null) {
-        params.page = "0";
-    }

509-517: Remove unused getSelectedStatus()

No radio named ‘status’ exists; function is unused.

Apply this diff:

-function getSelectedStatus() {
-    const radios = document.getElementsByName('status');
-    for (const radio of radios) {
-        if (radio.checked) {
-            return radio.value;
-        }
-    }
-    return '';
-}
core/src/main/java/greencity/controller/PlaceController.java (2)

65-75: Update Javadoc to reflect multipart-only update

Doc says multipart or JSON, but mapping consumes only multipart. Adjust to avoid misleading clients.

Apply this diff:

-     * <li><b>Content-Type:</b> {@code multipart/form-data} or
-     * {@code application/json}</li>
+     * <li><b>Content-Type:</b> {@code multipart/form-data}</li>

440-443: Remove no-op ResponseEntity call in delete()

Method returns void; the ResponseEntity line has no effect.

Apply this diff:

     public void delete(@PathVariable Long id) {
         placeService.deleteById(id);
-        ResponseEntity.status(HttpStatus.OK);
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between be6bb0c and 9b3549c.

📒 Files selected for processing (5)
  • core/src/main/java/greencity/controller/PlaceController.java (4 hunks)
  • core/src/main/resources/static/scripts/management/places/buttonsAJAX.js (4 hunks)
  • core/src/main/resources/templates/core/management_places.html (2 hunks)
  • service-api/src/main/java/greencity/security/jwt/JwtTool.java (2 hunks)
  • service-api/src/test/java/greencity/security/jwt/JwtToolTest.java (2 hunks)
🧰 Additional context used
🪛 GitHub Actions: CI/CD GreenCity
service-api/src/test/java/greencity/security/jwt/JwtToolTest.java

[error] 1-1: File has not been previously formatted. Please run 'mvn -f service-api net.revelc.code.formatter:formatter-maven-plugin:2.23.0:format' before validating.

🔇 Additional comments (5)
service-api/src/test/java/greencity/security/jwt/JwtToolTest.java (1)

145-162: Cookie fallback coverage looks solid.

Nice job validating that the extractor correctly switches to the cookie path when the header is absent—the assertions mirror the production logic well.

core/src/main/resources/templates/core/management_places.html (2)

338-340: Confirm status handling; consider removing editable field

If status is set server‑side for new places, this readonly input may be redundant and confusing. Consider dropping it from the form or making it a hidden field.


342-349: Add category error span and map to server field “categoryId”

Without an error span here, neither client nor server validation can surface errors to the user. Backend reports field “categoryId”.

Apply this diff:

                             <select class="form-control" id="category" name="category">
                                 <option
                                     th:each="category : ${categoryList}"
                                     th:text="${category.getNameEn()}"
                                     th:value="${category.getId()}">
                                 </option>
                             </select>
+                            <span class="errorSpan" th:id="errorModalSavecategoryId"></span>
core/src/main/resources/static/scripts/management/places/buttonsAJAX.js (1)

137-146: Verify URL for create/update matches backend changes

POST/PUT are sent to “/management/places”, while backend save is at “/place/v2/save” and update at “/place/update”. Ensure the management endpoint forwards to the new API and accepts the same part names: addPlaceDto/placeUpdateDto and images.

core/src/main/java/greencity/controller/PlaceController.java (1)

107-113: Update endpoint looks correct (multipart + @Valid + 204)

Consumes multipart, validates DTO, injects userId, and delegates with optional images. LGTM.

@sonarqubecloud
Copy link

@BohdanLys BohdanLys self-requested a review October 1, 2025 13:43
Copy link
Contributor

@BohdanLys BohdanLys left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix SonarQube issues to maintain the quality of code

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (6)
service/src/test/java/greencity/service/PlaceServiceImplTest.java (3)

1003-1011: Broken test: stubs obsolete API (findByName) but update now uses categoryId lookup.

This test won't fail for a missing category with the current flow. The service implementation calls categoryRepo.findById(1L), not categoryService.findByName(...). Update the stub to mock the repository lookup by ID.

     void updateFromUIThrowsNotFoundExceptionForCategoryTest() {
         PlaceUpdateDto dto = new PlaceUpdateDto();
         dto.setId(1L);
         dto.setCategoryId(1L);
 
+        when(userRepo.findById(TestConst.USER_ID)).thenReturn(Optional.of(user));
+        when(placeRepo.findById(1L)).thenReturn(Optional.of(ModelUtils.getPlace()));
-        when(categoryService.findByName("Nonexistent Category"))
-            .thenThrow(new NotFoundException("Category not found"));
+        when(categoryRepo.findById(1L)).thenReturn(Optional.empty());
+        
         assertThrows(NotFoundException.class, () -> placeServiceImpl.update(dto, null, TestConst.USER_ID));
     }

1038-1040: Fix self-comparison in verification assertion.

The verify lambda compares the same object to itself (place.getId().equals(place.getId())), which always passes regardless of whether the update worked. Compare against expected values instead.

         placeService.update(dto, images, 1L);
 
-        verify(placeRepo).save(argThat(place -> place.getId().equals(place.getId()) &&
-            place.getName().equals(place.getName()) &&
+        verify(placeRepo).save(argThat(place -> 
+            place.getId().equals(existingPlace.getId()) &&
+            place.getName().equals("New Place") &&
+            place.getCategory().equals(category) &&
             place.getOpeningHoursList().size() == 1));

1086-1090: Missing address field prevents geocoding mock from matching.

The test mocks getResultFromGeoCode("Bad Place") at line 1084, but the DTO doesn't set an address field. If the service calls googleApiService.getResultFromGeoCode(dto.getAddress()), the mock won't match (address will be null). Add address("Bad Place") to the DTO builder.

         PlaceUpdateDto dto = PlaceUpdateDto.builder()
             .id(existingPlace.getId())
             .categoryId(category.getId())
             .name("Bad Place")
+            .address("Bad Place")
             .build();
core/src/test/java/greencity/webcontroller/ManagementPlacesControllerTest.java (2)

103-103: Use numeric type for categoryId in JSON payload.

The test uses "categoryId": "1" (string) instead of "categoryId": 1 (number). While JSON parsing may coerce this, using the correct numeric type makes the test more strict and future-proof.

                 "name": "Тестове місце",
                 "address": "Смиків, південна 7",
-                "categoryId": "1",
+                "categoryId": 1,
                 "openingHoursList": [

136-139: Normalize id and categoryId to numeric types in update payload.

Both "id" and "categoryId" are specified as strings when they should be numeric values to match the DTO types strictly.

             {
-                "id": "1",
+                "id": 1,
                 "name": "Тестове місце",
                 "address": "Смиків, південна 7",
-                "categoryId": "1",
+                "categoryId": 1,
                 "openingHoursList": [
service/src/test/java/greencity/ModelUtils.java (1)

1144-1156: Complete the getPlaceAddDto refactoring: remove unused code and add required address field.

This helper has three issues that make it produce incomplete test data:

  1. Unused CategoryDto variable (lines 1147-1148): Created but never used—remove it.
  2. Missing address field: The new API requires address, but it's not set. Tests using this helper may fail.
  3. Orphaned DiscountValueDto set (lines 1153-1154): Created but never assigned to the DTO, suggesting incomplete refactoring from the discount removal.
     public static AddPlaceDto getPlaceAddDto() {
         AddPlaceDto addPlaceDto = new AddPlaceDto();
         addPlaceDto.setName("Test");
-        CategoryDto category = new CategoryDto();
-        category.setNameEn("category");
         addPlaceDto.setCategoryId(1L);
+        addPlaceDto.setAddress("Kyiv");
         HashSet<OpeningHoursDto> openingHoursDtos = new HashSet<>();
         openingHoursDtos.add(getOpeningHoursDto());
         addPlaceDto.setOpeningHoursList(openingHoursDtos);
-        HashSet<DiscountValueDto> discountValueDtos = new HashSet<>();
-        discountValueDtos.add(getDiscountValueDto());
         return addPlaceDto;
     }
🧹 Nitpick comments (5)
service-api/src/main/java/greencity/security/jwt/JwtTool.java (2)

17-17: Consider using explicit imports instead of wildcard.

The wildcard import java.util.* reduces code clarity and can lead to naming conflicts. Prefer explicit imports like java.util.Arrays, java.util.Calendar, java.util.Collections, etc.

Apply this change to use explicit imports:

-import java.util.*;
+import java.util.Arrays;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;

132-137: Eliminate unused variable and redundant method call.

Line 132 declares cookies but never uses it, and line 134 redundantly calls request.getCookies() again. This reduces code clarity and makes an unnecessary method call.

Apply this diff to use the declared variable:

 Cookie[] cookies = request.getCookies();
 
-String accessToken = Optional.ofNullable(request.getCookies()).stream().flatMap(Arrays::stream)
+String accessToken = Optional.ofNullable(cookies).stream().flatMap(Arrays::stream)
     .filter(c -> c.getName().equals("accessToken"))
     .findFirst()
     .map(Cookie::getValue).orElse(null);
service/src/test/java/greencity/service/PlaceServiceImplTest.java (1)

234-240: Remove debug println from test verification.

The System.out.println(place.getLocation()) at line 235 pollutes test output and serves no verification purpose. Remove it to keep the test clean.

         verify(placeRepo).save(argThat(place -> {
-            System.out.println(place.getLocation());
             return place.getName().equals("My Cafe") &&
                 place.getAuthor().equals(user) &&
                 place.getCategory().equals(category) &&
                 place.getOpeningHoursList().size() == 1;
         }));
core/src/test/java/greencity/webcontroller/ManagementPlacesControllerTest.java (1)

151-156: Inconsistent variable naming: consider renaming for clarity.

The variable is named addPlaceDto but it's used for an update operation and the multipart parameter name is "placeUpdateDto". Consider renaming the variable to placeUpdateDto or updateDto for consistency.

-        MockMultipartFile addPlaceDto = new MockMultipartFile(
+        MockMultipartFile placeUpdateDto = new MockMultipartFile(
             "placeUpdateDto",
             "",
             "application/json",
             (json)
                 .getBytes());
 
         this.mockMvc.perform(multipart(HttpMethod.PUT, "/management/places/")
-            .file(addPlaceDto)
+            .file(placeUpdateDto)
service/src/test/java/greencity/ModelUtils.java (1)

2092-2094: Consider removing commented-out code.

The commented lines for placeName and locationName appear to be remnants from the refactoring. If they're no longer needed, remove them to keep the codebase clean.

     public static AddPlaceDto getAddPlaceDto() {
         return AddPlaceDto.builder()
             .categoryId(1L)
-//            .placeName("test")
-//            .locationName("Test")
             .openingHoursList(Set.of(OpeningHoursDto.builder()
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8124dc9 and 12566e0.

📒 Files selected for processing (7)
  • core/src/main/java/greencity/webcontroller/ManagementPlacesController.java (3 hunks)
  • core/src/test/java/greencity/webcontroller/ManagementPlacesControllerTest.java (4 hunks)
  • service-api/src/main/java/greencity/security/jwt/JwtTool.java (2 hunks)
  • service-api/src/test/java/greencity/security/jwt/JwtToolTest.java (2 hunks)
  • service/src/test/java/greencity/ModelUtils.java (3 hunks)
  • service/src/test/java/greencity/service/NotificationServiceImplTest.java (3 hunks)
  • service/src/test/java/greencity/service/PlaceServiceImplTest.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/src/main/java/greencity/webcontroller/ManagementPlacesController.java
  • service/src/test/java/greencity/service/NotificationServiceImplTest.java
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2025-01-31T08:55:30.651Z
Learnt from: urio999
Repo: ita-social-projects/GreenCity PR: 8082
File: service/src/main/java/greencity/mapping/events/EventResponseDtoMapper.java:0-0
Timestamp: 2025-01-31T08:55:30.651Z
Learning: In the EventResponseDtoMapper, null checks for address should use Optional or null-safe operators instead of assertions to ensure runtime safety, as assertions can be disabled with the -da flag.

Applied to files:

  • service/src/test/java/greencity/service/PlaceServiceImplTest.java
  • service/src/test/java/greencity/ModelUtils.java
🧬 Code graph analysis (1)
service/src/test/java/greencity/service/PlaceServiceImplTest.java (2)
service/src/test/java/greencity/ModelUtils.java (1)
  • ModelUtils (298-3682)
service/src/test/java/greencity/TestConst.java (1)
  • TestConst (3-15)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: build
🔇 Additional comments (3)
service-api/src/test/java/greencity/security/jwt/JwtToolTest.java (1)

150-202: Excellent test coverage for cookie-based JWT extraction.

The three new test methods comprehensively cover the cookie extraction scenarios:

  • Successful extraction when the accessToken cookie is present
  • Exception handling when cookies exist but don't contain the token
  • Exception handling when the cookies array is null

The tests follow the AAA pattern, use appropriate mocking, and verify both behavior and error messages.

service/src/test/java/greencity/service/PlaceServiceImplTest.java (2)

244-287: Good test coverage for save failure scenarios.

The new tests effectively cover edge cases for the save flow: user not found, category not found, empty geocoding results, and duplicate place detection. This provides solid protection against regressions.


1044-1076: Solid coverage of update failure paths.

These tests ensure that update operations properly validate user existence, place existence, and category existence before proceeding. Good defensive testing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Eco places (management page)

8 participants