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)));
}
}

View File

@@ -0,0 +1,27 @@
spring:
application:
name: swiss
# autoconfigure:
# exclude:
# - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
# - org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration
datasource:
url: jdbc:postgresql://localhost:5432/swiss?currentSchema=swiss
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
flyway:
enabled: false
security: true
#logging:
# level:
# root: DEBUG
#logging:
# level:
# org:
# hibernate:
# sql: DEBUG
# type:
# descriptor:
# sql:
# BasicBinder: TRACE

View File

@@ -0,0 +1,39 @@
spring:
application:
name: swiss
# autoconfigure:
# exclude:
# - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
# - org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration
datasource:
url: jdbc:h2:file:./localswiss
driver-class-name: org.h2.Driver
username: sa
password: password
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
jackson:
serialization:
indent-output: true
flyway:
url: jdbc:h2:file:./localswiss
user: sa
password: password
locations: flyway
baseline-on-migrate: true
security: true
#logging:
# level:
# root: DEBUG
#logging:
# level:
# org:
# hibernate:
# sql: DEBUG
# type:
# descriptor:
# sql:
# BasicBinder: TRACE

View File

@@ -0,0 +1,33 @@
spring:
application:
name: swiss
# autoconfigure:
# exclude:
# - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
# - org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration
datasource:
url: jdbc:postgresql://${DB_URL:localhost:5432}/swiss?currentSchema=swiss
username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
jpa:
hibernate:
ddl-auto: none
flyway:
url: jdbc:postgresql://${DB_URL:localhost:5432}/swiss?currentSchema=swiss
user: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres}
baseline-on-migrate: true
management:
server:
port: 8081
endpoints:
web:
exposure:
include: "*"
endpoint:
health:
probes:
enabled: true
security: true

View File

@@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCp5PMrGbzVKQSE
LSSkMoJnPUKu0f+ySIRe39qd5r1w07S6TAEqIwBdXwCdwTo8QnggNNxlC3j2pa0J
lKI3cNrnNitcs8oI0jQ4LN/HsN/cwp3V+m0sBaEVt9mZK0qSj2mxx+EnHpLF3Z+L
7EAGPlezpyGKsJHi1hgAXcbKdLcfC/h3g/TtIvPj7VfR86VGArY2VsZQWFsKvkQa
rGVosARQo0b2L/qsqYr9CvCEZ6gSTMS0+xr9Fc9AsYOyuzsw2zHvq8i/L8alK/Tt
CJobZK1O0Kedbv5R1gEU+NhGzrdZe1YwCCg5QZCk6j9NXN8tpMIFpeORsxXWxB72
Dzc6+/FNAgMBAAECggEAB/mFeYkyfi339o1Y6jUtvlNXkTWtwyxYvExVKnLFgymI
0vLU3im472kRchY7Gc+D7H0WuE5+zdMOiYPWznPnbpFyHR6aVeoqBdYDZg/1Hhtr
hbsEy1tzSX3xApnP3QvKygvIE4pBmPSTc+Gxyqk7/CSU9DngCy4B/+hm96NdYiFd
yI5VByp+DMhk/dtgWFOHZcG4MvrRaLA8ZRnSkCa5QrL+la3lf4M9FJedpo9GvjG6
c8M+pgLKMmw4bFDqfDyEpYhJxQaAqEMFaiBuMMDymI6OXpzBG92kflK7VvR4YBym
DUhqhmQ4qAe37Wr2vVob+Dsf39IuS+FWDNZSW6lrMQKBgQDnGVBv0Xv3+pb/NmqA
1sRJvhMtFaVkIKJuf1qK7SNzU5tCbNAfaJgQQVFNswnjpbS300T1ehNorsDeDi05
WqeWdRPY/Qz+19AJ1UiCMUku/SWWN21byUQU4yven6mJ6rgkKbAv4E4VtpcG3q8B
W/aIL1KAHvEFJ3rI21QD4A+NvQKBgQC8M1rlXu64kKbw4D5KormMFvjkoROsXwn/
eqN0wCN9avD7+LN/MUhqn7S0t0iQJRH+kaOjS6yl1v9JFI8bBLMQ1Uk1Mtpi4Mvl
AWtPLUYXxeP+zlS1ueBuDzRsNUGRqYM1MVlr7sko5gOAPQHHaDbNRYSqaskLtRR0
/BjItmDC0QKBgGCySOPgxXxnUBMNk9bBBnTMoX111zRkK1MM2rfSrcitrQNIQHVD
8Iysp/ZY+cRVK57XOb11DPX6WR0Q1X9wHTtpVZqvl2ZyqsvSgHppYPPWXInUO1/y
gRg0TcDjEa9xlQccomoF8uZG9j6boqJw9mDZXC3bxIGhmVC95ROSBzAJAoGBAJHz
3dUuV0IpZF4/+e8V3YHIOwPL657tIarQ6Dzd2WglbHhsun+0r62I57KSxaKMLTVY
qygzwtPmNZruZ8ETVu+CCUFJi9XM8jNKc3c27Dn5jUSJrWY1ndic0BHvB0e4x3mU
KP4sdDLUlvh3145WwtFUzXsAT6RVrWTAMVRPJCFRAoGBAMEW8V36c+XJuD9qDvg3
qG3uPaGS1IytkpSAtfSV+hXJ14N57L/oxPTVyrZKDgu0ic3KAHEJG/WN+SQPfaWT
zj8YnFFeSISRRo4Y1Q4Rg/Yer5fKVdkvGKPR9lCEhbxEBqZz4qSMUHGak1exjTeW
DapsPxEG0wEN8rMoNzYMNuc3
-----END PRIVATE KEY-----

View File

@@ -0,0 +1,9 @@
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqeTzKxm81SkEhC0kpDKC
Zz1CrtH/skiEXt/anea9cNO0ukwBKiMAXV8AncE6PEJ4IDTcZQt49qWtCZSiN3Da
5zYrXLPKCNI0OCzfx7Df3MKd1fptLAWhFbfZmStKko9pscfhJx6Sxd2fi+xABj5X
s6chirCR4tYYAF3GynS3Hwv4d4P07SLz4+1X0fOlRgK2NlbGUFhbCr5EGqxlaLAE
UKNG9i/6rKmK/QrwhGeoEkzEtPsa/RXPQLGDsrs7MNsx76vIvy/GpSv07QiaG2St
TtCnnW7+UdYBFPjYRs63WXtWMAgoOUGQpOo/TVzfLaTCBaXjkbMV1sQe9g83Ovvx
TQIDAQAB
-----END PUBLIC KEY-----

View File

@@ -0,0 +1,303 @@
create table player
(
birthday date not null,
id bigint generated by default as identity
primary key,
club varchar(255),
email varchar(255) not null,
first_name varchar(255) not null,
last_name varchar(255) not null,
middle_name varchar(255),
phone_number varchar(255) not null,
sex varchar(255) not null
constraint player_sex_check
check ((sex)::text = ANY ((ARRAY ['M'::character varying, 'V'::character varying])::text[])),
strength varchar(255) not null
constraint player_strength_check
check ((strength)::text = ANY
((ARRAY ['D5'::character varying, 'D6'::character varying, 'D7'::character varying, 'D8'::character varying, 'D9'::character varying, 'DR'::character varying])::text[]))
);
create table tournament
(
date date,
courts bigint,
id bigint generated by default as identity
primary key,
max_events bigint,
name varchar(255),
status varchar(255)
constraint tournament_status_check
check ((status)::text = ANY
((ARRAY ['UPCOMING'::character varying, 'DIVIDED'::character varying, 'DRAWN'::character varying, 'ONGOING'::character varying, 'CLOSED'::character varying])::text[]))
);
create table event
(
id bigint generated by default as identity
primary key,
tournament_id bigint
constraint fkidhc25ppai44aclt55uu9n0fd
references tournament on delete cascade,
status varchar(255)
constraint event_status_check
check ((status)::text = ANY
((ARRAY ['NOT_STARTED'::character varying, 'READY_TO_PLAY'::character varying, 'IN_PROGRESS'::character varying, 'FINISHED'::character varying])::text[])),
type varchar(255)
constraint event_type_check
check ((type)::text = ANY
((ARRAY ['HE'::character varying, 'DE'::character varying, 'HD'::character varying, 'DD'::character varying, 'GD'::character varying])::text[]))
);
create table eventgroup
(
type smallint
constraint eventgroup_type_check
check ((type >= 0) AND (type <= 4)),
event_id bigint
constraint fksdpnxwfbha8j4fiwcxkfyknhv
references event on delete cascade,
id bigint generated by default as identity
primary key,
name varchar(255),
status varchar(255)
constraint eventgroup_status_check
check ((status)::text = ANY
((ARRAY ['NOT_STARTED'::character varying, 'READY_TO_PLAY'::character varying, 'IN_PROGRESS'::character varying, 'FINISHED'::character varying])::text[]))
);
create table event_groups
(
event_id bigint not null
constraint fkd4r6j627kmoyxt2k0814d8k18
references event on delete cascade,
groups_id bigint not null
unique
constraint fkg4tgtn29hlh54nqxvk3uplupr
references eventgroup on delete cascade
);
create table registration
(
event_id bigint
constraint fks4x1uat6i8fx26qpdrfwfg3ya
references event on delete cascade,
id bigint generated by default as identity
primary key,
partner_id bigint
constraint fkhmyyu2ljiwo8x10kjfab1pkru
references player on delete cascade,
player_id bigint
constraint fkswk5vywwvd2r4knle35xjygao
references player on delete cascade,
tournament_id bigint
constraint fktfm09huujek0o03wklcg9ewpq
references tournament on delete cascade
);
create table event_registrations
(
event_id bigint not null
constraint fks4kjleulewhu881p4nygwdi0
references event on delete cascade,
registrations_id bigint not null
unique
constraint fkl4jouqv1u8b3wvwv97i1cqmm7
references registration on delete cascade
);
create table player_partner_registrations
(
partner_registrations_id bigint not null
unique
constraint fkohcyc057xfh1bq2gcjjhx90b5
references registration on delete cascade,
player_id bigint not null
constraint fk61ldsgn3vwru4tjdiraxrsaeb
references player on delete cascade
);
create table player_registrations
(
player_id bigint not null
constraint fklc29ku8hopaa9wbmru6dtbax3
references player on delete cascade,
registrations_id bigint not null
unique
constraint fkasxo61ngph7cyje01l1n1ldhg
references registration on delete cascade
);
create table team
(
group_id bigint
constraint fk6sh9ago3tae2l8og75kbrf4mx
references eventgroup on delete cascade,
id bigint generated by default as identity
primary key,
player1_id bigint
constraint fkphn10d6c8k5b758iklivfyn5y
references player on delete cascade,
player2_id bigint
constraint fk962rmngjyud0ijcw4w7d9vfs0
references player on delete cascade
);
create table eventgroup_teams
(
group_id bigint not null
constraint fk2o8i34jh5bk79ya6pc8w3k9of
references eventgroup on delete cascade,
teams_id bigint not null
unique
constraint fk832tg34v55x5kuma6ia55w4dq
references team on delete cascade
);
create table round
(
status smallint
constraint round_status_check
check ((status >= 0) AND (status <= 3)),
drawn_out_id bigint
constraint fk3w19bu9yiv9px837huabw6abs
references team on delete cascade,
group_id bigint
constraint fk3ytfpp2rat10y2x43g7cl61p3
references eventgroup on delete cascade,
id bigint generated by default as identity
primary key,
name varchar(255)
);
create table eventgroup_rounds
(
group_id bigint not null
constraint fksghgh2lq09c28y7xcsmvwd63s
references eventgroup on delete cascade,
rounds_id bigint not null
unique
constraint fkkl6r766uq2l1wpe2yals44nyw
references round on delete cascade
);
create table match
(
played boolean,
status smallint
constraint match_status_check
check ((status >= 0) AND (status <= 3)),
type smallint
constraint match_type_check
check ((type >= 0) AND (type <= 4)),
court bigint,
end_time timestamp(6),
id bigint generated by default as identity
primary key,
round_id bigint
constraint fkol8rkyfucvsv37sd7kebpm1nw
references round on delete cascade,
start_time timestamp(6),
team1_id bigint
constraint fkglt3t5urqflayn544lpn5ua3s
references team on delete cascade,
team2_id bigint
constraint fkci4coem3xydhawrap0y36qlmg
references team on delete cascade
);
create table game
(
id bigint generated by default as identity
primary key,
match_id bigint
constraint fkkspobx32vu8ykuguwl3u0nuhd
references match on delete cascade,
score1 bigint,
score2 bigint
);
create table match_games
(
games_id bigint not null
unique
constraint fkqt1fsmogbyfoo0w5bilc6rmku
references game on delete cascade,
match_id bigint not null
constraint fkgab11a4ifq5ygm14rpsrvrsuh
references match on delete cascade
);
create table round_matches
(
matches_id bigint not null
unique
constraint fk9quwtp4ipel9hm56p4oh0f4fh
references match on delete cascade,
round_id bigint not null
constraint fkr8lyri4t4xil8ajv8e3gjrd47
references round on delete cascade
);
create table round_quit
(
quit_id bigint not null
unique
constraint fkgjta0bnl9jey9c64pmcbbnj5v
references team on delete cascade,
round_id bigint not null
constraint fknkc29tpads6tq669y77d0dvwh
references round on delete cascade
);
create table tournament_costs_per_event
(
costs_per_event real,
tournament_id bigint not null
constraint fkts9nbg8r28e9ncafhnhdohai
references tournament on delete cascade
);
create table tournament_events
(
events_id bigint not null
unique
constraint fkfo9dvc9isnkm8hj8il8ajiea6
references event on delete cascade,
tournament_id bigint not null
constraint fkb4la5xkmiin27q1du88n7gbxs
references tournament on delete cascade
);
create table tournament_player
(
paid boolean not null,
present boolean not null,
id bigint generated by default as identity
primary key,
player_id bigint
constraint fkrqw4qfs65btri9sfkbof5xyxq
references player on delete cascade,
tournament_id bigint
constraint fkrrm3jbmm1fxx5t9t5f8t46ebc
references tournament on delete cascade
);
create table tournament_tournament_players
(
tournament_id bigint not null
constraint fkn7jcr3q0c3lh5gm93t0piyjyt
references tournament on delete cascade,
tournament_players_id bigint not null
unique
constraint fkhhmn6g3oxtqiu99ch61h1ubcf
references tournament_player on delete cascade
);
create table tournament_player_events
(
tournament_player_id bigint not null
constraint fksihteat3lqwdp92fkp33oglqt
references tournament_player on delete cascade,
events varchar(255)
);

3903
src/main/resources/export.sql Executable file

File diff suppressed because it is too large Load Diff

3855
src/main/resources/export2.sql Executable file

File diff suppressed because it is too large Load Diff