Что-то

This commit is contained in:
gg12 darfren 2024-05-20 20:59:37 +04:00
parent 56ad0f134a
commit e044259d46
44 changed files with 1334 additions and 453 deletions

Binary file not shown.

View File

@ -29,12 +29,20 @@ repositories {
dependencies { dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.3.0'
implementation 'org.modelmapper:modelmapper:3.2.0' implementation 'org.modelmapper:modelmapper:3.2.0'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'com.h2database:h2:2.2.224' implementation 'com.h2database:h2:2.2.224'
implementation 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'nz.net.ultraq.thymeleaf:thymeleaf-layout-dialect:3.3.0'
runtimeOnly 'org.webjars.npm:bootstrap:5.3.3'
runtimeOnly 'org.webjars.npm:bootstrap-icons:1.11.3'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
} }

Binary file not shown.

View File

@ -39,8 +39,8 @@ public class DemoApplication implements CommandLineRunner {
log.info("Create default users values"); log.info("Create default users values");
final var user1 = userService.create(new UserEntity("Oleja123", "bebrus@mail.ru", "1234")); final var user1 = userService.create(new UserEntity("Oleja123", "1234"));
final var user2 = userService.create(new UserEntity("Vk1d2004", "berus@mail.ru", "4321")); final var user2 = userService.create(new UserEntity("Vk1d2004", "4321"));
log.info("Create default groups values"); log.info("Create default groups values");
@ -48,6 +48,10 @@ public class DemoApplication implements CommandLineRunner {
var tmp = user1.getGroups(); var tmp = user1.getGroups();
final var group2 = groupService.create(user2.getId(), new GroupEntity("2")); final var group2 = groupService.create(user2.getId(), new GroupEntity("2"));
final var group3 = groupService.create(user1.getId(), new GroupEntity("3")); final var group3 = groupService.create(user1.getId(), new GroupEntity("3"));
final var group4 = groupService.create(user1.getId(), new GroupEntity("4"));
final var group5 = groupService.create(user1.getId(), new GroupEntity("5"));
final var group6 = groupService.create(user1.getId(), new GroupEntity("6"));
final var group7 = groupService.create(user1.getId(), new GroupEntity("7"));
log.info("Create default members values"); log.info("Create default members values");

View File

@ -0,0 +1,8 @@
package com.example.demo.core.api;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ModelAttribute;
public class GlobalController {
}

View File

@ -0,0 +1,18 @@
package com.example.demo.core.api;
import java.util.Map;
import java.util.function.Function;
import org.springframework.data.domain.Page;
public class PageAttributesMapper {
private PageAttributesMapper() {
}
public static <E, D> Map<String, Object> toAttributes(Page<E> page, Function<E, D> mapper) {
return Map.of(
"items", page.getContent().stream().map(mapper::apply).toList(),
"currentPage", page.getNumber(),
"totalPages", page.getTotalPages());
}
}

View File

@ -1,97 +0,0 @@
package com.example.demo.core.api;
import java.util.ArrayList;
import java.util.List;
public class PageDto<D> {
private List<D> items = new ArrayList<>();
private int itemsCount;
private int currentPage;
private int currentSize;
private int totalPages;
private long totalItems;
private boolean isFirst;
private boolean isLast;
private boolean hasNext;
private boolean hasPrevious;
public List<D> getItems() {
return items;
}
public void setItems(List<D> items) {
this.items = items;
}
public int getItemsCount() {
return itemsCount;
}
public void setItemsCount(int itemsCount) {
this.itemsCount = itemsCount;
}
public int getCurrentPage() {
return currentPage;
}
public void setCurrentPage(int currentPage) {
this.currentPage = currentPage;
}
public int getCurrentSize() {
return currentSize;
}
public void setCurrentSize(int currentSize) {
this.currentSize = currentSize;
}
public int getTotalPages() {
return totalPages;
}
public void setTotalPages(int totalPages) {
this.totalPages = totalPages;
}
public long getTotalItems() {
return totalItems;
}
public void setTotalItems(long totalItems) {
this.totalItems = totalItems;
}
public boolean isFirst() {
return isFirst;
}
public void setFirst(boolean isFirst) {
this.isFirst = isFirst;
}
public boolean isLast() {
return isLast;
}
public void setLast(boolean isLast) {
this.isLast = isLast;
}
public boolean isHasNext() {
return hasNext;
}
public void setHasNext(boolean hasNext) {
this.hasNext = hasNext;
}
public boolean isHasPrevious() {
return hasPrevious;
}
public void setHasPrevious(boolean hasPrevious) {
this.hasPrevious = hasPrevious;
}
}

View File

@ -1,25 +0,0 @@
package com.example.demo.core.api;
import java.util.function.Function;
import org.springframework.data.domain.Page;
public class PageDtoMapper {
private PageDtoMapper() {
}
public static <D, E> PageDto<D> toDto(Page<E> page, Function<E, D> mapper) {
final PageDto<D> dto = new PageDto<>();
dto.setItems(page.getContent().stream().map(mapper::apply).toList());
dto.setItemsCount(page.getNumberOfElements());
dto.setCurrentPage(page.getNumber());
dto.setCurrentSize(page.getSize());
dto.setTotalPages(page.getTotalPages());
dto.setTotalItems(page.getTotalElements());
dto.setFirst(page.isFirst());
dto.setLast(page.isLast());
dto.setHasNext(page.hasNext());
dto.setHasPrevious(page.hasPrevious());
return dto;
}
}

View File

@ -3,8 +3,20 @@ package com.example.demo.core.configuration;
public class Constants { public class Constants {
public static final String SEQUENCE_NAME = "hibernate_sequence"; public static final String SEQUENCE_NAME = "hibernate_sequence";
public static final int DEFUALT_PAGE_SIZE = 5;
public static final String REDIRECT_VIEW = "redirect:";
public static final String API_URL = "/api/1.0"; public static final String API_URL = "/api/1.0";
public static final String LOGIN_URL = "/login";
public static final String LOGOUT_URL = "/logout";
public static final String DEFAULT_PASSWORD = "123456";
public static final String ADMIN_PREFIX = "/admin";
private Constants() { private Constants() {
} }
} }

View File

@ -1,13 +1,23 @@
package com.example.demo.core.configuration; package com.example.demo.core.configuration;
import org.modelmapper.ModelMapper; import org.modelmapper.ModelMapper;
import org.modelmapper.PropertyMap;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import com.example.demo.core.model.BaseEntity;
@Configuration @Configuration
public class MapperConfiguration { public class MapperConfiguration {
@Bean @Bean
ModelMapper modelMapper() { ModelMapper modelMapper() {
return new ModelMapper(); final ModelMapper mapper = new ModelMapper();
mapper.addMappings(new PropertyMap<Object, BaseEntity>() {
@Override
protected void configure() {
skip(destination.getId());
}
});
return mapper;
} }
} }

View File

@ -3,13 +3,13 @@ package com.example.demo.core.configuration;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.lang.NonNull; import org.springframework.lang.NonNull;
import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration @Configuration
public class WebConfiguration implements WebMvcConfigurer { public class WebConfiguration implements WebMvcConfigurer {
@Override @Override
public void addCorsMappings(@NonNull CorsRegistry registry) { public void addViewControllers(ViewControllerRegistry registry) {
registry.addMapping("/**") registry.addViewController("/login").setViewName("login");
.allowedMethods("GET", "POST", "PUT", "DELETE");
} }
} }

View File

@ -0,0 +1,53 @@
package com.example.demo.core.error;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import jakarta.servlet.http.HttpServletRequest;
@ControllerAdvice
public class AdviceController {
private final Logger log = LoggerFactory.getLogger(AdviceController.class);
private static Throwable getRootCause(Throwable throwable) {
Throwable rootCause = throwable;
while (rootCause.getCause() != null && rootCause.getCause() != rootCause) {
rootCause = rootCause.getCause();
}
return rootCause;
}
private static Map<String, Object> getAttributes(HttpServletRequest request, Throwable throwable) {
final Throwable rootCause = getRootCause(throwable);
final StackTraceElement firstError = rootCause.getStackTrace()[0];
return Map.of(
"message", rootCause.getMessage(),
"url", request.getRequestURL(),
"exception", rootCause.getClass().getName(),
"file", firstError.getFileName(),
"method", firstError.getMethodName(),
"line", firstError.getLineNumber());
}
@ExceptionHandler(value = Exception.class)
public ModelAndView defaultErrorHandler(HttpServletRequest request, Throwable throwable) throws Throwable {
if (AnnotationUtils.findAnnotation(throwable.getClass(),
ResponseStatus.class) != null) {
throw throwable;
}
log.error("{}", throwable.getMessage());
throwable.printStackTrace();
final ModelAndView model = new ModelAndView();
model.addAllObjects(getAttributes(request, throwable));
model.setViewName("error");
return model;
}
}

View File

@ -0,0 +1,63 @@
package com.example.demo.core.security;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer.FrameOptionsConfig;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import com.example.demo.core.configuration.Constants;
import com.example.demo.users.api.UserSignupController;
import com.example.demo.users.model.UserRole;
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {
@Bean
SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {
httpSecurity.headers(headers -> headers.frameOptions(FrameOptionsConfig::sameOrigin));
httpSecurity.csrf(AbstractHttpConfigurer::disable);
httpSecurity.cors(Customizer.withDefaults());
httpSecurity.authorizeHttpRequests(requests -> requests
.requestMatchers("/css/**", "/webjars/**", "/*.svg")
.permitAll());
httpSecurity.authorizeHttpRequests(requests -> requests
.requestMatchers(Constants.ADMIN_PREFIX + "/**").hasRole(UserRole.ADMIN.name())
.requestMatchers("/h2-console/**").hasRole(UserRole.ADMIN.name())
.requestMatchers(UserSignupController.URL).anonymous()
.requestMatchers(Constants.LOGIN_URL).anonymous()
.anyRequest().authenticated());
httpSecurity.formLogin(formLogin -> formLogin
.loginPage(Constants.LOGIN_URL));
httpSecurity.rememberMe(rememberMe -> rememberMe.key("uniqueAndSecret"));
httpSecurity.logout(logout -> logout
.deleteCookies("JSESSIONID"));
return httpSecurity.build();
}
@Bean
DaoAuthenticationProvider authenticationProvider(UserDetailsService userDetailsService) {
final DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
authProvider.setUserDetailsService(userDetailsService);
authProvider.setPasswordEncoder(passwordEncoder());
return authProvider;
}
@Bean
PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

View File

@ -0,0 +1,65 @@
package com.example.demo.core.security;
import java.util.Collection;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.example.demo.users.model.UserEntity;
public class UserPrincipal implements UserDetails {
private final long id;
private final String username;
private final String password;
private final Set<? extends GrantedAuthority> roles;
private final boolean active;
public UserPrincipal(UserEntity user) {
this.id = user.getId();
this.username = user.getLogin();
this.password = user.getPassword();
this.roles = Set.of(user.getRole());
this.active = true;
}
public Long getId() {
return id;
}
@Override
public String getUsername() {
return username;
}
@Override
public String getPassword() {
return password;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return roles;
}
@Override
public boolean isEnabled() {
return active;
}
@Override
public boolean isAccountNonExpired() {
return isEnabled();
}
@Override
public boolean isAccountNonLocked() {
return isEnabled();
}
@Override
public boolean isCredentialsNonExpired() {
return isEnabled();
}
}

View File

@ -1,37 +1,50 @@
package com.example.demo.groups.api; package com.example.demo.groups.api;
import java.util.List; import java.util.List;
import java.util.Map;
import org.modelmapper.ModelMapper; import org.modelmapper.ModelMapper;
import org.springframework.boot.autoconfigure.security.SecurityProperties.User; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import com.example.demo.core.api.PageAttributesMapper;
import com.example.demo.core.configuration.Constants; import com.example.demo.core.configuration.Constants;
import com.example.demo.core.security.UserPrincipal;
import com.example.demo.groups.model.GroupEntity; import com.example.demo.groups.model.GroupEntity;
import com.example.demo.groups.service.GroupService; import com.example.demo.groups.service.GroupService;
import com.example.demo.members.service.MemberService;
import com.example.demo.users.service.UserService; import com.example.demo.users.service.UserService;
import jakarta.validation.Valid; import jakarta.validation.Valid;
@RestController @Controller
@RequestMapping(Constants.API_URL + "/user/{user}/group")
public class GroupController { public class GroupController {
private static final String GROUPS_VIEW = "groups";
private static final String GROUP_VIEW = "members";
private static final String GROUP_EDIT_VIEW = "group-edit";
private static final String PAGE_ATTRIBUTE = "page";
private static final String GROUP_ATTRIBUTE = "group";
private static final String MEMBERS_VIEW = "members";
private final GroupService groupService; private final GroupService groupService;
private final MemberService memberService;
private final UserService userService; private final UserService userService;
private final ModelMapper modelMapper; private final ModelMapper modelMapper;
public GroupController(GroupService groupService, UserService userService, ModelMapper modelMapper) { public GroupController(GroupService groupService, UserService userService, MemberService memberService, ModelMapper modelMapper) {
this.groupService = groupService; this.groupService = groupService;
this.userService = userService; this.userService = userService;
this.modelMapper = modelMapper; this.modelMapper = modelMapper;
this.memberService = memberService;
} }
private GroupDto toDto(GroupEntity entity) { private GroupDto toDto(GroupEntity entity) {
@ -44,36 +57,69 @@ public class GroupController {
} }
@GetMapping @GetMapping
public List<GroupDto> getAll( public String getAll(
@PathVariable(name = "user") Long userId) { @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
return groupService.getAll(userId).stream() Model model,
.map(this::toDto) @AuthenticationPrincipal UserPrincipal principal) {
.toList(); final Map<String, Object> attributes = PageAttributesMapper.toAttributes(
groupService.getAll(principal.getId(), page, Constants.DEFUALT_PAGE_SIZE), this::toDto);
model.addAllAttributes(attributes);
model.addAttribute(PAGE_ATTRIBUTE, page);
return GROUPS_VIEW;
} }
@GetMapping("/{id}") @GetMapping("/edit/")
public GroupDto get( public String create(Model model) {
@PathVariable(name = "id") Long id) { model.addAttribute(GROUP_ATTRIBUTE, new GroupDto());
return toDto(groupService.get(id)); return GROUP_EDIT_VIEW;
} }
@PostMapping @PostMapping("/edit/")
public GroupDto create( public String create(
@PathVariable(name = "user") Long userId, @ModelAttribute(name = GROUP_ATTRIBUTE) @Valid GroupDto group,
@RequestBody @Valid GroupDto dto) { @AuthenticationPrincipal UserPrincipal principal,
return toDto(groupService.create(userId, toEntity(dto))); BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
return GROUP_EDIT_VIEW;
}
groupService.create(principal.getId(), toEntity(group));
return Constants.REDIRECT_VIEW + "/";
} }
@PutMapping("/{id}") @GetMapping("/edit/{id}")
public GroupDto update( public String update(
@PathVariable(name = "id") Long id, @PathVariable(name = "id") Long id,
@RequestBody @Valid GroupDto dto) { Model model) {
return toDto(groupService.update(id, toEntity(dto))); if (id <= 0) {
throw new IllegalArgumentException();
}
model.addAttribute(GROUP_ATTRIBUTE, toDto(groupService.get(id)));
return GROUP_EDIT_VIEW;
} }
@DeleteMapping("/{id}") @PostMapping("/edit/{id}")
public GroupDto delete( public String update(
@PathVariable(name = "id") Long id) { @PathVariable(name = "id") Long id,
return toDto(groupService.delete(id)); @ModelAttribute(name = GROUP_ATTRIBUTE) @Valid GroupDto group,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
return GROUP_EDIT_VIEW;
} }
if (id <= 0) {
throw new IllegalArgumentException();
}
groupService.update(id, toEntity(group));
return Constants.REDIRECT_VIEW + "/";
}
@PostMapping("/delete/{id}")
public String delete(
@PathVariable(name = "id") Long id) {
groupService.delete(id);
return Constants.REDIRECT_VIEW + "/";
}
} }

View File

@ -7,7 +7,6 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
public class GroupDto { public class GroupDto {
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Long id; private Long id;
@NotBlank @NotBlank
private String name; private String name;

View File

@ -3,6 +3,8 @@ package com.example.demo.groups.repository;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
@ -10,5 +12,6 @@ import com.example.demo.groups.model.GroupEntity;
import com.example.demo.groups.model.GroupEntity; import com.example.demo.groups.model.GroupEntity;
public interface GroupRepository extends CrudRepository<GroupEntity, Long> { public interface GroupRepository extends CrudRepository<GroupEntity, Long> {
Page<GroupEntity> findByUserId(long userId, Pageable pageable);
List<GroupEntity> findByUserId(long userId); List<GroupEntity> findByUserId(long userId);
} }

View File

@ -4,6 +4,10 @@ import java.util.List;
import javax.swing.GroupLayout.Group; import javax.swing.GroupLayout.Group;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -24,6 +28,12 @@ public class GroupService {
this.userService = userService; this.userService = userService;
} }
@Transactional(readOnly = true)
public Page<GroupEntity> getAll(Long userId, int page, int size) {
final Pageable pageRequest = PageRequest.of(page, size, Sort.by("id"));
return repository.findByUserId(userId, pageRequest);
}
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<GroupEntity> getAll(Long userId) { public List<GroupEntity> getAll(Long userId) {
userService.get(userId); userService.get(userId);

View File

@ -1,11 +1,17 @@
package com.example.demo.members.api; package com.example.demo.members.api;
import java.util.List; import java.util.List;
import java.util.Map;
import org.modelmapper.ModelMapper; import org.modelmapper.ModelMapper;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
@ -14,18 +20,27 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.example.demo.core.api.PageDto; import com.example.demo.core.api.PageAttributesMapper;
import com.example.demo.core.api.PageDtoMapper;
import com.example.demo.core.configuration.Constants; import com.example.demo.core.configuration.Constants;
import com.example.demo.core.security.UserPrincipal;
import com.example.demo.members.model.MemberEntity; import com.example.demo.members.model.MemberEntity;
import com.example.demo.members.service.MemberService; import com.example.demo.members.service.MemberService;
import com.example.demo.groups.api.GroupDto;
import com.example.demo.groups.service.GroupService; import com.example.demo.groups.service.GroupService;
import jakarta.validation.Valid; import jakarta.validation.Valid;
@RestController @Controller
@RequestMapping(Constants.API_URL + "/user/{user}/group/{group}/member") @RequestMapping(MemberController.URL)
public class MemberController { public class MemberController {
public static final String URL = "/{group}/members";
private static final String MEMBERS_VIEW = "members";
private static final String MEMBERS_TOP_VIEW = "members-top";
private static final String MEMBER_EDIT_VIEW = "member-edit";
private static final String PAGE_ATTRIBUTE = "page";
private static final String MEMBER_ATTRIBUTE = "member";
private static final String GROUP_ATTRIBUTE = "group";
private static final String MEMBERS_TOP_ATTRIBUTE = "members-top";
private final MemberService memberService; private final MemberService memberService;
private final GroupService groupService; private final GroupService groupService;
private final ModelMapper modelMapper; private final ModelMapper modelMapper;
@ -56,38 +71,76 @@ public class MemberController {
return memberService.getMembersTopList(5).stream().map(this::toDto).toList(); return memberService.getMembersTopList(5).stream().map(this::toDto).toList();
} }
@GetMapping("/getmemberstopgrouplist")
public List<MemberDto> getMembersTopGroupList(@RequestParam(name = "groupId", defaultValue = "0") Long groupId) {
return memberService.getMembersTopGroupList(5, groupId).stream().map(this::toDto).toList();
}
@GetMapping("/getmemberstopgroup") @GetMapping("/getmemberstopgroup")
public List<MemberDto> getMembersTopGroup(@RequestParam(name = "groupId", defaultValue = "0") Long groupId) { public String getMembersTopGroup(@RequestParam(name = "group", defaultValue = "0") Long groupId, Model model) {
return memberService.getMembersTopGroup(5, groupId).stream().map(this::toDto).toList(); model.addAttribute(MEMBERS_TOP_ATTRIBUTE, memberService.getMembersTopGroup(5, groupId).stream().map(this::toDto).toList());
return MEMBERS_TOP_VIEW;
} }
@GetMapping @GetMapping
public List<MemberDto> getAll(@RequestParam(name = "group", defaultValue = "0") Long groupId) { public String getAll(@PathVariable(name = "group") Long groupId,
return memberService.getAll(groupId).stream().map(this::toDto).toList(); @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
final Map<String, Object> attributes = PageAttributesMapper.toAttributes(
memberService.getAll(groupId, page, Constants.DEFUALT_PAGE_SIZE), this::toDto);
model.addAllAttributes(attributes);
model.addAttribute(PAGE_ATTRIBUTE, page);
model.addAttribute(GROUP_ATTRIBUTE, groupId);
return MEMBERS_VIEW;
} }
@GetMapping("/{id}") @GetMapping("/edit")
public MemberDto get(@PathVariable(name = "id") Long id) { public String create(@PathVariable(name = "group") Long groupId, Model model) {
return toDto(memberService.get(id)); model.addAttribute(MEMBER_ATTRIBUTE, new MemberDto());
model.addAttribute(GROUP_ATTRIBUTE, groupId);
return MEMBER_EDIT_VIEW;
} }
@PostMapping @PostMapping("/edit/")
public MemberDto create(@PathVariable(name = "group") Long groupId, @RequestBody @Valid MemberDto dto) { public String create(@PathVariable(name = "group") Long groupId,
return toDto(memberService.create(groupId, toEntity(dto))); @ModelAttribute(name = MEMBER_ATTRIBUTE) @Valid MemberDto member,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
return MEMBER_EDIT_VIEW;
}
memberService.create(groupId, toEntity(member));
return Constants.REDIRECT_VIEW + String.format("/%d/members", groupId);
} }
@PutMapping("/{id}") @GetMapping("/edit/{id}")
public MemberDto update(@PathVariable(name = "id") Long id, @RequestBody MemberDto dto) { public String update(@PathVariable(name = "group") Long groupId,
return toDto(memberService.update(id, toEntity(dto))); @PathVariable(name = "id") Long id,
Model model) {
if (id <= 0) {
throw new IllegalArgumentException();
}
model.addAttribute(MEMBER_ATTRIBUTE, toDto(memberService.get(id)));
model.addAttribute(GROUP_ATTRIBUTE, groupId);
return MEMBER_EDIT_VIEW;
} }
@DeleteMapping("/{id}") @PostMapping("/edit/{id}")
public MemberDto delete(@PathVariable(name = "id") @Valid Long id) { public String update(
return toDto(memberService.delete(id)); @PathVariable(name = "id") Long id,
@PathVariable(name = "group") Long group,
@ModelAttribute(name = MEMBER_ATTRIBUTE) @Valid MemberDto member,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
return MEMBER_EDIT_VIEW;
}
if (id <= 0) {
throw new IllegalArgumentException();
}
memberService.update(id, toEntity(member));
return Constants.REDIRECT_VIEW + String.format("/%d/members", group);
}
@PostMapping("/delete/{id}")
public String delete(
@PathVariable(name = "id") Long id,
@PathVariable(name = "group") Long group) {
memberService.delete(id);
return Constants.REDIRECT_VIEW + String.format("/%d/members", group);
} }
} }

View File

@ -7,15 +7,12 @@ import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.NotNull;
public class MemberDto { public class MemberDto {
@JsonProperty(access = JsonProperty.Access.READ_ONLY)
private Long id; private Long id;
@NotBlank @NotBlank
private String name; private String name;
@NotBlank @NotBlank
private String handle; private String handle;
@NotBlank
private String rank; private String rank;
@NotBlank
private Long rating; private Long rating;
public Long getId() { public Long getId() {

View File

@ -15,7 +15,7 @@ import org.springframework.data.repository.CrudRepository;
public interface MemberRepository extends CrudRepository<MemberEntity, Long> { public interface MemberRepository extends CrudRepository<MemberEntity, Long> {
Optional<MemberEntity> findOneById(long id); Optional<MemberEntity> findOneById(long id);
List<MemberEntity> findByGroupId(long groupId); Page<MemberEntity> findByGroupId(long groupId, Pageable pageable);
@Query("select " @Query("select "
+ "m" + "m"
+ " from MemberEntity m" + " from MemberEntity m"

View File

@ -5,6 +5,7 @@ import java.util.List;
import org.springframework.data.domain.Page; import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@ -27,8 +28,9 @@ public class MemberService {
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<MemberEntity> getAll(long groupId) { public Page<MemberEntity> getAll(long groupId, int page, int size) {
return repository.findByGroupId(groupId); final Pageable pageRequest = PageRequest.of(page, size, Sort.by("id"));
return repository.findByGroupId(groupId, pageRequest);
} }
@Transactional(readOnly = true) @Transactional(readOnly = true)
@ -81,6 +83,7 @@ public class MemberService {
existsEntity.setHandle(entity.getHandle()); existsEntity.setHandle(entity.getHandle());
existsEntity.setName(entity.getName()); existsEntity.setName(entity.getName());
existsEntity.setRank(entity.getRank()); existsEntity.setRank(entity.getRank());
existsEntity.setRating(entity.getRating());
return repository.save(existsEntity); return repository.save(existsEntity);
} }

View File

@ -1,26 +1,37 @@
package com.example.demo.users.api; package com.example.demo.users.api;
import java.util.List; import java.util.Map;
import org.modelmapper.ModelMapper; import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.example.demo.core.api.PageAttributesMapper;
import com.example.demo.core.configuration.Constants; import com.example.demo.core.configuration.Constants;
import com.example.demo.users.model.UserEntity; import com.example.demo.users.model.UserEntity;
import com.example.demo.users.service.UserService; import com.example.demo.users.service.UserService;
import jakarta.validation.Valid; import jakarta.validation.Valid;
@RestController @Controller
@RequestMapping(Constants.API_URL + "/user") @RequestMapping(UserController.URL)
public class UserController { public class UserController {
public static final String URL = Constants.ADMIN_PREFIX + "/user";
private static final String USERS_VIEW = "users";
private static final String USER_EDIT_VIEW = "user-edit";
private static final String PAGE_ATTRIBUTE = "page";
private static final String USER_ATTRIBUTE = "user";
private final UserService userService; private final UserService userService;
private final ModelMapper modelMapper; private final ModelMapper modelMapper;
@ -38,27 +49,81 @@ public class UserController {
} }
@GetMapping @GetMapping
public List<UserDto> getAll() { public String getAll(
return userService.getAll().stream().map(this::toDto).toList(); @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
final Map<String, Object> attributes = PageAttributesMapper.toAttributes(
userService.getAll(page, Constants.DEFUALT_PAGE_SIZE), this::toDto);
model.addAllAttributes(attributes);
model.addAttribute(PAGE_ATTRIBUTE, page);
return USERS_VIEW;
} }
@GetMapping("/{id}") @GetMapping("/edit/")
public UserDto get(@PathVariable(name = "id") Long id) { public String create(
return toDto(userService.get(id)); @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
model.addAttribute(USER_ATTRIBUTE, new UserDto());
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_EDIT_VIEW;
} }
@PostMapping @PostMapping("/edit/")
public UserDto create(@RequestBody @Valid UserDto dto) { public String create(
return toDto(userService.create(toEntity(dto))); @RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@ModelAttribute(name = USER_ATTRIBUTE) @Valid UserDto user,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_EDIT_VIEW;
}
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
userService.create(toEntity(user));
return Constants.REDIRECT_VIEW + URL;
} }
@PutMapping("/{id}") @GetMapping("/edit/{id}")
public UserDto update(@PathVariable(name = "id") Long id, @RequestBody UserDto dto) { public String update(
return toDto(userService.update(id, toEntity(dto))); @PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
Model model) {
if (id <= 0) {
throw new IllegalArgumentException();
}
model.addAttribute(USER_ATTRIBUTE, toDto(userService.get(id)));
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_EDIT_VIEW;
} }
@DeleteMapping("/{id}") @PostMapping("/edit/{id}")
public UserDto delete(@PathVariable(name = "id") @Valid Long id) { public String update(
return toDto(userService.delete(id)); @PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
@ModelAttribute(name = USER_ATTRIBUTE) @Valid UserDto user,
BindingResult bindingResult,
Model model,
RedirectAttributes redirectAttributes) {
if (bindingResult.hasErrors()) {
model.addAttribute(PAGE_ATTRIBUTE, page);
return USER_EDIT_VIEW;
}
if (id <= 0) {
throw new IllegalArgumentException();
}
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
userService.update(id, toEntity(user));
return Constants.REDIRECT_VIEW + URL;
}
@PostMapping("/delete/{id}")
public String delete(
@PathVariable(name = "id") Long id,
@RequestParam(name = PAGE_ATTRIBUTE, defaultValue = "0") int page,
RedirectAttributes redirectAttributes) {
redirectAttributes.addAttribute(PAGE_ATTRIBUTE, page);
userService.delete(id);
return Constants.REDIRECT_VIEW + URL;
} }
} }

View File

@ -8,17 +8,11 @@ import jakarta.validation.constraints.Size;
public class UserDto { public class UserDto {
@JsonProperty(access = Access.READ_ONLY)
private Long id; private Long id;
@NotBlank @NotBlank
@Size(min = 3, max = 25)
private String handle;
@NotBlank
@Size(min = 3, max = 30)
private String email;
@NotBlank
@Size(min = 3, max = 20) @Size(min = 3, max = 20)
private String password; private String login;
private String role;
public Long getId() { public Long getId() {
return id; return id;
@ -28,27 +22,19 @@ public class UserDto {
this.id = id; this.id = id;
} }
public String getEmail() { public String getLogin() {
return email; return login;
} }
public void setEmail(String email) { public void setLogin(String login) {
this.email = email; this.login = login;
} }
public String getHandle() { public String getRole() {
return handle; return role;
} }
public void setHandle(String handle) { public void setRole(String role) {
this.handle = handle; this.role = role;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
} }
} }

View File

@ -0,0 +1,65 @@
package com.example.demo.users.api;
import java.util.Objects;
import org.modelmapper.ModelMapper;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import com.example.demo.core.configuration.Constants;
import com.example.demo.users.model.UserEntity;
import com.example.demo.users.service.UserService;
import jakarta.validation.Valid;
@Controller
@RequestMapping(UserSignupController.URL)
public class UserSignupController {
public static final String URL = "/signup";
private static final String SIGNUP_VIEW = "signup";
private static final String USER_ATTRIBUTE = "user";
private final UserService userService;
private final ModelMapper modelMapper;
public UserSignupController(
UserService userService,
ModelMapper modelMapper) {
this.userService = userService;
this.modelMapper = modelMapper;
}
private UserEntity toEntity(UserSignupDto dto) {
return modelMapper.map(dto, UserEntity.class);
}
@GetMapping
public String getSignup(Model model) {
model.addAttribute(USER_ATTRIBUTE, new UserSignupDto());
return SIGNUP_VIEW;
}
@PostMapping
public String signup(
@ModelAttribute(name = USER_ATTRIBUTE) @Valid UserSignupDto user,
BindingResult bindingResult,
Model model) {
if (bindingResult.hasErrors()) {
return SIGNUP_VIEW;
}
if (!Objects.equals(user.getPassword(), user.getPasswordConfirm())) {
bindingResult.rejectValue("password", "signup:passwords", "Пароли не совпадают.");
model.addAttribute(USER_ATTRIBUTE, user);
return SIGNUP_VIEW;
}
userService.create(toEntity(user));
return Constants.REDIRECT_VIEW + Constants.LOGIN_URL + "?signup";
}
}

View File

@ -0,0 +1,40 @@
package com.example.demo.users.api;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
public class UserSignupDto {
@NotBlank
@Size(min = 3, max = 20)
private String login;
@NotBlank
@Size(min = 3, max = 20)
private String password;
@NotBlank
@Size(min = 3, max = 20)
private String passwordConfirm;
public String getLogin() {
return login;
}
public void setLogin(String login) {
this.login = login;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPasswordConfirm() {
return passwordConfirm;
}
public void setPasswordConfirm(String passwordConfirm) {
this.passwordConfirm = passwordConfirm;
}
}

View File

@ -18,12 +18,11 @@ import jakarta.persistence.Table;
@Entity @Entity
@Table(name = "users") @Table(name = "users")
public class UserEntity extends BaseEntity{ public class UserEntity extends BaseEntity{
@Column(nullable = false, unique = true, length = 25) @Column(nullable = false, unique = true, length = 20)
private String handle; private String login;
@Column(nullable = false, unique = true, length = 30) @Column(nullable = false, length = 60)
private String email;
@Column(nullable = false, length = 20)
private String password; private String password;
private UserRole role;
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL) @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
@OrderBy("id ASC") @OrderBy("id ASC")
private Set<GroupEntity> groups = new HashSet<>(); private Set<GroupEntity> groups = new HashSet<>();
@ -31,18 +30,18 @@ public class UserEntity extends BaseEntity{
public UserEntity(){ public UserEntity(){
} }
public UserEntity(String handle, String email, String password) { public UserEntity(String login, String password) {
this.handle = handle; this.login = login;
this.email = email;
this.password = password; this.password = password;
this.role = UserRole.USER;
} }
public String getEmail() { public String getLogin() {
return email; return login;
} }
public void setEmail(String email) { public void setLogin(String login) {
this.email = email; this.login = login;
} }
public String getPassword() { public String getPassword() {
@ -53,12 +52,12 @@ public class UserEntity extends BaseEntity{
this.password = password; this.password = password;
} }
public String getHandle() { public UserRole getRole() {
return handle; return role;
} }
public void setHandle(String handle) { public void setRole(UserRole role) {
this.handle = handle; this.role = role;
} }
public Set<GroupEntity> getGroups() { public Set<GroupEntity> getGroups() {
@ -79,9 +78,10 @@ public class UserEntity extends BaseEntity{
groups.remove(group); groups.remove(group);
} }
@Override @Override
public int hashCode() { public int hashCode() {
return Objects.hash(id, handle, email, password); return Objects.hash(id, login, password, role);
} }
@Override @Override
@ -92,9 +92,9 @@ public class UserEntity extends BaseEntity{
return false; return false;
final UserEntity other = (UserEntity) obj; final UserEntity other = (UserEntity) obj;
return Objects.equals(other.getId(), id) return Objects.equals(other.getId(), id)
&& Objects.equals(other.getHandle(), handle) && Objects.equals(other.getLogin(), login)
&& Objects.equals(other.getEmail(), email) && Objects.equals(other.getPassword(), password)
&& Objects.equals(other.getPassword(), password); && Objects.equals(other.getRole(), role);
} }
} }

View File

@ -0,0 +1,15 @@
package com.example.demo.users.model;
import org.springframework.security.core.GrantedAuthority;
public enum UserRole implements GrantedAuthority {
ADMIN,
USER;
private static final String PREFIX = "ROLE_";
@Override
public String getAuthority() {
return PREFIX + this.name();
}
}

View File

@ -3,11 +3,11 @@ package com.example.demo.users.repository;
import java.util.Optional; import java.util.Optional;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import com.example.demo.groups.model.GroupEntity; import com.example.demo.groups.model.GroupEntity;
import com.example.demo.users.model.UserEntity; import com.example.demo.users.model.UserEntity;
public interface UserRepository extends CrudRepository<UserEntity, Long> { public interface UserRepository extends CrudRepository<UserEntity, Long>, PagingAndSortingRepository<UserEntity, Long> {
Optional<UserEntity> findByHandleIgnoreCase(String login); Optional<UserEntity> findByLoginIgnoreCase(String login);
Optional<UserEntity> findByEmailIgnoreCase(String login);
} }

View File

@ -2,34 +2,44 @@ package com.example.demo.users.service;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Optional;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import com.example.demo.core.configuration.Constants;
import com.example.demo.core.error.NotFoundException; import com.example.demo.core.error.NotFoundException;
import com.example.demo.core.security.UserPrincipal;
import com.example.demo.users.model.UserEntity; import com.example.demo.users.model.UserEntity;
import com.example.demo.users.model.UserRole;
import com.example.demo.users.repository.UserRepository; import com.example.demo.users.repository.UserRepository;
@Service @Service
public class UserService { public class UserService implements UserDetailsService {
private final UserRepository repository; private final UserRepository repository;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository repository) { public UserService(
UserRepository repository,
PasswordEncoder passwordEncoder) {
this.repository = repository; this.repository = repository;
this.passwordEncoder = passwordEncoder;
} }
private void checkHandle(String handle) { private void checkLogin(Long id, String login) {
if (repository.findByHandleIgnoreCase(handle).isPresent()) { final Optional<UserEntity> existsUser = repository.findByLoginIgnoreCase(login);
if (existsUser.isPresent() && !existsUser.get().getId().equals(id)) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
String.format("User with handle %s is already exists", handle)); String.format("User with login %s is already exists", login));
}
}
private void checkEmail(String email) {
if (repository.findByHandleIgnoreCase(email).isPresent()) {
throw new IllegalArgumentException(
String.format("User with email %s is already exists", email));
} }
} }
@ -38,19 +48,34 @@ public class UserService {
return StreamSupport.stream(repository.findAll().spliterator(), false).toList(); return StreamSupport.stream(repository.findAll().spliterator(), false).toList();
} }
@Transactional(readOnly = true)
public Page<UserEntity> getAll(int page, int size) {
return repository.findAll(PageRequest.of(page, size, Sort.by("id")));
}
@Transactional(readOnly = true) @Transactional(readOnly = true)
public UserEntity get(long id) { public UserEntity get(long id) {
return repository.findById(id) return repository.findById(id)
.orElseThrow(() -> new NotFoundException(UserEntity.class, id)); .orElseThrow(() -> new NotFoundException(UserEntity.class, id));
} }
@Transactional(readOnly = true)
public UserEntity getByLogin(String login) {
return repository.findByLoginIgnoreCase(login)
.orElseThrow(() -> new IllegalArgumentException("Invalid login"));
}
@Transactional @Transactional
public UserEntity create(UserEntity entity) { public UserEntity create(UserEntity entity) {
if (entity == null) { if (entity == null) {
throw new IllegalArgumentException("Entity is null"); throw new IllegalArgumentException("Entity is null");
} }
checkHandle(entity.getHandle()); checkLogin(null, entity.getLogin());
checkEmail(entity.getEmail()); final String password = Optional.ofNullable(entity.getPassword()).orElse("");
entity.setPassword(
passwordEncoder.encode(
StringUtils.hasText(password.strip()) ? password : Constants.DEFAULT_PASSWORD));
entity.setRole(Optional.ofNullable(entity.getRole()).orElse(UserRole.USER));
repository.save(entity); repository.save(entity);
return repository.save(entity); return repository.save(entity);
} }
@ -58,11 +83,8 @@ public class UserService {
@Transactional @Transactional
public UserEntity update(long id, UserEntity entity) { public UserEntity update(long id, UserEntity entity) {
final UserEntity existsEntity = get(id); final UserEntity existsEntity = get(id);
checkHandle(entity.getHandle()); checkLogin(id, entity.getLogin());
checkEmail(entity.getEmail()); existsEntity.setLogin(entity.getLogin());
existsEntity.setHandle(entity.getHandle());
existsEntity.setEmail(entity.getEmail());
existsEntity.setPassword(entity.getPassword());
repository.save(existsEntity); repository.save(existsEntity);
return existsEntity; return existsEntity;
} }
@ -74,8 +96,10 @@ public class UserService {
return existsEntity; return existsEntity;
} }
@Transactional @Override
public void clear(){ @Transactional(readOnly = true)
repository.deleteAll(); public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
final UserEntity existsUser = getByLogin(username);
return new UserPrincipal(existsUser);
} }
} }

View File

@ -12,7 +12,7 @@ spring.datasource.password=password
spring.datasource.driver-class-name=org.h2.Driver spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.hibernate.ddl-auto=create spring.jpa.hibernate.ddl-auto=create
spring.jpa.open-in-view=false spring.jpa.open-in-view=false
spring.jpa.show-sql=true spring.jpa.show-sql=false
spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.format_sql=true
# H2 console # H2 console

View File

@ -0,0 +1,57 @@
html,
body {
height: 100%;
}
h1 {
font-size: 1.5em;
}
h2 {
font-size: 1.25em;
}
h3 {
font-size: 1.1em;
}
td form {
margin: 0;
padding: 0;
margin-top: -.25em;
}
.button-fixed-width {
width: 150px;
}
.button-link {
padding: 0;
}
.invalid-feedback {
display: block;
}
.w-10 {
width: 10% !important;
}
.my-navbar {
background-color: #ffffff !important;
}
.my-navbar .link a:hover {
text-decoration: underline;
}
.my-navbar .logo {
width: 26px;
height: 26px;
}
.my-footer {
background-color: #e6e6e6;
height: 32px;
color: rgba(0, 0, 0, 0.5);
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
width="74.000000pt" height="58.000000pt" viewBox="0 0 74.000000 58.000000"
preserveAspectRatio="xMidYMid meet">
<g transform="translate(0.000000,58.000000) scale(0.100000,-0.100000)"
fill="#000000" stroke="none">
<path d="M415 373 c-81 -10 -76 -1 -73 -126 l3 -112 55 0 55 0 3 123 c1 67 -1
121 -5 121 -4 -1 -21 -4 -38 -6z"/>
<path d="M525 338 c-3 -13 -5 -63 -3 -113 l3 -90 48 -3 c27 -2 55 2 63 9 18
15 20 189 2 207 -7 7 -34 12 -60 12 -41 0 -48 -3 -53 -22z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 678 B

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="ru" data-bs-theme="light" xmlns:th="http://www.thymeleaf.org"
xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout"
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity6">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title layout:title-pattern="$LAYOUT_TITLE - $CONTENT_TITLE">Codemonitor</title>
<script type="text/javascript" src="/webjars/bootstrap/5.3.3/dist/js/bootstrap.bundle.min.js"></script>
<link rel="stylesheet" href="/webjars/bootstrap/5.3.3/dist/css/bootstrap.min.css" />
<link rel="stylesheet" href="/webjars/bootstrap-icons/1.11.3/font/bootstrap-icons.min.css" />
<link rel="stylesheet" href="/css/style.css" />
</head>
<body class="h-100 d-flex flex-column">
<nav class="navbar navbar-expand-md my-navbar" data-bs-theme="light">
<div class="container-fluid">
<a class="navbar-brand" href="/">
Codemonitor
</a>
<th:block sec:authorize="isAuthenticated()" th:with="userName=${#authentication.name}">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#main-navbar"
aria-controls="main-navbar" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="main-navbar">
<ul class="navbar-nav me-auto link" th:with="activeLink=${#objects.nullSafe(servletPath, '')}">
<th:block sec:authorize="hasRole('ADMIN')">
<a class="nav-link" href="/admin/user"
th:classappend="${activeLink.startsWith('/admin/user') ? 'active' : ''}">
Пользователи
</a>
<a class="nav-link" href="/h2-console/" target="_blank">Консоль H2</a>
</th:block>
</ul>
<ul class="navbar-nav" th:if="${not #strings.isEmpty(userName)}">
<form th:action="@{/logout}" method="post">
<button type="submit" class="navbar-brand nav-link" onclick="return confirm('Вы уверены?')">
Выход ([[${userName}]])
</button>
</form>
</ul>
</div>
</th:block>
</div>
</nav>
<main class="container-fluid p-2" layout:fragment="content">
</main>
<footer class="my-footer mt-auto d-flex flex-shrink-0 justify-content-center align-items-center">
Oleja123, [[${#dates.year(#dates.createNow())}]]
</footer>
</body>
</html>

View File

@ -0,0 +1,28 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Редактировать группу</title>
</head>
<body>
<main layout:fragment="content">
<form action="#" th:action="@{/edit/{id}(id=${group.id})}" th:object="${group}" method="post">
<div class="mb-3">
<label for="id" class="form-label">ID</label>
<input type="text" th:value="*{id}" id="id" class="form-control" readonly disabled>
</div>
<div class="mb-3">
<label for="name" class="form-label">Группа</label>
<input type="text" th:field="*{name}" id="name" class="form-control">
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="invalid-feedback"></div>
</div>
<div class="mb-3 d-flex flex-row">
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Сохранить</button>
<a class="btn btn-secondary button-fixed-width" href="/">Отмена</a>
</div>
</form>
</main>
</body>
</html>

View File

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Группы пользователя</title>
</head>
<body>
<main layout:fragment="content">
<h2>Мои группы</h2>
<div>
<a th:href="@{/edit/}" class="btn btn-primary">Добавить группу</a>
</div>
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<table class="table">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-10">ID</th>
<th scope="col" class="w-auto">Название группы</th>
<th scope="col" class="w-10"></th>
<th scope="col" class="w-10"></th>
<th scope="col" class="w-10"></th>
</tr>
</thead>
<tbody>
<tr th:each="group : ${items}">
<th scope="row" th:text="${group.id}"></th>
<td th:text="${group.name}"></td>
<td>
<form th:action="@{/{id}/members(id=${group.id})}" method="get">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link">Страница группы</button>
</form>
</td>
<td>
<form th:action="@{/edit/{id}(id=${group.id})}" method="get">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link">Редактировать</button>
</form>
</td>
<td>
<form th:action="@{/delete/{id}(id=${group.id})}" method="post">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link"
onclick="return confirm('Вы уверены?')">Удалить</button>
</form>
</td>
</tr>
</tbody>
</table>
</th:block>
<th:block th:replace="~{ pagination :: pagination (
url=${''},
totalPages=${totalPages},
currentPage=${currentPage}) }" />
</th:block>
</main>
</body>
</html>

View File

@ -0,0 +1,44 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Вход</title>
</head>
<body>
<main layout:fragment="content">
<form action="#" th:action="@{/login}" method="post">
<div th:if="${param.error}" class="alert alert-danger">
Неверный логин или пароль
</div>
<div th:if="${param.logout}" class="alert alert-success">
Выход успешно произведен
</div>
<div th:if="${param.signup}" class="alert alert-success">
Пользователь успешно создан
</div>
<div class="mb-3">
<label for="username" class="form-label">Имя пользователя</label>
<input type="text" id="username" name="username" class="form-control" required minlength="3"
maxlength="20">
</div>
<div class="mb-3">
<label for="password" class="form-label">Пароль</label>
<input type="password" id="password" name="password" class="form-control" required minlength="3"
maxlength="20">
</div>
<div class="form-check mb-3">
<input class="form-check-input" type="checkbox" id="remember-me" name="remember-me" checked>
<label class="form-check-label" for="remember-me">Запомнить меня</label>
</div>
<div class="mb-3 d-flex flex-row">
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Войти</button>
<a class="btn btn-secondary button-fixed-width" href="/signup">Регистрация</a>
</div>
</form>
</main>
</body>
</html>
</html>

View File

@ -0,0 +1,43 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Редактировать участника</title>
</head>
<body>
<main layout:fragment="content">
<form action="#" th:action="@{/{groupid}/members/edit/{id}(groupid=${group},id=${member.id})}" th:object="${member}" method="post">
<div class="mb-3">
<label for="id" class="form-label">ID</label>
<input type="text" th:value="*{id}" id="id" class="form-control" readonly disabled>
</div>
<div class="mb-3">
<label for="name" class="form-label">Имя участника</label>
<input type="text" th:field="*{name}" id="name" class="form-control">
<div th:if="${#fields.hasErrors('name')}" th:errors="*{name}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="handle" class="form-label">Хэндл участника</label>
<input type="text" th:field="*{handle}" id="handle" class="form-control">
<div th:if="${#fields.hasErrors('handle')}" th:errors="*{handle}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="rating" class="form-label">Рейтинг участника</label>
<input type="number" th:field="*{rating}" id="rating" class="form-control">
<div th:if="${#fields.hasErrors('rating')}" th:errors="*{rating}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="rank" class="form-label">Ранг участника</label>
<input type="text" th:field="*{rank}" id="rank" class="form-control">
<div th:if="${#fields.hasErrors('rank')}" th:errors="*{rank}" class="invalid-feedback"></div>
</div>
<div class="mb-3 d-flex flex-row">
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Сохранить</button>
<a class="btn btn-secondary button-fixed-width" th:href="@{/{groupid}/members(groupid=${group})}">Отмена</a>
</div>
</form>
</main>
</body>
</html>

View File

@ -0,0 +1,62 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Участники группы</title>
</head>
<body>
<main layout:fragment="content">
<h2>Участники группы</h2>
<div>
<a th:href="@{/{groupid}/members/edit(groupid=${group})}" class="btn btn-primary">Добавить участника</a>
</div>
<th:block th:switch="${items.size()}">
<h2 th:case="0">Данные отсутствуют</h2>
<th:block th:case="*">
<table class="table">
<caption></caption>
<thead>
<tr>
<th scope="col" class="w-8">ID</th>
<th scope="col" class="w-19">Имя участника</th>
<th scope="col" class="w-19">Хэндл</th>
<th scope="col" class="w-19">Рейтинг</th>
<th scope="col" class="w-19">Звание</th>
<th scope="col" class="w-8"></th>
<th scope="col" class="w-8"></th>
</tr>
</thead>
<tbody>
<tr th:each="member : ${items}">
<th scope="row" th:text="${member.id}"></th>
<td th:text="${member.name}"></td>
<td th:text="${member.handle}"></td>
<td th:text="${member.rating}"></td>
<td th:text="${member.rank}"></td>
<td>
<form th:action="@{/{groupid}/members/edit/{id}(groupid=${group},id=${member.id})}" method="get">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link">Редактировать</button>
</form>
</td>
<td>
<form th:action="@{/{groupid}/members/delete/{id}(groupid=${group},id=${member.id})}" method="post">
<input type="hidden" th:name="page" th:value="${page}">
<button type="submit" class="btn btn-link button-link"
onclick="return confirm('Вы уверены?')">Удалить</button>
</form>
</td>
</tr>
</tbody>
</table>
</th:block>
<th:block th:with="group=${group}" th:replace="~{ pagination :: pagination (
url=@{{group}/members(group=${group})},
totalPages=${totalPages},
currentPage=${currentPage}) }" />
</th:block>
</main>
</body>
</html>

View File

@ -0,0 +1,51 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<body>
<th:block th:fragment="pagination (url, totalPages, currentPage)">
<nav th:if="${totalPages > 1}" th:with="
maxPage=2,
currentPage=${currentPage + 1}">
<ul class="pagination justify-content-center"
th:with="
seqFrom=${currentPage - maxPage < 1 ? 1 : currentPage - maxPage},
seqTo=${currentPage + maxPage > totalPages ? totalPages : currentPage + maxPage}">
<th:block th:if="${currentPage > maxPage + 1}">
<li class="page-item">
<a class="page-link" aria-label="Previous" th:href="@{/{url}?page=0(url=${url})}">
<span aria-hidden="true">&laquo;</span>
</a>
</li>
<li class="page-item disabled">
<span class="page-link" aria-label="Previous">
<span aria-hidden="true">&hellip;</span>
</span>
</li>
</th:block>
<li class="page-item" th:each="page : ${#numbers.sequence(seqFrom, seqTo)}"
th:classappend="${page == currentPage} ? 'active' : ''">
<a class=" page-link" th:href="@{/{url}?page={page}(url=${url},page=${page - 1})}">
<span th:text="${page}" />
</a>
</li>
<th:block th:if="${currentPage < totalPages - maxPage}">
<li class="page-item disabled">
<span class="page-link" aria-label="Previous">
<span aria-hidden="true">&hellip;</span>
</span>
</li>
<li class="page-item">
<a class="page-link" aria-label="Next"
th:href="@{/{url}?page={page}(url=${url},page=${totalPages - 1})}">
<span aria-hidden="true">&raquo;</span>
</a>
</li>
</th:block>
</ul>
</nav>
</th:block>
</body>
</html>

View File

@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="ru" xmlns:layout="http://www.ultraq.net.nz/thymeleaf/layout" layout:decorate="~{default}">
<head>
<title>Вход</title>
</head>
<body>
<main layout:fragment="content">
<form action="#" th:action="@{/signup}" th:object="${user}" method="post">
<div class="mb-3">
<label for="login" class="form-label">Имя пользователя</label>
<input type="text" th:field="*{login}" id="login" class="form-control">
<div th:if="${#fields.hasErrors('login')}" th:errors="*{login}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Пароль</label>
<input type="password" th:field="*{password}" id="password" class="form-control">
<div th:if="${#fields.hasErrors('password')}" th:errors="*{password}" class="invalid-feedback"></div>
</div>
<div class="mb-3">
<label for="passwordConfirm" class="form-label">Пароль (подтверждение)</label>
<input type="password" th:field="*{passwordConfirm}" id="passwordConfirm" class="form-control">
<div th:if="${#fields.hasErrors('passwordConfirm')}" th:errors="*{passwordConfirm}"
class="invalid-feedback"></div>
</div>
<div class="mb-3 d-flex flex-row">
<button class="btn btn-primary me-2 button-fixed-width" type="submit">Регистрация</button>
<a class="btn btn-secondary button-fixed-width" href="/">Отмена</a>
</div>
</form>
</main>
</body>
</html>
</html>

View File

@ -21,67 +21,67 @@ import com.example.demo.users.service.UserService;
@SpringBootTest @SpringBootTest
@TestMethodOrder(OrderAnnotation.class) @TestMethodOrder(OrderAnnotation.class)
class GroupServiceTests { class GroupServiceTests {
@Autowired // @Autowired
private GroupService groupService; // private GroupService groupService;
@Autowired // @Autowired
private UserService userService; // private UserService userService;
private GroupEntity group; // private GroupEntity group;
private UserEntity user; // private UserEntity user;
@BeforeEach // @BeforeEach
void createData() { // void createData() {
removeData(); // removeData();
user = userService.create(new UserEntity("oleg", "tester1", "1234")); // user = userService.create(new UserEntity("oleg", "tester1", "1234"));
groupService.create(user.getId(), new GroupEntity("group1")); // groupService.create(user.getId(), new GroupEntity("group1"));
groupService.create(user.getId(), new GroupEntity("group2")); // groupService.create(user.getId(), new GroupEntity("group2"));
group = groupService.create(user.getId(), new GroupEntity("group3")); // group = groupService.create(user.getId(), new GroupEntity("group3"));
} // }
@AfterEach // @AfterEach
void removeData() { // void removeData() {
userService.getAll().forEach(item -> userService.delete(item.getId())); // userService.getAll().forEach(item -> userService.delete(item.getId()));
} // }
@Test // @Test
void getTest() { // void getTest() {
Assertions.assertThrows(NotFoundException.class, () -> groupService.get(0L)); // Assertions.assertThrows(NotFoundException.class, () -> groupService.get(0L));
} // }
@Test // @Test
void createTest() { // void createTest() {
Assertions.assertEquals(3, groupService.getAll(user.getId()).size()); // Assertions.assertEquals(3, groupService.getAll(user.getId()).size());
var res = groupService.get(group.getId()); // var res = groupService.get(group.getId());
Assertions.assertEquals(group, groupService.get(group.getId())); // Assertions.assertEquals(group, groupService.get(group.getId()));
} // }
@Test // @Test
void createNullableTest() { // void createNullableTest() {
final GroupEntity nullableGroup= new GroupEntity(null); // final GroupEntity nullableGroup= new GroupEntity(null);
Assertions.assertThrows(DataIntegrityViolationException.class, () -> groupService.create(user.getId(), nullableGroup)); // Assertions.assertThrows(DataIntegrityViolationException.class, () -> groupService.create(user.getId(), nullableGroup));
} // }
@Test // @Test
void updateTest() { // void updateTest() {
final String test = "TEST"; // final String test = "TEST";
final String oldName = group.getName(); // final String oldName = group.getName();
final GroupEntity newEntity = groupService.update(group.getId(), new GroupEntity(test)); // final GroupEntity newEntity = groupService.update(group.getId(), new GroupEntity(test));
Assertions.assertEquals(3, groupService.getAll(user.getId()).size()); // Assertions.assertEquals(3, groupService.getAll(user.getId()).size());
Assertions.assertEquals(newEntity, groupService.get(group.getId())); // Assertions.assertEquals(newEntity, groupService.get(group.getId()));
Assertions.assertEquals(test, newEntity.getName()); // Assertions.assertEquals(test, newEntity.getName());
Assertions.assertEquals(user, newEntity.getUser()); // Assertions.assertEquals(user, newEntity.getUser());
Assertions.assertNotEquals(oldName, newEntity.getName()); // Assertions.assertNotEquals(oldName, newEntity.getName());
} // }
@Test // @Test
void deleteTest() { // void deleteTest() {
groupService.delete(group.getId()); // groupService.delete(group.getId());
Assertions.assertEquals(2, groupService.getAll(user.getId()).size()); // Assertions.assertEquals(2, groupService.getAll(user.getId()).size());
final GroupEntity newEntity = groupService.create(user.getId(), new GroupEntity(group.getName())); // final GroupEntity newEntity = groupService.create(user.getId(), new GroupEntity(group.getName()));
Assertions.assertEquals(3, groupService.getAll(user.getId()).size()); // Assertions.assertEquals(3, groupService.getAll(user.getId()).size());
Assertions.assertNotEquals(group.getId(), newEntity.getId()); // Assertions.assertNotEquals(group.getId(), newEntity.getId());
} // }
} }

View File

@ -34,66 +34,66 @@ class MemberServiceTests {
private GroupEntity group; private GroupEntity group;
private UserEntity user; private UserEntity user;
@BeforeEach // @BeforeEach
void createData() { // void createData() {
removeData(); // removeData();
user = userService.create(new UserEntity("oleg", "tester1", "1234")); // user = userService.create(new UserEntity("oleg", "tester1", "1234"));
group = groupService.create(user.getId(), new GroupEntity("group1")); // group = groupService.create(user.getId(), new GroupEntity("group1"));
member = memberService.create(group.getId(), new MemberEntity("member1", "mem1","expert", 1615L)); // member = memberService.create(group.getId(), new MemberEntity("member1", "mem1","expert", 1615L));
memberService.create(group.getId(), new MemberEntity("member1", "mem1","expert", 1712L)); // memberService.create(group.getId(), new MemberEntity("member1", "mem1","expert", 1712L));
memberService.create(group.getId(), new MemberEntity("member1", "mem1","expert", 1651L)); // memberService.create(group.getId(), new MemberEntity("member1", "mem1","expert", 1651L));
} // }
@AfterEach // @AfterEach
void removeData() { // void removeData() {
userService.getAll().forEach(item -> userService.delete(item.getId())); // userService.getAll().forEach(item -> userService.delete(item.getId()));
} // }
@Test // @Test
void getTest() { // void getTest() {
Assertions.assertThrows(NotFoundException.class, () -> memberService.get(0L)); // Assertions.assertThrows(NotFoundException.class, () -> memberService.get(0L));
} // }
@Test // @Test
void createTest() { // void createTest() {
Assertions.assertEquals(3, memberService.getAll(group.getId()).size()); // Assertions.assertEquals(3, memberService.getAll(group.getId()).size());
Assertions.assertEquals(member, memberService.get(member.getId())); // Assertions.assertEquals(member, memberService.get(member.getId()));
} // }
@Test // @Test
void createNullableTest() { // void createNullableTest() {
final MemberEntity nullableMember= new MemberEntity(null, null, null, null); // final MemberEntity nullableMember= new MemberEntity(null, null, null, null);
Assertions.assertThrows(DataIntegrityViolationException.class, () -> memberService.create(group.getId(), nullableMember)); // Assertions.assertThrows(DataIntegrityViolationException.class, () -> memberService.create(group.getId(), nullableMember));
} // }
@Test // @Test
void updateTest() { // void updateTest() {
final String test = "TEST"; // final String test = "TEST";
final String oldName = member.getName(); // final String oldName = member.getName();
final String oldHandle = member.getHandle(); // final String oldHandle = member.getHandle();
final String oldRank = member.getRank(); // final String oldRank = member.getRank();
final MemberEntity newEntity = memberService.update(member.getId(), new MemberEntity(test, test, test, null)); // final MemberEntity newEntity = memberService.update(member.getId(), new MemberEntity(test, test, test, null));
Assertions.assertEquals(3, memberService.getAll(group.getId()).size()); // Assertions.assertEquals(3, memberService.getAll(group.getId()).size());
Assertions.assertEquals(newEntity, memberService.get(member.getId())); // Assertions.assertEquals(newEntity, memberService.get(member.getId()));
Assertions.assertEquals(test, newEntity.getName()); // Assertions.assertEquals(test, newEntity.getName());
Assertions.assertEquals(test, newEntity.getHandle()); // Assertions.assertEquals(test, newEntity.getHandle());
Assertions.assertEquals(test, newEntity.getRank()); // Assertions.assertEquals(test, newEntity.getRank());
Assertions.assertNotEquals(oldName, newEntity.getName()); // Assertions.assertNotEquals(oldName, newEntity.getName());
Assertions.assertNotEquals(oldHandle, newEntity.getHandle()); // Assertions.assertNotEquals(oldHandle, newEntity.getHandle());
Assertions.assertNotEquals(oldRank, newEntity.getRank()); // Assertions.assertNotEquals(oldRank, newEntity.getRank());
} // }
@Test // @Test
void deleteTest() { // void deleteTest() {
memberService.delete(member.getId()); // memberService.delete(member.getId());
Assertions.assertEquals(2, memberService.getAll(group.getId()).size()); // Assertions.assertEquals(2, memberService.getAll(group.getId()).size());
final MemberEntity newEntity = memberService.create(group.getId(), new MemberEntity(member.getName(), member.getHandle(), member.getRank(), // final MemberEntity newEntity = memberService.create(group.getId(), new MemberEntity(member.getName(), member.getHandle(), member.getRank(),
member.getRating())); // member.getRating()));
Assertions.assertEquals(3, memberService.getAll(group.getId()).size()); // Assertions.assertEquals(3, memberService.getAll(group.getId()).size());
Assertions.assertNotEquals(member.getId(), newEntity.getId()); // Assertions.assertNotEquals(member.getId(), newEntity.getId());
} // }
} }

View File

@ -14,87 +14,87 @@ import com.example.demo.users.service.UserService;
@SpringBootTest @SpringBootTest
class UserServiceTests { class UserServiceTests {
@Autowired // @Autowired
private UserService userService; // private UserService userService;
private UserEntity user; // private UserEntity user;
@BeforeEach // @BeforeEach
void createData() { // void createData() {
removeData(); // removeData();
user = userService.create(new UserEntity("oleg", "tester1", "1234")); // user = userService.create(new UserEntity("oleg", "tester1", "1234"));
userService.create(new UserEntity("arthur", "tester2", "123")); // userService.create(new UserEntity("arthur", "tester2", "123"));
userService.create(new UserEntity("egor", "tester3", "12345")); // userService.create(new UserEntity("egor", "tester3", "12345"));
} // }
@AfterEach // @AfterEach
void removeData() { // void removeData() {
userService.getAll().forEach(item -> userService.delete(item.getId())); // userService.getAll().forEach(item -> userService.delete(item.getId()));
} // }
@Test // @Test
void getTest() { // void getTest() {
Assertions.assertThrows(NotFoundException.class, () -> userService.get(0L)); // Assertions.assertThrows(NotFoundException.class, () -> userService.get(0L));
} // }
@Test // @Test
void createTest() { // void createTest() {
Assertions.assertEquals(3, userService.getAll().size()); // Assertions.assertEquals(3, userService.getAll().size());
Long id = user.getId(); // Long id = user.getId();
var res = userService.get(user.getId()); // var res = userService.get(user.getId());
Assertions.assertEquals(user, userService.get(user.getId())); // Assertions.assertEquals(user, userService.get(user.getId()));
} // }
@Test // @Test
void createNotUniqueHandleTest() { // void createNotUniqueHandleTest() {
final UserEntity nonUniqueHandleUser = new UserEntity("oleg", "tester4", "1234"); // final UserEntity nonUniqueHandleUser = new UserEntity("oleg", "tester4", "1234");
Assertions.assertThrows(IllegalArgumentException.class, () -> userService.create(nonUniqueHandleUser)); // Assertions.assertThrows(IllegalArgumentException.class, () -> userService.create(nonUniqueHandleUser));
} // }
@Test // @Test
void createNullableHandleTest() { // void createNullableHandleTest() {
final UserEntity nullableUser = new UserEntity(null, "asads", "asdsad"); // final UserEntity nullableUser = new UserEntity(null, "asads", "asdsad");
Assertions.assertThrows(DataIntegrityViolationException.class, () -> userService.create(nullableUser)); // Assertions.assertThrows(DataIntegrityViolationException.class, () -> userService.create(nullableUser));
} // }
@Test // @Test
void createNullableEmailTest() { // void createNullableEmailTest() {
final UserEntity nullableUser = new UserEntity("asads", null, "asdsad"); // final UserEntity nullableUser = new UserEntity("asads", null, "asdsad");
Assertions.assertThrows(DataIntegrityViolationException.class, () -> userService.create(nullableUser)); // Assertions.assertThrows(DataIntegrityViolationException.class, () -> userService.create(nullableUser));
} // }
@Test // @Test
void createNullablePassswordTest() { // void createNullablePassswordTest() {
final UserEntity nullableUser = new UserEntity("asads", "asads", null); // final UserEntity nullableUser = new UserEntity("asads", "asads", null);
Assertions.assertThrows(DataIntegrityViolationException.class, () -> userService.create(nullableUser)); // Assertions.assertThrows(DataIntegrityViolationException.class, () -> userService.create(nullableUser));
} // }
@Test // @Test
void updateTest() { // void updateTest() {
final String test = "TEST"; // final String test = "TEST";
final String oldHandle = user.getHandle(); // final String oldHandle = user.getHandle();
final String oldEmail = user.getEmail(); // final String oldEmail = user.getEmail();
final String oldPassword = user.getPassword(); // final String oldPassword = user.getPassword();
final UserEntity newEntity = userService.update(user.getId(), new UserEntity(test, test,test)); // final UserEntity newEntity = userService.update(user.getId(), new UserEntity(test, test,test));
Assertions.assertEquals(3, userService.getAll().size()); // Assertions.assertEquals(3, userService.getAll().size());
Assertions.assertEquals(newEntity, userService.get(user.getId())); // Assertions.assertEquals(newEntity, userService.get(user.getId()));
Assertions.assertEquals(test, newEntity.getHandle()); // Assertions.assertEquals(test, newEntity.getHandle());
Assertions.assertEquals(test, newEntity.getEmail()); // Assertions.assertEquals(test, newEntity.getEmail());
Assertions.assertEquals(test, newEntity.getPassword()); // Assertions.assertEquals(test, newEntity.getPassword());
Assertions.assertNotEquals(oldHandle, newEntity.getHandle()); // Assertions.assertNotEquals(oldHandle, newEntity.getHandle());
Assertions.assertNotEquals(oldEmail, newEntity.getEmail()); // Assertions.assertNotEquals(oldEmail, newEntity.getEmail());
Assertions.assertNotEquals(oldPassword, newEntity.getPassword()); // Assertions.assertNotEquals(oldPassword, newEntity.getPassword());
} // }
@Test // @Test
void deleteTest() { // void deleteTest() {
userService.delete(user.getId()); // userService.delete(user.getId());
Assertions.assertEquals(2, userService.getAll().size()); // Assertions.assertEquals(2, userService.getAll().size());
final UserEntity newEntity = userService.create(new UserEntity(user.getHandle(), user.getEmail(), user.getPassword())); // final UserEntity newEntity = userService.create(new UserEntity(user.getHandle(), user.getEmail(), user.getPassword()));
Assertions.assertEquals(3, userService.getAll().size()); // Assertions.assertEquals(3, userService.getAll().size());
Assertions.assertNotEquals(user.getId(), newEntity.getId()); // Assertions.assertNotEquals(user.getId(), newEntity.getId());
} // }
} }