Move to new server

This commit is contained in:
2024-10-12 13:37:59 +02:00
commit 2959eb5cfd
111 changed files with 12795 additions and 0 deletions

View File

@@ -0,0 +1,15 @@
package nl.connectedit.swiss;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement
public class SwissApplication {
public static void main(String[] args) {
SpringApplication.run(SwissApplication.class, args);
}
}

View File

@@ -0,0 +1,46 @@
package nl.connectedit.swiss.authentication;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ResponseStatusException;
@RestController
@ConditionalOnExpression("${security}")
@RequiredArgsConstructor
@CrossOrigin
public class AuthenticationController {
private final JwtUserDetailsService jwtUserDetailsService;
private final AuthenticationManager authenticationManager;
private final JwtTokenService jwtTokenService;
@PostMapping("/authenticate")
public AuthenticationResponse authenticate(@RequestBody @Valid final AuthenticationRequest authenticationRequest) {
try {
authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
authenticationRequest.getUsername(), authenticationRequest.getPassword()));
} catch (final BadCredentialsException ex) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED);
}
final UserDetails userDetails = jwtUserDetailsService.loadUserByUsername(authenticationRequest.getUsername());
final AuthenticationResponse authenticationResponse = new AuthenticationResponse();
authenticationResponse.setAccessToken(jwtTokenService.generateToken(userDetails));
authenticationResponse.setUsername(authenticationRequest.getUsername());
return authenticationResponse;
}
}

View File

@@ -0,0 +1,20 @@
package nl.connectedit.swiss.authentication;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AuthenticationRequest {
@NotNull
@Size(max = 255)
private String username;
@NotNull
@Size(max = 255)
private String password;
}

View File

@@ -0,0 +1,14 @@
package nl.connectedit.swiss.authentication;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class AuthenticationResponse {
private String accessToken;
private String username;
}

View File

@@ -0,0 +1,13 @@
package nl.connectedit.swiss.authentication;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class Client {
private Long id;
private String hash;
}

View File

@@ -0,0 +1,23 @@
package nl.connectedit.swiss.authentication;
import org.springframework.stereotype.Repository;
import org.springframework.util.DigestUtils;
import java.util.Optional;
@Repository
public class ClientRepository {
private static final String hash = "$2a$12$FjsFqFTorg0sXCiSISFS3.xvSCzmAATIcA7wh5w8WtQ7eYZC.H4UW";
Optional<Client> findByLogin(String username) {
if (username.equals("bcholten")) {
var client = new Client();
client.setId(1L);
client.setHash(hash);
return Optional.of(client);
} else {
return Optional.empty();
}
}
}

View File

@@ -0,0 +1,57 @@
package nl.connectedit.swiss.authentication;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Profile;
import org.springframework.http.HttpHeaders;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import java.io.IOException;
@Component
@ConditionalOnExpression("${security}")
@RequiredArgsConstructor
public class JwtRequestFilter extends OncePerRequestFilter {
private final JwtTokenService jwtTokenService;
private final JwtUserDetailsService jwtUserDetailsService;
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain chain) throws IOException, ServletException {
// look for Bearer auth header
final String header = request.getHeader(HttpHeaders.AUTHORIZATION);
if (header == null || !header.startsWith("Bearer ")) {
chain.doFilter(request, response);
return;
}
final String token = header.substring(7);
final String username = jwtTokenService.validateTokenAndGetUsername(token);
if (username == null) {
// validation failed or token expired
chain.doFilter(request, response);
return;
}
// set user details on spring security context
final JwtUserDetails userDetails = jwtUserDetailsService.loadUserByUsername(username);
final UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authentication);
// continue with authenticated user
chain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,62 @@
package nl.connectedit.swiss.authentication;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@ConditionalOnExpression("${security}")
@EnableWebSecurity
@RequiredArgsConstructor
public class JwtSecurityConfig {
private final JwtRequestFilter jwtRequestFilter;
@Bean
BCryptPasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(
final AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
@Bean
public SecurityFilterChain configure(final HttpSecurity http) throws Exception {
return http.cors(withDefaults())
.csrf((csrf) -> csrf.disable())
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/", "/authenticate", "/testdata").permitAll()
.anyRequest().hasAuthority(UserRoles.ROLE_USER))
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedMethods("HEAD", "GET", "PUT", "POST", "DELETE", "PATCH");
}
};
}
}

View File

@@ -0,0 +1,51 @@
package nl.connectedit.swiss.authentication;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.time.Instant;
@Service
@ConditionalOnExpression("${security}")
public class JwtTokenService {
private static final Duration JWT_TOKEN_VALIDITY = Duration.ofDays(7);
private final Algorithm rsa256;
private final JWTVerifier verifier;
public JwtTokenService(@Value("classpath:certs/public.pem") final RSAPublicKey publicKey,
@Value("classpath:certs/private.pem") final RSAPrivateKey privateKey) {
this.rsa256 = Algorithm.RSA256(publicKey, privateKey);
this.verifier = JWT.require(this.rsa256).build();
}
public String generateToken(final UserDetails userDetails) {
final Instant now = Instant.now();
return JWT.create()
.withSubject(userDetails.getUsername())
.withIssuer("app")
.withIssuedAt(now)
.withExpiresAt(now.plusMillis(JWT_TOKEN_VALIDITY.toMillis()))
.sign(this.rsa256);
}
public String validateTokenAndGetUsername(final String token) {
try {
return verifier.verify(token).getSubject();
} catch (final JWTVerificationException verificationEx) {
// log.warn("token invalid: {}", verificationEx.getMessage());
return null;
}
}
}

View File

@@ -0,0 +1,19 @@
package nl.connectedit.swiss.authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Collection;
public class JwtUserDetails extends User {
public final Long id;
public JwtUserDetails(final Long id, final String username, final String hash,
final Collection<? extends GrantedAuthority> authorities) {
super(username, hash, authorities);
this.id = id;
}
}

View File

@@ -0,0 +1,27 @@
package nl.connectedit.swiss.authentication;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Collections;
import java.util.List;
@Service
@RequiredArgsConstructor
public class JwtUserDetailsService implements UserDetailsService {
private final ClientRepository clientRepository;
@Override
public JwtUserDetails loadUserByUsername(final String username) {
final Client client = clientRepository.findByLogin(username).orElseThrow(
() -> new UsernameNotFoundException("User " + username + " not found"));
final List<SimpleGrantedAuthority> roles = Collections.singletonList(new SimpleGrantedAuthority(UserRoles.ROLE_USER));
return new JwtUserDetails(client.getId(), username, client.getHash(), roles);
}
}

View File

@@ -0,0 +1,13 @@
package nl.connectedit.swiss.authentication;
import lombok.Data;
@Data
public class LoginCredentialsDto {
private String username;
private String password;
private String ipAddres;
}

View File

@@ -0,0 +1,16 @@
package nl.connectedit.swiss.authentication;
import lombok.Data;
@Data
public class UserDto {
private String username;
private String token;
private String refreshToken;
private String ipAddress;
}

View File

@@ -0,0 +1,8 @@
package nl.connectedit.swiss.authentication;
public class UserRoles {
public static final String USER = "USER";
public static final String ROLE_USER = "ROLE_" + USER;
}

View File

@@ -0,0 +1,25 @@
package nl.connectedit.swiss.config;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
//@Component
public class CorsConfig extends OncePerRequestFilter {
@Override
protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response,
final FilterChain filterChain) throws ServletException, IOException {
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "GET, POST, DELETE, PUT, PATCH, HEAD");
response.addHeader("Access-Control-Allow-Headers", "Origin, Accept, X-Requested-With, Content-Type, Access-Control-Request-Method, Access-Control-Request-Headers");
response.addHeader("Access-Control-Expose-Headers", "Access-Control-Allow-Origin, Access-Control-Allow-Credentials");
response.addHeader("Access-Control-Allow-Credentials", "true");
response.addIntHeader("Access-Control-Max-Age", 10);
filterChain.doFilter(request, response);
}
}

View File

@@ -0,0 +1,14 @@
package nl.connectedit.swiss.config;
import com.fasterxml.jackson.databind.Module;
import com.fasterxml.jackson.datatype.hibernate5.jakarta.Hibernate5JakartaModule;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class JsonConfig {
@Bean
public Module hibernateModule() {
return new Hibernate5JakartaModule();
}
}

View File

@@ -0,0 +1,26 @@
package nl.connectedit.swiss.config;
import jakarta.servlet.DispatcherType;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.web.SecurityFilterChain;
//@Configuration
//@EnableWebSecurity
public class SecurityConfig {
// @Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll());
// .authorizeHttpRequests(request -> {
// request.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll();
// request.requestMatchers("/error").permitAll();
// });
return http.build();
}
}

View File

@@ -0,0 +1,53 @@
package nl.connectedit.swiss.controller;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Player;
import nl.connectedit.swiss.dto.PlayerDto;
import nl.connectedit.swiss.mapper.PlayerMapper;
import nl.connectedit.swiss.service.PlayerService;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@CrossOrigin
@RequiredArgsConstructor
public class PlayerController {
private final PlayerService playerService;
private final PlayerMapper playerMapper;
@GetMapping("/players")
public ResponseEntity<List<PlayerDto>> getPlayers() {
return ResponseEntity.ok(playerService.findAllPlayers()
.stream()
.map(playerMapper::toDto)
.toList());
}
@GetMapping("/players/{id}")
public ResponseEntity<PlayerDto> getPlayer(@PathVariable Long id) {
return ResponseEntity.ok(playerMapper.toDto(playerService.findPlayerById(id)));
}
@PostMapping("/players")
public ResponseEntity<PlayerDto> createPlayer(@RequestBody PlayerDto playerDto) {
Player player;
try {
player = playerMapper.toEntity(playerDto);
} catch (NullPointerException e) {
return ResponseEntity.badRequest().build();
}
return ResponseEntity.ok(playerMapper.toDto(playerService.savePlayer(player)));
}
@PutMapping("/players/{id}")
public ResponseEntity<PlayerDto> updatePlayer(@PathVariable("id") Long id, @RequestBody PlayerDto playerDto) {
var player = playerMapper.toEntity(playerDto);
player.setId(id);
return ResponseEntity.ok(playerMapper.toDto(playerService.savePlayer(player)));
}
}

View File

@@ -0,0 +1,53 @@
package nl.connectedit.swiss.controller;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.dto.TournamentRegistrationDto;
import nl.connectedit.swiss.mapper.TournamentPlayerRegistrationMapper;
import nl.connectedit.swiss.service.PlayerService;
import nl.connectedit.swiss.service.RegistrationService;
import nl.connectedit.swiss.service.TournamentService;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@CrossOrigin
@RequiredArgsConstructor
public class RegistrationController {
private final PlayerService playerService;
private final RegistrationService registrationService;
private final TournamentService tournamentService;
private final TournamentPlayerRegistrationMapper tournamentPlayerRegistrationMapper;
@GetMapping("/players/{playerId}/registrations")
public List<TournamentRegistrationDto> getRegistrationsForPlayer(@PathVariable Long playerId) {
var player = playerService.findPlayerById(playerId);
var tournaments = tournamentService.findAllTournaments();
return tournamentPlayerRegistrationMapper.mapToDto(tournaments, player);
}
@PostMapping("/players/{playerId}/registrations/{tournamentId}")
@ResponseStatus(value = HttpStatus.NO_CONTENT)
public TournamentRegistrationDto registerTournamentRegistrations(@PathVariable Long playerId, @PathVariable Long tournamentId, @RequestBody TournamentRegistrationDto tournamentRegistrationDto) {
var player = playerService.findPlayerById(playerId);
var tournament = tournamentService.findTournamentById(tournamentId);
for (var eventRegistration : tournamentRegistrationDto.getEvents()) {
registrationService.updateOrAddRegistrations(eventRegistration, tournament, player);
}
return tournamentPlayerRegistrationMapper.mapToTournamentRegistrationDto(tournament, player);
}
}

View File

@@ -0,0 +1,21 @@
package nl.connectedit.swiss.controller;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
//@ControllerAdvice
public class RestResponseEntityExceptionHandler
extends ResponseEntityExceptionHandler {
@ExceptionHandler({AccessDeniedException.class})
public ResponseEntity<Object> handleAccessDeniedException(
Exception ex, WebRequest request) {
return new ResponseEntity<Object>(ex.getMessage(), new HttpHeaders(), HttpStatus.FORBIDDEN);
}
}

View File

@@ -0,0 +1,507 @@
package nl.connectedit.swiss.controller;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import nl.connectedit.swiss.domain.*;
import nl.connectedit.swiss.domain.entity.Player;
import nl.connectedit.swiss.domain.entity.Registration;
import nl.connectedit.swiss.domain.entity.Tournament;
import nl.connectedit.swiss.domain.entity.TournamentPlayer;
import nl.connectedit.swiss.dto.*;
import nl.connectedit.swiss.service.*;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.LocalDate;
import java.time.Month;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.concurrent.ThreadLocalRandom;
import static nl.connectedit.swiss.domain.PlayerStrength.*;
@RestController
@CrossOrigin
@RequiredArgsConstructor
public class TestController {
private final TournamentService tournamentService;
private final PlayerService playerService;
private final TournamentDivideService tournamentDivideService;
private final TournamentDrawService tournamentDrawService;
private final TournamentPlayService tournamentPlayService;
private final ObjectMapper objectMapper;
private final RestTemplateBuilder restTemplateBuilder;
private RestTemplate restTemplate;
private final PlayerController playerController;
private String authorizationHeader;
@Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
@GetMapping("/testdata")
// @PostConstruct
// @Transactional
public void init(@RequestHeader(name = "Authorization", required = false) String authorizationHeader) {
this.authorizationHeader = authorizationHeader;
restTemplate = restTemplateBuilder.build();
var tournament = createTournament();
getMales().forEach(player -> savePlayer(player, Sex.M));
getFemales().forEach(player -> savePlayer(player, Sex.V));
registerForSingles(tournament);
registerForDoubles(tournament);
}
@SuppressWarnings("DataFlowIssue")
private List<PlayerDto> getPlayers() {
var headers = new HttpHeaders();
headers.add("Authorization", authorizationHeader);
var entity = new HttpEntity<>(headers);
// return Arrays.asList(restTemplate.getForEntity("http://localhost:8080/players", PlayerDto[].class).getBody());
return Arrays.asList(restTemplate.exchange("http://localhost:8080/players", HttpMethod.GET, entity, PlayerDto[].class).getBody());
}
private void registerForSingles(TournamentDto tournament) {
var eventIdHE = tournament.getEvents().stream().filter(event -> event.getType().equals("HE")).findFirst().get().getId();
var eventIdDE = tournament.getEvents().stream().filter(event -> event.getType().equals("DE")).findFirst().get().getId();
for (var player : getPlayers()) {
var tournamentRegistrationDto = new TournamentRegistrationDto();
tournamentRegistrationDto.setId(tournament.getId());
var eventRegistrationDto = new EventRegistrationDto();
eventRegistrationDto.setId(player.getSex().equals("M") ? eventIdHE : eventIdDE);
eventRegistrationDto.setRegistered(true);
eventRegistrationDto.setPlayer(player.getId());
eventRegistrationDto.setType(player.getSex().equals("M") ? "HE" : "DE");
tournamentRegistrationDto.setEvents(List.of(eventRegistrationDto));
var headers = new HttpHeaders();
headers.add("Authorization", authorizationHeader);
var entity = new HttpEntity<>(tournamentRegistrationDto, headers);
restTemplate.postForObject("http://localhost:8080/players/%d/registrations/%d".formatted(player.getId(), tournament.getId()), entity, TournamentRegistrationDto.class);
}
}
private void registerForDoubles(TournamentDto tournament) {
var males = getPlayers().stream().filter(p -> p.getSex().equals("M")).toArray(PlayerDto[]::new);
var females = getPlayers().stream().filter(p -> p.getSex().equals("V")).toArray(PlayerDto[]::new);
for (int i = 0; i <= 6; i++) {
registerForDoubles(tournament, males[i * 2], males[i * 2 + 1]);
registerForDoubles(tournament, males[i * 2 + 1], males[i * 2]);
}
for (int i = 0; i <= 8; i++) {
registerForDoubles(tournament, females[i * 2], females[i * 2 + 1]);
registerForDoubles(tournament, females[i * 2 + 1], females[i * 2]);
}
for (int i = 18; i <= 29; i++) {
registerForDoubles(tournament, males[i], females[i]);
registerForDoubles(tournament, females[i], males[i]);
}
}
private void registerForDoubles(TournamentDto tournament, PlayerDto player, PlayerDto partner) {
var eventType = player.getSex().equals(partner.getSex()) ? (player.getSex().equals("M") ? "HD" : "DD") : "GD";
var eventId = tournament.getEvents().stream().filter(event -> event.getType().equals(eventType)).findFirst().get().getId();
// for (var player : getPlayers()) {
var tournamentRegistrationDto = new TournamentRegistrationDto();
tournamentRegistrationDto.setId(tournament.getId());
var eventRegistrationDto = new EventRegistrationDto();
eventRegistrationDto.setId(eventId);
eventRegistrationDto.setRegistered(true);
eventRegistrationDto.setPlayer(player.getId());
eventRegistrationDto.setPartner(partner.getId());
eventRegistrationDto.setType(eventType);
tournamentRegistrationDto.setEvents(List.of(eventRegistrationDto));
var headers = new HttpHeaders();
headers.add("Authorization", authorizationHeader);
var entity = new HttpEntity<>(tournamentRegistrationDto, headers);
restTemplate.postForObject("http://localhost:8080/players/%d/registrations/%d".formatted(player.getId(), tournament.getId()), entity, TournamentRegistrationDto.class);
// }
}
private TournamentDto createTournament() {
var headers = new HttpHeaders();
headers.add("Authorization", authorizationHeader);
var entity = new HttpEntity<>(getTournament(), headers);
return restTemplate.postForObject("http://localhost:8080/tournaments", entity, TournamentDto.class);
}
private TournamentDto getTournament() {
var tournamentDto = new TournamentDto();
tournamentDto.setName("Testtoernooi");
tournamentDto.setDate("14-12-2024");
tournamentDto.setStatus("UPCOMING");
tournamentDto.setMaxEvents(2L);
tournamentDto.setCourts(9L);
tournamentDto.setCostsPerEvent(List.of(6f, 10f, 0f));
return tournamentDto;
}
private void savePlayer(String firstName, Sex sex) {
Random random = new Random();
String[] lastNames = {
"Meedendorp", "Holstege", "Goedhart", "Zijlma", "Duursma", "Keizer", "Verschoor",
"Mulder", "Koers", "Duindam", "Castelein", "Coemans", "Huijbers", "Kelder",
"Stein", "Rakhorst", "Brugman", "Seinen", "Mayer", "Gijsman", "Kingma",
"Jansen", "Westerik", "Brehler", "Ebbers", "Lensink", "Lups", "Verboom"
};
String[] clubs = {
"BC Holten", "BC Reflex", "ZBC", "WSV Apeldoorn", "BC IJsselstad", "Flits", "ELO United", "BC Kwiek"
};
var playerDto = new PlayerDto();
playerDto.setFirstName(firstName);
playerDto.setLastName(lastNames[random.nextInt(lastNames.length)]);
playerDto.setClub(clubs[random.nextInt(clubs.length)]);
playerDto.setSex(sex.name());
var birthday = randomDate(LocalDate.of(1950, 1, 1), LocalDate.of(2010, 1, 1)).format(DateTimeFormatter.ofPattern("dd-MM-yyyy"));
playerDto.setBirthday(birthday);
playerDto.setPhoneNumber("0612345678");
playerDto.setEmail("aaaa@bbb.cc");
playerDto.setStrength(getRandomStrength().name());
var headers = new HttpHeaders();
headers.add("Authorization", authorizationHeader);
var entity = new HttpEntity<>(playerDto, headers);
restTemplate.postForObject("http://localhost:8080/players", entity, PlayerDto.class);
}
private List<String> getMales() {
return List.of("Michel", "Eric", "Leon", "Luuk", "Jeffrey", "Jason", "Oleg", "Gerjan", "Gerard", "Henk",
"Peter", "Gerrit", "Wilco", "Guido", "Sander", "Roy", "Yafiq", "Martijn", "Dick", "Willem", "Layo",
"Thomas", "Gerben", "Bert", "Bart", "Nico", "Jan", "Diederik", "Gert", "Dennis", "Pieter");
}
private List<String> getFemales() {
return List.of("Amber", "Lisa", "Vanja", "Evelien", "Daphne", "Willemijn", "Miranda", "Inge", "Esmee",
"Joanne", "Laura", "Nienke", "Patty", "Rosan", "Vera", "Hedwig", "Lois", "Liedewij", "Gera", "Carolien",
"Anne", "Dominique", "Linda", "Esther", "Marilyn", "Ilse", "Emily", "Eva", "Kitty", "Floor", "Tess", "Fenna");
}
private PlayerStrength getRandomStrength() {
var random = new Random().nextInt(0, 12);
return switch(random) {
case 0 -> D5;
case 1, 2 -> D6;
case 3, 4, 5 -> D7;
case 6, 7 -> D8;
case 8, 9 -> D9;
default -> DR;
};
}
public static LocalDate randomDate(LocalDate startInclusive, LocalDate endExclusive) {
long startEpochDay = startInclusive.toEpochDay();
long endEpochDay = endExclusive.toEpochDay();
long randomDay = ThreadLocalRandom
.current()
.nextLong(startEpochDay, endEpochDay);
return LocalDate.ofEpochDay(randomDay);
}
/*
private void deRest() {
var malePlayers = List.of(
savePlayer("Michel", Sex.M),
savePlayer("Eric", Sex.M),
savePlayer("Leon", Sex.M),
savePlayer("Luuk", Sex.M),
savePlayer("Jeffrey", Sex.M),
savePlayer("Jason", Sex.M),
savePlayer("Oleg", Sex.M),
savePlayer("Gerjan", Sex.M),
savePlayer("Gerard", Sex.M),
savePlayer("Henk", Sex.M),
savePlayer("Peter", Sex.M),
savePlayer("Gerrit", Sex.M),
savePlayer("Wilco", Sex.M),
savePlayer("Guido", Sex.M),
savePlayer("Sander", Sex.M),
savePlayer("Roy", Sex.M),
savePlayer("Yafiq", Sex.M),
savePlayer("Martijn", Sex.M),
savePlayer("Dick", Sex.M),
savePlayer("Willem", Sex.M),
savePlayer("Layo", Sex.M),
savePlayer("Thomas", Sex.M),
savePlayer("Gerben", Sex.M),
savePlayer("Bert", Sex.M),
savePlayer("Bart", Sex.M),
savePlayer("Nico", Sex.M),
savePlayer("Jan", Sex.M),
savePlayer("Diederik", Sex.M),
savePlayer("Gert", Sex.M),
savePlayer("Dennis", Sex.M),
savePlayer("Pieter", Sex.M)
);
var femalePlayers = List.of(
savePlayer("Amber", Sex.V),
savePlayer("Lisa", Sex.V),
savePlayer("Vanja", Sex.V),
savePlayer("Evelien", Sex.V),
savePlayer("Daphne", Sex.V),
savePlayer("Willemijn", Sex.V),
savePlayer("Miranda", Sex.V),
savePlayer("Inge", Sex.V),
savePlayer("Esmee", Sex.V),
savePlayer("Joanne", Sex.V),
savePlayer("Laura", Sex.V),
savePlayer("Nienke", Sex.V),
savePlayer("Patty", Sex.V),
savePlayer("Rosan", Sex.V),
savePlayer("Vera", Sex.V),
savePlayer("Hedwig", Sex.V),
savePlayer("Lois", Sex.V),
savePlayer("Liedewij", Sex.V),
savePlayer("Gera", Sex.V),
savePlayer("Carolien", Sex.V),
savePlayer("Anne", Sex.V),
savePlayer("Dominique", Sex.V),
savePlayer("Linda", Sex.V),
savePlayer("Esther", Sex.V),
savePlayer("Marilyn", Sex.V),
savePlayer("Ilse", Sex.V),
savePlayer("Emily", Sex.V),
savePlayer("Eva", Sex.V),
savePlayer("Kitty", Sex.V),
savePlayer("Floor", Sex.V),
savePlayer("Tess", Sex.V),
savePlayer("Fenna", Sex.V)
);
malePlayers.forEach(player -> registerForSingles(tournamentId, player));
femalePlayers.forEach(player -> registerForSingles(tournamentId, player));
malePlayers = new ArrayList<>(malePlayers);
malePlayers.add(savePlayer("Rolf", Sex.M));
for (var i = 0; i < 14; i += 2) {
registerForDoubles(tournamentId, malePlayers.get(i), malePlayers.get(i + 1));
registerForDoubles(tournamentId, malePlayers.get(i + 1), malePlayers.get(i));
}
for (var i = 0; i < 16; i += 2) {
registerForDoubles(tournamentId, femalePlayers.get(i), femalePlayers.get(i + 1));
registerForDoubles(tournamentId, femalePlayers.get(i + 1), femalePlayers.get(i));
}
for (var i = 16; i < malePlayers.size(); i++) {
registerForDoubles(tournamentId, malePlayers.get(i), femalePlayers.get(i));
registerForDoubles(tournamentId, femalePlayers.get(i), malePlayers.get(i));
}
divideTournament(tournamentId);
drawTournament(tournamentId);
// startRound(2L, 1L);
// playRound(2L, 0, 0);
startRound(tournamentId, 2L);
playRound(tournamentId, 1, 0);
finishRound(tournamentId, 2L);
newRound(tournamentId, 2L);
startRound(tournamentId, 8L);
playRound(tournamentId, 1, 1);
if (1==1) return;
finishRound(tournamentId, 8L);
newRound(tournamentId, 2L);
startRound(tournamentId, 9L);
playRound(tournamentId, 1, 2);
finishRound(tournamentId, 9L);
newRound(tournamentId, 2L);
startRound(tournamentId, 10L);
playRound(tournamentId, 1, 3);
// finishRound(2L, 10L);
}
private void addToTournament(Long tournamentId, Player player) {
var tournament = tournamentService.findTournamentById(tournamentId);
TournamentPlayer tournamentPlayer = new TournamentPlayer();
tournamentPlayer.setTournament(tournament);
tournamentPlayer.setPlayer(player);
tournament.getTournamentPlayers().add(tournamentPlayer);
tournamentService.saveTournament(tournament);
}
private void divideTournament(Long tournamentId) {
var tournament = tournamentService.findTournamentById(tournamentId);
tournamentDivideService.divide(tournament);
}
private void drawTournament(Long tournamentId) {
var tournament = tournamentService.findTournamentById(tournamentId);
tournamentDrawService.draw(tournament);
}
private void playRound(long tournamentId, int group, int round) {
var tournament = tournamentService.findTournamentById(tournamentId);
for (var match : tournament.getEvents().get(0).getGroups().get(group).getRounds().get(round).getMatches()) {
tournamentPlayService.startMatch(tournament, match.getId(), 1L);
}
for (var match : tournament.getEvents().get(0).getGroups().get(group).getRounds().get(round).getMatches()) {
tournament = tournamentService.findTournamentById(tournamentId);
var resultDto = new ResultDto();
resultDto.getGames().add(getRandomGame());
resultDto.getGames().add(getRandomGame());
resultDto.setMatchId(match.getId());
tournamentPlayService.saveResult(tournament, match.getId(), resultDto);
}
}
private GameDto getRandomGame() {
Random random = new Random();
var gameDto = new GameDto();
gameDto.setScore1(21L);
gameDto.setScore2(random.nextLong(19));
return gameDto;
}
private void finishRound(long tournamentId, long roundId) {
var tournament = tournamentService.findTournamentById(tournamentId);
tournamentPlayService.finishRound(tournament, roundId);
}
private void newRound(long tournamentId, long groupId) {
var tournament = tournamentService.findTournamentById(tournamentId);
tournamentPlayService.newRound(tournament, groupId);
}
private void startRound(long tournamentId, long roundId) {
var tournament = tournamentService.findTournamentById(tournamentId);
tournamentPlayService.startRound(tournament, roundId);
}
// private void saveTournament1() {
// var tournament = new Tournament();
// tournament.setName("Zwitsers Laddersysteem BC Holten 2023");
// tournament.setDate(LocalDate.of(2023, Month.DECEMBER, 12));
// tournament.setStatus(TournamentStatus.CLOSED);
// tournamentService.saveTournament(tournament);
// }
private Long saveTournament() {
var tournament = new Tournament();
tournament.setName("Testtoernooi");
tournament.setDate(LocalDate.of(2024, Month.DECEMBER, 14));
tournament.setStatus(TournamentStatus.UPCOMING);
tournament.setMaxEvents(2L);
tournament.setCourts(9L);
tournament.setCostsPerEvent(List.of(6f, 10f));
tournamentService.saveTournament(tournament);
return tournament.getId();
}
private void registerForSingles(Long tournamentId, Player player) {
var tournament = tournamentService.findTournamentById(tournamentId);
var registration = new Registration();
registration.setPlayer(player);
var event = tournament.getEventByType(player.getSex() == Sex.M ? EventType.HE : EventType.DE);
registration.setPartner(null);
registration.setTournament(tournament);
registration.setEvent(event);
if (event.getRegistrations() == null) event.setRegistrations(new ArrayList<>());
event.getRegistrations().add(registration);
// player.getRegistrations().add(registration);
tournamentService.saveTournament(tournament);
}
private void registerForDoubles(Long tournamentId, Player player, Player partner) {
var tournament = tournamentService.findTournamentById(tournamentId);
var registration = new Registration();
registration.setPlayer(player);
registration.setPartner(partner);
var event = tournament.getEventByType(
player.getSex() == partner.getSex() ?
(player.getSex() == Sex.M ? EventType.HD : EventType.DD) : EventType.GD);
registration.setTournament(tournament);
registration.setEvent(event);
event.getRegistrations().add(registration);
// player.getRegistrations().add(registration);
tournamentService.saveTournament(tournament);
}
// private Player savePlayer(String firstName, Sex sex) {
// Random random = new Random();
//
// String[] lastNames = {
// "Meedendorp", "Holstege", "Goedhart", "Zijlma", "Duursma", "Keizer", "Verschoor",
// "Mulder", "Koers", "Duindam", "Castelein", "Coemans", "Huijbers", "Kelder",
// "Stein", "Rakhorst", "Brugman", "Seinen", "Mayer", "Gijsman", "Kingma",
// "Jansen", "Westerik", "Brehler", "Ebbers", "Lensink", "Lups", "Verboom"
// };
//
// String[] clubs = {
// "BC Holten", "BC Reflex", "ZBC", "WSV Apeldoorn", "BC IJsselstad", "Flits", "ELO United", "BC Kwiek"
// };
//
// var player = new Player();
// player.setFirstName(firstName);
// player.setLastName(lastNames[random.nextInt(lastNames.length)]);
// player.setClub(clubs[random.nextInt(clubs.length)]);
// player.setSex(sex);
// player.setBirthday(randomDate(LocalDate.of(1950, 1, 1), LocalDate.of(2010, 1, 1)));
// player.setPhoneNumber("0612345678");
// player.setEmail("aaaa@bbb.cc");
// player.setStrength(getRandomStrength());
//
// playerService.savePlayer(player);
// return player;
// }
*/
}

View File

@@ -0,0 +1,184 @@
package nl.connectedit.swiss.controller;
import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.domain.entity.Tournament;
import nl.connectedit.swiss.dto.ResultDto;
import nl.connectedit.swiss.dto.TournamentDto;
import nl.connectedit.swiss.dto.TournamentValidationDto;
import nl.connectedit.swiss.mapper.TournamentMapper;
import nl.connectedit.swiss.mapper.TournamentValidationMapper;
import nl.connectedit.swiss.service.*;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Objects;
@RestController
@CrossOrigin
@RequiredArgsConstructor
public class TournamentController {
private final TournamentService tournamentService;
private final TournamentMapper tournamentMapper;
private final TournamentValidationService tournamentValidationService;
private final TournamentValidationMapper tournamentValidationMapper;
private final TournamentDivideService tournamentDivideService;
private final TournamentDrawService tournamentDrawService;
private final TournamentPlayService tournamentPlayService;
@GetMapping("/tournaments")
public ResponseEntity<List<TournamentDto>> getTournaments(@RequestParam(value = "status", required = false) TournamentStatus status) {
List<Tournament> tournaments;
if (Objects.nonNull(status)) {
tournaments = tournamentService.findAllTournamentsWithStatus(status);
} else {
tournaments = tournamentService.findAllTournaments();
}
return ResponseEntity.ok(tournaments
.stream()
.map(tournamentMapper::toDto)
.toList());
}
@GetMapping("/tournaments/{id}")
@Transactional
public ResponseEntity<TournamentDto> getTournament(@PathVariable Long id) {
var tournament = tournamentService.findTournamentById(id);
if (tournament == null) {
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(tournamentMapper.toDto(tournament));
}
@PostMapping("/tournaments")
public ResponseEntity<TournamentDto> createTournament(@RequestBody TournamentDto tournamentDto) {
var tournament = tournamentMapper.toEntity(tournamentDto);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentService.saveTournament(tournament)));
}
@PutMapping("/tournaments/{id}")
public ResponseEntity<TournamentDto> updateTournament(@PathVariable Long id, @RequestBody TournamentDto tournamentDto) {
var newTournament = tournamentMapper.toEntity(tournamentDto);
var tournament = tournamentService.updateTournament(id, newTournament);
return ResponseEntity.ok(tournamentMapper.toDto(tournament));
}
@GetMapping("/tournaments/{id}/validate")
public ResponseEntity<TournamentValidationDto> validateTournament(@PathVariable Long id) {
var tournament = tournamentService.findTournamentById(id);
var tournamentValidation = tournamentValidationService.validate(tournament);
return ResponseEntity.ok(tournamentValidationMapper.toDto(tournamentValidation));
}
@PostMapping("/tournaments/{id}/divide")
public ResponseEntity<TournamentDto> divideTournamentNew(@PathVariable Long id) {
var tournament = tournamentService.findTournamentById(id);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentDivideService.divide(tournament)));
}
@PostMapping("/tournaments/{id}/divide/clear")
public ResponseEntity<TournamentDto> clearTournamentDivision(@PathVariable Long id) {
var tournament = tournamentService.findTournamentById(id);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentDivideService.clear(tournament)));
}
@PostMapping("/tournaments/{id}/draw")
public ResponseEntity<TournamentDto> drawTournament(@PathVariable Long id) {
var tournament = tournamentService.findTournamentById(id);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentDrawService.draw(tournament)));
}
@PostMapping("/tournaments/{id}/draw/clear")
public ResponseEntity<TournamentDto> clearTournamentDraw(@PathVariable Long id) {
var tournament = tournamentService.findTournamentById(id);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentDrawService.clear(tournament)));
}
@PostMapping("/tournaments/{tournamentId}/rounds/{roundId}/start")
public ResponseEntity<TournamentDto> startRound(@PathVariable Long tournamentId, @PathVariable Long roundId) {
var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.startRound(tournament, roundId)));
}
@PostMapping("/tournaments/{tournamentId}/rounds/{roundId}/finish")
public ResponseEntity<TournamentDto> finishRound(@PathVariable Long tournamentId, @PathVariable Long roundId) {
var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.finishRound(tournament, roundId)));
}
@PostMapping("/tournaments/{tournamentId}/groups/{groupId}/finish")
public ResponseEntity<TournamentDto> finishGroup(@PathVariable Long tournamentId, @PathVariable Long groupId) {
var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.finishGroup(tournament, groupId)));
}
@PostMapping("/tournaments/{tournamentId}/groups/{groupId}/new")
public ResponseEntity<TournamentDto> newRound(@PathVariable Long tournamentId, @PathVariable Long groupId) {
var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.newRound(tournament, groupId)));
}
@PostMapping("/tournaments/{tournamentId}/matches/{matchId}/start/{court}")
public ResponseEntity<TournamentDto> startMatch(@PathVariable Long tournamentId, @PathVariable Long matchId, @PathVariable Long court) {
var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.startMatch(tournament, matchId, court)));
}
@PostMapping("/tournaments/{tournamentId}/matches/{matchId}/stop")
public ResponseEntity<TournamentDto> stopMatch(@PathVariable Long tournamentId, @PathVariable Long matchId) {
var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.stopMatch(tournament, matchId)));
}
@PostMapping("/tournaments/{tournamentId}/matches/{matchId}")
public ResponseEntity<TournamentDto> saveResult(@PathVariable Long tournamentId, @PathVariable Long matchId, @RequestBody ResultDto resultDto) {
var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.saveResult(tournament, matchId, resultDto)));
}
@PatchMapping("/tournaments/{tournamentId}/players/{playerId}/paid/{paid}")
public ResponseEntity<Void> updatePaid(@PathVariable Long tournamentId, @PathVariable Long playerId, @PathVariable Boolean paid) {
var tournament = tournamentService.findTournamentById(tournamentId);
tournamentPlayService.updatePaid(tournament, playerId, paid);
return ResponseEntity.noContent().build();
}
@PatchMapping("/tournaments/{tournamentId}/players/{playerId}/present/{present}")
public ResponseEntity<Void> updatePresent(@PathVariable Long tournamentId, @PathVariable Long playerId, @PathVariable Boolean present) {
var tournament = tournamentService.findTournamentById(tournamentId);
tournamentPlayService.updatePresent(tournament, playerId, present);
return ResponseEntity.noContent().build();
}
}

View File

@@ -0,0 +1,15 @@
package nl.connectedit.swiss.domain;
import lombok.Data;
import nl.connectedit.swiss.domain.entity.Group;
import java.util.ArrayList;
import java.util.List;
@Data
public class EventDivision {
private Long eventId;
private List<Group> groups = new ArrayList<>();
}

View File

@@ -0,0 +1,20 @@
package nl.connectedit.swiss.domain;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public enum EventType {
HE("Herenenkel", false),
DE("Damesenkel", false),
HD("Herendubbel", true),
DD("Damesdubbel", true),
GD("Gemengd dubbel", true);
private final String text;
private final boolean isDoublesEvent;
}

View File

@@ -0,0 +1,16 @@
package nl.connectedit.swiss.domain;
import lombok.Builder;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
@Builder
public class EventValidation {
private Long eventId;
@Builder.Default
private List<Validation> validations = new ArrayList<>();
}

View File

@@ -0,0 +1,17 @@
package nl.connectedit.swiss.domain;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum PlayerStrength {
D5(8),
D6(6),
D7(4),
D8(3),
D9(2),
DR(1);
private final int coefficient;
}

View File

@@ -0,0 +1,10 @@
package nl.connectedit.swiss.domain;
import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
public enum Sex {
M, V
}

View File

@@ -0,0 +1,8 @@
package nl.connectedit.swiss.domain;
public enum Status {
NOT_STARTED,
READY_TO_PLAY,
IN_PROGRESS,
FINISHED
}

View File

@@ -0,0 +1,17 @@
package nl.connectedit.swiss.domain;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class TournamentDivision {
private Long tournamentId;
private boolean divided;
private List<EventDivision> eventDivisions = new ArrayList<>();
}

View File

@@ -0,0 +1,17 @@
package nl.connectedit.swiss.domain;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public enum TournamentStatus {
UPCOMING("Nieuw"),
DIVIDED("Ingedeeld"),
DRAWN("Geloot"),
ONGOING("Bezig"),
CLOSED("Gesloten");
private final String text;
}

View File

@@ -0,0 +1,20 @@
package nl.connectedit.swiss.domain;
import lombok.Data;
import java.util.List;
@Data
public class TournamentValidation {
private Long tournamentId;
private List<Validation> validations;
private List<EventValidation> eventValidations;
public boolean hasErrors() {
return !validations.isEmpty();
}
}

View File

@@ -0,0 +1,19 @@
package nl.connectedit.swiss.domain;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.AllArgsConstructor;
import lombok.Data;
@Data
@AllArgsConstructor
@JsonInclude(JsonInclude.Include.NON_NULL)
public class Validation {
public enum Severity {
INFO, WARN, ERROR
}
private Severity severity;
private String message;
}

View File

@@ -0,0 +1,27 @@
package nl.connectedit.swiss.domain.entity;
import org.hibernate.proxy.HibernateProxy;
import java.util.Objects;
public abstract class AbstractEntity {
protected abstract Long getId();
@Override
public final boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
Class<?> oEffectiveClass = o instanceof HibernateProxy ? ((HibernateProxy) o).getHibernateLazyInitializer().getPersistentClass() : o.getClass();
Class<?> thisEffectiveClass = this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass() : this.getClass();
if (thisEffectiveClass != oEffectiveClass) return false;
AbstractEntity abstractEntity = (AbstractEntity) o;
return getId() != null && Objects.equals(getId(), abstractEntity.getId());
}
@Override
public final int hashCode() {
return this instanceof HibernateProxy ? ((HibernateProxy) this).getHibernateLazyInitializer().getPersistentClass().hashCode() : getClass().hashCode();
}
}

View File

@@ -0,0 +1,62 @@
package nl.connectedit.swiss.domain.entity;
import jakarta.persistence.*;
import lombok.*;
import nl.connectedit.swiss.domain.EventType;
import nl.connectedit.swiss.domain.Status;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Entity
@Table
@Getter
@Setter
@RequiredArgsConstructor
@Builder
@AllArgsConstructor
public class Event extends AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Tournament tournament;
@OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
// @Builder.Default
private List<Registration> registrations;// = new ArrayList<>();
@Enumerated(EnumType.STRING)
private Status status;
@Enumerated(EnumType.STRING)
private EventType type;
@OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
private List<Group> groups;// = new ArrayList<>();
public static List<Event> getBlankEventSet(Tournament tournament) {
return Arrays.stream(EventType.values())
.map(type -> Event
.builder()
.type(type)
.status(Status.NOT_STARTED)
.tournament(tournament)
.build()
)
.collect(Collectors.toCollection(ArrayList::new));
}
public void addRegistration(Registration registration) {
this.registrations.add(registration);
}
@Override
public String toString() {
return this.getType().getText();
}
}

View File

@@ -0,0 +1,26 @@
package nl.connectedit.swiss.domain.entity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table
@Getter
@Setter
@Builder
@RequiredArgsConstructor
@AllArgsConstructor
public class Game extends AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Match match;
private Long score1;
private Long score2;
}

View File

@@ -0,0 +1,38 @@
package nl.connectedit.swiss.domain.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import nl.connectedit.swiss.domain.EventType;
import java.util.ArrayList;
import java.util.List;
import nl.connectedit.swiss.domain.Status;
@Entity
@Table(name = "eventgroup")
@Getter
@Setter
public class Group extends AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne
private Event event;
@Enumerated(EnumType.STRING)
private Status status;
private EventType type;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Round> rounds;// = new ArrayList<>();
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Team> teams;// = new ArrayList<>();
}

View File

@@ -0,0 +1,49 @@
package nl.connectedit.swiss.domain.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import nl.connectedit.swiss.domain.EventType;
import nl.connectedit.swiss.domain.Status;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table
@Getter
@Setter
@RequiredArgsConstructor
public class Match extends AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private EventType type;
private Status status;
@ManyToOne
private Round round;
@ManyToOne
private Team team1;
@ManyToOne
private Team team2;
private Boolean played;
private Long court;
@OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
private List<Game> games;// = new ArrayList<>();
private LocalDateTime startTime;
private LocalDateTime endTime;
}

View File

@@ -0,0 +1,77 @@
package nl.connectedit.swiss.domain.entity;
import jakarta.persistence.*;
import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import nl.connectedit.swiss.domain.PlayerStrength;
import nl.connectedit.swiss.domain.Sex;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import static org.springframework.util.StringUtils.hasLength;
@Entity
@Table
@Getter
@Setter
@NoArgsConstructor
public class Player extends AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(nullable = false)
@NotEmpty
private String firstName;
@Column
private String middleName;
@Column(nullable = false)
@NotEmpty
private String lastName;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private Sex sex;
@Column(nullable = false)
@DateTimeFormat(pattern = "dd-MM-yyyy")
private LocalDate birthday;
@Column(nullable = false)
private String phoneNumber;
@Column(nullable = false)
private String email;
@Column
private String club;
@Column(nullable = false)
@Enumerated(EnumType.STRING)
private PlayerStrength strength;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Registration> registrations;// = new ArrayList<>();
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Registration> partnerRegistrations;// = new ArrayList<>();
public String getFullName() {
return hasLength(middleName) ?
String.join(" ", firstName, middleName, lastName) :
String.join(" ", firstName, lastName);
}
@Override
public String toString() {
return this.getFullName();
}
}

View File

@@ -0,0 +1,35 @@
package nl.connectedit.swiss.domain.entity;
import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
@Entity
@Table
@Getter
@Setter
@RequiredArgsConstructor
public class Registration extends AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Event event;
@ManyToOne
private Tournament tournament;
@ManyToOne
@JoinColumn(name = "player_id")
private Player player;
@Nullable
@ManyToOne
@JoinColumn(name = "partner_id")
private Player partner;
}

View File

@@ -0,0 +1,39 @@
package nl.connectedit.swiss.domain.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
import nl.connectedit.swiss.domain.Status;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table
@Getter
@Setter
@RequiredArgsConstructor
public class Round extends AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@ManyToOne
private Group group;
private Status status;
@OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
private List<Match> matches;// = new ArrayList<>();
@OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
private List<Team> quit;// = new ArrayList<>();
@ManyToOne
private Team drawnOut;
}

View File

@@ -0,0 +1,34 @@
package nl.connectedit.swiss.domain.entity;
import jakarta.annotation.Nullable;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Setter;
@Entity
@Table
@Getter
@Setter
@RequiredArgsConstructor
public class Team extends AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Group group;
@ManyToOne
private Player player1;
@Nullable
@ManyToOne
private Player player2;
@Override
public String toString() {
return player2 != null ? player1.toString() + " + " + player2.toString() : player1.toString();
}
}

View File

@@ -0,0 +1,71 @@
package nl.connectedit.swiss.domain.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import nl.connectedit.swiss.domain.EventType;
import nl.connectedit.swiss.domain.TournamentStatus;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Entity
@Table
@Getter
@Setter
public class Tournament extends AbstractEntity {
public Tournament() {
initialize();
}
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private LocalDate date;
@Enumerated(EnumType.STRING)
private TournamentStatus status;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Event> events;// = new ArrayList<>();
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<TournamentPlayer> tournamentPlayers;
private Long maxEvents;
@ElementCollection
private List<Float> costsPerEvent;
private Long courts;
public void initialize() {
this.events = new ArrayList<>();
this.events.addAll(Event.getBlankEventSet(this));
this.status = TournamentStatus.UPCOMING;
}
public Event getEventByType(EventType type) {
return events
.stream()
.filter(event -> event.getType().equals(type))
.findFirst()
.orElse(null);
}
public List<Registration> getRegistrations() {
return events
.stream()
.map(Event::getRegistrations)
.flatMap(List::stream)
.collect(Collectors.toCollection(ArrayList::new));
}
}

View File

@@ -0,0 +1,31 @@
package nl.connectedit.swiss.domain.entity;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Entity
@Table
@Getter
@Setter
public class TournamentPlayer extends AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne
private Tournament tournament;
@ElementCollection
private List<String> events;
@ManyToOne
private Player player;
private boolean paid;
private boolean present;
}

View File

@@ -0,0 +1,4 @@
package nl.connectedit.swiss.dto;
public class AbstractDto {
}

View File

@@ -0,0 +1,22 @@
package nl.connectedit.swiss.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class EventDto extends AbstractDto {
private Long id;
private String type;
private String status;
private boolean isDoublesEvent;
private List<RegistrationDto> registrations;
private List<GroupDto> groups;
}

View File

@@ -0,0 +1,20 @@
package nl.connectedit.swiss.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class EventRegistrationDto extends AbstractDto {
private Long id;
private String type;
private boolean isDoublesEvent;
private boolean registered;
private Long player;
private Long partner;
}

View File

@@ -0,0 +1,13 @@
package nl.connectedit.swiss.dto;
import lombok.Data;
import java.util.List;
@Data
public class EventValidationDto {
private Long eventId;
private List<ValidationDto> validations;
}

View File

@@ -0,0 +1,13 @@
package nl.connectedit.swiss.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class GameDto extends AbstractDto {
private Long score1;
private Long score2;
}

View File

@@ -0,0 +1,26 @@
package nl.connectedit.swiss.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class GroupDto extends AbstractDto {
private Long id;
private String name;
private String type;
private String status;
private List<TeamDto> teams;
private List<RoundDto> rounds;
private StandingsDto standings;
}

View File

@@ -0,0 +1,32 @@
package nl.connectedit.swiss.dto;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.List;
@Getter
@Setter
public class MatchDto extends AbstractDto {
private Long id;
private String type;
private String status;
private TeamDto team1;
private TeamDto team2;
private Boolean played;
private List<GameDto> games;
private LocalDateTime startTime;
private LocalDateTime endTime;
private Long court;
}

View File

@@ -0,0 +1,34 @@
package nl.connectedit.swiss.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
import org.springframework.format.annotation.DateTimeFormat;
@Getter
@Setter
public class PlayerDto extends AbstractDto {
private Long id;
private String firstName;
private String middleName;
private String lastName;
private String sex;
@DateTimeFormat(pattern = "dd-MM-yyyy")
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private String birthday;
private String phoneNumber;
private String email;
private String club;
private String strength;
}

View File

@@ -0,0 +1,15 @@
package nl.connectedit.swiss.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class RegistrationDto extends AbstractDto {
private Long id;
private PlayerDto player;
private PlayerDto partner;
}

View File

@@ -0,0 +1,15 @@
package nl.connectedit.swiss.dto;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ResultDto extends AbstractDto {
private Long matchId;
private List<GameDto> games = new ArrayList<>();
}

View File

@@ -0,0 +1,26 @@
package nl.connectedit.swiss.dto;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class RoundDto extends AbstractDto {
private Long id;
private String name;
private List<MatchDto> matches;
private String status;
private List<TeamDto> quit;
private TeamDto drawnOut;
private StandingsDto standings;
}

View File

@@ -0,0 +1,13 @@
package nl.connectedit.swiss.dto;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class StandingsDto {
private List<StandingsEntryDto> entries = new ArrayList<>();
}

View File

@@ -0,0 +1,69 @@
package nl.connectedit.swiss.dto;
import java.util.Comparator;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import nl.connectedit.swiss.domain.entity.Team;
@Getter
@Setter
@ToString
public class StandingsEntry {
public StandingsEntry(Team team) {
this.team = team;
}
private Long position;
private Team team;
private Long played = 0L;
private Long won = 0L;
private Long lost = 0L;
private Long points = 0L;
private Long gamesWon = 0L;
private Long gamesLost = 0L;
private Long pointsWon = 0L;
private Long pointsLost = 0L;
public static int compare(StandingsEntry s1, StandingsEntry s2) {
var pointsPerMatchS1 = safeDivide(s1.points, s1.played);
var pointsPerMatchS2 = safeDivide(s2.points, s2.played);
if (pointsPerMatchS1 > pointsPerMatchS2) {
return -1;
} else if (pointsPerMatchS1 < pointsPerMatchS2) {
return 1;
} else {
if (s1.played < s2.played) {
return -1;
} else if (s1.played > s2.played) {
return 1;
} else {
var gamesPerMatchS1 = safeDivide(s1.gamesWon - s1.gamesLost, s1.played);
var gamesPerMatchS2 = safeDivide(s2.gamesWon - s2.gamesLost, s2.played);
if (gamesPerMatchS1 > gamesPerMatchS2) {
return -1;
} else if (gamesPerMatchS1 < gamesPerMatchS2) {
return 1;
} else {
var ptsPerMatchS1 = safeDivide(s1.pointsWon - s1.pointsLost, s1.played);
var ptsPerMatchS2 = safeDivide(s2.pointsWon - s2.pointsLost, s2.played);
return Double.compare(ptsPerMatchS2, ptsPerMatchS1);
}
}
}
}
private static double safeDivide(Long a, Long b) {
return (a == 0 && b == 0) ? 0 : (double) a / b;
}
}

View File

@@ -0,0 +1,30 @@
package nl.connectedit.swiss.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class StandingsEntryDto {
private Long position;
private TeamDto team;
private Long played;
private Long won;
private Long lost;
private Long points;
private Long gamesWon;
private Long gamesLost;
private Long pointsWon;
private Long pointsLost;
}

View File

@@ -0,0 +1,13 @@
package nl.connectedit.swiss.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TeamDto extends AbstractDto {
private PlayerDto player1;
private PlayerDto player2;
}

View File

@@ -0,0 +1,32 @@
package nl.connectedit.swiss.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class TournamentDto extends AbstractDto {
private Long id;
private String name;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private String date;
private String status;
private List<EventDto> events;
private List<TournamentPlayerDto> tournamentPlayers;
private Long maxEvents;
private List<Float> costsPerEvent;
private Long courts;
}

View File

@@ -0,0 +1,23 @@
package nl.connectedit.swiss.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class TournamentPlayerDto extends AbstractDto {
private Long playerId;
private String name;
private List<String> events;
private Boolean paid;
private Boolean present;
}

View File

@@ -0,0 +1,25 @@
package nl.connectedit.swiss.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter;
import lombok.Setter;
import java.util.List;
@Getter
@Setter
public class TournamentRegistrationDto extends AbstractDto {
private Long id;
private String name;
private Boolean editable;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private String date;
private String status;
private List<EventRegistrationDto> events;
}

View File

@@ -0,0 +1,19 @@
package nl.connectedit.swiss.dto;
import lombok.Getter;
import lombok.Setter;
import nl.connectedit.swiss.domain.Validation;
import java.util.List;
@Getter
@Setter
public class TournamentValidationDto extends AbstractDto {
private Long id;
private List<Validation> validations;
private List<EventValidationDto> eventValidations;
}

View File

@@ -0,0 +1,11 @@
package nl.connectedit.swiss.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class ValidationDto extends AbstractDto {
private String severity;
private String message;
}

View File

@@ -0,0 +1,8 @@
package nl.connectedit.swiss.mapper;
import nl.connectedit.swiss.domain.entity.AbstractEntity;
import nl.connectedit.swiss.dto.AbstractDto;
public interface DtoMapper<E extends AbstractEntity, D extends AbstractDto> {
D toDto(E entity);
}

View File

@@ -0,0 +1,8 @@
package nl.connectedit.swiss.mapper;
import nl.connectedit.swiss.domain.entity.AbstractEntity;
import nl.connectedit.swiss.dto.AbstractDto;
public interface EntityMapper<D extends AbstractDto, E extends AbstractEntity> {
E toEntity(D dto);
}

View File

@@ -0,0 +1,66 @@
package nl.connectedit.swiss.mapper;
import java.util.Arrays;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.Status;
import nl.connectedit.swiss.domain.entity.Event;
import nl.connectedit.swiss.domain.EventType;
import nl.connectedit.swiss.dto.EventDto;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class EventMapper implements DtoMapper<Event, EventDto>, EntityMapper<EventDto, Event> {
private final RegistrationMapper registrationMapper;
private final GroupMapper groupMapper;
@Override
public Event toEntity(EventDto eventDto) {
Event event = new Event();
event.setId(eventDto.getId());
event.setType(
Arrays.stream(EventType.values())
.filter(ts -> ts.getText().equals(eventDto.getType()))
.findFirst()
.orElse(null)
);
event.setStatus(Status.valueOf(eventDto.getStatus()));
event.setRegistrations(
eventDto.getRegistrations()
.stream()
.map(registrationMapper::toEntity)
.toList()
);
return event;
}
@Override
public EventDto toDto(Event event) {
EventDto eventDto = new EventDto();
eventDto.setId(event.getId());
eventDto.setType(event.getType().name());
eventDto.setStatus(event.getStatus().name());
eventDto.setDoublesEvent(event.getType().isDoublesEvent());
if (event.getRegistrations() != null) {
eventDto.setRegistrations(
event.getRegistrations()
.stream()
.map(registrationMapper::toDto)
.toList()
);
}
if (event.getGroups() != null) {
eventDto.setGroups(
event.getGroups()
.stream()
.map(groupMapper::toDto)
.toList()
);
}
return eventDto;
}
}

View File

@@ -0,0 +1,18 @@
package nl.connectedit.swiss.mapper;
import nl.connectedit.swiss.domain.entity.Game;
import nl.connectedit.swiss.dto.GameDto;
import org.springframework.stereotype.Component;
@Component
public class GameMapper implements DtoMapper<Game, GameDto> {
@Override
public GameDto toDto(Game game) {
var gameDto = new GameDto();
gameDto.setScore1(game.getScore1());
gameDto.setScore2(game.getScore2());
return gameDto;
}
}

View File

@@ -0,0 +1,55 @@
package nl.connectedit.swiss.mapper;
import java.util.ArrayList;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.Status;
import nl.connectedit.swiss.domain.entity.Group;
import nl.connectedit.swiss.domain.entity.Round;
import nl.connectedit.swiss.dto.GroupDto;
import nl.connectedit.swiss.dto.RoundDto;
import nl.connectedit.swiss.service.StandingsService;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class GroupMapper implements DtoMapper<Group, GroupDto> {
private final TeamMapper teamMapper;
private final RoundMapper roundMapper;
private final StandingsService standingsService;
private final StandingsMapper standingsMapper;
@Override
public GroupDto toDto(Group group) {
var groupDto = new GroupDto();
groupDto.setId(group.getId());
groupDto.setName(group.getName());
groupDto.setType(group.getType().name());
groupDto.setStatus(group.getStatus().name());
groupDto.setTeams(
group.getTeams()
.stream()
.map(teamMapper::toDto)
.toList());
var standingsGroup = new ArrayList<Round>();
var rounds = new ArrayList<RoundDto>();
if (group.getRounds() != null) {
for (var round : group.getRounds()) {
if (round.getStatus() == Status.FINISHED) {
standingsGroup.add(round);
}
rounds.add(roundMapper.toDto(round, standingsGroup, group.getTeams()));
}
}
groupDto.setRounds(rounds);
var standings = standingsService.getStandings(group.getRounds(), group.getTeams());
groupDto.setStandings(standingsMapper.toDto(standings));
return groupDto;
}
}

View File

@@ -0,0 +1,39 @@
package nl.connectedit.swiss.mapper;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Match;
import nl.connectedit.swiss.dto.MatchDto;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class MatchMapper implements DtoMapper<Match, MatchDto> {
private final TeamMapper teamMapper;
private final GameMapper gameMapper;
@Override
public MatchDto toDto(Match match) {
var matchDto = new MatchDto();
matchDto.setId(match.getId());
matchDto.setType(match.getType().name());
matchDto.setStatus(match.getStatus().name());
matchDto.setTeam1(teamMapper.toDto(match.getTeam1()));
matchDto.setTeam2(teamMapper.toDto(match.getTeam2()));
matchDto.setStartTime(match.getStartTime());
matchDto.setEndTime(match.getEndTime());
matchDto.setCourt(match.getCourt());
if (match.getGames() != null) {
matchDto.setGames(
match.getGames()
.stream()
.map(gameMapper::toDto)
.toList());
}
return matchDto;
}
}

View File

@@ -0,0 +1,57 @@
package nl.connectedit.swiss.mapper;
import nl.connectedit.swiss.domain.entity.Player;
import nl.connectedit.swiss.domain.PlayerStrength;
import nl.connectedit.swiss.domain.Sex;
import nl.connectedit.swiss.dto.PlayerDto;
import org.springframework.stereotype.Component;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Objects;
@Component
public class PlayerMapper implements DtoMapper<Player, PlayerDto>, EntityMapper<PlayerDto, Player> {
@Override
public Player toEntity(PlayerDto playerDto) {
Objects.requireNonNull(playerDto.getFirstName(), "Voornaam is verplicht");
Objects.requireNonNull(playerDto.getLastName(), "Achternaam is verplicht");
Objects.requireNonNull(playerDto.getSex(), "Geslacht is verplicht");
Objects.requireNonNull(playerDto.getPhoneNumber(), "Telefoon is verplicht");
Objects.requireNonNull(playerDto.getEmail(), "Emailadres is verplicht");
Objects.requireNonNull(playerDto.getBirthday(), "Geboortedatum is verplicht");
Objects.requireNonNull(playerDto.getStrength(), "Speelsterkte is verplicht");
Player player = new Player();
player.setId(playerDto.getId());
player.setFirstName(playerDto.getFirstName());
player.setMiddleName(playerDto.getMiddleName());
player.setLastName(playerDto.getLastName());
player.setSex(Sex.valueOf(playerDto.getSex()));
player.setBirthday(LocalDate.parse(playerDto.getBirthday(), DateTimeFormatter.ofPattern("dd-MM-yyyy")));
player.setPhoneNumber(playerDto.getPhoneNumber());
player.setEmail(playerDto.getEmail());
player.setClub(playerDto.getClub());
player.setStrength(PlayerStrength.valueOf(playerDto.getStrength()));
return player;
}
@Override
public PlayerDto toDto(Player player) {
PlayerDto playerDto = new PlayerDto();
playerDto.setId(player.getId());
playerDto.setFirstName(player.getFirstName());
playerDto.setMiddleName(player.getMiddleName());
playerDto.setLastName(player.getLastName());
playerDto.setSex(player.getSex().name());
playerDto.setBirthday(player.getBirthday().format(DateTimeFormatter.ofPattern("dd-MM-yyyy")));
playerDto.setPhoneNumber(player.getPhoneNumber());
playerDto.setEmail(player.getEmail());
playerDto.setClub(player.getClub());
playerDto.setStrength(player.getStrength().name());
return playerDto;
}
}

View File

@@ -0,0 +1,36 @@
package nl.connectedit.swiss.mapper;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Registration;
import nl.connectedit.swiss.dto.RegistrationDto;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class RegistrationMapper implements DtoMapper<Registration, RegistrationDto>, EntityMapper<RegistrationDto, Registration> {
private final PlayerMapper playerMapper;
@Override
public Registration toEntity(RegistrationDto registrationDto) {
Registration registration = new Registration();
registration.setId(registrationDto.getId());
registration.setPlayer(playerMapper.toEntity(registrationDto.getPlayer()));
if (registrationDto.getPartner() != null) {
registration.setPartner(playerMapper.toEntity(registrationDto.getPartner()));
}
return registration;
}
@Override
public RegistrationDto toDto(Registration registration) {
RegistrationDto registrationDto = new RegistrationDto();
registrationDto.setId(registration.getId());
// registrationDto.setTournament(registration.getEvent().getTournament().getId());
registrationDto.setPlayer(playerMapper.toDto(registration.getPlayer()));
if (registration.getPartner() != null) {
registrationDto.setPartner(playerMapper.toDto(registration.getPartner()));
}
return registrationDto;
}
}

View File

@@ -0,0 +1,53 @@
package nl.connectedit.swiss.mapper;
import java.util.List;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Group;
import nl.connectedit.swiss.domain.entity.Round;
import nl.connectedit.swiss.domain.entity.Team;
import nl.connectedit.swiss.dto.RoundDto;
import nl.connectedit.swiss.service.StandingsService;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class RoundMapper {
private final MatchMapper matchMapper;
private final TeamMapper teamMapper;
private final StandingsService standingsService;
private final StandingsMapper standingsMapper;
public RoundDto toDto(Round round, List<Round> roundsForStandings, List<Team> teamsForStandings) {
var roundDto = new RoundDto();
roundDto.setId(round.getId());
roundDto.setName(round.getName());
roundDto.setMatches(
round.getMatches()
.stream()
.map(matchMapper::toDto)
.toList());
roundDto.setStatus(round.getStatus().name());
if (round.getQuit() != null) {
roundDto.setQuit(
round.getQuit()
.stream()
.map(teamMapper::toDto)
.toList());
}
if (round.getDrawnOut() != null) {
roundDto.setDrawnOut(teamMapper.toDto(round.getDrawnOut()));
}
var standings = standingsService.getStandings(roundsForStandings, teamsForStandings);
roundDto.setStandings(standingsMapper.toDto(standings));
return roundDto;
}
}

View File

@@ -0,0 +1,42 @@
package nl.connectedit.swiss.mapper;
import java.util.Iterator;
import java.util.List;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.dto.StandingsDto;
import nl.connectedit.swiss.dto.StandingsEntry;
import nl.connectedit.swiss.dto.StandingsEntryDto;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class StandingsMapper {
private final TeamMapper teamMapper;
public StandingsDto toDto(List<StandingsEntry> standings) {
var standingsDto = new StandingsDto();
Long position = 1L;
for (Iterator<StandingsEntry> i = standings.iterator(); i.hasNext(); position++ ) {
standingsDto.getEntries().add(toDto(i.next(), position));
}
return standingsDto;
}
private StandingsEntryDto toDto(StandingsEntry standingsEntry, Long position) {
var standingsEntryDto = new StandingsEntryDto();
standingsEntryDto.setPosition(position);
standingsEntryDto.setTeam(teamMapper.toDto(standingsEntry.getTeam()));
standingsEntryDto.setPlayed(standingsEntry.getPlayed());
standingsEntryDto.setWon(standingsEntry.getWon());
standingsEntryDto.setLost(standingsEntry.getLost());
standingsEntryDto.setPoints(standingsEntry.getPoints());
standingsEntryDto.setGamesWon(standingsEntry.getGamesWon());
standingsEntryDto.setGamesLost(standingsEntry.getGamesLost());
standingsEntryDto.setPointsWon(standingsEntry.getPointsWon());
standingsEntryDto.setPointsLost(standingsEntry.getPointsLost());
return standingsEntryDto;
}
}

View File

@@ -0,0 +1,25 @@
package nl.connectedit.swiss.mapper;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Team;
import nl.connectedit.swiss.dto.TeamDto;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class TeamMapper implements DtoMapper<Team, TeamDto> {
private final PlayerMapper playerMapper;
@Override
public TeamDto toDto(Team team) {
var teamDto = new TeamDto();
teamDto.setPlayer1(playerMapper.toDto(team.getPlayer1()));
if (team.getPlayer2() != null) {
teamDto.setPlayer2(playerMapper.toDto(team.getPlayer2()));
}
return teamDto;
}
}

View File

@@ -0,0 +1,81 @@
package nl.connectedit.swiss.mapper;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Tournament;
import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.domain.entity.TournamentPlayer;
import nl.connectedit.swiss.dto.TournamentDto;
import nl.connectedit.swiss.dto.TournamentPlayerDto;
import org.springframework.stereotype.Component;
@Component
@RequiredArgsConstructor
public class TournamentMapper implements DtoMapper<Tournament, TournamentDto>, EntityMapper<TournamentDto, Tournament> {
private final EventMapper eventMapper;
private final TournamentPlayerMapper tournamentPlayerMapper;
@Override
public Tournament toEntity(TournamentDto tournamentDto) {
Tournament tournament = new Tournament();
tournament.setId(tournamentDto.getId());
tournament.setName(tournamentDto.getName());
tournament.setDate(LocalDate.parse(tournamentDto.getDate(), DateTimeFormatter.ofPattern("dd-MM-yyyy")));
tournament.setStatus(
Arrays.stream(TournamentStatus.values())
.filter(ts -> ts.getText().equals(tournamentDto.getStatus()))
.findFirst()
.orElse(TournamentStatus.UPCOMING)
);
tournament.setMaxEvents(tournamentDto.getMaxEvents());
tournament.setCostsPerEvent(tournamentDto.getCostsPerEvent());
tournament.setCourts(tournamentDto.getCourts());
return tournament;
}
@Override
public TournamentDto toDto(Tournament tournament) {
TournamentDto tournamentDto = new TournamentDto();
tournamentDto.setId(tournament.getId());
tournamentDto.setName(tournament.getName());
tournamentDto.setDate(tournament.getDate().format(DateTimeFormatter.ofPattern("dd-MM-yyyy")));
tournamentDto.setStatus(tournament.getStatus().name());
tournamentDto.setEvents(
tournament.getEvents()
.stream()
.map(eventMapper::toDto)
.toList()
);
if (tournament.getTournamentPlayers() != null) {
var tournamentPlayers = tournament.getTournamentPlayers();
tournamentPlayers.sort(Comparator.comparing((TournamentPlayer tournamentPlayer) -> tournamentPlayer.getPlayer().getLastName())
.thenComparing((TournamentPlayer tournamentPlayer) -> tournamentPlayer.getPlayer().getFirstName()));
tournamentDto.setTournamentPlayers(tournamentPlayers
.stream()
.map(tournamentPlayerMapper::toDto)
.toList()
);
}
tournamentDto.setMaxEvents(tournament.getMaxEvents() == null ? 2L : tournament.getMaxEvents());
if (tournament.getCostsPerEvent() == null || tournament.getCostsPerEvent().isEmpty()) {
tournamentDto.setCostsPerEvent(List.of(0f, 0f, 0f));
} else {
tournamentDto.setCostsPerEvent(tournament.getCostsPerEvent());
}
tournamentDto.setCourts(tournament.getCourts() == null ? 1L : tournament.getCourts());
return tournamentDto;
}
}

View File

@@ -0,0 +1,29 @@
package nl.connectedit.swiss.mapper;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Team;
import nl.connectedit.swiss.domain.entity.TournamentPlayer;
import nl.connectedit.swiss.dto.TeamDto;
import nl.connectedit.swiss.dto.TournamentPlayerDto;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
@Component
@RequiredArgsConstructor
public class TournamentPlayerMapper implements DtoMapper<TournamentPlayer, TournamentPlayerDto> {
private final PlayerMapper playerMapper;
@Override
public TournamentPlayerDto toDto(TournamentPlayer tournamentPlayer) {
var tournamentPlayerDto = new TournamentPlayerDto();
tournamentPlayerDto.setPlayerId(tournamentPlayer.getPlayer().getId());
tournamentPlayerDto.setName(tournamentPlayer.getPlayer().getFullName());
tournamentPlayerDto.setEvents(new ArrayList<>(tournamentPlayer.getEvents()));
tournamentPlayerDto.setPaid(tournamentPlayer.isPaid());
tournamentPlayerDto.setPresent(tournamentPlayer.isPresent());
return tournamentPlayerDto;
}
}

View File

@@ -0,0 +1,78 @@
package nl.connectedit.swiss.mapper;
import java.time.format.DateTimeFormatter;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.domain.entity.Event;
import nl.connectedit.swiss.domain.EventType;
import nl.connectedit.swiss.domain.entity.Player;
import nl.connectedit.swiss.domain.Sex;
import nl.connectedit.swiss.domain.entity.Tournament;
import nl.connectedit.swiss.dto.EventRegistrationDto;
import nl.connectedit.swiss.dto.TournamentRegistrationDto;
import org.springframework.stereotype.Component;
import java.util.List;
@Component
@RequiredArgsConstructor
public class TournamentPlayerRegistrationMapper {
public List<TournamentRegistrationDto> mapToDto(List<Tournament> tournaments, Player player) {
return tournaments.stream()
.map(tournament -> this.mapToTournamentRegistrationDto(tournament, player))
.toList();
}
public TournamentRegistrationDto mapToTournamentRegistrationDto(Tournament tournament, Player player) {
TournamentRegistrationDto tournamentRegistrationDto = new TournamentRegistrationDto();
tournamentRegistrationDto.setId(tournament.getId());
tournamentRegistrationDto.setName(tournament.getName());
tournamentRegistrationDto.setEditable(tournament.getStatus() == TournamentStatus.UPCOMING);
tournamentRegistrationDto.setDate(tournament.getDate().format(DateTimeFormatter.ofPattern("dd-MM-yyyy")));
tournamentRegistrationDto.setStatus(tournament.getStatus().name());
tournamentRegistrationDto.setEvents(
tournament.getEvents()
.stream()
.filter(event -> isRelevant(event, player))
.map(event -> this.mapToEventRegistrationDto(event, player))
.toList()
);
return tournamentRegistrationDto;
}
private boolean isRelevant(Event event, Player player) {
if (player.getSex() == Sex.M) {
return switch(event.getType()) {
case EventType.HE, EventType.HD, EventType.GD -> true;
default -> false;
};
} else {
return switch(event.getType()) {
case EventType.DE, EventType.DD, EventType.GD -> true;
default -> false;
};
}
}
private EventRegistrationDto mapToEventRegistrationDto(Event event, Player player) {
EventRegistrationDto eventRegistrationDto = new EventRegistrationDto();
eventRegistrationDto.setId(event.getId());
eventRegistrationDto.setType(event.getType().name());
eventRegistrationDto.setDoublesEvent(event.getType().isDoublesEvent());
for (var registration : event.getRegistrations()) {
if (registration.getPlayer().getId().equals(player.getId())) {
eventRegistrationDto.setRegistered(true);
eventRegistrationDto.setPlayer(registration.getPlayer().getId());
if (registration.getPartner() != null) {
eventRegistrationDto.setPartner(registration.getPartner().getId());
}
break;
} else {
eventRegistrationDto.setRegistered(false);
}
}
return eventRegistrationDto;
}
}

View File

@@ -0,0 +1,45 @@
package nl.connectedit.swiss.mapper;
import nl.connectedit.swiss.domain.Validation;
import nl.connectedit.swiss.domain.EventValidation;
import nl.connectedit.swiss.domain.TournamentValidation;
import nl.connectedit.swiss.dto.EventValidationDto;
import nl.connectedit.swiss.dto.TournamentValidationDto;
import nl.connectedit.swiss.dto.ValidationDto;
import org.springframework.stereotype.Component;
@Component
public class TournamentValidationMapper {
public TournamentValidationDto toDto(TournamentValidation tournamentValidation) {
var tournamentValidationDto = new TournamentValidationDto();
tournamentValidationDto.setId(tournamentValidation.getTournamentId());
tournamentValidationDto.setValidations(tournamentValidation.getValidations());
tournamentValidationDto.setEventValidations(
tournamentValidation.getEventValidations()
.stream()
.map(this::toEventValidationDto)
.toList()
);
return tournamentValidationDto;
}
private EventValidationDto toEventValidationDto(EventValidation eventValidation) {
var eventValidationDto = new EventValidationDto();
eventValidationDto.setEventId(eventValidation.getEventId());
eventValidationDto.setValidations(
eventValidation.getValidations()
.stream()
.map(this::toValidationDto)
.toList()
);
return eventValidationDto;
}
private ValidationDto toValidationDto(Validation validation) {
var validationDto = new ValidationDto();
validationDto.setSeverity(validation.getSeverity().name());
validationDto.setMessage(validation.getMessage());
return validationDto;
}
}

View File

@@ -0,0 +1,8 @@
package nl.connectedit.swiss.repository;
import nl.connectedit.swiss.domain.entity.Player;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PlayerRepository extends JpaRepository<Player, Long> {
}

View File

@@ -0,0 +1,17 @@
package nl.connectedit.swiss.repository;
import nl.connectedit.swiss.domain.entity.Registration;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import java.util.List;
public interface RegistrationRepository extends JpaRepository<Registration, Long> {
@Query("""
select r from Registration r
where r.player.id = :playerId
""")
List<Registration> findRegistrationsForPlayer(@Param("playerId") Long playerId);
}

View File

@@ -0,0 +1,12 @@
package nl.connectedit.swiss.repository;
import nl.connectedit.swiss.domain.entity.Tournament;
import nl.connectedit.swiss.domain.TournamentStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.List;
public interface TournamentRepository extends JpaRepository<Tournament, Long> {
List<Tournament> findAllByStatus(TournamentStatus status);
}

View File

@@ -0,0 +1,29 @@
package nl.connectedit.swiss.service;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Player;
import nl.connectedit.swiss.repository.PlayerRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class PlayerService {
private final PlayerRepository playerRepository;
public Player savePlayer(Player player) {
return playerRepository.save(player);
}
public List<Player> findAllPlayers() {
return playerRepository.findAll();
}
public Player findPlayerById(Long id) {
return playerRepository.getReferenceById(id);
}
}

View File

@@ -0,0 +1,53 @@
package nl.connectedit.swiss.service;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Player;
import nl.connectedit.swiss.domain.entity.Registration;
import nl.connectedit.swiss.domain.entity.Tournament;
import nl.connectedit.swiss.dto.EventRegistrationDto;
import nl.connectedit.swiss.repository.RegistrationRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class RegistrationService {
private final TournamentService tournamentService;
private final PlayerService playerService;
public void updateOrAddRegistrations(EventRegistrationDto eventRegistration, Tournament tournament, Player player) {
var event = tournament.getEvents().stream().filter(e -> e.getId().equals(eventRegistration.getId())).findFirst().orElseThrow();
if (!eventRegistration.isRegistered()) { // remove any existing registration
event.getRegistrations().removeIf(registration -> registration.getPlayer().equals(player));
} else {
var optionalExistingPlayerRegistration = event.getRegistrations().stream()
.filter(registration -> registration.getPlayer().equals(player))
.findAny();
if (optionalExistingPlayerRegistration.isEmpty()) { // no previous registration for this event
var newRegistration = new Registration();
newRegistration.setTournament(tournament);
newRegistration.setEvent(event);
newRegistration.setPlayer(player);
if (eventRegistration.getPartner() != null){
var partner = playerService.findPlayerById(eventRegistration.getPartner());
newRegistration.setPartner(partner);
}
event.addRegistration(newRegistration);
} else { // change existing registration
var existingPlayerRegistration = optionalExistingPlayerRegistration.get();
if (eventRegistration.getPartner() != null){
var partner = playerService.findPlayerById(eventRegistration.getPartner());
existingPlayerRegistration.setPartner(partner);
} else {
existingPlayerRegistration.setPartner(null);
}
}
}
tournamentService.saveTournament(tournament);
}
}

View File

@@ -0,0 +1,51 @@
package nl.connectedit.swiss.service;
import java.util.Collection;
import java.util.Objects;
import nl.connectedit.swiss.domain.entity.Event;
import nl.connectedit.swiss.domain.entity.Group;
import nl.connectedit.swiss.domain.entity.Match;
import nl.connectedit.swiss.domain.entity.Round;
import nl.connectedit.swiss.domain.entity.Tournament;
import org.springframework.stereotype.Component;
@Component
public class ServiceUtil {
static Group getGroup(Tournament tournament, Long groupId) {
return tournament.getEvents()
.stream()
.map(Event::getGroups)
.flatMap(Collection::stream)
.filter(group -> Objects.equals(group.getId(), groupId))
.findAny()
.orElseThrow();
}
static Round getRound(Tournament tournament, Long roundId) {
return tournament.getEvents()
.stream()
.map(Event::getGroups)
.flatMap(Collection::stream)
.map(Group::getRounds)
.flatMap(Collection::stream)
.filter(round -> Objects.equals(round.getId(), roundId))
.findAny()
.orElseThrow();
}
static Match getMatch(Tournament tournament, Long matchId) {
return tournament.getEvents()
.stream()
.map(Event::getGroups)
.flatMap(Collection::stream)
.map(Group::getRounds)
.flatMap(Collection::stream)
.map(Round::getMatches)
.flatMap(Collection::stream)
.filter(match -> Objects.equals(match.getId(), matchId))
.findAny()
.orElseThrow();
}
}

View File

@@ -0,0 +1,116 @@
package nl.connectedit.swiss.service;
import nl.connectedit.swiss.domain.entity.Group;
import nl.connectedit.swiss.domain.entity.Match;
import nl.connectedit.swiss.domain.entity.Round;
import nl.connectedit.swiss.domain.entity.Team;
import nl.connectedit.swiss.dto.StandingsEntry;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Service
public class StandingsService {
public List<StandingsEntry> getStandings(List<Round> rounds, List<Team> teams) {
var standings = new ArrayList<StandingsEntry>();
teams.forEach(team -> standings.add(new StandingsEntry(team)));
if (rounds != null) {
for (var round : rounds) {
addRoundToStandings(standings, round);
}
}
return sortStandings(standings);
}
private void addRoundToStandings(List<StandingsEntry> standings, Round round) {
var matches = round.getMatches();
for (var match : matches) {
addMatchToStandingsEntry(standings, match);
}
}
private void addMatchToStandingsEntry(List<StandingsEntry> standings, Match match) {
if (!match.getPlayed()) return;
var standingTeam1 = getStandingsEntryForTeam(standings, match.getTeam1());
var standingTeam2 = getStandingsEntryForTeam(standings, match.getTeam2());
standingTeam1.setPlayed(standingTeam1.getPlayed() + 1);
standingTeam2.setPlayed(standingTeam2.getPlayed() + 1);
standingTeam1.setPointsWon(standingTeam1.getPointsWon() + match.getGames().get(0).getScore1());
standingTeam2.setPointsWon(standingTeam2.getPointsWon() + match.getGames().get(0).getScore2());
standingTeam1.setPointsLost(standingTeam1.getPointsLost() + match.getGames().get(0).getScore2());
standingTeam2.setPointsLost(standingTeam2.getPointsLost() + match.getGames().get(0).getScore1());
standingTeam1.setPointsWon(standingTeam1.getPointsWon() + match.getGames().get(1).getScore1());
standingTeam2.setPointsWon(standingTeam2.getPointsWon() + match.getGames().get(1).getScore2());
standingTeam1.setPointsLost(standingTeam1.getPointsLost() + match.getGames().get(1).getScore2());
standingTeam2.setPointsLost(standingTeam2.getPointsLost() + match.getGames().get(1).getScore1());
if (match.getGames().size() == 3) {
standingTeam1.setPointsWon(standingTeam1.getPointsWon() + match.getGames().get(2).getScore1());
standingTeam2.setPointsWon(standingTeam2.getPointsWon() + match.getGames().get(2).getScore2());
standingTeam1.setPointsLost(standingTeam1.getPointsLost() + match.getGames().get(2).getScore2());
standingTeam2.setPointsLost(standingTeam2.getPointsLost() + match.getGames().get(2).getScore1());
if (match.getGames().get(2).getScore1() > match.getGames().get(2).getScore2()) {
standingTeam1.setWon(standingTeam1.getWon() + 1);
standingTeam1.setPoints(standingTeam1.getPoints() + 2);
standingTeam1.setGamesWon(standingTeam1.getGamesWon() + 2);
standingTeam1.setGamesLost(standingTeam1.getGamesLost() + 1);
standingTeam2.setLost(standingTeam2.getLost() + 1);
standingTeam2.setGamesWon(standingTeam2.getGamesWon() + 1);
standingTeam2.setGamesLost(standingTeam2.getGamesLost() + 2);
} else {
standingTeam1.setLost(standingTeam1.getLost() + 1);
standingTeam1.setGamesWon(standingTeam1.getGamesWon() + 1);
standingTeam1.setGamesLost(standingTeam1.getGamesLost() + 2);
standingTeam2.setWon(standingTeam2.getWon() + 1);
standingTeam2.setPoints(standingTeam2.getPoints() + 2);
standingTeam2.setGamesWon(standingTeam2.getGamesWon() + 2);
standingTeam2.setGamesLost(standingTeam2.getGamesLost() + 1);
}
} else {
if (match.getGames().get(1).getScore1() > match.getGames().get(1).getScore2()) {
standingTeam1.setWon(standingTeam1.getWon() + 1);
standingTeam1.setPoints(standingTeam1.getPoints() + 2);
standingTeam1.setGamesWon(standingTeam1.getGamesWon() + 2);
standingTeam2.setLost(standingTeam2.getLost() + 1);
standingTeam2.setGamesLost(standingTeam2.getGamesLost() + 2);
} else {
standingTeam1.setLost(standingTeam1.getLost() + 1);
standingTeam1.setGamesLost(standingTeam2.getGamesLost() + 2);
standingTeam2.setWon(standingTeam2.getWon() + 1);
standingTeam2.setPoints(standingTeam2.getPoints() + 2);
standingTeam2.setGamesWon(standingTeam2.getGamesWon() + 2);
}
}
}
private StandingsEntry getStandingsEntryForTeam(List<StandingsEntry> standings, Team team) {
return standings.stream().filter(standingsEntry -> Objects.equals(standingsEntry.getTeam(), team)).findAny().orElse(null);
}
private List<StandingsEntry> sortStandings(List<StandingsEntry> standings) {
var sortedStandings = standings
.stream()
.sorted(StandingsEntry::compare)
.toList();
var position = 0L;
for (var standing : sortedStandings) {
standing.setPosition(++position);
}
return sortedStandings;
}
}

View File

@@ -0,0 +1,157 @@
package nl.connectedit.swiss.service;
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.EventType;
import nl.connectedit.swiss.domain.Status;
import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.domain.entity.Event;
import nl.connectedit.swiss.domain.entity.Group;
import nl.connectedit.swiss.domain.entity.Registration;
import nl.connectedit.swiss.domain.entity.Team;
import nl.connectedit.swiss.domain.entity.Tournament;
import nl.connectedit.swiss.repository.TournamentRepository;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class TournamentDivideService {
private final TournamentValidationService tournamentValidationService;
private final TournamentRepository tournamentRepository;
@Transactional
public Tournament divide(Tournament tournament) {
if (tournamentValidationService.validate(tournament).hasErrors()) {
return tournament;
}
for (var event : tournament.getEvents()) {
if (event.getRegistrations().size() >= 4) {
divide(event);
}
}
tournament.setStatus(TournamentStatus.DIVIDED);
tournamentRepository.save(tournament);
return tournament;
}
public void divide(Event event) {
List<Registration> registrations;
if (event.getType().isDoublesEvent()) {
registrations = groupRegistrations(event.getRegistrations());
} else {
registrations = event.getRegistrations();
}
event.setGroups(new ArrayList<>());
if (registrations.size() <= 16) {
var group = getGroup(registrations, event.getType());
group.setEvent(event);
event.getGroups().add(group);
} else {
var groups = getGroups(registrations, event.getType());
event.getGroups().addAll(groups);
}
}
private List<Registration> groupRegistrations(List<Registration> registrations) {
var groupedRegistrations = new ArrayList<Registration>();
nextRegistration:
for (var registration : registrations) {
for (var groupedRegistration : groupedRegistrations) {
if (Objects.equals(groupedRegistration.getPartner(), registration.getPlayer())) {
continue nextRegistration;
}
}
groupedRegistrations.add(registration);
}
return groupedRegistrations;
}
private Group getGroup(List<Registration> registrations, EventType type) {
var group = new Group();
group.setName(type.getText());
group.setType(type);
group.setStatus(Status.IN_PROGRESS);
group.setTeams(new ArrayList<>());
for (var registration : registrations) {
group.getTeams().add(getTeam(registration, group));
}
return group;
}
private List<Group> getGroups(List<Registration> orgRegistrations, EventType type) {
var registrations = new ArrayList<>(orgRegistrations);
var group1 = new Group();
group1.setName(type.getText() + " 1");
group1.setType(type);
group1.setStatus(Status.IN_PROGRESS);
group1.setTeams(new ArrayList<>());
var group2 = new Group();
group2.setName(type.getText() + " 2");
group2.setType(type);
group2.setStatus(Status.IN_PROGRESS);
group2.setTeams(new ArrayList<>());
var registrationStrengthMap = new HashMap<Registration, Integer>();
for (var registration : registrations) {
var strength = registration.getPlayer().getStrength().getCoefficient();
if (registration.getPartner() != null) {
strength += registration.getPartner().getStrength().getCoefficient();
}
registrationStrengthMap.put(registration, strength);
}
var sortedRegistrations = registrationStrengthMap
.entrySet()
.stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
.map(Map.Entry::getKey)
.toList();
for (Iterator<Registration> i = sortedRegistrations.iterator(); i.hasNext(); ) {
group1.getTeams().add(getTeam(i.next(), group1));
if (i.hasNext()) {
group2.getTeams().add(getTeam(i.next(), group2));
}
}
return List.of(group1, group2);
}
private Team getTeam(Registration registration, Group group) {
var team = new Team();
team.setPlayer1(registration.getPlayer());
team.setPlayer2(registration.getPartner());
team.setGroup(group);
return team;
}
public Tournament clear(Tournament tournament) {
for (var event : tournament.getEvents()) {
event.getGroups().clear();
}
tournament.setStatus(TournamentStatus.UPCOMING);
tournamentRepository.save(tournament);
return tournament;
}
}

View File

@@ -0,0 +1,183 @@
package nl.connectedit.swiss.service;
import java.util.*;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.Status;
import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.domain.entity.*;
import nl.connectedit.swiss.repository.TournamentRepository;
import org.springframework.stereotype.Service;
@Service
@RequiredArgsConstructor
public class TournamentDrawService {
private final TournamentRepository tournamentRepository;
public Tournament draw(Tournament tournament) {
if (!tournamentIsReadyForDraw(tournament)) return null;
for (var event : tournament.getEvents()) {
for (var group : event.getGroups()) {
var round = new Round();
round.setName("Ronde 1");
round.setGroup(group);
round.setMatches(createMatchList(group, round));
round.setStatus(Status.NOT_STARTED);
group.getRounds().add(round);
}
}
registerTournamentPlayers(tournament);
tournament.setStatus(TournamentStatus.DRAWN);
tournamentRepository.save(tournament);
return tournament;
}
private void registerTournamentPlayers(Tournament tournament) {
var players = new HashSet<Player>();
for (var event : tournament.getEvents()) {
for (var group : event.getGroups()) {
for (var team : group.getTeams()) {
players.add(team.getPlayer1());
if (team.getPlayer2() != null) players.add(team.getPlayer2());
}
}
}
for (var player : players) {
var tournamentPlayer = new TournamentPlayer();
tournamentPlayer.setTournament(tournament);
tournamentPlayer.setPlayer(player);
tournamentPlayer.setEvents(
tournament.getEvents()
.stream()
.filter(event ->
event.getRegistrations()
.stream()
.anyMatch(r -> r.getPlayer().equals(player)))
.map(Event::getType)
.map(Enum::name)
.toList()
);
tournament.getTournamentPlayers().add(tournamentPlayer);
}
}
public Tournament clear(Tournament tournament) {
if (!tournamentIsDrawn(tournament)) return tournament;
for (var event : tournament.getEvents()) {
for (var group : event.getGroups()) {
group.getRounds().clear();
}
}
tournament.getTournamentPlayers().clear();
tournament.setStatus(TournamentStatus.DIVIDED);
tournamentRepository.save(tournament);
return tournament;
}
private List<Match> createMatchList(Group group, Round round) {
List<Match> matches;
var drawCounter = 0;
do {
drawCounter++;
matches = new ArrayList<>();
var teams = new ArrayList<>(group.getTeams());
Collections.shuffle(teams);
for (Iterator<Team> i = teams.iterator(); i.hasNext(); ) {
var match = new Match();
match.setType(group.getType());
match.setStatus(Status.NOT_STARTED);
match.setTeam1(i.next());
if (i.hasNext()) {
match.setTeam2(i.next());
} else {
round.setDrawnOut(match.getTeam1());
break;
}
match.setPlayed(false);
match.setRound(round);
matches.add(match);
}
} while (!drawIsValid(matches) || drawCounter == 1000);
return matches;
}
private boolean drawIsValid(List<Match> matches) {
for (var match : matches) {
var clubs = getClubsFromMatch(match);
var distinctClubs = new HashSet<>(clubs).size();
if (!match.getType().isDoublesEvent()) {
if (distinctClubs == 1) {
return false;
}
} else {
if (distinctClubs == 1) {
return false;
} else if (distinctClubs == 2 && (teamPlayersHaveDifferentClub(match.getTeam1()) ^ teamPlayersHaveDifferentClub(match.getTeam2()))) {
return false;
}
}
}
return true;
}
private List<String> getClubsFromMatch(Match match) {
var clubs = new ArrayList<String>();
clubs.add(match.getTeam1().getPlayer1().getClub());
if (match.getTeam1().getPlayer2() != null) {
clubs.add(match.getTeam1().getPlayer2().getClub());
}
clubs.add(match.getTeam2().getPlayer1().getClub());
if (match.getTeam2().getPlayer2() != null) {
clubs.add(match.getTeam2().getPlayer2().getClub());
}
return clubs;
}
private boolean teamPlayersHaveDifferentClub(Team team) {
if (team.getPlayer2() == null) return true;
return !Objects.equals(team.getPlayer1().getClub(), team.getPlayer2().getClub());
}
private boolean tournamentIsReadyForDraw(Tournament tournament) {
return tournament.getStatus() == TournamentStatus.DIVIDED &&
tournament.getEvents()
.stream()
.map(Event::getGroups)
.flatMap(List::stream)
.map(Group::getRounds)
.flatMap(List::stream)
.toList()
.isEmpty();
}
private boolean tournamentIsDrawn(Tournament tournament) {
if (tournament.getStatus() != TournamentStatus.DRAWN) return false;
for (var event : tournament.getEvents()) {
for (var group : event.getGroups()) {
for (var round : group.getRounds()) {
if (round.getStatus() != Status.NOT_STARTED) return false;
}
}
}
return true;
}
}

View File

@@ -0,0 +1,279 @@
package nl.connectedit.swiss.service;
import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import nl.connectedit.swiss.domain.Status;
import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.domain.entity.*;
import nl.connectedit.swiss.dto.ResultDto;
import nl.connectedit.swiss.dto.StandingsEntry;
import nl.connectedit.swiss.repository.TournamentRepository;
import org.springframework.stereotype.Service;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Random;
import java.util.stream.Collectors;
import static nl.connectedit.swiss.service.ServiceUtil.*;
@Service
@RequiredArgsConstructor
@Log
public class TournamentPlayService {
private final TournamentRepository tournamentRepository;
private final StandingsService standingsService;
public Tournament startRound(Tournament tournament, Long roundId) {
getRound(tournament, roundId).setStatus(Status.IN_PROGRESS);
tournament.setStatus(TournamentStatus.ONGOING);
tournamentRepository.save(tournament);
return tournament;
}
public Tournament finishRound(Tournament tournament, Long roundId) {
getRound(tournament, roundId).setStatus(Status.FINISHED);
tournamentRepository.save(tournament);
return tournament;
}
public Tournament finishGroup(Tournament tournament, Long groupId) {
getGroup(tournament, groupId).setStatus(Status.FINISHED);
tournamentRepository.save(tournament);
return tournament;
}
public Tournament newRound(Tournament tournament, Long groupId) {
var group = getGroup(tournament, groupId);
var standings = standingsService.getStandings(group.getRounds(), group.getTeams());
var remainingTeams = standings.stream()
.map(StandingsEntry::getTeam)
.collect(Collectors.toCollection(ArrayList::new));
var round = new Round();
round.setName("Ronde " + (group.getRounds().size() + 1));
round.setGroup(group);
round.setStatus(Status.NOT_STARTED);
if (remainingTeams.size() % 2 == 1) {
var random = new Random();
var drawnOutPlayers = getDrawnOutPlayers(tournament);
do {
var randomTeam = remainingTeams.get(random.nextInt(remainingTeams.size()));
if (!drawnOutPlayers.contains(randomTeam.getPlayer1()) && (randomTeam.getPlayer2() == null || !drawnOutPlayers.contains(randomTeam.getPlayer2()))) {
remainingTeams.remove(randomTeam);
round.setDrawnOut(randomTeam);
break;
}
} while(true);
}
var matches = tryMatches(remainingTeams, group).reversed();
for (var match : matches) {
match.setType(group.getType());
match.setStatus(Status.NOT_STARTED);
match.setPlayed(false);
match.setRound(round);
}
round.setMatches(matches);
printRound(round, standings);
group.getRounds().add(round);
tournamentRepository.save(tournament);
return tournament;
}
private List<Player> getDrawnOutPlayers(Tournament tournament) {
var players = new ArrayList<Player>();
for (var event : tournament.getEvents()) {
for (var group : event.getGroups()) {
var playersInGroup = group.getTeams().stream()
.map(this::getPlayersInTeam)
.flatMap(List::stream)
.toList();
for (var player : playersInGroup) {
for (var round : group.getRounds()) {
var foundInRound = false;
for (var match : round.getMatches()) {
if (playerIsInMatch(match, player)) {
foundInRound = true;
}
}
if (!foundInRound) {
players.add(player);
log.info(player.getFullName() + " is al uitgeloot in het toernooi.");
}
}
}
}
}
return players;
}
private List<Player> getPlayersInTeam(Team team) {
return team.getPlayer2() == null ? List.of(team.getPlayer1()) : List.of(team.getPlayer1(), team.getPlayer2());
}
private boolean playerIsInTeam(Team team, Player player) {
return Objects.equals(team.getPlayer1(), player) || Objects.equals(team.getPlayer2(), player);
}
private boolean playerIsInMatch(Match match, Player player) {
return playerIsInTeam(match.getTeam1(), player) || playerIsInTeam(match.getTeam2(), player);
}
private void printRound(Round round, List<StandingsEntry> standings) {
for (var match: round.getMatches()) {
log.info("%s - %s".formatted(
String.valueOf(standings.stream().filter(entry -> entry.getTeam().equals(match.getTeam1())).map(StandingsEntry::getPosition).findFirst().get()),
String.valueOf(standings.stream().filter(entry -> entry.getTeam().equals(match.getTeam2())).map(StandingsEntry::getPosition).findFirst().get())));
}
}
private static class ConflictInDrawException extends RuntimeException {}
private List<Match> tryMatches(List<Team> remainingTeams, Group group) {
var newMatches = new ArrayList<Match>();
if (remainingTeams.isEmpty()) {
return newMatches;
}
var newMatch = new Match();
newMatch.setTeam1(remainingTeams.getFirst());
for (var opponentIndex = 1; opponentIndex < remainingTeams.size(); opponentIndex++) {
if (!findPreviousMatchOccurence(newMatch.getTeam1(), remainingTeams.get(opponentIndex), group)) {
newMatch.setTeam2(remainingTeams.get(opponentIndex));
} else {
log.info("Wedstrijd %s - %s kwam al eerder voor.".formatted(newMatch.getTeam1().toString(), remainingTeams.get(opponentIndex).toString()));
continue;
}
var newRemainingTeams = getRemainingTeams(remainingTeams, opponentIndex);
if (newRemainingTeams.size() == 1) {
newMatches.add(newMatch);
break;
} else {
try {
newMatches.addAll(tryMatches(newRemainingTeams, group));
newMatches.add(newMatch);
break;
} catch (ConflictInDrawException ignored) {
}
}
}
if (newMatch.getTeam2() == null) {
log.info("Geen tegenstander gevonden voor %s.".formatted(newMatch.getTeam1().toString()));
throw new ConflictInDrawException();
}
return newMatches;
}
private List<Team> getRemainingTeams(List<Team> remainingTeams, int opponentIndex) {
var newRemainingTeams = new ArrayList<Team>();
for (var remainingTeamIndex = 1; remainingTeamIndex < remainingTeams.size(); remainingTeamIndex++) {
if (remainingTeamIndex == opponentIndex) continue;
newRemainingTeams.add(remainingTeams.get(remainingTeamIndex));
}
return newRemainingTeams;
}
private boolean findPreviousMatchOccurence(Team team1, Team team2, Group group) {
for (var round : group.getRounds()) {
for (var match : round.getMatches()) {
if ((Objects.equals(match.getTeam1(), team1) && Objects.equals(match.getTeam2(), team2))
|| (Objects.equals(match.getTeam1(), team2) && Objects.equals(match.getTeam2(), team1))) {
return true;
}
}
}
return false;
}
private boolean playerHasSkippedRoundBefore(Player player, Tournament tournament) {
var hasSkippedRound = false;
return hasSkippedRound;
}
public Tournament startMatch(Tournament tournament, Long matchId, Long court) {
var match = getMatch(tournament, matchId);
match.setStatus(Status.IN_PROGRESS);
match.setStartTime(LocalDateTime.now());
match.setCourt(court);
tournamentRepository.save(tournament);
return tournament;
}
public Tournament stopMatch(Tournament tournament, Long matchId) {
var match = getMatch(tournament, matchId);
match.setStatus(Status.NOT_STARTED);
match.setStartTime(null);
match.setCourt(null);
tournamentRepository.save(tournament);
return tournament;
}
public Tournament saveResult(Tournament tournament, Long matchId, ResultDto result) {
var match = getMatch(tournament, matchId);
if (match.getGames().isEmpty()) {
match.setEndTime(LocalDateTime.now());
match.setStatus(Status.FINISHED);
match.setPlayed(true);
}
match.getGames().clear();
match.getGames().addAll(resultToGames(result, match));
tournamentRepository.save(tournament);
return tournament;
}
private List<Game> resultToGames(ResultDto result, Match match) {
var games = new ArrayList<Game>();
for (var game : result.getGames()) {
if (game.getScore1() != null) {
games.add(Game.builder()
.score1(game.getScore1())
.score2(game.getScore2())
.match(match)
.build());
}
}
return games;
}
public void updatePaid(Tournament tournament, Long playerId, Boolean paid) {
var tournamentPlayer = tournament.getTournamentPlayers()
.stream()
.filter(player -> player.getPlayer().getId().equals(playerId))
.findFirst()
.get();
tournamentPlayer.setPaid(paid);
tournamentRepository.save(tournament);
}
public void updatePresent(Tournament tournament, Long playerId, Boolean present) {
var tournamentPlayer = tournament.getTournamentPlayers()
.stream()
.filter(player -> player.getPlayer().getId().equals(playerId))
.findFirst()
.get();
tournamentPlayer.setPresent(present);
tournamentRepository.save(tournament);
}
}

View File

@@ -0,0 +1,43 @@
package nl.connectedit.swiss.service;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Tournament;
import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.repository.TournamentRepository;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
@RequiredArgsConstructor
public class TournamentService {
private final TournamentRepository tournamentRepository;
public List<Tournament> findAllTournaments() {
return tournamentRepository.findAll();
}
public List<Tournament> findAllTournamentsWithStatus(TournamentStatus status) {
return tournamentRepository.findAllByStatus(status);
}
public Tournament findTournamentById(Long id) {
return tournamentRepository.findById(id).orElse(null);
}
public Tournament saveTournament(Tournament tournament) {
return tournamentRepository.save(tournament);
}
public Tournament updateTournament(Long tournamentId, Tournament newTournament) {
Tournament tournament = findTournamentById(tournamentId);
tournament.setName(newTournament.getName());
tournament.setDate(newTournament.getDate());
tournament.setMaxEvents(newTournament.getMaxEvents());
tournament.setCostsPerEvent(newTournament.getCostsPerEvent());
tournament.setCourts(newTournament.getCourts());
return tournamentRepository.save(tournament);
}
}

View File

@@ -0,0 +1,223 @@
package nl.connectedit.swiss.service;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.*;
import nl.connectedit.swiss.domain.entity.Event;
import nl.connectedit.swiss.domain.entity.Player;
import nl.connectedit.swiss.domain.entity.Registration;
import nl.connectedit.swiss.domain.entity.Tournament;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
import static nl.connectedit.swiss.domain.Validation.Severity.*;
@Service
@RequiredArgsConstructor
public class TournamentValidationService {
private final PlayerService playerService;
public TournamentValidation validate(Tournament tournament) {
var players = playerService.findAllPlayers();
TournamentValidation tournamentValidation = new TournamentValidation();
tournamentValidation.setTournamentId(tournament.getId());
var validations = new ArrayList<Validation>();
checkForTooManyRegistrationsPerPlayer(tournament, players, validations);
tournamentValidation.setValidations(validations);
tournamentValidation.setEventValidations(tournament
.getEvents()
.stream()
.map(this::validate)
.toList()
);
return tournamentValidation;
}
private EventValidation validate(Event event) {
var validations = new ArrayList<Validation>();
if (event.getStatus() != Status.NOT_STARTED) {
return EventValidation.builder().eventId(event.getId()).build();
}
checkNumberOfRegistrations(event, validations);
checkMultipleGroupsNeeded(event, validations);
if (event.getType().isDoublesEvent()) {
checkForEmptyPartnerRegistrations(event, validations);
checkForInvalidPartnerRegistrations(event, validations);
checkForMultiplePartnerRegistrations(event, validations);
}
return EventValidation.builder().eventId(event.getId()).validations(validations).build();
}
private void checkNumberOfRegistrations(Event event, List<Validation> validations) {
if (event.getRegistrations().isEmpty()) {
addValidation(validations, ERROR, "Geen inschrijvingen");
} else if (event.getRegistrations().size() < 4 ) {
addValidation(validations, ERROR, "Te weinig inschrijvingen");
}
}
private void checkMultipleGroupsNeeded(Event event, List<Validation> validations) {
if (event.getType().isDoublesEvent()) {
if (event.getRegistrations().size() > 32) {
addValidation(validations, WARN, "Meer dan 32 inschrijvingen, 2 groepen nodig");
}
} else {
if (event.getRegistrations().size() > 16) {
addValidation(validations, WARN, "Meer dan 16 inschrijvingen, 2 groepen nodig");
}
}
}
private void checkForEmptyPartnerRegistrations(Event event, List<Validation> validations) {
var registrations = event.getRegistrations();
for (var registration : registrations) {
var partner = registration.getPartner();
if (partner == null) {
addValidation(validations, ERROR, "%s heeft geen partner", registration.getPlayer().getFullName());
}
}
}
private void checkForInvalidPartnerRegistrations(Event event, List<Validation> validations) {
var registrations = event.getRegistrations();
for (var registration : registrations) {
if (registration.getPartner() != null) {
var optionalPartnerRegistration = getRegistrationForPlayer(registrations, registration.getPartner());
if (optionalPartnerRegistration.isEmpty()) {
addValidation(validations, ERROR,
"%s heeft als geregistreerde partner %s, maar deze staat niet ingeschreven voor het onderdeel %s",
registration.getPlayer().getFullName(), registration.getPartner().getFullName(), event.getType().getText());
} else {
var partnerRegistration = optionalPartnerRegistration.get();
var partnerPartner = partnerRegistration.getPartner();
if (partnerPartner == null) {
addValidation(validations, ERROR,
"%s heeft als geregistreerde partner %s, maar deze heeft nog geen geregistreerde partner",
registration.getPlayer().getFullName(), partnerRegistration.getPlayer().getFullName());
} else {
if (!Objects.equals(registration.getPlayer(), partnerPartner)) {
addValidation(validations, ERROR,
"%s heeft als geregistreerde partner %s, maar deze staat ingeschreven met partner %s",
registration.getPlayer().getFullName(), partnerRegistration.getPlayer().getFullName(), partnerRegistration.getPartner().getFullName());
}
}
}
}
}
}
private void checkForMultiplePartnerRegistrations(Event event, List<Validation> validations) {
var registrations = event.getRegistrations();
for (var registration : registrations) {
var partners = getPartners(registrations, registration.getPlayer());
if (partners.size() > 1) {
addValidation(validations, ERROR,
"%s is met meerdere spelers als partner geregistreerd (%s)",
registration.getPlayer().getFullName(), getPlayersAsList(partners));
}
}
}
private void checkForTooManyRegistrationsPerPlayer(Tournament tournament, List<Player> players, List<Validation> validations) {
var playerRegistrations = new ArrayList<Registration>();
for (var player : players) {
playerRegistrations.clear();
for (var event : tournament.getEvents()) {
var eventRegistration = getRegistrationForPlayer(event.getRegistrations(), player);
eventRegistration.ifPresent(playerRegistrations::add);
}
if (playerRegistrations.size() > tournament.getMaxEvents()) {
addValidation(validations, ERROR,
"%s staat voor meer dan %d onderdelen ingeschreven, nl. %s",
player.getFullName(), tournament.getMaxEvents(), getEventsAsList(playerRegistrations, tournament));
}
}
}
private String getEventsAsList(List<Registration> registrations, Tournament tournament) {
if (registrations.isEmpty()) {
return "";
} else if (registrations.size() == 1) {
return tournament
.getEvents()
.stream()
.filter(event -> event.getRegistrations().contains(registrations.getFirst()))
.findFirst()
.get()
.getType()
.getText();
} else {
return String.join(", ", registrations
.stream()
.limit(registrations.size() - 1)
.map(registration -> this.getTypeForRegistration(registration, tournament))
.map(EventType::getText)
.toList()
) + " en " + getTypeForRegistration(registrations.getLast(), tournament).getText();
}
}
private EventType getTypeForRegistration(Registration registration, Tournament tournament) {
return tournament
.getEvents()
.stream()
.filter(event -> event.getRegistrations().contains(registration))
.findFirst()
.get()
.getType();
}
private String getPlayersAsList(List<Player> players) {
if (players.isEmpty()) {
return "";
} else if (players.size() == 1) {
return players.getFirst().getFullName();
} else {
return String.join(", ", players
.stream()
.limit(players.size() - 1)
.map(Player::getFullName)
.toList()
) + " en " + players.getLast().getFullName();
}
}
private Optional<Registration> getRegistrationForPlayer(List<Registration> registrations, Player player) {
return registrations
.stream()
.filter(registration -> Objects.equals(registration.getPlayer(), player)).findFirst();
}
private List<Player> getPartners(List<Registration> registrations, Player player) {
List<Player> partners = new ArrayList<>();
for (var registration : registrations) {
if (registration.getPartner() != null && registration.getPartner().equals(player)) {
partners.add(registration.getPlayer());
}
}
return partners;
}
private void addValidation(List<Validation> validations, Validation.Severity severity, String message, Object... params) {
validations.add(new Validation(severity, message.formatted(params)));
}
}