Skip to content

Commit b8ffdb9

Browse files
committed
refactor: architecture
remove the double relation between subscription and user
1 parent 631033d commit b8ffdb9

File tree

11 files changed

+68
-55
lines changed

11 files changed

+68
-55
lines changed
Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,17 @@
11
package com.thisaster.testtask.subscription.entity;
22

3-
import com.thisaster.testtask.user.entity.User;
43
import jakarta.persistence.Column;
54
import jakarta.persistence.Entity;
65
import jakarta.persistence.GeneratedValue;
76
import jakarta.persistence.GenerationType;
87
import jakarta.persistence.Id;
9-
import jakarta.persistence.ManyToMany;
108
import jakarta.persistence.Table;
119
import lombok.AllArgsConstructor;
1210
import lombok.Builder;
1311
import lombok.Getter;
1412
import lombok.NoArgsConstructor;
1513
import lombok.Setter;
1614

17-
import java.util.HashSet;
18-
import java.util.Set;
19-
2015
@Builder
2116
@Entity
2217
@Table(name = "subscriptions")
@@ -27,10 +22,7 @@
2722
public class Subscription {
2823
@Id
2924
@GeneratedValue(strategy = GenerationType.IDENTITY)
30-
private long id;
25+
private Long id;
3126
@Column(unique = true, nullable = false)
3227
private String name;
33-
@ManyToMany(mappedBy = "subscriptions")
34-
@Builder.Default
35-
private Set<User> users = new HashSet<>();
3628
}

src/main/java/com/thisaster/testtask/subscription/mapper/SubscriptionMapper.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
import com.thisaster.testtask.subscription.dto.SubscriptionDTO;
44
import com.thisaster.testtask.subscription.entity.Subscription;
55
import org.mapstruct.Mapper;
6-
import org.mapstruct.Mapping;
76
import org.mapstruct.MappingConstants;
87

98
import java.util.Set;
@@ -13,7 +12,6 @@ public abstract class SubscriptionMapper {
1312

1413
public abstract SubscriptionDTO toDTO(Subscription sub);
1514

16-
@Mapping(target = "users", ignore = true)
1715
public abstract Subscription toEntity(SubscriptionDTO subDTO);
1816

1917
public abstract Set<SubscriptionDTO> toDTOSet(Set<Subscription> subscriptions);

src/main/java/com/thisaster/testtask/subscription/repository/SubscriptionRepository.java

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.thisaster.testtask.subscription.repository;
22

33
import com.thisaster.testtask.subscription.entity.Subscription;
4-
import com.thisaster.testtask.user.entity.User;
54
import org.springframework.data.jpa.repository.JpaRepository;
65
import org.springframework.data.jpa.repository.Query;
76
import org.springframework.stereotype.Repository;
@@ -11,19 +10,15 @@
1110

1211
@Repository
1312
public interface SubscriptionRepository extends JpaRepository<Subscription, Long> {
14-
@Query(value = "SELECT s.id, s.name, COUNT(u.id) as user_count " +
13+
@Query(value = "SELECT s.id as id, s.name as name, COUNT(u.id) as userCount " +
1514
"FROM subscriptions s " +
1615
"LEFT JOIN user_subscription us ON s.id = us.subscription_id " +
1716
"LEFT JOIN user_ u ON us.user_id = u.id " +
18-
"GROUP BY s.id " +
19-
"ORDER BY user_count DESC " +
17+
"GROUP BY s.id, s.name " +
18+
"ORDER BY userCount DESC " +
2019
"LIMIT 3", nativeQuery = true)
2120
Set<Subscription> findTop3ByNameOrderByUsersCountDesc();
2221

23-
24-
@Query("SELECT s FROM Subscription s JOIN s.users u WHERE u = :user")
25-
Set<Subscription> findSubscriptionsByUser(User user);
26-
2722
@Query("SELECT s FROM Subscription s WHERE s.name = :name")
2823
Optional<Subscription> findByName(String name);
2924

src/main/java/com/thisaster/testtask/user/controller/UserController.java

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,11 @@
66
import com.thisaster.testtask.user.dto.UserDTO;
77
import com.thisaster.testtask.user.entity.User;
88
import com.thisaster.testtask.user.mapper.UserMapper;
9-
import com.thisaster.testtask.user.service.RoleService;
109
import com.thisaster.testtask.user.service.UserService;
1110
import lombok.RequiredArgsConstructor;
1211
import org.springframework.http.ResponseEntity;
1312
import org.springframework.validation.annotation.Validated;
14-
import org.springframework.web.bind.annotation.DeleteMapping;
15-
import org.springframework.web.bind.annotation.GetMapping;
16-
import org.springframework.web.bind.annotation.PathVariable;
17-
import org.springframework.web.bind.annotation.PostMapping;
18-
import org.springframework.web.bind.annotation.PutMapping;
19-
import org.springframework.web.bind.annotation.RequestBody;
20-
import org.springframework.web.bind.annotation.RequestMapping;
21-
import org.springframework.web.bind.annotation.RestController;
13+
import org.springframework.web.bind.annotation.*;
2214

2315
import java.util.Set;
2416

@@ -29,7 +21,6 @@ public class UserController {
2921
private final UserService userService;
3022
private final UserMapper userMapper;
3123
private final SubscriptionMapper subscriptionMapper;
32-
private final RoleService roleService;
3324

3425
@GetMapping("/{id}")
3526
public ResponseEntity<UserDTO> getUserInfo(@PathVariable Long id) {
@@ -47,8 +38,6 @@ public ResponseEntity<String> deleteUser(@PathVariable Long id) {
4738
@PutMapping("/{id}")
4839
public ResponseEntity<UserDTO> updateUser(@PathVariable Long id, @RequestBody @Validated UserDTO userDTO) {
4940
User user = userMapper.toEntity(userDTO);
50-
user.setRole(roleService.getByRoleName(userDTO.getRole()));
51-
5241
User updatedUser = userService.updateUser(id, user);
5342
UserDTO updatedDTO = userMapper.toDTO(updatedUser);
5443
return ResponseEntity.ok(updatedDTO);

src/main/java/com/thisaster/testtask/user/dto/UserDTO.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ public class UserDTO {
3434
@JsonProperty(access = JsonProperty.Access.READ_WRITE)
3535
private Set<SubscriptionDTO> subscriptions;
3636

37-
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
37+
@JsonProperty(access = JsonProperty.Access.READ_WRITE)
38+
39+
@NotBlank(message = "Role cannot be blank")
3840
@Pattern(regexp = "^(ADMIN|USER|SUPERVISOR)$", message = "Role must be either admin or user")
3941
private String role;
4042
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.thisaster.testtask.user.mapper;
2+
3+
import com.thisaster.testtask.user.entity.RoleEntity;
4+
import com.thisaster.testtask.user.service.RoleService;
5+
import org.mapstruct.Mapper;
6+
import org.mapstruct.MappingConstants;
7+
import org.mapstruct.Named;
8+
import org.springframework.beans.factory.annotation.Autowired;
9+
10+
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING)
11+
public abstract class RoleMapper {
12+
13+
@Autowired
14+
private RoleService roleService;
15+
16+
@Named("stringToEntity")
17+
public RoleEntity fromString(String roleName) {
18+
if (roleName == null) {
19+
return null;
20+
}
21+
return roleService.getByRoleName(roleName);
22+
}
23+
@Named("entityToString")
24+
public String toString(RoleEntity entity) {
25+
if (entity == null) {
26+
return null;
27+
}
28+
return entity.getName();
29+
}
30+
}

src/main/java/com/thisaster/testtask/user/mapper/UserMapper.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@
88
import org.mapstruct.MappingConstants;
99

1010
@Mapper(componentModel = MappingConstants.ComponentModel.SPRING,
11-
uses = {SubscriptionMapper.class})
12-
public abstract class UserMapper {
11+
uses = {SubscriptionMapper.class, RoleMapper.class})
12+
public interface UserMapper {
1313

14-
@Mapping(target = "role", source = "role.name")
15-
public abstract UserDTO toDTO(User user);
14+
@Mapping(target = "role", qualifiedByName = "entityToString", source = "role")
15+
UserDTO toDTO(User user);
1616

17-
@Mapping(target = "role", ignore = true) // всё равно подставим вручную в контроллере
18-
@Mapping(target = "roleId", ignore = true) // подставим вручную
19-
public abstract User toEntity(UserDTO userDTO);
17+
@Mapping(target = "role", qualifiedByName = "stringToEntity", source = "role")
18+
@Mapping(target = "roleId", expression = "java(roleMapper.fromString(userDTO.getRole()).getId())")
19+
User toEntity(UserDTO userDTO);
2020
}

src/main/java/com/thisaster/testtask/user/repository/UserRepository.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,6 @@ public interface UserRepository extends JpaRepository<User, Long> {
1212

1313
@Query("SELECT u FROM User u WHERE u.username = ?1")
1414
Optional<User> findByUsername(String username);
15+
16+
int countBySubscriptions_Id(Long subscriptionId);
1517
}

src/main/java/com/thisaster/testtask/user/service/UserService.java

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -68,34 +68,34 @@ public void subscribeUserToSub(Long userId, Subscription subscription) {
6868
}
6969

7070
boolean addedToUser = user.getSubscriptions().add(existingSubscription);
71-
boolean addedToSub = existingSubscription.getUsers().add(user);
7271

73-
if (addedToUser || addedToSub) {
72+
if (addedToUser) {
7473
userRepository.save(user);
75-
subscriptionRepository.save(existingSubscription);
7674
}
7775
}
7876

7977
@Transactional
8078
public void removeSubFromUser(Long userId, Long subscriptionId) {
81-
User user = userRepository.findById(userId).orElseThrow(()
82-
-> new EntityNotFoundException("User not found with id: " + userId));
79+
User user = userRepository.findById(userId)
80+
.orElseThrow(() -> new EntityNotFoundException("User not found with id: " + userId));
8381

8482
Subscription subscription = subscriptionRepository.findById(subscriptionId)
8583
.orElseThrow(() -> new EntityNotFoundException("Subscription not found with id: " + subscriptionId));
8684

87-
subscription.getUsers().remove(user);
88-
if (subscription.getUsers().isEmpty()) {
85+
boolean removed = user.getSubscriptions().remove(subscription);
86+
if (removed) userRepository.save(user);
87+
88+
int count = userRepository.countBySubscriptions_Id(subscriptionId);
89+
90+
if (count == 0) {
8991
subscriptionRepository.delete(subscription);
9092
}
91-
user.getSubscriptions().remove(subscription);
92-
userRepository.save(user);
9393
}
9494

9595
public Set<Subscription> getUserSubscriptions(Long userId) {
9696
User user = userRepository.findById(userId)
9797
.orElseThrow(() -> new EntityNotFoundException("User not found with id: " + userId));
98-
return subscriptionRepository.findSubscriptionsByUser(user);
98+
return user.getSubscriptions();
9999
}
100100

101101
public void deleteUser(Long userId) {
@@ -114,6 +114,7 @@ private void updateUserDetails(User existingUser, User newUser) {
114114
}
115115

116116
existingUser.setRole(roleService.getByRoleId(newUser.getRoleId()));
117+
existingUser.setRoleId(newUser.getRoleId());
117118
}
118119

119120
@Transactional

src/test/java/com/thisaster/testtask/user/controller/UserControllerTest.java

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import static org.hamcrest.core.StringContains.containsString;
2525
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
2626
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
27+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
2728

2829
@Slf4j
2930
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@@ -94,9 +95,7 @@ void shouldGetUserSubscriptions() throws Exception {
9495
mockMvc.perform(get("/api/users/{id}/subscriptions", userId)
9596
.header("Authorization", "Bearer " + token))
9697
.andExpect(status().isOk())
97-
.andExpect(jsonPath("$[0].name", is("Premium Plan")))
98-
.andExpect(jsonPath("$[1].name", is("Newsletter")))
99-
.andExpect(jsonPath("$[2].name", is("Basic Access")));
98+
.andExpect(jsonPath("$[*].name", containsInAnyOrder("Premium Plan", "Newsletter", "Basic Access")));
10099
}
101100

102101
@Test
@@ -115,7 +114,7 @@ void shouldDeleteUserSubscription() throws Exception {
115114
@Order(6)
116115
void shouldUpdateUser(@Value("classpath:user/updateUser.json") Resource json) throws Exception {
117116
Long userId = 3L;
118-
mockMvc.perform(put("/api/users/{userId}", userId)
117+
MvcResult result = mockMvc.perform(put("/api/users/{userId}", userId)
119118
.header("Authorization", "Bearer " + token)
120119
.contentType(MediaType.APPLICATION_JSON)
121120
.content(json.getContentAsByteArray()))
@@ -127,7 +126,11 @@ void shouldUpdateUser(@Value("classpath:user/updateUser.json") Resource json) th
127126
.andExpect(jsonPath("$.subscriptions", hasSize(3)))
128127
.andExpect(jsonPath("$.subscriptions[*].name", containsInAnyOrder(
129128
"Premium Plan", "Newsletter", "Yandex Premium"
130-
)));
129+
)))
130+
.andExpect(jsonPath("$.role", is("SUPERVISOR")))
131+
.andReturn();
132+
133+
System.out.println("Result : " + result.getResponse().getContentAsString());
131134

132135
contains(subscriptionRepository.existsByName("Yandex Premium"));
133136
}

src/test/resources/user/updateUser.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,6 @@
1212
{
1313
"name": "Yandex Premium"
1414
}
15-
]
15+
],
16+
"role": "SUPERVISOR"
1617
}

0 commit comments

Comments
 (0)