Skip to content

Commit 3097128

Browse files
committed
Implemented endpoint to get supplies by user
1 parent 7e9e75a commit 3097128

File tree

9 files changed

+436
-0
lines changed

9 files changed

+436
-0
lines changed

CLAUDE.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,3 +118,5 @@ With the app running:
118118
- Code should be self-explanatory with comments when additional explanation is needed
119119
- All new code must have automated tests
120120
- Architecture tests are enforced via ArchUnit (see `src/test/java/org/lucoenergia/conluz/architecture/`)
121+
- when injecting beans, always use the interface. This also applies to integration tests
122+
- when creating tests over services that has an interface, always use the name of the interface + "Test" for naming them

src/main/java/org/lucoenergia/conluz/domain/admin/supply/get/GetSupplyRepository.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import org.lucoenergia.conluz.domain.admin.supply.Supply;
44
import org.lucoenergia.conluz.domain.shared.SupplyCode;
55
import org.lucoenergia.conluz.domain.shared.SupplyId;
6+
import org.lucoenergia.conluz.domain.shared.UserId;
67
import org.lucoenergia.conluz.domain.shared.pagination.PagedRequest;
78
import org.lucoenergia.conluz.domain.shared.pagination.PagedResult;
89

@@ -20,4 +21,6 @@ public interface GetSupplyRepository {
2021

2122
PagedResult<Supply> findAll(PagedRequest pagedRequest);
2223
List<Supply> findAll();
24+
25+
List<Supply> findByUserId(UserId userId);
2326
}

src/main/java/org/lucoenergia/conluz/domain/admin/supply/get/GetSupplyService.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,25 @@
22

33
import org.lucoenergia.conluz.domain.admin.supply.Supply;
44
import org.lucoenergia.conluz.domain.shared.SupplyId;
5+
import org.lucoenergia.conluz.domain.shared.UserId;
56
import org.lucoenergia.conluz.domain.shared.pagination.PagedRequest;
67
import org.lucoenergia.conluz.domain.shared.pagination.PagedResult;
78

9+
import java.util.List;
10+
811
public interface GetSupplyService {
912

1013
PagedResult<Supply> findAll(PagedRequest pagedRequest);
1114

1215
Supply getById(SupplyId id);
16+
17+
/**
18+
* Retrieves a list of supplies associated with a specified user.
19+
*
20+
* @param userId the identifier of the user whose supplies are to be retrieved
21+
* @param requestingUserId the identifier of the user making the request
22+
* @param isAdmin a flag indicating if the requesting user has administrative privileges
23+
* @return a list of supplies associated with the specified user
24+
*/
25+
List<Supply> getByUserId(UserId userId, UserId requestingUserId, boolean isAdmin);
1326
}

src/main/java/org/lucoenergia/conluz/infrastructure/admin/supply/SupplyRepository.java

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

33
import org.springframework.data.jpa.repository.JpaRepository;
44

5+
import java.util.List;
56
import java.util.Optional;
67
import java.util.UUID;
78

@@ -10,4 +11,6 @@ public interface SupplyRepository extends JpaRepository<SupplyEntity, UUID> {
1011
Optional<SupplyEntity> findByCode(String code);
1112

1213
int countByCode(String code);
14+
15+
List<SupplyEntity> findByUserId(UUID userId);
1316
}

src/main/java/org/lucoenergia/conluz/infrastructure/admin/supply/get/GetSupplyRepositoryDatabase.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import org.lucoenergia.conluz.domain.admin.supply.Supply;
55
import org.lucoenergia.conluz.domain.shared.SupplyCode;
66
import org.lucoenergia.conluz.domain.shared.SupplyId;
7+
import org.lucoenergia.conluz.domain.shared.UserId;
78
import org.lucoenergia.conluz.domain.shared.pagination.PagedRequest;
89
import org.lucoenergia.conluz.domain.shared.pagination.PagedResult;
910
import org.lucoenergia.conluz.infrastructure.admin.supply.SupplyEntity;
@@ -79,4 +80,10 @@ public List<Supply> findAll() {
7980

8081
return allSupplies.getItems();
8182
}
83+
84+
@Override
85+
public List<Supply> findByUserId(UserId userId) {
86+
List<SupplyEntity> supplyEntities = supplyRepository.findByUserId(userId.getId());
87+
return supplyEntityMapper.mapList(supplyEntities);
88+
}
8289
}

src/main/java/org/lucoenergia/conluz/infrastructure/admin/supply/get/GetSupplyServiceImpl.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@
88
import org.lucoenergia.conluz.domain.admin.user.User;
99
import org.lucoenergia.conluz.domain.admin.user.auth.AuthService;
1010
import org.lucoenergia.conluz.domain.shared.SupplyId;
11+
import org.lucoenergia.conluz.domain.shared.UserId;
1112
import org.lucoenergia.conluz.domain.shared.pagination.PagedRequest;
1213
import org.lucoenergia.conluz.domain.shared.pagination.PagedResult;
1314
import org.springframework.security.access.AccessDeniedException;
1415
import org.springframework.stereotype.Service;
1516
import org.springframework.transaction.annotation.Transactional;
1617

18+
import java.util.List;
1719
import java.util.Optional;
1820

1921
@Transactional(readOnly = true)
@@ -52,4 +54,19 @@ public Supply getById(SupplyId id) {
5254

5355
throw new AccessDeniedException("You do not have permission to access this supply");
5456
}
57+
58+
@Override
59+
public List<Supply> getByUserId(UserId userId, UserId requestingUserId, boolean isAdmin) {
60+
// If user is ADMIN, return all supplies for the requested user
61+
if (isAdmin) {
62+
return repository.findByUserId(userId);
63+
}
64+
65+
// Otherwise, only if the requesting user is the same as the requested user
66+
if (userId.getId().equals(requestingUserId.getId())) {
67+
return repository.findByUserId(userId);
68+
}
69+
70+
throw new AccessDeniedException("You do not have permission to access supplies for this user");
71+
}
5572
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package org.lucoenergia.conluz.infrastructure.admin.user.get;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
5+
import io.swagger.v3.oas.annotations.responses.ApiResponses;
6+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
7+
import org.lucoenergia.conluz.domain.admin.supply.Supply;
8+
import org.lucoenergia.conluz.domain.admin.supply.get.GetSupplyService;
9+
import org.lucoenergia.conluz.domain.admin.user.Role;
10+
import org.lucoenergia.conluz.domain.admin.user.User;
11+
import org.lucoenergia.conluz.domain.admin.user.auth.AuthService;
12+
import org.lucoenergia.conluz.domain.shared.UserId;
13+
import org.lucoenergia.conluz.infrastructure.admin.supply.SupplyResponse;
14+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.ApiTag;
15+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.BadRequestErrorResponse;
16+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.ForbiddenErrorResponse;
17+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.InternalServerErrorResponse;
18+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.NotFoundErrorResponse;
19+
import org.lucoenergia.conluz.infrastructure.shared.web.apidocs.response.UnauthorizedErrorResponse;
20+
import org.springframework.security.access.prepost.PreAuthorize;
21+
import org.springframework.web.bind.annotation.GetMapping;
22+
import org.springframework.web.bind.annotation.PathVariable;
23+
import org.springframework.web.bind.annotation.RequestMapping;
24+
import org.springframework.web.bind.annotation.RestController;
25+
26+
import java.util.List;
27+
import java.util.UUID;
28+
29+
/**
30+
* Get supplies for a specific user
31+
*/
32+
@RestController
33+
@RequestMapping(value = "/api/v1/users")
34+
public class GetSuppliesByUserIdController {
35+
36+
private final GetSupplyService supplyService;
37+
private final AuthService authService;
38+
39+
public GetSuppliesByUserIdController(GetSupplyService supplyService, AuthService authService) {
40+
this.supplyService = supplyService;
41+
this.authService = authService;
42+
}
43+
44+
@GetMapping("/{id}/supplies")
45+
@Operation(
46+
summary = "Retrieves all supplies for a specific user",
47+
description = """
48+
This endpoint retrieves all supplies associated with a specific user by their unique identifier.
49+
50+
**Authorization Rules:**
51+
- Users with role ADMIN can retrieve supplies for any user
52+
- Users with role PARTNER can only retrieve their own supplies
53+
54+
Authentication is required using a Bearer token.
55+
""",
56+
tags = ApiTag.USERS,
57+
operationId = "getSuppliesByUserId",
58+
security = @SecurityRequirement(name = "bearerToken")
59+
)
60+
@ApiResponses(value = {
61+
@ApiResponse(
62+
responseCode = "200",
63+
description = "Supplies retrieved successfully",
64+
useReturnTypeSchema = true
65+
)
66+
})
67+
@ForbiddenErrorResponse
68+
@UnauthorizedErrorResponse
69+
@BadRequestErrorResponse
70+
@NotFoundErrorResponse
71+
@InternalServerErrorResponse
72+
@PreAuthorize("isAuthenticated()")
73+
public List<SupplyResponse> getSuppliesByUserId(@PathVariable("id") UUID userId) {
74+
User currentUser = authService.getCurrentUser()
75+
.orElseThrow(() -> new IllegalStateException("User must be authenticated"));
76+
77+
UserId targetUserId = UserId.of(userId);
78+
UserId requestingUserId = UserId.of(currentUser.getId());
79+
boolean isAdmin = currentUser.getRole() == Role.ADMIN;
80+
81+
List<Supply> supplies = supplyService.getByUserId(targetUserId, requestingUserId, isAdmin);
82+
83+
return supplies.stream()
84+
.map(SupplyResponse::new)
85+
.toList();
86+
}
87+
}
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package org.lucoenergia.conluz.infrastructure.admin.supply.get;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.lucoenergia.conluz.domain.admin.supply.Supply;
5+
import org.lucoenergia.conluz.domain.admin.supply.get.GetSupplyRepository;
6+
import org.lucoenergia.conluz.domain.admin.supply.get.GetSupplyService;
7+
import org.lucoenergia.conluz.domain.admin.user.User;
8+
import org.lucoenergia.conluz.domain.admin.user.UserMother;
9+
import org.lucoenergia.conluz.domain.shared.UserId;
10+
import org.mockito.Mockito;
11+
import org.springframework.security.access.AccessDeniedException;
12+
13+
import java.util.Arrays;
14+
import java.util.List;
15+
import java.util.UUID;
16+
17+
import static org.junit.jupiter.api.Assertions.*;
18+
import static org.mockito.Mockito.*;
19+
20+
class GetSupplyServiceTest {
21+
22+
private final GetSupplyRepository repository = Mockito.mock(GetSupplyRepository.class);
23+
private final GetSupplyService service = new GetSupplyServiceImpl(repository, null);
24+
25+
@Test
26+
void getByUserIdWithAuthorization_shouldReturnSuppliesWhenUserIsAdmin() {
27+
// Given
28+
UUID userId = UUID.randomUUID();
29+
UUID requestingUserId = UUID.randomUUID();
30+
UserId userIdValue = UserId.of(userId);
31+
UserId requestingUserIdValue = UserId.of(requestingUserId);
32+
33+
User user = UserMother.randomUserWithId(userId);
34+
Supply supply1 = new Supply.Builder()
35+
.withId(UUID.randomUUID())
36+
.withCode("ES001")
37+
.withUser(user)
38+
.withName("Supply 1")
39+
.withAddress("Address 1")
40+
.withPartitionCoefficient(1.0f)
41+
.withEnabled(true)
42+
.build();
43+
Supply supply2 = new Supply.Builder()
44+
.withId(UUID.randomUUID())
45+
.withCode("ES002")
46+
.withUser(user)
47+
.withName("Supply 2")
48+
.withAddress("Address 2")
49+
.withPartitionCoefficient(1.0f)
50+
.withEnabled(true)
51+
.build();
52+
53+
List<Supply> expectedSupplies = Arrays.asList(supply1, supply2);
54+
when(repository.findByUserId(userIdValue)).thenReturn(expectedSupplies);
55+
56+
// When
57+
List<Supply> result = service.getByUserId(userIdValue, requestingUserIdValue, true);
58+
59+
// Then
60+
assertNotNull(result);
61+
assertEquals(2, result.size());
62+
assertEquals(expectedSupplies, result);
63+
verify(repository).findByUserId(userIdValue);
64+
}
65+
66+
@Test
67+
void getByUserIdWithAuthorization_shouldReturnSuppliesWhenUserRequestsOwnSupplies() {
68+
// Given
69+
UUID userId = UUID.randomUUID();
70+
UserId userIdValue = UserId.of(userId);
71+
72+
User user = UserMother.randomUserWithId(userId);
73+
Supply supply = new Supply.Builder()
74+
.withId(UUID.randomUUID())
75+
.withCode("ES001")
76+
.withUser(user)
77+
.withName("Supply 1")
78+
.withAddress("Address 1")
79+
.withPartitionCoefficient(1.0f)
80+
.withEnabled(true)
81+
.build();
82+
83+
List<Supply> expectedSupplies = Arrays.asList(supply);
84+
when(repository.findByUserId(userIdValue)).thenReturn(expectedSupplies);
85+
86+
// When
87+
List<Supply> result = service.getByUserId(userIdValue, userIdValue, false);
88+
89+
// Then
90+
assertNotNull(result);
91+
assertEquals(1, result.size());
92+
assertEquals(expectedSupplies, result);
93+
verify(repository).findByUserId(userIdValue);
94+
}
95+
96+
@Test
97+
void getByUserIdWithAuthorization_shouldThrowAccessDeniedWhenNonAdminRequestsOtherUserSupplies() {
98+
// Given
99+
UUID userId = UUID.randomUUID();
100+
UUID requestingUserId = UUID.randomUUID();
101+
UserId userIdValue = UserId.of(userId);
102+
UserId requestingUserIdValue = UserId.of(requestingUserId);
103+
104+
// When & Then
105+
AccessDeniedException exception = assertThrows(AccessDeniedException.class, () -> {
106+
service.getByUserId(userIdValue, requestingUserIdValue, false);
107+
});
108+
109+
assertEquals("You do not have permission to access supplies for this user", exception.getMessage());
110+
verify(repository, never()).findByUserId(any());
111+
}
112+
}

0 commit comments

Comments
 (0)