Compare commits

..

32 Commits

Author SHA1 Message Date
Michel ten Voorde
1a1a0e373f Update counter
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-11-06 16:39:00 +01:00
Michel ten Voorde
d5df8f5d32 Cleanup
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-11-03 14:32:13 +01:00
Michel ten Voorde
1df0ebee5c Corrected cascading
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-27 17:22:16 +01:00
Michel ten Voorde
ce9f10acb9 Add logging
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-27 16:37:50 +01:00
Michel ten Voorde
001a83e75a Add logging
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-27 16:33:36 +01:00
Michel ten Voorde
277bcadb5d Add logging
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-27 15:55:57 +01:00
Michel ten Voorde
b836112ba5 Add logging
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-27 15:48:04 +01:00
Michel ten Voorde
180d431f8d Fix when no rounds
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-27 14:41:10 +01:00
cca133d67c Fix when no groups
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-25 15:27:19 +02:00
1385dc56a1 Revert "Hibernate warning fixes"
This reverts commit dd653cb525.
2025-10-25 15:22:03 +02:00
1d0cc270e3 Fix when no substitutions 2025-10-25 15:22:00 +02:00
dd653cb525 Hibernate warning fixes
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-24 15:18:18 +02:00
97af2628fb Use Java 25 on Jenkins
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-24 14:43:15 +02:00
34a5231081 Use Java 25 on Jenkins
Some checks failed
Gitea/swiss-backend/pipeline/head There was a failure building this commit
2025-10-24 14:42:31 +02:00
753ed4f49f Use Java 25 on Jenkins 2025-10-24 14:42:17 +02:00
01f3096e60 Revert "Rollback to Java 21"
Some checks failed
Gitea/swiss-backend/pipeline/head There was a failure building this commit
This reverts commit 1aee1eeb30.
2025-10-24 14:38:14 +02:00
1aee1eeb30 Rollback to Java 21
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-24 14:34:12 +02:00
7f26fcc109 Cleanup
Some checks failed
Gitea/swiss-backend/pipeline/head There was a failure building this commit
2025-10-08 22:08:43 +02:00
1ddb628b27 Fixed Byte Buddy version
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-08 21:50:06 +02:00
46662ac553 Updated auth config
Some checks failed
Gitea/swiss-backend/pipeline/head There was a failure building this commit
2025-10-08 21:36:05 +02:00
f46c619be3 Java 25
Some checks failed
Gitea/swiss-backend/pipeline/head There was a failure building this commit
2025-10-07 10:57:24 +02:00
9361640f4c Cleanup
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-04 22:40:20 +02:00
2527d3490f NPE fix
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-10-02 22:10:00 +02:00
2a213c831a Moved logic to backend
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-09-25 23:12:39 +02:00
45c50924bc Invallers
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-09-23 23:21:18 +02:00
b43358f071 Invallers
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-09-18 23:27:10 +02:00
b4bbefff46 Invallers
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-09-17 08:58:45 +02:00
da7ea66d3e Invallers
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-09-11 23:43:10 +02:00
d7dee35d6e WIP: substitutions
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-09-10 10:35:41 +02:00
2cc188540b Registreer TournamentPlayer gelijk bij inschrijven
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-09-01 23:00:10 +02:00
06b112305b Fix for editing match results
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-08-28 23:21:38 +02:00
e89a7d4bff Various improvements
All checks were successful
Gitea/swiss-backend/pipeline/head This commit looks good
2025-08-25 23:37:35 +02:00
47 changed files with 1084 additions and 796 deletions

View File

@@ -1,4 +1,4 @@
FROM eclipse-temurin:21 FROM eclipse-temurin:25
COPY target/swiss*.jar swiss-backend.jar COPY target/swiss*.jar swiss-backend.jar

3
Jenkinsfile vendored
View File

@@ -5,6 +5,9 @@ pipeline {
disableConcurrentBuilds() disableConcurrentBuilds()
} }
agent any agent any
tools {
jdk 'JDK 25'
}
/* /*
agent { agent {

33
TODO
View File

@@ -5,3 +5,36 @@ https://bootify.io/spring-security/rest-api-spring-security-with-jwt.html
https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/cluster-management/update-cluster https://access.crunchydata.com/documentation/postgres-operator/latest/tutorials/cluster-management/update-cluster
Token refresh Token refresh
db update:
round.is_finals_round
koppeltabel eventgroup_teams
tournament.active
set search_path to swiss;
ALTER TABLE match
ADD counter_id bigint;
;
ALTER TABLE round
ADD is_finals_round boolean;
ALTER TABLE round
ADD is_final_round boolean DEFAULT (false);
;
ALTER TABLE tournament_player
ADD counting boolean;
ALTER TABLE tournament_player
ADD counts bigint;
UPDATE tournament_player SET counting = false;
UPDATE tournament_player SET counts = 0;
tournament_player.substitute_id + constraint

338
pom.xml
View File

@@ -1,185 +1,183 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<parent> <parent>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId> <artifactId>spring-boot-starter-parent</artifactId>
<version>3.3.4</version> <version>3.5.6</version>
<relativePath/> <!-- lookup parent from repository --> <relativePath/> <!-- lookup parent from repository -->
</parent> </parent>
<groupId>nl.connected-it</groupId> <groupId>nl.connected-it</groupId>
<artifactId>swiss-backend</artifactId> <artifactId>swiss-backend</artifactId>
<version>0.0.1-SNAPSHOT</version> <version>0.0.1-SNAPSHOT</version>
<name>swiss-backend</name> <name>swiss-backend</name>
<description>Swiss Backend</description> <description>Swiss Backend</description>
<properties> <properties>
<java.version>21</java.version> <java.version>25</java.version>
<maven.compiler.source>21</maven.compiler.source> <maven.compiler.source>25</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target> <maven.compiler.target>25</maven.compiler.target>
<mapstruct.version>1.6.0.RC1</mapstruct.version> </properties>
</properties> <dependencies>
<dependencies> <dependency>
<dependency> <groupId>org.springframework.boot</groupId>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId>
<artifactId>spring-boot-starter-actuator</artifactId> </dependency>
</dependency> <dependency>
<dependency> <groupId>org.springframework.boot</groupId>
<groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId>
<artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId> <artifactId>spring-boot-starter-security</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<dependency> <dependency>
<groupId>com.auth0</groupId> <groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId> <artifactId>java-jwt</artifactId>
<version>4.4.0</version> <version>4.5.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5-jakarta</artifactId>
<version>2.20.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>11.13.2</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
<version>11.13.2</version>
<scope>runtime</scope>
</dependency> </dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-hibernate5-jakarta</artifactId>
<version>2.17.2</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
<version>10.18.0</version>
</dependency>
<dependency>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
<version>10.18.0</version>
<scope>runtime</scope>
</dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId> <artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>
<!-- <optional>true</optional>--> <!-- <optional>true</optional>-->
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId> <artifactId>spring-security-test</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
</dependencies> </dependencies>
<build> <build>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.hibernate.orm.tooling</groupId> <groupId>org.hibernate.orm.tooling</groupId>
<artifactId>hibernate-enhance-maven-plugin</artifactId> <artifactId>hibernate-enhance-maven-plugin</artifactId>
<version>${hibernate.version}</version> <version>6.6.31.Final</version>
<executions> <executions>
<execution> <execution>
<id>enhance</id> <id>enhance</id>
<goals> <goals>
<goal>enhance</goal> <goal>enhance</goal>
</goals> </goals>
<configuration> <configuration>
<enableLazyInitialization>true</enableLazyInitialization> <enableLazyInitialization>true</enableLazyInitialization>
<enableDirtyTracking>true</enableDirtyTracking> <enableDirtyTracking>true</enableDirtyTracking>
<enableAssociationManagement>true</enableAssociationManagement> <enableAssociationManagement>true</enableAssociationManagement>
</configuration> </configuration>
</execution> </execution>
</executions> </executions>
</plugin> <dependencies>
<plugin> <dependency>
<groupId>org.graalvm.buildtools</groupId> <groupId>net.bytebuddy</groupId>
<artifactId>native-maven-plugin</artifactId> <artifactId>byte-buddy</artifactId>
</plugin> <version>1.17.7</version>
<plugin> </dependency>
<groupId>org.springframework.boot</groupId> </dependencies>
<artifactId>spring-boot-maven-plugin</artifactId> </plugin>
<configuration> <plugin>
<excludes> <groupId>org.graalvm.buildtools</groupId>
<exclude> <artifactId>native-maven-plugin</artifactId>
<groupId>org.projectlombok</groupId> </plugin>
<artifactId>lombok</artifactId> <plugin>
</exclude> <groupId>org.springframework.boot</groupId>
</excludes> <artifactId>spring-boot-maven-plugin</artifactId>
</configuration> <configuration>
</plugin> <excludes>
<plugin> <exclude>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.projectlombok</groupId>
<artifactId>maven-compiler-plugin</artifactId> <artifactId>lombok</artifactId>
<version>3.13.0</version> </exclude>
<configuration> </excludes>
<source>21</source> </configuration>
<target>21</target> </plugin>
<annotationProcessorPaths> <plugin>
<path> <groupId>org.apache.maven.plugins</groupId>
<groupId>org.projectlombok</groupId> <artifactId>maven-compiler-plugin</artifactId>
<artifactId>lombok</artifactId> <version>3.14.1</version>
<version>${lombok.version}</version> <configuration>
</path> <source>25</source>
<!-- <target>25</target>
<path> <annotationProcessorPaths>
<groupId>org.projectlombok</groupId> <path>
<artifactId>lombok-mapstruct-binding</artifactId> <groupId>org.projectlombok</groupId>
<version>0.2.0</version> <artifactId>lombok</artifactId>
</path> <version>${lombok.version}</version>
<path> </path>
<groupId>org.mapstruct</groupId> </annotationProcessorPaths>
<artifactId>mapstruct-processor</artifactId> </configuration>
<version>${mapstruct.version}</version> </plugin>
</path> <plugin>
--> <groupId>org.flywaydb</groupId>
</annotationProcessorPaths> <artifactId>flyway-maven-plugin</artifactId>
</configuration> <version>11.13.2</version>
</plugin> <configuration>
<plugin> <url>jdbc:postgresql://localhost:5432/swiss?currentSchema=swiss</url>
<groupId>org.flywaydb</groupId> <user>postgres</user>
<artifactId>flyway-maven-plugin</artifactId> <password>postgres</password>
<version>10.18.0</version> </configuration>
<configuration> </plugin>
<url>jdbc:postgresql://localhost:5432/swiss?currentSchema=swiss</url> </plugins>
<user>postgres</user>
<password>postgres</password>
</configuration>
</plugin>
</plugins>
</build> </build>
</project> </project>

View File

@@ -0,0 +1,44 @@
package nl.connectedit.swiss.auth;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Instant;
import java.util.stream.Collectors;
@RestController
@CrossOrigin
@RequestMapping("/api/auth")
public class AuthController {
private final JwtEncoder encoder;
public AuthController(JwtEncoder encoder) {
this.encoder = encoder;
}
@PostMapping
public String auth(Authentication authentication) {
Instant now = Instant.now();
long expiry = 36000L;
String scope = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
JwtClaimsSet claims = JwtClaimsSet.builder()
.issuer("self")
.issuedAt(now)
.expiresAt(now.plusSeconds(expiry))
.subject(authentication.getName())
.claim("scope", scope)
.build();
return this.encoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
}
}

View File

@@ -0,0 +1,9 @@
package nl.connectedit.swiss.auth;
public final class Roles {
public static final String USER = "USER";
private Roles() {
}
}

View File

@@ -1,46 +0,0 @@
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

@@ -1,20 +0,0 @@
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

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

View File

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

View File

@@ -1,23 +0,0 @@
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

@@ -1,57 +0,0 @@
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

@@ -1,62 +0,0 @@
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

@@ -1,51 +0,0 @@
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

@@ -1,19 +0,0 @@
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

@@ -1,27 +0,0 @@
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

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

View File

@@ -1,16 +0,0 @@
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

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

View File

@@ -1,26 +1,148 @@
package nl.connectedit.swiss.config; package nl.connectedit.swiss.config;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import jakarta.servlet.DispatcherType; import jakarta.servlet.DispatcherType;
import nl.connectedit.swiss.auth.Roles;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity; 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.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
//@Configuration import java.security.interfaces.RSAPrivateKey;
//@EnableWebSecurity import java.security.interfaces.RSAPublicKey;
import java.util.Arrays;
@Configuration
@EnableWebSecurity
public class SecurityConfig { public class SecurityConfig {
// @Bean private final RSAPublicKey key;
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { private final RSAPrivateKey priv;
public SecurityConfig(@Value("${jwt.public.key}") RSAPublicKey key,
@Value("${jwt.private.key}") RSAPrivateKey priv) {
this.key = key;
this.priv = priv;
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http http
.csrf(AbstractHttpConfigurer::disable) .cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeHttpRequests(auth -> auth.anyRequest().permitAll()); .csrf(csrf -> csrf.ignoringRequestMatchers("/api/auth"))
// .authorizeHttpRequests(request -> { .authorizeHttpRequests(authorize -> authorize
// request.dispatcherTypeMatchers(DispatcherType.FORWARD).permitAll(); // Allow OPTIONS requests for CORS preflight
// request.requestMatchers("/error").permitAll(); .requestMatchers(HttpMethod.OPTIONS, "/**").permitAll()
// }); // Allow POST to /api/auth without JWT (Basic Auth will still be required)
.requestMatchers(HttpMethod.POST, "/api/auth").permitAll()
// All other requests require authentication
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.exceptionHandling(exceptions -> exceptions
.authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
.accessDeniedHandler(new BearerTokenAccessDeniedHandler())
);
return http.build(); return http.build();
} }
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// Allow your Angular app's origin(s)
configuration.setAllowedOrigins(Arrays.asList(
"http://localhost:4200",
"http://localhost:8080",
// Add your production URL here when deploying
"https://badminton-toernooi.nl",
"https://test.badminton-toernooi.nl"
));
configuration.setAllowedMethods(Arrays.asList(
"GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"
));
configuration.setAllowedHeaders(Arrays.asList(
"Authorization",
"Content-Type",
"Accept"
));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
UserDetailsService users() {
return new InMemoryUserDetailsManager(
User.withUsername("bcholten")
.password(passwordEncoder().encode("bcholten"))
.roles(Roles.USER)
.build()
);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
JwtDecoder jwtDecoder() {
return NimbusJwtDecoder.withPublicKey(this.key).build();
}
@Bean
JwtEncoder jwtEncoder() {
JWK jwk = new RSAKey.Builder(this.key).privateKey(this.priv).build();
JWKSource<SecurityContext> jwks = new ImmutableJWKSet<>(new JWKSet(jwk));
return new NimbusJwtEncoder(jwks);
}
@Bean
public JwtAuthenticationConverter jwtAuthenticationConverter() {
JwtGrantedAuthoritiesConverter grantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter();
// Remove the SCOPE_ prefix
grantedAuthoritiesConverter.setAuthorityPrefix("");
JwtAuthenticationConverter jwtAuthenticationConverter = new JwtAuthenticationConverter();
jwtAuthenticationConverter.setJwtGrantedAuthoritiesConverter(grantedAuthoritiesConverter);
return jwtAuthenticationConverter;
}
} }

View File

@@ -1,6 +1,8 @@
package nl.connectedit.swiss.controller; package nl.connectedit.swiss.controller;
import jakarta.annotation.security.RolesAllowed;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.auth.Roles;
import nl.connectedit.swiss.domain.entity.Player; import nl.connectedit.swiss.domain.entity.Player;
import nl.connectedit.swiss.dto.PlayerDto; import nl.connectedit.swiss.dto.PlayerDto;
import nl.connectedit.swiss.mapper.PlayerMapper; import nl.connectedit.swiss.mapper.PlayerMapper;
@@ -12,6 +14,7 @@ import java.util.List;
@RestController @RestController
@CrossOrigin @CrossOrigin
@RolesAllowed(Roles.USER)
@RequiredArgsConstructor @RequiredArgsConstructor
public class PlayerController { public class PlayerController {

View File

@@ -1,6 +1,8 @@
package nl.connectedit.swiss.controller; package nl.connectedit.swiss.controller;
import jakarta.annotation.security.RolesAllowed;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.auth.Roles;
import nl.connectedit.swiss.domain.TournamentStatus; import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.dto.TournamentRegistrationDto; import nl.connectedit.swiss.dto.TournamentRegistrationDto;
import nl.connectedit.swiss.mapper.TournamentPlayerRegistrationMapper; import nl.connectedit.swiss.mapper.TournamentPlayerRegistrationMapper;
@@ -20,6 +22,7 @@ import java.util.List;
@RestController @RestController
@CrossOrigin @CrossOrigin
@RolesAllowed(Roles.USER)
@RequiredArgsConstructor @RequiredArgsConstructor
public class RegistrationController { public class RegistrationController {

View File

@@ -3,10 +3,12 @@ package nl.connectedit.swiss.controller;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.annotation.PostConstruct; import jakarta.annotation.PostConstruct;
import jakarta.annotation.security.RolesAllowed;
import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletRequest;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows; import lombok.SneakyThrows;
import nl.connectedit.swiss.auth.Roles;
import nl.connectedit.swiss.domain.*; import nl.connectedit.swiss.domain.*;
import nl.connectedit.swiss.domain.entity.Player; import nl.connectedit.swiss.domain.entity.Player;
import nl.connectedit.swiss.domain.entity.Registration; import nl.connectedit.swiss.domain.entity.Registration;
@@ -39,6 +41,7 @@ import static nl.connectedit.swiss.domain.PlayerStrength.*;
@RestController @RestController
@CrossOrigin @CrossOrigin
@RolesAllowed(Roles.USER)
@RequiredArgsConstructor @RequiredArgsConstructor
public class TestController { public class TestController {
@@ -95,7 +98,7 @@ public class TestController {
var eventIdHE = tournament.getEvents().stream().filter(event -> event.getType().equals("HE")).findFirst().get().getId(); 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(); var eventIdDE = tournament.getEvents().stream().filter(event -> event.getType().equals("DE")).findFirst().get().getId();
for (var player : getPlayers()) { for (var player : getPlayers().subList(0, 31)) {
var tournamentRegistrationDto = new TournamentRegistrationDto(); var tournamentRegistrationDto = new TournamentRegistrationDto();
tournamentRegistrationDto.setId(tournament.getId()); tournamentRegistrationDto.setId(tournament.getId());
var eventRegistrationDto = new EventRegistrationDto(); var eventRegistrationDto = new EventRegistrationDto();
@@ -162,12 +165,13 @@ public class TestController {
private TournamentDto getTournament() { private TournamentDto getTournament() {
var tournamentDto = new TournamentDto(); var tournamentDto = new TournamentDto();
tournamentDto.setName("Testtoernooi"); tournamentDto.setName("Testtoernooi aangemaakt op " + LocalDate.now().format(DateTimeFormatter.ofPattern("dd-MM-yyyy")));
tournamentDto.setDate("14-12-2024"); tournamentDto.setDate("14-12-2024");
tournamentDto.setStatus("UPCOMING"); tournamentDto.setStatus("UPCOMING");
tournamentDto.setMaxEvents(2L); tournamentDto.setMaxEvents(2L);
tournamentDto.setCourts(9L); tournamentDto.setCourts(9L);
tournamentDto.setCostsPerEvent(List.of(6f, 10f, 0f)); tournamentDto.setCostsPerEvent(List.of(6f, 10f, 0f));
tournamentDto.setActive(true);
return tournamentDto; return tournamentDto;
} }

View File

@@ -1,11 +1,14 @@
package nl.connectedit.swiss.controller; package nl.connectedit.swiss.controller;
import jakarta.annotation.security.RolesAllowed;
import jakarta.transaction.Transactional; import jakarta.transaction.Transactional;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.auth.Roles;
import nl.connectedit.swiss.domain.TournamentStatus; import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.domain.entity.Tournament; import nl.connectedit.swiss.domain.entity.Tournament;
import nl.connectedit.swiss.dto.ResultDto; import nl.connectedit.swiss.dto.ResultDto;
import nl.connectedit.swiss.dto.TournamentDto; import nl.connectedit.swiss.dto.TournamentDto;
import nl.connectedit.swiss.dto.TournamentPlayerSubstitutionDto;
import nl.connectedit.swiss.dto.TournamentValidationDto; import nl.connectedit.swiss.dto.TournamentValidationDto;
import nl.connectedit.swiss.mapper.TournamentMapper; import nl.connectedit.swiss.mapper.TournamentMapper;
import nl.connectedit.swiss.mapper.TournamentValidationMapper; import nl.connectedit.swiss.mapper.TournamentValidationMapper;
@@ -19,180 +22,208 @@ import java.util.Objects;
@RestController @RestController
@CrossOrigin @CrossOrigin
@RolesAllowed(Roles.USER)
@RequiredArgsConstructor @RequiredArgsConstructor
public class TournamentController { public class TournamentController {
private final TournamentService tournamentService; private final TournamentService tournamentService;
private final TournamentMapper tournamentMapper; private final TournamentMapper tournamentMapper;
private final TournamentValidationService tournamentValidationService; private final TournamentValidationService tournamentValidationService;
private final TournamentValidationMapper tournamentValidationMapper; private final TournamentValidationMapper tournamentValidationMapper;
private final TournamentDivideService tournamentDivideService; private final TournamentDivideService tournamentDivideService;
private final TournamentDrawService tournamentDrawService; private final TournamentDrawService tournamentDrawService;
private final TournamentPlayService tournamentPlayService; private final TournamentPlayService tournamentPlayService;
@GetMapping("/tournaments") @GetMapping("/tournaments")
public ResponseEntity<List<TournamentDto>> getTournaments(@RequestParam(value = "status", required = false) TournamentStatus status) { public ResponseEntity<List<TournamentDto>> getTournaments(@RequestParam(value = "status", required = false) TournamentStatus status) {
List<Tournament> tournaments; List<Tournament> tournaments;
if (Objects.nonNull(status)) { if (Objects.nonNull(status)) {
tournaments = tournamentService.findAllTournamentsWithStatus(status); tournaments = tournamentService.findAllTournamentsWithStatus(status);
} else { } else {
tournaments = tournamentService.findAllTournaments(); tournaments = tournamentService.findAllTournaments();
}
return ResponseEntity.ok(tournaments
.stream()
.map(tournamentMapper::toDto)
.toList());
} }
@GetMapping("/tournaments/{id}") return ResponseEntity.ok(tournaments
@Transactional .stream()
public ResponseEntity<TournamentDto> getTournament(@PathVariable Long id) { .map(tournamentMapper::toDto)
var tournament = tournamentService.findTournamentById(id); .toList());
}
if (tournament == null) { @GetMapping("/tournaments/{id}")
return ResponseEntity.notFound().build(); @Transactional
} public ResponseEntity<TournamentDto> getTournament(@PathVariable Long id) {
var tournament = tournamentService.findTournamentById(id);
return ResponseEntity.ok(tournamentMapper.toDto(tournament)); if (tournament == null) {
return ResponseEntity.notFound().build();
} }
@PostMapping("/tournaments") return ResponseEntity.ok(tournamentMapper.toDto(tournament));
public ResponseEntity<TournamentDto> createTournament(@RequestBody TournamentDto tournamentDto) { }
Tournament tournament;
try { @PostMapping("/tournaments")
tournament = tournamentMapper.toEntity(tournamentDto); public ResponseEntity<TournamentDto> createTournament(@RequestBody TournamentDto tournamentDto) {
} catch (NullPointerException | DateTimeParseException e) { Tournament tournament;
return ResponseEntity.badRequest().build(); try {
} tournament = tournamentMapper.toEntity(tournamentDto);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentService.saveTournament(tournament))); } catch (NullPointerException | DateTimeParseException e) {
return ResponseEntity.badRequest().build();
} }
return ResponseEntity.ok(tournamentMapper.toDto(tournamentService.saveTournament(tournament)));
}
@PutMapping("/tournaments/{id}") @PutMapping("/tournaments/{id}")
public ResponseEntity<TournamentDto> updateTournament(@PathVariable Long id, @RequestBody TournamentDto tournamentDto) { public ResponseEntity<TournamentDto> updateTournament(@PathVariable Long id, @RequestBody TournamentDto tournamentDto) {
var newTournament = tournamentMapper.toEntity(tournamentDto); var newTournament = tournamentMapper.toEntity(tournamentDto);
var tournament = tournamentService.updateTournament(id, newTournament); var tournament = tournamentService.updateTournament(id, newTournament);
return ResponseEntity.ok(tournamentMapper.toDto(tournament)); return ResponseEntity.ok(tournamentMapper.toDto(tournament));
} }
@GetMapping("/tournaments/{id}/validate") @GetMapping("/tournaments/{id}/validate")
public ResponseEntity<TournamentValidationDto> validateTournament(@PathVariable Long id) { public ResponseEntity<TournamentValidationDto> validateTournament(@PathVariable Long id) {
var tournament = tournamentService.findTournamentById(id); var tournament = tournamentService.findTournamentById(id);
var tournamentValidation = tournamentValidationService.validate(tournament); var tournamentValidation = tournamentValidationService.validate(tournament);
return ResponseEntity.ok(tournamentValidationMapper.toDto(tournamentValidation)); return ResponseEntity.ok(tournamentValidationMapper.toDto(tournamentValidation));
} }
@PostMapping("/tournaments/{id}/divide") @PostMapping("/tournaments/{id}/divide")
public ResponseEntity<TournamentDto> divideTournamentNew(@PathVariable Long id) { public ResponseEntity<TournamentDto> divideTournamentNew(@PathVariable Long id) {
var tournament = tournamentService.findTournamentById(id); var tournament = tournamentService.findTournamentById(id);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentDivideService.divide(tournament))); return ResponseEntity.ok(tournamentMapper.toDto(tournamentDivideService.divide(tournament)));
} }
@PostMapping("/tournaments/{id}/divide/clear") @PostMapping("/tournaments/{id}/divide/clear")
public ResponseEntity<TournamentDto> clearTournamentDivision(@PathVariable Long id) { public ResponseEntity<TournamentDto> clearTournamentDivision(@PathVariable Long id) {
var tournament = tournamentService.findTournamentById(id); var tournament = tournamentService.findTournamentById(id);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentDivideService.clear(tournament))); return ResponseEntity.ok(tournamentMapper.toDto(tournamentDivideService.clear(tournament)));
} }
@PostMapping("/tournaments/{id}/draw") @PostMapping("/tournaments/{id}/draw")
public ResponseEntity<TournamentDto> drawTournament(@PathVariable Long id) { public ResponseEntity<TournamentDto> drawTournament(@PathVariable Long id) {
var tournament = tournamentService.findTournamentById(id); var tournament = tournamentService.findTournamentById(id);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentDrawService.draw(tournament))); return ResponseEntity.ok(tournamentMapper.toDto(tournamentDrawService.draw(tournament)));
} }
@PostMapping("/tournaments/{id}/draw/clear") @PostMapping("/tournaments/{id}/draw/clear")
public ResponseEntity<TournamentDto> clearTournamentDraw(@PathVariable Long id) { public ResponseEntity<TournamentDto> clearTournamentDraw(@PathVariable Long id) {
var tournament = tournamentService.findTournamentById(id); var tournament = tournamentService.findTournamentById(id);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentDrawService.clear(tournament))); return ResponseEntity.ok(tournamentMapper.toDto(tournamentDrawService.clear(tournament)));
} }
@PostMapping("/tournaments/{tournamentId}/rounds/{roundId}/start") @PostMapping("/tournaments/{tournamentId}/rounds/{roundId}/start")
public ResponseEntity<TournamentDto> startRound(@PathVariable Long tournamentId, @PathVariable Long roundId) { public ResponseEntity<TournamentDto> startRound(@PathVariable Long tournamentId, @PathVariable Long roundId) {
var tournament = tournamentService.findTournamentById(tournamentId); var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.startRound(tournament, roundId))); return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.startRound(tournament, roundId)));
} }
@PostMapping("/tournaments/{tournamentId}/rounds/{roundId}/finish") @PostMapping("/tournaments/{tournamentId}/rounds/{roundId}/finish")
public ResponseEntity<TournamentDto> finishRound(@PathVariable Long tournamentId, @PathVariable Long roundId) { public ResponseEntity<TournamentDto> finishRound(@PathVariable Long tournamentId, @PathVariable Long roundId) {
var tournament = tournamentService.findTournamentById(tournamentId); var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.finishRound(tournament, roundId))); return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.finishRound(tournament, roundId)));
} }
@PostMapping("/tournaments/{tournamentId}/groups/{groupId}/finish") @PostMapping("/tournaments/{tournamentId}/groups/{groupId}/finish")
public ResponseEntity<TournamentDto> finishGroup(@PathVariable Long tournamentId, @PathVariable Long groupId) { public ResponseEntity<TournamentDto> finishGroup(@PathVariable Long tournamentId, @PathVariable Long groupId) {
var tournament = tournamentService.findTournamentById(tournamentId); var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.finishGroup(tournament, groupId))); return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.finishGroup(tournament, groupId)));
} }
@PostMapping("/tournaments/{tournamentId}/groups/{groupId}/reopen") @PostMapping("/tournaments/{tournamentId}/groups/{groupId}/reopen")
public ResponseEntity<TournamentDto> reopenGroup(@PathVariable Long tournamentId, @PathVariable Long groupId) { public ResponseEntity<TournamentDto> reopenGroup(@PathVariable Long tournamentId, @PathVariable Long groupId) {
var tournament = tournamentService.findTournamentById(tournamentId); var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.reopenGroup(tournament, groupId))); return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.reopenGroup(tournament, groupId)));
} }
@PostMapping("/tournaments/{tournamentId}/groups/{groupId}/new") @PostMapping("/tournaments/{tournamentId}/groups/{groupId}/new")
public ResponseEntity<TournamentDto> newRound(@PathVariable Long tournamentId, @PathVariable Long groupId) { public ResponseEntity<TournamentDto> newRound(@PathVariable Long tournamentId, @PathVariable Long groupId) {
var tournament = tournamentService.findTournamentById(tournamentId); var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.newRound(tournament, groupId))); return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.newRound(tournament, groupId)));
} }
@PostMapping("/tournaments/{tournamentId}/matches/{matchId}/start") @PostMapping("/tournaments/{tournamentId}/matches/{matchId}/start")
public ResponseEntity<TournamentDto> startMatch(@PathVariable Long tournamentId, @PathVariable Long matchId, public ResponseEntity<TournamentDto> startMatch(@PathVariable Long tournamentId,
@RequestParam("court") Long court, @RequestParam("counter") Long counter) { @PathVariable Long matchId,
var tournament = tournamentService.findTournamentById(tournamentId); @RequestParam("court") Long court,
@RequestParam("counter") Long counter) {
var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.startMatch(tournament, matchId, court, counter))); return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.startMatch(tournament, matchId, court, counter)));
} }
@PostMapping("/tournaments/{tournamentId}/matches/{matchId}/stop") @PostMapping("/tournaments/{tournamentId}/matches/{matchId}/stop")
public ResponseEntity<TournamentDto> stopMatch(@PathVariable Long tournamentId, @PathVariable Long matchId) { public ResponseEntity<TournamentDto> stopMatch(@PathVariable Long tournamentId, @PathVariable Long matchId) {
var tournament = tournamentService.findTournamentById(tournamentId); var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.stopMatch(tournament, matchId))); return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.stopMatch(tournament, matchId)));
} }
@PostMapping("/tournaments/{tournamentId}/matches/{matchId}") @PostMapping("/tournaments/{tournamentId}/matches/{matchId}")
public ResponseEntity<TournamentDto> saveResult(@PathVariable Long tournamentId, @PathVariable Long matchId, @RequestBody ResultDto resultDto) { public ResponseEntity<TournamentDto> saveResult(@PathVariable Long tournamentId,
var tournament = tournamentService.findTournamentById(tournamentId); @PathVariable Long matchId,
@RequestBody ResultDto resultDto) {
var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.saveResult(tournament, matchId, resultDto))); return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.saveResult(tournament, matchId, resultDto)));
} }
@PatchMapping("/tournaments/{tournamentId}/players/{playerId}/paid/{paid}") @PatchMapping("/tournaments/{tournamentId}/players/{playerId}/paid/{paid}")
public ResponseEntity<Void> updatePaid(@PathVariable Long tournamentId, @PathVariable Long playerId, @PathVariable Boolean paid) { public ResponseEntity<Void> updatePaid(@PathVariable Long tournamentId,
var tournament = tournamentService.findTournamentById(tournamentId); @PathVariable Long playerId,
@PathVariable Boolean paid) {
var tournament = tournamentService.findTournamentById(tournamentId);
tournamentPlayService.updatePaid(tournament, playerId, paid); tournamentPlayService.updatePaid(tournament, playerId, paid);
return ResponseEntity.noContent().build(); return ResponseEntity.noContent().build();
} }
@PatchMapping("/tournaments/{tournamentId}/players/{playerId}/present/{present}") @PatchMapping("/tournaments/{tournamentId}/players/{playerId}/present/{present}")
public ResponseEntity<Void> updatePresent(@PathVariable Long tournamentId, @PathVariable Long playerId, @PathVariable Boolean present) { public ResponseEntity<Void> updatePresent(@PathVariable Long tournamentId,
var tournament = tournamentService.findTournamentById(tournamentId); @PathVariable Long playerId,
@PathVariable Boolean present) {
var tournament = tournamentService.findTournamentById(tournamentId);
tournamentPlayService.updatePresent(tournament, playerId, present); tournamentPlayService.updatePresent(tournament, playerId, present);
return ResponseEntity.noContent().build();
}
@PostMapping("/tournaments/{tournamentId}/players/{playerId}/substitutions")
public ResponseEntity<TournamentDto> substitutePlayer(@PathVariable Long tournamentId,
@PathVariable Long playerId,
@RequestBody TournamentPlayerSubstitutionDto[] substitutions) {
var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.playerSubstitutions(tournament, playerId, substitutions)));
}
@PatchMapping("/tournaments/{tournamentId}/matches/{matchId}/update")
public ResponseEntity<TournamentDto> updateCounter(@PathVariable Long tournamentId,
@PathVariable Long matchId,
@RequestParam("counter") Long counter) {
var tournament = tournamentService.findTournamentById(tournamentId);
return ResponseEntity.ok(tournamentMapper.toDto(tournamentPlayService.updateCounter(tournament, matchId, counter)));
}
return ResponseEntity.noContent().build();
}
} }

View File

@@ -1,6 +1,5 @@
package nl.connectedit.swiss.dto; package nl.connectedit.swiss.domain;
import java.util.Comparator;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import lombok.ToString; import lombok.ToString;

View File

@@ -27,8 +27,7 @@ public class Event extends AbstractEntity {
private Tournament tournament; private Tournament tournament;
@OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) @OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
// @Builder.Default private List<Registration> registrations;
private List<Registration> registrations;// = new ArrayList<>();
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private Status status; private Status status;
@@ -37,7 +36,7 @@ public class Event extends AbstractEntity {
private EventType type; private EventType type;
@OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY) @OneToMany(cascade = CascadeType.MERGE, fetch = FetchType.LAZY)
private List<Group> groups;// = new ArrayList<>(); private List<Group> groups;
public static List<Event> getBlankEventSet(Tournament tournament) { public static List<Event> getBlankEventSet(Tournament tournament) {
return Arrays.stream(EventType.values()) return Arrays.stream(EventType.values())

View File

@@ -33,7 +33,12 @@ public class Group extends AbstractEntity {
@OrderBy("name") @OrderBy("name")
private List<Round> rounds; private List<Round> rounds;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY) @ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
private List<Team> teams; @JoinTable(
name = "eventgroup_teams",
joinColumns = @JoinColumn(name = "group_id"),
inverseJoinColumns = @JoinColumn(name = "teams_id")
)
private List<Team> teams = new ArrayList<>();
} }

View File

@@ -37,4 +37,6 @@ public class Round extends AbstractEntity {
@ManyToOne @ManyToOne
private Team drawnOut; private Team drawnOut;
private Boolean isFinalsRound = Boolean.FALSE;
} }

View File

@@ -2,6 +2,8 @@ package nl.connectedit.swiss.domain.entity;
import jakarta.annotation.Nullable; import jakarta.annotation.Nullable;
import jakarta.persistence.*; import jakarta.persistence.*;
import java.util.ArrayList;
import java.util.List;
import lombok.Getter; import lombok.Getter;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.Setter; import lombok.Setter;
@@ -17,8 +19,8 @@ public class Team extends AbstractEntity {
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@ManyToOne @ManyToMany(mappedBy = "teams", fetch = FetchType.LAZY)
private Group group; private List<Group> groups = new ArrayList<>();
@ManyToOne @ManyToOne
private Player player1; private Player player1;

View File

@@ -31,5 +31,13 @@ public class TournamentPlayer extends AbstractEntity {
private boolean counting; private boolean counting;
private Long counts; private Long counts = 0L;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<TournamentPlayerSubstitution> substitutions;
public void incrementCounts() {
this.counts++;
}
} }

View File

@@ -0,0 +1,28 @@
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 TournamentPlayerSubstitution extends AbstractEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
// @ManyToMany(mappedBy = "substitutions", fetch = FetchType.LAZY)
// private List<TournamentPlayer> tournamentPlayer;
@ManyToOne
private Event event;
@ManyToOne
private TournamentPlayer substitute;
}

View File

@@ -23,4 +23,7 @@ public class RoundDto extends AbstractDto {
private StandingsDto standings; private StandingsDto standings;
private Boolean isFinalsRound;
} }

View File

@@ -3,6 +3,7 @@ package nl.connectedit.swiss.dto;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.Getter; import lombok.Getter;
import lombok.Setter; import lombok.Setter;
import nl.connectedit.swiss.domain.entity.TournamentPlayerSubstitution;
import java.util.List; import java.util.List;
@@ -31,4 +32,12 @@ public class TournamentDto extends AbstractDto {
private Boolean active; private Boolean active;
private List<TournamentPlayerSubstitutionDto> substitutions;
private List<Long> playersPlaying;
private List<Long> playersCounting;
private List<Long> playersAvailable;
} }

View File

@@ -10,6 +10,8 @@ import java.util.List;
@Setter @Setter
public class TournamentPlayerDto extends AbstractDto { public class TournamentPlayerDto extends AbstractDto {
private Long id;
private Long playerId; private Long playerId;
private String name; private String name;
@@ -24,4 +26,6 @@ public class TournamentPlayerDto extends AbstractDto {
private Long counts; private Long counts;
private List<TournamentPlayerSubstitutionDto> substitutions;
} }

View File

@@ -0,0 +1,16 @@
package nl.connectedit.swiss.dto;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
public class TournamentPlayerSubstitutionDto extends AbstractDto {
private Long substitutionId;
private String event;
// private TournamentPlayerDto substitute;
private Long substitute;
}

View File

@@ -1,13 +1,16 @@
package nl.connectedit.swiss.mapper; package nl.connectedit.swiss.mapper;
import java.util.Arrays;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.EventType;
import nl.connectedit.swiss.domain.Status; import nl.connectedit.swiss.domain.Status;
import nl.connectedit.swiss.domain.entity.Event; import nl.connectedit.swiss.domain.entity.Event;
import nl.connectedit.swiss.domain.EventType; import nl.connectedit.swiss.domain.entity.Group;
import nl.connectedit.swiss.dto.EventDto; import nl.connectedit.swiss.dto.EventDto;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Comparator;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class EventMapper implements DtoMapper<Event, EventDto>, EntityMapper<EventDto, Event> { public class EventMapper implements DtoMapper<Event, EventDto>, EntityMapper<EventDto, Event> {
@@ -56,6 +59,7 @@ public class EventMapper implements DtoMapper<Event, EventDto>, EntityMapper<Eve
eventDto.setGroups( eventDto.setGroups(
event.getGroups() event.getGroups()
.stream() .stream()
.sorted(Comparator.comparing(Group::getName))
.map(groupMapper::toDto) .map(groupMapper::toDto)
.toList() .toList()
); );

View File

@@ -1,14 +1,14 @@
package nl.connectedit.swiss.mapper; package nl.connectedit.swiss.mapper;
import java.util.List;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Group;
import nl.connectedit.swiss.domain.entity.Round; import nl.connectedit.swiss.domain.entity.Round;
import nl.connectedit.swiss.domain.entity.Team; import nl.connectedit.swiss.domain.entity.Team;
import nl.connectedit.swiss.dto.RoundDto; import nl.connectedit.swiss.dto.RoundDto;
import nl.connectedit.swiss.service.StandingsService; import nl.connectedit.swiss.service.StandingsService;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.List;
@Component @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class RoundMapper { public class RoundMapper {
@@ -45,6 +45,8 @@ public class RoundMapper {
var standings = standingsService.getStandings(roundsForStandings, teamsForStandings); var standings = standingsService.getStandings(roundsForStandings, teamsForStandings);
roundDto.setStandings(standingsMapper.toDto(standings)); roundDto.setStandings(standingsMapper.toDto(standings));
roundDto.setIsFinalsRound(round.getIsFinalsRound());
return roundDto; return roundDto;
} }

View File

@@ -1,12 +1,13 @@
package nl.connectedit.swiss.mapper; package nl.connectedit.swiss.mapper;
import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.StandingsEntry;
import nl.connectedit.swiss.dto.StandingsDto;
import nl.connectedit.swiss.dto.StandingsEntryDto;
import org.springframework.stereotype.Component;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; 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 @Component
@RequiredArgsConstructor @RequiredArgsConstructor

View File

@@ -1,83 +1,186 @@
package nl.connectedit.swiss.mapper; package nl.connectedit.swiss.mapper;
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.*;
import nl.connectedit.swiss.dto.TournamentDto;
import org.springframework.stereotype.Component;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.*; import java.util.ArrayList;
import java.util.Arrays;
import lombok.RequiredArgsConstructor; import java.util.Comparator;
import nl.connectedit.swiss.domain.entity.Tournament; import java.util.List;
import nl.connectedit.swiss.domain.TournamentStatus; import java.util.stream.Collectors;
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 @Component
@RequiredArgsConstructor @RequiredArgsConstructor
public class TournamentMapper implements DtoMapper<Tournament, TournamentDto>, EntityMapper<TournamentDto, Tournament> { public class TournamentMapper implements DtoMapper<Tournament, TournamentDto>, EntityMapper<TournamentDto, Tournament> {
private final EventMapper eventMapper; private final EventMapper eventMapper;
private final TournamentPlayerMapper tournamentPlayerMapper; private final TournamentPlayerMapper tournamentPlayerMapper;
@Override @Override
public Tournament toEntity(TournamentDto tournamentDto) { public Tournament toEntity(TournamentDto tournamentDto) {
Tournament tournament = new Tournament(); Tournament tournament = new Tournament();
tournament.setId(tournamentDto.getId()); tournament.setId(tournamentDto.getId());
tournament.setName(tournamentDto.getName()); tournament.setName(tournamentDto.getName());
tournament.setDate(LocalDate.parse(tournamentDto.getDate(), DateTimeFormatter.ofPattern("dd-MM-yyyy"))); tournament.setDate(LocalDate.parse(tournamentDto.getDate(), DateTimeFormatter.ofPattern("dd-MM-yyyy")));
tournament.setStatus( tournament.setStatus(
Arrays.stream(TournamentStatus.values()) Arrays.stream(TournamentStatus.values())
.filter(ts -> ts.getText().equals(tournamentDto.getStatus())) .filter(ts -> ts.getText().equals(tournamentDto.getStatus()))
.findFirst() .findFirst()
.orElse(TournamentStatus.UPCOMING) .orElse(TournamentStatus.UPCOMING)
); );
tournament.setActive(tournamentDto.getActive()); tournament.setActive(tournamentDto.getActive());
tournament.setMaxEvents(tournamentDto.getMaxEvents()); tournament.setMaxEvents(tournamentDto.getMaxEvents());
tournament.setCostsPerEvent(tournamentDto.getCostsPerEvent()); tournament.setCostsPerEvent(tournamentDto.getCostsPerEvent());
tournament.setCourts(tournamentDto.getCourts()); tournament.setCourts(tournamentDto.getCourts());
return tournament; 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.setActive(tournament.getActive());
tournamentDto.setEvents(
tournament.getEvents()
.stream()
.sorted(Comparator.comparing(Event::getType))
.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()
);
} }
@Override tournamentDto.setMaxEvents(tournament.getMaxEvents() == null ? 2L : tournament.getMaxEvents());
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.setActive(tournament.getActive());
tournamentDto.setEvents(
tournament.getEvents()
.stream()
.map(eventMapper::toDto)
.toList()
);
if (tournament.getTournamentPlayers() != null) { if (tournament.getCostsPerEvent() == null || tournament.getCostsPerEvent().isEmpty()) {
var tournamentPlayers = tournament.getTournamentPlayers(); tournamentDto.setCostsPerEvent(List.of(0f, 0f, 0f));
} else {
tournamentPlayers.sort(Comparator.comparing((TournamentPlayer tournamentPlayer) -> tournamentPlayer.getPlayer().getLastName()) tournamentDto.setCostsPerEvent(tournament.getCostsPerEvent());
.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;
} }
}
tournamentDto.setCourts(tournament.getCourts() == null ? 1L : tournament.getCourts());
tournamentDto.setPlayersCounting(getPlayersCounting(tournament));
tournamentDto.setPlayersPlaying(getPlayersPlaying(tournament));
tournamentDto.setPlayersAvailable(getPlayersAvailable(tournament));
return tournamentDto;
}
private List<Long> getPlayersCounting(Tournament tournament) {
var playersCounting = new ArrayList<Long>();
for (var event : tournament.getEvents()) {
if (event.getGroups() != null) {
for (var group : event.getGroups()) {
if (group.getRounds() != null) {
for (var round : group.getRounds()) {
for (var match : round.getMatches()) {
if (match.getStatus() == Status.IN_PROGRESS && match.getCounter() != null) {
var counter = getTournamentPlayerFromPlayerId(tournament, match.getCounter().getId());
if (counter != null) {
playersCounting.add(counter.getId());
}
}
}
}
}
}
}
}
return playersCounting;
}
private List<Long> getPlayersPlaying(Tournament tournament) {
var playersPlaying = new ArrayList<Long>();
for (var event : tournament.getEvents()) {
if (event.getGroups() != null) {
for (var group : event.getGroups()) {
if (group.getRounds() != null) {
for (var round : group.getRounds()) {
for (var match : round.getMatches()) {
if (match.getStatus() == Status.IN_PROGRESS) {
playersPlaying.addAll(getPlayersInMatch(tournament, match));
}
}
}
}
}
}
}
return playersPlaying;
}
private List<Long> getPlayersAvailable(Tournament tournament) {
if (tournament.getTournamentPlayers() == null) {
return List.of();
}
var playersAvailable = tournament.getTournamentPlayers()
.stream()
.map(TournamentPlayer::getId)
.collect(Collectors.toCollection(ArrayList::new));
playersAvailable.removeAll(getPlayersCounting(tournament));
playersAvailable.removeAll(getPlayersPlaying(tournament));
return playersAvailable;
}
private List<Long> getPlayersInMatch(Tournament tournament, Match match) {
var playersInMatch = new ArrayList<Long>();
playersInMatch.add(getPlayerOrSubstitute(tournament, match.getTeam1().getPlayer1(), match.getType()).getId());
playersInMatch.add(getPlayerOrSubstitute(tournament, match.getTeam2().getPlayer1(), match.getType()).getId());
if (match.getType().isDoublesEvent()) {
playersInMatch.add(getPlayerOrSubstitute(tournament, match.getTeam1().getPlayer2(), match.getType()).getId());
playersInMatch.add(getPlayerOrSubstitute(tournament, match.getTeam2().getPlayer2(), match.getType()).getId());
}
return playersInMatch;
}
private TournamentPlayer getPlayerOrSubstitute(Tournament tournament, Player player, EventType type) {
var tournamentPlayer = getTournamentPlayerFromPlayerId(tournament, player.getId());
for (var substitution : tournamentPlayer.getSubstitutions()) {
if (substitution.getEvent().getType() == type && substitution.getSubstitute() != null) {
return substitution.getSubstitute();
}
}
return tournamentPlayer;
}
private TournamentPlayer getTournamentPlayerFromPlayerId(Tournament tournament, Long playerId) {
for (var tournamentPlayer : tournament.getTournamentPlayers()) {
if (tournamentPlayer.getPlayer().getId().equals(playerId)) {
return tournamentPlayer;
}
}
return null;
}
}

View File

@@ -3,8 +3,10 @@ package nl.connectedit.swiss.mapper;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Team; import nl.connectedit.swiss.domain.entity.Team;
import nl.connectedit.swiss.domain.entity.TournamentPlayer; import nl.connectedit.swiss.domain.entity.TournamentPlayer;
import nl.connectedit.swiss.domain.entity.TournamentPlayerSubstitution;
import nl.connectedit.swiss.dto.TeamDto; import nl.connectedit.swiss.dto.TeamDto;
import nl.connectedit.swiss.dto.TournamentPlayerDto; import nl.connectedit.swiss.dto.TournamentPlayerDto;
import nl.connectedit.swiss.dto.TournamentPlayerSubstitutionDto;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.ArrayList; import java.util.ArrayList;
@@ -16,6 +18,7 @@ public class TournamentPlayerMapper implements DtoMapper<TournamentPlayer, Tourn
@Override @Override
public TournamentPlayerDto toDto(TournamentPlayer tournamentPlayer) { public TournamentPlayerDto toDto(TournamentPlayer tournamentPlayer) {
var tournamentPlayerDto = new TournamentPlayerDto(); var tournamentPlayerDto = new TournamentPlayerDto();
tournamentPlayerDto.setId(tournamentPlayer.getId());
tournamentPlayerDto.setPlayerId(tournamentPlayer.getPlayer().getId()); tournamentPlayerDto.setPlayerId(tournamentPlayer.getPlayer().getId());
tournamentPlayerDto.setName(tournamentPlayer.getPlayer().getFullName()); tournamentPlayerDto.setName(tournamentPlayer.getPlayer().getFullName());
tournamentPlayerDto.setEvents(new ArrayList<>(tournamentPlayer.getEvents())); tournamentPlayerDto.setEvents(new ArrayList<>(tournamentPlayer.getEvents()));
@@ -23,7 +26,26 @@ public class TournamentPlayerMapper implements DtoMapper<TournamentPlayer, Tourn
tournamentPlayerDto.setPresent(tournamentPlayer.isPresent()); tournamentPlayerDto.setPresent(tournamentPlayer.isPresent());
tournamentPlayerDto.setCounting(tournamentPlayer.isCounting()); tournamentPlayerDto.setCounting(tournamentPlayer.isCounting());
tournamentPlayerDto.setCounts(tournamentPlayer.getCounts()); tournamentPlayerDto.setCounts(tournamentPlayer.getCounts());
if (tournamentPlayer.getSubstitutions() != null) {
tournamentPlayerDto.setSubstitutions(
tournamentPlayer.getSubstitutions()
.stream()
.map(this::mapSubstitution)
.toList());
}
return tournamentPlayerDto; return tournamentPlayerDto;
} }
private TournamentPlayerSubstitutionDto mapSubstitution(TournamentPlayerSubstitution tournamentPlayerSubstitution) {
var tournamentPlayerSubstitutionDto = new TournamentPlayerSubstitutionDto();
tournamentPlayerSubstitutionDto.setSubstitutionId(tournamentPlayerSubstitution.getId());
tournamentPlayerSubstitutionDto.setEvent(tournamentPlayerSubstitution.getEvent().getType().name());
if (tournamentPlayerSubstitution.getSubstitute() != null) {
// tournamentPlayerSubstitutionDto.setSubstitute(toDto(tournamentPlayerSubstitution.getSubstitute()));
tournamentPlayerSubstitutionDto.setSubstitute(tournamentPlayerSubstitution.getSubstitute().getId());
}
return tournamentPlayerSubstitutionDto;
}
} }

View File

@@ -1,14 +1,11 @@
package nl.connectedit.swiss.service; package nl.connectedit.swiss.service;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import nl.connectedit.swiss.domain.entity.Player; import nl.connectedit.swiss.domain.entity.*;
import nl.connectedit.swiss.domain.entity.Registration;
import nl.connectedit.swiss.domain.entity.Tournament;
import nl.connectedit.swiss.dto.EventRegistrationDto; import nl.connectedit.swiss.dto.EventRegistrationDto;
import nl.connectedit.swiss.repository.RegistrationRepository;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.List; import java.util.ArrayList;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@@ -23,6 +20,7 @@ public class RegistrationService {
if (!eventRegistration.isRegistered()) { // remove any existing registration if (!eventRegistration.isRegistered()) { // remove any existing registration
event.getRegistrations().removeIf(registration -> registration.getPlayer().equals(player)); event.getRegistrations().removeIf(registration -> registration.getPlayer().equals(player));
removeEventFromTournamentPlayer(tournament, player, event);
} else { } else {
var optionalExistingPlayerRegistration = event.getRegistrations().stream() var optionalExistingPlayerRegistration = event.getRegistrations().stream()
.filter(registration -> registration.getPlayer().equals(player)) .filter(registration -> registration.getPlayer().equals(player))
@@ -32,14 +30,15 @@ public class RegistrationService {
newRegistration.setTournament(tournament); newRegistration.setTournament(tournament);
newRegistration.setEvent(event); newRegistration.setEvent(event);
newRegistration.setPlayer(player); newRegistration.setPlayer(player);
if (eventRegistration.getPartner() != null){ if (eventRegistration.getPartner() != null) {
var partner = playerService.findPlayerById(eventRegistration.getPartner()); var partner = playerService.findPlayerById(eventRegistration.getPartner());
newRegistration.setPartner(partner); newRegistration.setPartner(partner);
} }
event.addRegistration(newRegistration); event.addRegistration(newRegistration);
addEventToTournamentPlayer(tournament, player, event);
} else { // change existing registration } else { // change existing registration
var existingPlayerRegistration = optionalExistingPlayerRegistration.get(); var existingPlayerRegistration = optionalExistingPlayerRegistration.get();
if (eventRegistration.getPartner() != null){ if (eventRegistration.getPartner() != null) {
var partner = playerService.findPlayerById(eventRegistration.getPartner()); var partner = playerService.findPlayerById(eventRegistration.getPartner());
existingPlayerRegistration.setPartner(partner); existingPlayerRegistration.setPartner(partner);
} else { } else {
@@ -50,4 +49,44 @@ public class RegistrationService {
tournamentService.saveTournament(tournament); tournamentService.saveTournament(tournament);
} }
private void removeEventFromTournamentPlayer(Tournament tournament, Player player, Event event) {
var tournamentPlayer = findOrAddTournamentPlayer(tournament, player);
tournamentPlayer.getEvents().removeIf(eventType -> eventType.equals(event.getType().name()));
if (tournamentPlayer.getSubstitutions() != null) {
tournamentPlayer.getSubstitutions().removeIf(substitution -> substitution.getEvent().equals(event));
}
}
private void addEventToTournamentPlayer(Tournament tournament, Player player, Event event) {
var tournamentPlayer = findOrAddTournamentPlayer(tournament, player);
tournamentPlayer.getEvents().add(event.getType().name());
if (tournamentPlayer.getSubstitutions() == null) {
tournamentPlayer.setSubstitutions(new ArrayList<>());
}
var substitution = new TournamentPlayerSubstitution();
substitution.setEvent(event);
substitution.setSubstitute(null);
tournamentPlayer.getSubstitutions().add(substitution);
}
private TournamentPlayer findOrAddTournamentPlayer(Tournament tournament, Player player) {
for (var tournamentPlayer : tournament.getTournamentPlayers()) {
if (tournamentPlayer.getPlayer().getId().equals(player.getId())) {
return tournamentPlayer;
}
}
return registerTournamentPlayer(tournament, player);
}
private TournamentPlayer registerTournamentPlayer(Tournament tournament, Player player) {
var tournamentPlayer = new TournamentPlayer();
tournamentPlayer.setTournament(tournament);
tournamentPlayer.setPlayer(player);
tournamentPlayer.setEvents(new ArrayList<>());
tournament.getTournamentPlayers().add(tournamentPlayer);
return tournamentPlayer;
}
} }

View File

@@ -1,10 +1,9 @@
package nl.connectedit.swiss.service; package nl.connectedit.swiss.service;
import nl.connectedit.swiss.domain.entity.Group; import nl.connectedit.swiss.domain.StandingsEntry;
import nl.connectedit.swiss.domain.entity.Match; import nl.connectedit.swiss.domain.entity.Match;
import nl.connectedit.swiss.domain.entity.Round; import nl.connectedit.swiss.domain.entity.Round;
import nl.connectedit.swiss.domain.entity.Team; import nl.connectedit.swiss.domain.entity.Team;
import nl.connectedit.swiss.dto.StandingsEntry;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList; import java.util.ArrayList;

View File

@@ -9,6 +9,7 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log;
import nl.connectedit.swiss.domain.EventType; import nl.connectedit.swiss.domain.EventType;
import nl.connectedit.swiss.domain.Status; import nl.connectedit.swiss.domain.Status;
import nl.connectedit.swiss.domain.TournamentStatus; import nl.connectedit.swiss.domain.TournamentStatus;
@@ -22,6 +23,7 @@ import org.springframework.stereotype.Service;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Log
public class TournamentDivideService { public class TournamentDivideService {
private final TournamentValidationService tournamentValidationService; private final TournamentValidationService tournamentValidationService;
@@ -36,7 +38,7 @@ public class TournamentDivideService {
for (var event : tournament.getEvents()) { for (var event : tournament.getEvents()) {
if ((event.getType().isDoublesEvent() && event.getRegistrations().size() >= 8) if ((event.getType().isDoublesEvent() && event.getRegistrations().size() >= 8)
|| (!event.getType().isDoublesEvent() && event.getRegistrations().size() >= 4)) { || (!event.getType().isDoublesEvent() && event.getRegistrations().size() >= 4)) {
divide(event); divide(event);
} }
} }
@@ -64,6 +66,7 @@ public class TournamentDivideService {
event.getGroups().add(group); event.getGroups().add(group);
} else { } else {
var groups = getGroups(registrations, event.getType()); var groups = getGroups(registrations, event.getType());
groups.forEach(group -> group.setEvent(event));
event.getGroups().addAll(groups); event.getGroups().addAll(groups);
} }
} }
@@ -71,7 +74,7 @@ public class TournamentDivideService {
private List<Registration> groupRegistrations(List<Registration> registrations) { private List<Registration> groupRegistrations(List<Registration> registrations) {
var groupedRegistrations = new ArrayList<Registration>(); var groupedRegistrations = new ArrayList<Registration>();
nextRegistration: nextRegistration:
for (var registration : registrations) { for (var registration : registrations) {
for (var groupedRegistration : groupedRegistrations) { for (var groupedRegistration : groupedRegistrations) {
if (Objects.equals(groupedRegistration.getPartner(), registration.getPlayer())) { if (Objects.equals(groupedRegistration.getPartner(), registration.getPlayer())) {
@@ -90,13 +93,16 @@ nextRegistration:
group.setStatus(Status.IN_PROGRESS); group.setStatus(Status.IN_PROGRESS);
group.setTeams(new ArrayList<>()); group.setTeams(new ArrayList<>());
for (var registration : registrations) { for (var registration : registrations) {
group.getTeams().add(getTeam(registration, group)); // group.getTeams().add(getTeam(registration, group));
addTeamToGroup(registration, group);
} }
return group; return group;
} }
private List<Group> getGroups(List<Registration> orgRegistrations, EventType type) { private List<Group> getGroups(List<Registration> orgRegistrations, EventType type) {
var groups = new ArrayList<Group>();
var registrations = new ArrayList<>(orgRegistrations); var registrations = new ArrayList<>(orgRegistrations);
var group1 = new Group(); var group1 = new Group();
@@ -139,18 +145,28 @@ nextRegistration:
group2.getTeams().removeLast(); group2.getTeams().removeLast();
} }
return List.of(group1, group2); groups.add(group1);
groups.add(group2);
return groups;
} }
private Team getTeam(Registration registration, Group group) { private Team getTeam(Registration registration, Group group) {
var team = new Team(); var team = new Team();
team.setPlayer1(registration.getPlayer()); team.setPlayer1(registration.getPlayer());
team.setPlayer2(registration.getPartner()); team.setPlayer2(registration.getPartner());
team.setGroup(group); team.getGroups().add(group);
return team; return team;
} }
private void addTeamToGroup(Registration registration, Group group) {
var team = new Team();
team.setPlayer1(registration.getPlayer());
team.setPlayer2(registration.getPartner());
team.getGroups().add(group);
group.getTeams().add(team);
}
public Tournament clear(Tournament tournament) { public Tournament clear(Tournament tournament) {
for (var event : tournament.getEvents()) { for (var event : tournament.getEvents()) {
event.getGroups().clear(); event.getGroups().clear();

View File

@@ -31,7 +31,7 @@ public class TournamentDrawService {
} }
} }
registerTournamentPlayers(tournament); // registerTournamentPlayers(tournament);
tournament.setStatus(TournamentStatus.DRAWN); tournament.setStatus(TournamentStatus.DRAWN);
@@ -78,7 +78,7 @@ public class TournamentDrawService {
} }
} }
tournament.getTournamentPlayers().clear(); // tournament.getTournamentPlayers().clear();
tournament.setStatus(TournamentStatus.DIVIDED); tournament.setStatus(TournamentStatus.DIVIDED);

View File

@@ -2,11 +2,13 @@ package nl.connectedit.swiss.service;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.java.Log; import lombok.extern.java.Log;
import nl.connectedit.swiss.domain.EventType;
import nl.connectedit.swiss.domain.StandingsEntry;
import nl.connectedit.swiss.domain.Status; import nl.connectedit.swiss.domain.Status;
import nl.connectedit.swiss.domain.TournamentStatus; import nl.connectedit.swiss.domain.TournamentStatus;
import nl.connectedit.swiss.domain.entity.*; import nl.connectedit.swiss.domain.entity.*;
import nl.connectedit.swiss.dto.ResultDto; import nl.connectedit.swiss.dto.ResultDto;
import nl.connectedit.swiss.dto.StandingsEntry; import nl.connectedit.swiss.dto.TournamentPlayerSubstitutionDto;
import nl.connectedit.swiss.repository.TournamentRepository; import nl.connectedit.swiss.repository.TournamentRepository;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -14,7 +16,6 @@ import java.time.LocalDateTime;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Random;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import static nl.connectedit.swiss.service.ServiceUtil.*; import static nl.connectedit.swiss.service.ServiceUtil.*;
@@ -42,11 +43,86 @@ public class TournamentPlayService {
} }
public Tournament finishGroup(Tournament tournament, Long groupId) { public Tournament finishGroup(Tournament tournament, Long groupId) {
getGroup(tournament, groupId).setStatus(Status.FINISHED); var group = getGroup(tournament, groupId);
group.setStatus(Status.FINISHED);
checkForEventCompletion(tournament, group.getType());
tournamentRepository.save(tournament); tournamentRepository.save(tournament);
return tournament; return tournament;
} }
private void checkForEventCompletion(Tournament tournament, EventType type) {
Group group1 = null;
Group group2 = null;
Event event = null;
for (var e : tournament.getEvents()) {
if (e.getType() == type) {
if (e.getGroups().size() != 2) {
return;
}
group1 = e.getGroups().getFirst();
if (group1.getStatus() != Status.FINISHED) {
return;
}
group2 = e.getGroups().getLast();
if (group2.getStatus() != Status.FINISHED) {
return;
}
event = e;
}
}
var finalsGroup = new Group();
finalsGroup.setName(type.getText());
finalsGroup.setType(type);
finalsGroup.setStatus(Status.IN_PROGRESS);
finalsGroup.setEvent(group1.getEvent());
var standings1 = standingsService.getStandings(group1.getRounds(), group1.getTeams());
var standings2 = standingsService.getStandings(group2.getRounds(), group2.getTeams());
var team1_1 = standings1.get(0).getTeam();
var team1_2 = standings1.get(1).getTeam();
var team2_1 = standings2.get(0).getTeam();
var team2_2 = standings2.get(1).getTeam();
team1_1.getGroups().add(finalsGroup);
team1_2.getGroups().add(finalsGroup);
team2_1.getGroups().add(finalsGroup);
team2_2.getGroups().add(finalsGroup);
finalsGroup.setTeams(List.of(team1_1, team1_2, team2_1, team2_2));
var finalsRound = new Round();
finalsRound.setGroup(finalsGroup);
finalsRound.setStatus(Status.NOT_STARTED);
finalsRound.setName("Finales");
finalsRound.setIsFinalsRound(Boolean.TRUE);
var match1 = new Match();
match1.setRound(finalsRound);
match1.setType(type);
match1.setStatus(Status.NOT_STARTED);
match1.setPlayed(false);
match1.setTeam1(standings1.get(0).getTeam());
match1.setTeam2(standings2.get(0).getTeam());
var match2 = new Match();
match2.setRound(finalsRound);
match2.setType(type);
match2.setStatus(Status.NOT_STARTED);
match2.setPlayed(false);
match2.setTeam1(standings1.get(1).getTeam());
match2.setTeam2(standings2.get(1).getTeam());
finalsRound.setMatches(List.of(match1, match2));
finalsGroup.setRounds(List.of(finalsRound));
event.getGroups().add(finalsGroup);
}
public Tournament reopenGroup(Tournament tournament, Long groupId) { public Tournament reopenGroup(Tournament tournament, Long groupId) {
getGroup(tournament, groupId).setStatus(Status.IN_PROGRESS); getGroup(tournament, groupId).setStatus(Status.IN_PROGRESS);
tournamentRepository.save(tournament); tournamentRepository.save(tournament);
@@ -92,22 +168,74 @@ public class TournamentPlayService {
round.setMatches(matches); round.setMatches(matches);
printRound(round, standings);
group.getRounds().add(round); group.getRounds().add(round);
tournamentRepository.save(tournament); tournamentRepository.save(tournament);
return tournament; return tournament;
} }
private void printRound(Round round, List<StandingsEntry> standings) { public Tournament playerSubstitutions(Tournament tournament, Long playerId, TournamentPlayerSubstitutionDto[] substitutions) {
for (var match: round.getMatches()) { var tournamentPlayer = getTournamentPlayer(tournament, playerId);
log.info("%s - %s".formatted(
String.valueOf(standings.stream().filter(entry -> entry.getTeam().equals(match.getTeam1())).map(StandingsEntry::getPosition).findFirst().get()), var playerSubstitutions = new ArrayList<TournamentPlayerSubstitution>();
String.valueOf(standings.stream().filter(entry -> entry.getTeam().equals(match.getTeam2())).map(StandingsEntry::getPosition).findFirst().get())));
for (var substitution : substitutions) {
var playerSubstitution = new TournamentPlayerSubstitution();
var eventType = EventType.valueOf(substitution.getEvent());
var event = getEventByType(tournament, eventType);
playerSubstitution.setEvent(event);
TournamentPlayer substitute;
if (substitution.getSubstitute() != null) {
substitute = getTournamentPlayerById(tournament, substitution.getSubstitute());
} else {
substitute = null;
}
playerSubstitution.setSubstitute(substitute);
playerSubstitutions.add(playerSubstitution);
} }
tournamentPlayer.setSubstitutions(playerSubstitutions);
tournamentRepository.save(tournament);
return tournament;
} }
private Event getEvent(Tournament tournament, Long eventId) {
for (var event : tournament.getEvents()) {
if (event.getId().equals(eventId)) {
return event;
}
}
return null;
}
private Event getEventByType(Tournament tournament, EventType eventType) {
for (var event : tournament.getEvents()) {
if (event.getType().equals(eventType)) {
return event;
}
}
return null;
}
public Tournament updateCounter(Tournament tournament, Long matchId, Long counter) {
var match = getMatch(tournament, matchId);
var currentlyCountingPlayer = getTournamentPlayer(tournament, match.getCounter().getId());
currentlyCountingPlayer.setCounting(false);
var newCountingPlayer = getTournamentPlayer(tournament, counter);
newCountingPlayer.setCounting(true);
match.setCounter(newCountingPlayer.getPlayer());
tournamentRepository.save(tournament);
return tournament;
}
private static class ConflictInDrawException extends RuntimeException {} private static class ConflictInDrawException extends RuntimeException {}
private List<Match> tryMatches(List<Team> remainingTeams, Group group) { private List<Match> tryMatches(List<Team> remainingTeams, Group group) {
@@ -121,7 +249,6 @@ public class TournamentPlayService {
if (!findPreviousMatchOccurence(newMatch.getTeam1(), remainingTeams.get(opponentIndex), group)) { if (!findPreviousMatchOccurence(newMatch.getTeam1(), remainingTeams.get(opponentIndex), group)) {
newMatch.setTeam2(remainingTeams.get(opponentIndex)); newMatch.setTeam2(remainingTeams.get(opponentIndex));
} else { } else {
log.info("Wedstrijd %s - %s kwam al eerder voor.".formatted(newMatch.getTeam1().toString(), remainingTeams.get(opponentIndex).toString()));
continue; continue;
} }
var newRemainingTeams = getRemainingTeams(remainingTeams, opponentIndex); var newRemainingTeams = getRemainingTeams(remainingTeams, opponentIndex);
@@ -172,7 +299,7 @@ public class TournamentPlayService {
match.setStartTime(LocalDateTime.now()); match.setStartTime(LocalDateTime.now());
match.setCourt(court); match.setCourt(court);
var countingPlayer = getPlayer(tournament, counter); var countingPlayer = getTournamentPlayer(tournament, counter);
countingPlayer.setCounting(true); countingPlayer.setCounting(true);
match.setCounter(countingPlayer.getPlayer()); match.setCounter(countingPlayer.getPlayer());
@@ -180,7 +307,16 @@ public class TournamentPlayService {
return tournament; return tournament;
} }
private TournamentPlayer getPlayer(Tournament tournament, Long playerId) { private TournamentPlayer getTournamentPlayerById(Tournament tournament, Long id) {
for (var tournamentPlayer : tournament.getTournamentPlayers()) {
if (id.equals(tournamentPlayer.getId())) {
return tournamentPlayer;
}
}
return null;
}
private TournamentPlayer getTournamentPlayer(Tournament tournament, Long playerId) {
for (var tournamentPlayer : tournament.getTournamentPlayers()) { for (var tournamentPlayer : tournament.getTournamentPlayers()) {
if (playerId.equals(tournamentPlayer.getPlayer().getId())) { if (playerId.equals(tournamentPlayer.getPlayer().getId())) {
return tournamentPlayer; return tournamentPlayer;
@@ -195,7 +331,7 @@ public class TournamentPlayService {
match.setStartTime(null); match.setStartTime(null);
match.setCourt(null); match.setCourt(null);
var countingPlayer = getPlayer(tournament, match.getCounter().getId()); var countingPlayer = getTournamentPlayer(tournament, match.getCounter().getId());
countingPlayer.setCounting(false); countingPlayer.setCounting(false);
match.setCounter(null); match.setCounter(null);
@@ -213,9 +349,13 @@ public class TournamentPlayService {
match.getGames().clear(); match.getGames().clear();
match.getGames().addAll(resultToGames(result, match)); match.getGames().addAll(resultToGames(result, match));
var countingPlayer = getPlayer(tournament, match.getCounter().getId()); var counter = match.getCounter();
countingPlayer.setCounting(false); if (counter != null) { // match was already saved, this is a correction
match.setCounter(null); var countingPlayer = getTournamentPlayer(tournament, counter.getId());
countingPlayer.setCounting(false);
countingPlayer.incrementCounts();
match.setCounter(null);
}
tournamentRepository.save(tournament); tournamentRepository.save(tournament);
return tournament; return tournament;

View File

@@ -6,7 +6,7 @@ spring:
# - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration # - org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration
# - org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration # - org.springframework.boot.actuate.autoconfigure.security.servlet.ManagementWebSecurityAutoConfiguration
datasource: datasource:
url: jdbc:postgresql://localhost:5432/postgres?currentSchema=swiss url: jdbc:postgresql://localhost:5432/postgres?currentSchema=swiss2
username: ${DB_USERNAME:postgres} username: ${DB_USERNAME:postgres}
password: ${DB_PASSWORD:postgres} password: ${DB_PASSWORD:postgres}
flyway: flyway:

View File

@@ -11,7 +11,7 @@ spring:
password: ${DB_PASSWORD:postgres} password: ${DB_PASSWORD:postgres}
jpa: jpa:
hibernate: hibernate:
ddl-auto: none ddl-auto: update
flyway: flyway:
url: jdbc:postgresql://${DB_URL:localhost:5432}/${DB_NAME}?currentSchema=swiss url: jdbc:postgresql://${DB_URL:localhost:5432}/${DB_NAME}?currentSchema=swiss
user: ${DB_USERNAME:postgres} user: ${DB_USERNAME:postgres}
@@ -30,4 +30,10 @@ management:
probes: probes:
enabled: true enabled: true
security: true security: true
jwt:
private:
key: classpath:certs/private.pem
public:
key: classpath:certs/public.pem