diff --git a/.env.example b/.env.example
index 13be3183..a4a55986 100644
--- a/.env.example
+++ b/.env.example
@@ -6,7 +6,7 @@ POSTGRES_HOST=localhost
POSTGRES_PORT=5432
# backend
-API_BASE_URL=http://localhost:8080
+API_BASE_URL=http://localhost:8080/
JWT_SECRET=SomeVeryLongRandomSecretKeyWhichIsUsedToEncodeJwtTokensForAuthorization
# sentry
diff --git a/.github/workflows/api-ci.yml b/.github/workflows/api-ci.yml
index 88758cd6..4fd9730e 100644
--- a/.github/workflows/api-ci.yml
+++ b/.github/workflows/api-ci.yml
@@ -55,4 +55,5 @@ jobs:
AWS_ENDPOINT: ${{ secrets.AWS_ENDPOINT }}
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }}
+ JWT_SECRET: justforthisrunnerthishasnovaluelalalalalalalalalalalalalalalalalalalalalalalalalalla
working-directory: api
diff --git a/.github/workflows/generate-openapi.yaml b/.github/workflows/generate-openapi.yaml
index 0787a80f..db0c5fd3 100644
--- a/.github/workflows/generate-openapi.yaml
+++ b/.github/workflows/generate-openapi.yaml
@@ -99,6 +99,7 @@ jobs:
AWS_ENDPOINT: ${{ secrets.AWS_ENDPOINT }}
AWS_ACCESS_KEY: ${{ secrets.AWS_ACCESS_KEY }}
AWS_SECRET_KEY: ${{ secrets.AWS_SECRET_KEY }}
+ JWT_SECRET: justforthisrunnerthishasnovaluelalalalalalalalalalalalalalalalalalalalalalalalalalla
working-directory: api
# replace all occurences of "*/*" with "application/json" in the openapi.json file
@@ -130,3 +131,4 @@ jobs:
if: steps.check_authors.outputs.has_bot_author != 'true'
with:
commit_message: "chore: update openapi types"
+ skip_checkout: true
\ No newline at end of file
diff --git a/api/pom.xml b/api/pom.xml
index d7b74483..8a1eb413 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -18,6 +18,17 @@
1.6.3
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+ 2.6.0
+
org.springframework.boot
spring-boot-starter-data-jpa
@@ -30,22 +41,9 @@
org.springframework.boot
spring-boot-starter-websocket
-
- com.fasterxml.jackson.datatype
- jackson-datatype-jsr310
-
-
-
-
- io.sentry
- sentry-spring-boot-starter-jakarta
- 7.8.0
-
-
- io.sentry
- sentry-logback
- 7.8.0
+ org.springframework.boot
+ spring-boot-starter-security
@@ -65,11 +63,28 @@
test
+
+
+ io.sentry
+ sentry-spring-boot-starter-jakarta
+ 7.8.0
+
+
+ io.sentry
+ sentry-logback
+ 7.8.0
+
+
+
org.projectlombok
lombok
provided
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+
org.mapstruct
mapstruct
@@ -80,27 +95,27 @@
lombok-mapstruct-binding
0.2.0
-
software.amazon.awssdk
s3
2.33.9
+
- org.springframework.boot
- spring-boot-starter-test
- test
+ io.jsonwebtoken
+ jjwt-api
+ 0.11.5
- org.springdoc
- springdoc-openapi-starter-webmvc-ui
- 2.6.0
+ io.jsonwebtoken
+ jjwt-impl
+ 0.11.5
- com.google.cloud.sql
- postgres-socket-factory
- 1.11.0
+ io.jsonwebtoken
+ jjwt-jackson
+ 0.11.5
@@ -176,4 +191,5 @@
+
diff --git a/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java b/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java
new file mode 100644
index 00000000..3e44b701
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/auth/JwtAuthenticationFilter.java
@@ -0,0 +1,80 @@
+package pro.beerpong.api.auth;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import lombok.RequiredArgsConstructor;
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
+import org.springframework.util.AntPathMatcher;
+import org.springframework.util.StringUtils;
+import org.springframework.web.filter.OncePerRequestFilter;
+import pro.beerpong.api.control.GroupController;
+import pro.beerpong.api.service.AuthService;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.context.SecurityContextHolder;
+
+import java.io.IOException;
+import java.util.List;
+
+@RequiredArgsConstructor
+public class JwtAuthenticationFilter extends OncePerRequestFilter {
+ private static final String GROUPS_PATTERN = "/groups/**";
+ private static final String GROUP_ID_PATTERN = "/groups/{groupId}/**";
+ private static final List NO_VALIDATION_ENDPOINTS = List.of(
+ "/groups",
+ "/groups/" + GroupController.USER_GROUPS_ENDPOINT,
+ "/groups/{groupId}/" + GroupController.JOIN_GROUP_ENDPOINT
+ );
+
+ private final AuthService authService;
+ private final AntPathMatcher pathMatcher = new AntPathMatcher();
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest req,
+ HttpServletResponse res,
+ FilterChain chain) throws ServletException, IOException {
+ var header = req.getHeader("Authorization");
+
+ if (!StringUtils.hasText(header) || !header.startsWith("Bearer ")) {
+ res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ res.getWriter().write("Missing or invalid Authorization header!");
+ return;
+ }
+
+ var token = header.substring(7);
+ var user = authService.userFromAccessToken(token);
+
+ if (user == null) {
+ res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ res.getWriter().write("Invalid access token or invalid user-id!");
+ return;
+ }
+
+ if (!isExcludedFromValidation(req) && !authService.hasAccessToGroup(user, extractGroupId(req))) {
+ res.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ res.getWriter().write("No access to this group!");
+ return;
+ }
+
+ var auth = new UsernamePasswordAuthenticationToken(user, null, List.of(new SimpleGrantedAuthority("ROLE_GROUP_MEMBER")));
+ SecurityContextHolder.getContext().setAuthentication(auth);
+
+ chain.doFilter(req, res);
+ }
+
+ @Override
+ protected boolean shouldNotFilter(HttpServletRequest request) {
+ return !pathMatcher.match(GROUPS_PATTERN, request.getRequestURI());
+ }
+
+ private boolean isExcludedFromValidation(HttpServletRequest request) {
+ return NO_VALIDATION_ENDPOINTS.stream().anyMatch(s -> pathMatcher.match(s, request.getRequestURI()));
+ }
+
+ private String extractGroupId(HttpServletRequest request) {
+ return (pathMatcher.match(GROUP_ID_PATTERN, request.getRequestURI()) ?
+ pathMatcher.extractUriTemplateVariables(GROUP_ID_PATTERN, request.getRequestURI()).get("groupId") :
+ "");
+ }
+}
diff --git a/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java b/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java
new file mode 100644
index 00000000..46de7ef3
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/auth/JwtTokenProvider.java
@@ -0,0 +1,66 @@
+package pro.beerpong.api.auth;
+
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.security.Keys;
+import jakarta.annotation.PostConstruct;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Component;
+
+import java.security.Key;
+import java.util.Date;
+
+@Component
+public class JwtTokenProvider {
+ private Key key;
+ @Value("${jwt.secret}")
+ private String secret;
+ @Value("${jwt.access.expiration-ms}")
+ private long accessExpirationMs;
+
+ @PostConstruct
+ public void init() {
+ key = Keys.hmacShaKeyFor(secret.getBytes());
+ }
+
+ public String createRefreshToken(String userId) {
+ return Jwts.builder()
+ .setSubject(userId)
+ .claim("type", "refresh")
+ .signWith(key, SignatureAlgorithm.HS256)
+ .compact();
+ }
+
+ public String createAccessToken(String userId) {
+ Date now = new Date();
+ Date expiry = new Date(now.getTime() + accessExpirationMs);
+
+ return Jwts.builder()
+ .setSubject(userId)
+ .claim("type", "access")
+ .setIssuedAt(now)
+ .setExpiration(expiry)
+ .signWith(key, SignatureAlgorithm.HS256)
+ .compact();
+ }
+
+ public Jws parseToken(String token) {
+ return Jwts.parserBuilder()
+ .setSigningKey(key)
+ .build()
+ .parseClaimsJws(token);
+ }
+
+ public Claims validateToken(String token, String type) {
+ try {
+ var claims = parseToken(token).getBody();
+
+ if (!claims.get("type", String.class).equals(type)) {
+ return null;
+ }
+
+ return claims;
+ } catch (JwtException | IllegalArgumentException e) {
+ return null;
+ }
+ }
+}
diff --git a/api/src/main/java/pro/beerpong/api/auth/SecurityConfig.java b/api/src/main/java/pro/beerpong/api/auth/SecurityConfig.java
new file mode 100644
index 00000000..5022ecbf
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/auth/SecurityConfig.java
@@ -0,0 +1,33 @@
+package pro.beerpong.api.auth;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
+import org.springframework.security.config.http.SessionCreationPolicy;
+import org.springframework.security.web.SecurityFilterChain;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import pro.beerpong.api.service.AuthService;
+
+@Configuration
+@RequiredArgsConstructor
+public class SecurityConfig {
+ private final AuthService authService;
+
+ @Bean
+ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+ var jwtFilter = new JwtAuthenticationFilter(authService);
+
+ http
+ .csrf(AbstractHttpConfigurer::disable)
+ .sessionManagement(sm -> sm.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
+ .authorizeHttpRequests(auth -> auth
+ .requestMatchers("/groups/{groupId}/**").authenticated()
+ .anyRequest().permitAll()
+ )
+ .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class);
+
+ return http.build();
+ }
+}
diff --git a/api/src/main/java/pro/beerpong/api/control/AuthController.java b/api/src/main/java/pro/beerpong/api/control/AuthController.java
new file mode 100644
index 00000000..29bb0599
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/control/AuthController.java
@@ -0,0 +1,40 @@
+package pro.beerpong.api.control;
+
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
+import pro.beerpong.api.model.dto.*;
+import pro.beerpong.api.service.AuthService;
+
+@RestController
+@RequestMapping("/auth")
+@RequiredArgsConstructor
+public class AuthController {
+ private final AuthService authService;
+
+ @PostMapping("signup")
+ public ResponseEntity> signup(@RequestBody AuthSignupDto authSignupDto) {
+ var result = authService.registerDevice(authSignupDto);
+
+ if (result == null) {
+ return ResponseEnvelope.notOk(ErrorCodes.AUTH_REGISTER_INVALID_DTO);
+ }
+
+ return ResponseEnvelope.ok(result);
+ }
+
+ @PostMapping("refresh")
+ public ResponseEntity> refreshAuth(@RequestBody AuthRefreshDto dto) {
+ if (dto.getRefreshToken() == null || dto.getRefreshToken().trim().isEmpty()) {
+ return ResponseEnvelope.notOk(ErrorCodes.AUTH_REFRESH_INVALID_DTO);
+ }
+
+ var result = authService.refreshAuth(dto);
+
+ if (result == null) {
+ return ResponseEnvelope.notOk(ErrorCodes.AUTH_REFRESH_INVALID_TOKEN);
+ }
+
+ return ResponseEnvelope.ok(result);
+ }
+}
diff --git a/api/src/main/java/pro/beerpong/api/control/GroupController.java b/api/src/main/java/pro/beerpong/api/control/GroupController.java
index 19b0083e..8635582e 100644
--- a/api/src/main/java/pro/beerpong/api/control/GroupController.java
+++ b/api/src/main/java/pro/beerpong/api/control/GroupController.java
@@ -3,30 +3,46 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import pro.beerpong.api.model.dto.*;
import pro.beerpong.api.service.AssetService;
+import pro.beerpong.api.model.dto.*;
+import pro.beerpong.api.service.AuthService;
+import pro.beerpong.api.service.AssetService;
import pro.beerpong.api.service.GroupService;
import pro.beerpong.api.sockets.SocketEvent;
import pro.beerpong.api.sockets.SocketEventData;
import pro.beerpong.api.sockets.SubscriptionHandler;
+import java.util.List;
+
@RestController
@RequestMapping("/groups")
public class GroupController {
+ public static final String USER_GROUPS_ENDPOINT = "user";
+ public static final String JOIN_GROUP_ENDPOINT = "join";
+
private final GroupService groupService;
private final AssetService assetService;
private final SubscriptionHandler subscriptionHandler;
+ private final AuthService authService;
@Autowired
- public GroupController(GroupService groupService, AssetService assetService, SubscriptionHandler subscriptionHandler) {
+ public GroupController(GroupService groupService, AssetService assetService, SubscriptionHandler subscriptionHandler, AuthService authService) {
this.groupService = groupService;
this.assetService = assetService;
this.subscriptionHandler = subscriptionHandler;
+ this.authService = authService;
}
@PostMapping
- public ResponseEntity> createGroup(@RequestBody GroupCreateDto groupCreateDto) {
+ public ResponseEntity> createGroup(@RequestBody GroupCreateDto groupCreateDto,
+ @AuthenticationPrincipal UserDto user) {
+ if (user == null) {
+ return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER);
+ }
+
if (groupCreateDto.invalidName()) {
return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_NAME);
}
@@ -35,7 +51,7 @@ public ResponseEntity> createGroup(@RequestBody Group
return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_PROFILE_NAMES);
}
- var group = groupService.createGroup(groupCreateDto);
+ var group = groupService.createGroup(groupCreateDto, user);
if (group == null) {
return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_SPORT);
@@ -44,6 +60,17 @@ public ResponseEntity> createGroup(@RequestBody Group
return ResponseEnvelope.ok(group);
}
+ @GetMapping(USER_GROUPS_ENDPOINT)
+ public ResponseEntity>> findUserGroups(@AuthenticationPrincipal UserDto user) {
+ if (user == null) {
+ return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER);
+ }
+
+ var groups = groupService.findGroupsByUser(user);
+
+ return ResponseEnvelope.ok(groups);
+ }
+
@GetMapping
public ResponseEntity> findGroupByInviteCode(@RequestParam String inviteCode) {
if (inviteCode == null || inviteCode.trim().isEmpty()) {
@@ -133,4 +160,40 @@ public ResponseEntity> deleteWallpaper(@PathVariable
return ResponseEnvelope.ok(group);
}
+
+ @PostMapping("/{id}/" + JOIN_GROUP_ENDPOINT)
+ public ResponseEntity> joinGroup(@PathVariable String id, @AuthenticationPrincipal UserDto user) {
+ if (user == null) {
+ return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER);
+ }
+
+ if (id == null || id.trim().isEmpty()) {
+ return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_ID);
+ }
+
+ var groupMember = authService.joinGroup(user, id);
+
+ if (groupMember != null) {
+ return ResponseEnvelope.ok("OK");
+ } else {
+ return ResponseEnvelope.notOk(ErrorCodes.GROUP_ALREADY_IN_GROUP);
+ }
+ }
+
+ @PostMapping("/{id}/leave")
+ public ResponseEntity> leaveGroup(@PathVariable String id, @AuthenticationPrincipal UserDto user) {
+ if (user == null) {
+ return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER);
+ }
+
+ if (id == null || id.trim().isEmpty()) {
+ return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_ID);
+ }
+
+ if (authService.leaveGroup(user, id)) {
+ return ResponseEnvelope.ok("OK");
+ } else {
+ return ResponseEnvelope.notOk(ErrorCodes.AUTH_USER_NOT_IN_GROUP);
+ }
+ }
}
diff --git a/api/src/main/java/pro/beerpong/api/control/MatchController.java b/api/src/main/java/pro/beerpong/api/control/MatchController.java
index 3ccb4246..f2257338 100644
--- a/api/src/main/java/pro/beerpong/api/control/MatchController.java
+++ b/api/src/main/java/pro/beerpong/api/control/MatchController.java
@@ -2,6 +2,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import pro.beerpong.api.model.dto.*;
import pro.beerpong.api.service.MatchService;
@@ -15,6 +16,10 @@
@RestController
@RequestMapping("/groups/{groupId}/seasons/{seasonId}/matches")
public class MatchController {
+ // currently we only support games played with exactly 2 teams
+ private static final int MIN_TEAM_AMOUNT = 2;
+ private static final int MAX_TEAM_AMOUNT = 2;
+
private final MatchService matchService;
private final SeasonService seasonService;
private final SubscriptionHandler subscriptionHandler;
@@ -28,7 +33,12 @@ public MatchController(MatchService matchService, SeasonService seasonService, S
@PostMapping
public ResponseEntity> createMatch(@PathVariable String groupId, @PathVariable String seasonId,
- @RequestBody MatchCreateDto matchCreateDto) {
+ @RequestBody MatchCreateDto matchCreateDto,
+ @AuthenticationPrincipal UserDto user) {
+ if (user == null) {
+ return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER);
+ }
+
var pair = seasonService.getSeasonAndGroup(groupId, seasonId);
var error = seasonService.validateActiveSeason(MatchDto.class, pair);
@@ -40,7 +50,11 @@ public ResponseEntity> createMatch(@PathVariable Stri
return ResponseEnvelope.notOk(ErrorCodes.MATCH_CREATE_DTO_VALIDATION_FAILED);
}
- var match = matchService.createNewMatch(pair.getFirst(), pair.getSecond(), matchCreateDto);
+ if (matchCreateDto.getTeams().size() < MIN_TEAM_AMOUNT || matchCreateDto.getTeams().size() > MAX_TEAM_AMOUNT) {
+ return ResponseEnvelope.notOk(ErrorCodes.MATCH_WRONG_AMOUNT_OF_TEAMS);
+ }
+
+ var match = matchService.createNewMatch(pair.getFirst(), pair.getSecond(), matchCreateDto, user);
if (match != null) {
if (match.getSeason().getId().equals(seasonId) && match.getSeason().getGroupId().equals(groupId)) {
@@ -125,6 +139,10 @@ public ResponseEntity> updateMatch(@PathVariable Stri
return ResponseEnvelope.notOk(ErrorCodes.MATCH_CREATE_DTO_VALIDATION_FAILED);
}
+ if (matchCreateDto.getTeams().size() < MIN_TEAM_AMOUNT || matchCreateDto.getTeams().size() > MAX_TEAM_AMOUNT) {
+ return ResponseEnvelope.notOk(ErrorCodes.MATCH_WRONG_AMOUNT_OF_TEAMS);
+ }
+
if (matchCreateDto.getTeams().stream().anyMatch(teamCreateDto -> teamCreateDto.getExistingTeamId() == null)) {
return ResponseEnvelope.notOk(ErrorCodes.MATCH_CREATE_DTO_NEEDS_IDS);
}
@@ -156,10 +174,7 @@ public ResponseEntity> deleteMatchById(@PathVariable St
}
@PutMapping("/{id}/photos/{teamId}")
- public ResponseEntity> setPhoto(@PathVariable String groupId,
- @PathVariable String seasonId,
- @PathVariable String id,
- @PathVariable String teamId) {
+ public ResponseEntity> setPhoto(@PathVariable String groupId, @PathVariable String teamId, @PathVariable String id, @PathVariable String seasonId) {
var match = matchService.getMatchById(id);
if (match == null) {
@@ -186,10 +201,7 @@ public ResponseEntity> setPhoto(@PathVariable String g
}
@DeleteMapping("/{id}/photos/{teamId}")
- public ResponseEntity> deletePhoto(@PathVariable String groupId,
- @PathVariable String seasonId,
- @PathVariable String id,
- @PathVariable String teamId) {
+ public ResponseEntity> deletePhoto(@PathVariable String groupId, @PathVariable String teamId, @PathVariable String id, @PathVariable String seasonId) {
var match = matchService.getMatchById(id);
if (match == null) {
diff --git a/api/src/main/java/pro/beerpong/api/control/PlayerController.java b/api/src/main/java/pro/beerpong/api/control/PlayerController.java
index c805a1bc..d645b114 100644
--- a/api/src/main/java/pro/beerpong/api/control/PlayerController.java
+++ b/api/src/main/java/pro/beerpong/api/control/PlayerController.java
@@ -37,12 +37,22 @@ public ResponseEntity>> getPlayers(@PathVariabl
return ResponseEnvelope.notOk(ErrorCodes.INVALID_SEASON_ID);
}
- var players = seasonService.calcStatsForPlayersInSeason(seasonId, showInactive, showStats);
+ var season = seasonService.getSeasonById(seasonId);
+
+ if (season == null) {
+ return ResponseEnvelope.notOk(ErrorCodes.SEASON_NOT_FOUND);
+ }
+
+ if (!season.getGroupId().equals(groupId)) {
+ return ResponseEnvelope.notOk(ErrorCodes.SEASON_NOT_OF_GROUP);
+ }
+
+ var players = seasonService.calcStatsForPlayersInSeason(season, showInactive, showStats);
if (players != null) {
return ResponseEnvelope.ok(players);
} else {
- return ResponseEnvelope.notOk(ErrorCodes.SEASON_NOT_FOUND);
+ return ResponseEnvelope.notOk(ErrorCodes.ERROR);
}
}
diff --git a/api/src/main/java/pro/beerpong/api/control/ProfileController.java b/api/src/main/java/pro/beerpong/api/control/ProfileController.java
index 4310f62a..7f076abe 100644
--- a/api/src/main/java/pro/beerpong/api/control/ProfileController.java
+++ b/api/src/main/java/pro/beerpong/api/control/ProfileController.java
@@ -4,6 +4,7 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import pro.beerpong.api.service.AssetService;
import pro.beerpong.api.model.dto.*;
@@ -25,11 +26,17 @@ public class ProfileController {
private final SubscriptionHandler subscriptionHandler;
@PostMapping
- public ResponseEntity> createProfile(@PathVariable String groupId, @RequestBody ProfileCreateDto profileCreateDto) {
+ public ResponseEntity> createProfile(@PathVariable String groupId,
+ @RequestBody ProfileCreateDto profileCreateDto,
+ @AuthenticationPrincipal UserDto user) {
+ if (user == null) {
+ return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER);
+ }
+
var group = groupService.getGroupById(groupId);
if (group != null) {
- var dto = profileService.createPlayer(groupId, profileCreateDto);
+ var dto = profileService.createPlayer(groupId, profileCreateDto, user);
if (dto == null) {
return ResponseEnvelope.notOk(ErrorCodes.PROFILE_ALREADY_EXISTS);
@@ -62,7 +69,11 @@ public ResponseEntity> getProfileById(@PathVariable
var profile = profileService.getProfileById(id);
if (profile != null) {
- return ResponseEnvelope.ok(profile);
+ if (profile.getGroupId().equals(groupId)) {
+ return ResponseEnvelope.ok(profile);
+ } else {
+ return ResponseEnvelope.notOk(ErrorCodes.PROFILE_NOT_OF_GROUP);
+ }
} else {
return ResponseEnvelope.notOk(ErrorCodes.PROFILE_NOT_FOUND);
}
@@ -76,7 +87,7 @@ public ResponseEntity> updateProfile(@PathVariable
var group = groupService.getGroupById(groupId);
if (group != null) {
- var updatedProfile = profileService.updateProfile(id, profileCreateDto);
+ var updatedProfile = profileService.updateProfile(id, groupId, profileCreateDto);
if (updatedProfile != null) {
subscriptionHandler.callEvent(new SocketEvent<>(SocketEventData.PROFILE_UPDATE, groupId, updatedProfile));
diff --git a/api/src/main/java/pro/beerpong/api/control/RuleController.java b/api/src/main/java/pro/beerpong/api/control/RuleController.java
index a52615b2..77d30874 100644
--- a/api/src/main/java/pro/beerpong/api/control/RuleController.java
+++ b/api/src/main/java/pro/beerpong/api/control/RuleController.java
@@ -3,11 +3,9 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
-import pro.beerpong.api.model.dto.ErrorCodes;
-import pro.beerpong.api.model.dto.ResponseEnvelope;
-import pro.beerpong.api.model.dto.RuleCreateDto;
-import pro.beerpong.api.model.dto.RuleDto;
+import pro.beerpong.api.model.dto.*;
import pro.beerpong.api.service.RuleService;
import pro.beerpong.api.service.SeasonService;
import pro.beerpong.api.sockets.SocketEvent;
@@ -46,7 +44,14 @@ public ResponseEntity>> getRules(@PathVariable St
}
@PutMapping
- public ResponseEntity>> writeRules(@PathVariable String groupId, @PathVariable String seasonId, @RequestBody List rules) {
+ public ResponseEntity>> writeRules(@PathVariable String groupId,
+ @PathVariable String seasonId,
+ @RequestBody List rules,
+ @AuthenticationPrincipal UserDto user) {
+ if (user == null) {
+ return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER);
+ }
+
var pair = seasonService.getSeasonAndGroup(groupId, seasonId);
if (pair.getFirst() == null) {
@@ -57,9 +62,11 @@ public ResponseEntity>> writeRules(@PathVariable
return ResponseEnvelope.notOk(ErrorCodes.SEASON_NOT_OF_GROUP);
} else if (pair.getSecond().getEndDate() != null) {
return ResponseEnvelope.notOk(ErrorCodes.SEASON_ALREADY_ENDED);
+ } else if (rules.stream().anyMatch(RuleCreateDto::invalidDto)) {
+ return ResponseEnvelope.notOk(ErrorCodes.RULE_INVALID_DTO);
}
- var ruleDtos = ruleService.writeRules(groupId, pair.getSecond(), rules);
+ var ruleDtos = ruleService.writeRules(groupId, pair.getSecond(), rules, user);
if (ruleDtos != null) {
subscriptionHandler.callEvent(new SocketEvent<>(SocketEventData.RULES_WRITE, groupId, ruleDtos.toArray(new RuleDto[0])));
diff --git a/api/src/main/java/pro/beerpong/api/control/RuleMoveController.java b/api/src/main/java/pro/beerpong/api/control/RuleMoveController.java
index 5da2d6af..3024a1ab 100644
--- a/api/src/main/java/pro/beerpong/api/control/RuleMoveController.java
+++ b/api/src/main/java/pro/beerpong/api/control/RuleMoveController.java
@@ -34,6 +34,10 @@ public ResponseEntity> createRuleMove(@PathVariabl
return error;
}
+ if (dto.invalidDto()) {
+ return ResponseEnvelope.notOk(ErrorCodes.RULE_MOVE_INVALID_DTO);
+ }
+
var move = moveService.createRuleMove(pair.getFirst(), pair.getSecond(), dto, true);
if (move != null) {
@@ -52,6 +56,10 @@ public ResponseEntity> updateRuleMove(@PathVariabl
return error;
}
+ if (dto.invalidDto()) {
+ return ResponseEnvelope.notOk(ErrorCodes.RULE_MOVE_INVALID_DTO);
+ }
+
var move = moveService.getById(ruleMoveId);
if (move == null) {
diff --git a/api/src/main/java/pro/beerpong/api/control/SeasonController.java b/api/src/main/java/pro/beerpong/api/control/SeasonController.java
index 59033162..9195759b 100644
--- a/api/src/main/java/pro/beerpong/api/control/SeasonController.java
+++ b/api/src/main/java/pro/beerpong/api/control/SeasonController.java
@@ -1,12 +1,15 @@
package pro.beerpong.api.control;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
+import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import pro.beerpong.api.model.dto.*;
import pro.beerpong.api.service.SeasonService;
+import pro.beerpong.api.sockets.LocalTimeAdapter;
+import java.time.LocalTime;
+import java.time.format.DateTimeParseException;
import java.util.List;
@RestController
@@ -20,7 +23,13 @@ public SeasonController(SeasonService seasonService) {
}
@PutMapping("/active-season")
- public ResponseEntity> startNewSeason(@PathVariable String groupId, @RequestBody SeasonCreateDto dto) {
+ public ResponseEntity> startNewSeason(@PathVariable String groupId,
+ @RequestBody SeasonCreateDto dto,
+ @AuthenticationPrincipal UserDto user) {
+ if (user == null) {
+ return ResponseEnvelope.notOk(ErrorCodes.AUTH_INVALID_USER);
+ }
+
if (groupId == null || groupId.trim().isEmpty()) {
return ResponseEnvelope.notOk(ErrorCodes.INVALID_GROUP_ID);
}
@@ -33,7 +42,7 @@ public ResponseEntity> startNewSeason(@PathVariable
return ResponseEnvelope.notOk(ErrorCodes.INVALID_RULE_MOVES);
}
- var season = seasonService.startNewSeason(dto, groupId);
+ var season = seasonService.startNewSeason(dto, groupId, user);
if (season != null) {
return ResponseEnvelope.ok(season);
@@ -94,15 +103,29 @@ public ResponseEntity> updateSeasonById(@PathVariabl
return ResponseEnvelope.notOk(ErrorCodes.SEASON_ALREADY_ENDED);
}
- if (dto.getSeasonSettings().getWakeTimeHour() < 0 || dto.getSeasonSettings().getWakeTimeHour() > 23) {
+ var minMatches = (dto.getSeasonSettings().getMinMatchesToQualify() != null ? dto.getSeasonSettings().getMinMatchesToQualify() : season.get().getSeasonSettings().getMinMatchesToQualify());
+ var minTeamSize = (dto.getSeasonSettings().getMinTeamSize() != null ? dto.getSeasonSettings().getMinTeamSize() : season.get().getSeasonSettings().getMinTeamSize());
+ var maxTeamSize = (dto.getSeasonSettings().getMaxTeamSize() != null ? dto.getSeasonSettings().getMaxTeamSize() : season.get().getSeasonSettings().getMaxTeamSize());
+
+ LocalTime wakeTime = season.get().getSeasonSettings().getWakeTime();
+
+ if (dto.getSeasonSettings().getWakeTime() != null) {
+ try {
+ wakeTime = LocalTime.parse(dto.getSeasonSettings().getWakeTime(), LocalTimeAdapter.FORMATTER);
+ } catch (DateTimeParseException e) {
+ wakeTime = null;
+ }
+ }
+
+ if (wakeTime == null) {
return ResponseEnvelope.notOk(ErrorCodes.SEASON_WRONG_TIME_FORMAT);
- } else if (dto.getSeasonSettings().getMinTeamSize() > dto.getSeasonSettings().getMaxTeamSize()) {
+ } else if (minTeamSize > maxTeamSize) {
return ResponseEnvelope.notOk(ErrorCodes.SEASON_WRONG_TEAM_SIZES);
}
- dto.getSeasonSettings().setMinMatchesToQualify(Math.min(Math.max(dto.getSeasonSettings().getMinMatchesToQualify(), 0), 1000));
- dto.getSeasonSettings().setMinTeamSize(Math.min(Math.max(dto.getSeasonSettings().getMinTeamSize(), 1), 10));
- dto.getSeasonSettings().setMaxTeamSize(Math.min(Math.max(dto.getSeasonSettings().getMaxTeamSize(), 1), 10));
+ dto.getSeasonSettings().setMinMatchesToQualify(Math.min(Math.max(minMatches, 0), 1000));
+ dto.getSeasonSettings().setMinTeamSize(Math.min(Math.max(minTeamSize, 1), 10));
+ dto.getSeasonSettings().setMaxTeamSize(Math.min(Math.max(maxTeamSize, 1), 10));
SeasonDto updatedSeason = seasonService.updateSeason(season.get(), dto);
if (updatedSeason != null) {
diff --git a/api/src/main/java/pro/beerpong/api/mapping/AssetMapper.java b/api/src/main/java/pro/beerpong/api/mapping/AssetMapper.java
index 91b1053a..629dcaf2 100644
--- a/api/src/main/java/pro/beerpong/api/mapping/AssetMapper.java
+++ b/api/src/main/java/pro/beerpong/api/mapping/AssetMapper.java
@@ -4,7 +4,9 @@
import org.mapstruct.Mapping;
import org.springframework.beans.factory.annotation.Value;
import pro.beerpong.api.model.dao.Asset;
+import pro.beerpong.api.model.dao.GroupMember;
import pro.beerpong.api.model.dto.AssetMetadataDto;
+import pro.beerpong.api.model.dto.GroupMemberDto;
@Mapper(componentModel = "spring")
public abstract class AssetMapper {
@@ -16,7 +18,11 @@ public abstract class AssetMapper {
@Mapping(target = "url", expression = "java(generateUrl(asset))")
public abstract AssetMetadataDto assetToAssetMetadataDto(Asset asset);
- public abstract Asset assetMetadataDtoToAsset(AssetMetadataDto asset);
+ public abstract Asset assetMetadataDtoToAsset(AssetMetadataDto dto);
+
+ @Mapping(source = "group.id", target = "groupId")
+ @Mapping(source = "user.id", target = "userId")
+ public abstract GroupMemberDto groupMemberToGroupMemberDto(GroupMember groupMember);
public String generateUrl(Asset asset) {
return "https://" + bucket + "." + endpoint + "/" + asset.getId();
diff --git a/api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java b/api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java
index f1bea6af..edda96ac 100644
--- a/api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java
+++ b/api/src/main/java/pro/beerpong/api/mapping/GroupMapper.java
@@ -2,9 +2,7 @@
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
-import org.springframework.web.util.UriComponentsBuilder;
import pro.beerpong.api.control.GroupPresetsController;
-import pro.beerpong.api.model.dao.Asset;
import pro.beerpong.api.model.dao.Group;
import pro.beerpong.api.model.dto.GroupCreateDto;
import pro.beerpong.api.model.dto.GroupDto;
diff --git a/api/src/main/java/pro/beerpong/api/mapping/GroupMemberMapper.java b/api/src/main/java/pro/beerpong/api/mapping/GroupMemberMapper.java
new file mode 100644
index 00000000..2322fd92
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/mapping/GroupMemberMapper.java
@@ -0,0 +1,15 @@
+package pro.beerpong.api.mapping;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import pro.beerpong.api.model.dao.GroupMember;
+import pro.beerpong.api.model.dao.Team;
+import pro.beerpong.api.model.dto.GroupMemberDto;
+import pro.beerpong.api.model.dto.TeamDto;
+
+@Mapper(componentModel = "spring")
+public interface GroupMemberMapper {
+ @Mapping(source = "group.id", target = "groupId")
+ @Mapping(source = "user.id", target = "userId")
+ GroupMemberDto groupMemberToGroupMemberDto(GroupMember groupMember);
+}
diff --git a/api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java b/api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java
index 4aaa0087..48f4cfa6 100644
--- a/api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java
+++ b/api/src/main/java/pro/beerpong/api/mapping/RuleMapper.java
@@ -5,7 +5,7 @@
import pro.beerpong.api.model.dto.RuleCreateDto;
import pro.beerpong.api.model.dto.RuleDto;
-@Mapper(componentModel = "spring")
+@Mapper(componentModel = "spring", uses = AssetMapper.class)
public interface RuleMapper {
Rule ruleCreateDtoToRule(RuleCreateDto dto);
diff --git a/api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java b/api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java
index c9dd7bb4..a909fb6e 100644
--- a/api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java
+++ b/api/src/main/java/pro/beerpong/api/mapping/SeasonMapper.java
@@ -4,7 +4,7 @@
import pro.beerpong.api.model.dao.Season;
import pro.beerpong.api.model.dto.SeasonDto;
-@Mapper(componentModel = "spring")
+@Mapper(componentModel = "spring", uses = AssetMapper.class)
public interface SeasonMapper {
SeasonDto seasonToSeasonDto(Season season);
}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/mapping/SeasonSettingsMapper.java b/api/src/main/java/pro/beerpong/api/mapping/SeasonSettingsMapper.java
new file mode 100644
index 00000000..6abe0388
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/mapping/SeasonSettingsMapper.java
@@ -0,0 +1,19 @@
+package pro.beerpong.api.mapping;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import pro.beerpong.api.model.dao.SeasonSettings;
+import pro.beerpong.api.model.dto.SeasonSettingsDto;
+import pro.beerpong.api.sockets.LocalTimeAdapter;
+
+import java.time.LocalTime;
+
+@Mapper(componentModel = "spring")
+public abstract class SeasonSettingsMapper {
+ @Mapping(target = "wakeTime", expression = "java(parseTime(seasonSettingsDto))")
+ public abstract SeasonSettings seasonSettingsDtoToSeasonSettings(SeasonSettingsDto seasonSettingsDto);
+
+ protected LocalTime parseTime(SeasonSettingsDto seasonSettingsDto) {
+ return LocalTime.parse(seasonSettingsDto.getWakeTime(), LocalTimeAdapter.FORMATTER);
+ }
+}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/mapping/UserMapper.java b/api/src/main/java/pro/beerpong/api/mapping/UserMapper.java
new file mode 100644
index 00000000..a6830bf0
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/mapping/UserMapper.java
@@ -0,0 +1,15 @@
+package pro.beerpong.api.mapping;
+
+import org.mapstruct.Mapper;
+import org.mapstruct.Mapping;
+import pro.beerpong.api.model.dao.Team;
+import pro.beerpong.api.model.dao.User;
+import pro.beerpong.api.model.dto.TeamDto;
+import pro.beerpong.api.model.dto.UserDto;
+
+@Mapper(componentModel = "spring")
+public interface UserMapper {
+ UserDto userToUserDto(User user);
+
+ User userDtoToUser(UserDto userDto);
+}
diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Device.java b/api/src/main/java/pro/beerpong/api/model/dao/Device.java
new file mode 100644
index 00000000..c8ba5cff
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/model/dao/Device.java
@@ -0,0 +1,19 @@
+package pro.beerpong.api.model.dao;
+
+import jakarta.persistence.*;
+import lombok.Data;
+import pro.beerpong.api.util.InstallationType;
+
+@Entity(name = "devices")
+@Data
+public class Device {
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ private String id;
+ private InstallationType type;
+ private String deviceId;
+ private String pushNotificationToken;
+ @ManyToOne
+ @JoinColumn(name = "user_id")
+ private User user;
+}
diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Group.java b/api/src/main/java/pro/beerpong/api/model/dao/Group.java
index 676fe545..057c49ce 100644
--- a/api/src/main/java/pro/beerpong/api/model/dao/Group.java
+++ b/api/src/main/java/pro/beerpong/api/model/dao/Group.java
@@ -5,6 +5,7 @@
import pro.beerpong.api.model.dto.GroupPreset;
import java.time.ZonedDateTime;
+import java.util.List;
@Entity(name = "groups")
@Data
@@ -21,6 +22,11 @@ public class Group {
@OneToOne
@JoinColumn(name = "assetIdWallpaper")
private Asset wallpaperAsset;
+ @OneToMany(mappedBy = "group")
+ private List members;
+ @ManyToOne
+ @JoinColumn(name = "createdBy")
+ private GroupMember createdBy;
private ZonedDateTime createdAt;
private String sportPreset;
private String customSportName;
diff --git a/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java b/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java
new file mode 100644
index 00000000..741678c5
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/model/dao/GroupMember.java
@@ -0,0 +1,21 @@
+package pro.beerpong.api.model.dao;
+
+import jakarta.persistence.*;
+import lombok.Data;
+
+import java.util.List;
+
+@Entity(name = "group_members")
+@Data
+public class GroupMember {
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ private String id;
+ private boolean active;
+ @ManyToOne
+ @JoinColumn(name = "group_id")
+ private Group group;
+ @ManyToOne
+ @JoinColumn(name = "user_id")
+ private User user;
+}
diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Match.java b/api/src/main/java/pro/beerpong/api/model/dao/Match.java
index 8a3cd76a..3bcd3ebc 100644
--- a/api/src/main/java/pro/beerpong/api/model/dao/Match.java
+++ b/api/src/main/java/pro/beerpong/api/model/dao/Match.java
@@ -21,4 +21,8 @@ public class Match {
@OneToMany(mappedBy = "match")
private List teams;
+
+ @ManyToOne
+ @JoinColumn(name = "createdBy")
+ private GroupMember createdBy;
}
diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Profile.java b/api/src/main/java/pro/beerpong/api/model/dao/Profile.java
index 9cd80327..fa79a51f 100644
--- a/api/src/main/java/pro/beerpong/api/model/dao/Profile.java
+++ b/api/src/main/java/pro/beerpong/api/model/dao/Profile.java
@@ -18,4 +18,8 @@ public class Profile {
@ManyToOne
@JoinColumn(name = "groupId")
private Group group;
+
+ @ManyToOne
+ @JoinColumn(name = "createdBy")
+ private GroupMember createdBy;
}
diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Rule.java b/api/src/main/java/pro/beerpong/api/model/dao/Rule.java
index c9c0924e..ed6df542 100644
--- a/api/src/main/java/pro/beerpong/api/model/dao/Rule.java
+++ b/api/src/main/java/pro/beerpong/api/model/dao/Rule.java
@@ -6,7 +6,7 @@
@Entity(name = "rules")
@Data
-public class Rule implements Cloneable {
+public class Rule {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@@ -20,9 +20,7 @@ public class Rule implements Cloneable {
@JoinColumn(name = "seasonId")
private Season season;
- @Override
- @SneakyThrows
- public Rule clone() {
- return (Rule) super.clone();
- }
+ @ManyToOne
+ @JoinColumn(name = "createdBy")
+ private GroupMember createdBy;
}
diff --git a/api/src/main/java/pro/beerpong/api/model/dao/RuleMove.java b/api/src/main/java/pro/beerpong/api/model/dao/RuleMove.java
index 933dc6a6..b3b5ce92 100644
--- a/api/src/main/java/pro/beerpong/api/model/dao/RuleMove.java
+++ b/api/src/main/java/pro/beerpong/api/model/dao/RuleMove.java
@@ -8,7 +8,7 @@
@Entity(name = "rule_moves")
@Data
-public class RuleMove implements Cloneable {
+public class RuleMove {
@Id
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
@@ -27,10 +27,4 @@ public class RuleMove implements Cloneable {
@OneToMany(mappedBy = "move")
private List matchMoves;
-
- @Override
- @SneakyThrows
- public RuleMove clone() {
- return (RuleMove) super.clone();
- }
}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/model/dao/Season.java b/api/src/main/java/pro/beerpong/api/model/dao/Season.java
index 4d111729..bbbdd48f 100644
--- a/api/src/main/java/pro/beerpong/api/model/dao/Season.java
+++ b/api/src/main/java/pro/beerpong/api/model/dao/Season.java
@@ -23,4 +23,8 @@ public class Season {
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn
private SeasonSettings seasonSettings;
+
+ @ManyToOne
+ @JoinColumn(name = "createdBy")
+ private GroupMember createdBy;
}
diff --git a/api/src/main/java/pro/beerpong/api/model/dao/SeasonSettings.java b/api/src/main/java/pro/beerpong/api/model/dao/SeasonSettings.java
index 7752c3fe..a4a4efa2 100644
--- a/api/src/main/java/pro/beerpong/api/model/dao/SeasonSettings.java
+++ b/api/src/main/java/pro/beerpong/api/model/dao/SeasonSettings.java
@@ -1,13 +1,12 @@
package pro.beerpong.api.model.dao;
-import jakarta.persistence.Entity;
-import jakarta.persistence.GeneratedValue;
-import jakarta.persistence.GenerationType;
-import jakarta.persistence.Id;
+import jakarta.persistence.*;
import lombok.Data;
import pro.beerpong.api.util.DailyLeaderboard;
import pro.beerpong.api.util.RankingAlgorithm;
+import java.time.LocalTime;
+
@Entity(name = "season_settings")
@Data
public class SeasonSettings {
@@ -15,10 +14,24 @@ public class SeasonSettings {
@GeneratedValue(strategy = GenerationType.UUID)
private String id;
- private int minMatchesToQualify = 1;
- private int minTeamSize = 1;
- private int maxTeamSize = 10;
- private RankingAlgorithm rankingAlgorithm = RankingAlgorithm.AVERAGE;
- private DailyLeaderboard dailyLeaderboard = DailyLeaderboard.WAKE_TIME;
- private int wakeTimeHour = 0;
+ private int minMatchesToQualify;
+ private int minTeamSize;
+ private int maxTeamSize;
+ private RankingAlgorithm rankingAlgorithm;
+ private DailyLeaderboard dailyLeaderboard;
+ @Column(columnDefinition = "time default '00:00:00'")
+ private LocalTime wakeTime;
+
+ public static SeasonSettings createDefault() {
+ var seasonSettings = new SeasonSettings();
+
+ seasonSettings.setMinMatchesToQualify(1);
+ seasonSettings.setMinTeamSize(1);
+ seasonSettings.setMaxTeamSize(10);
+ seasonSettings.setRankingAlgorithm(RankingAlgorithm.AVERAGE);
+ seasonSettings.setDailyLeaderboard(DailyLeaderboard.WAKE_TIME);
+ seasonSettings.setWakeTime(LocalTime.of(0, 0));
+
+ return seasonSettings;
+ }
}
diff --git a/api/src/main/java/pro/beerpong/api/model/dao/User.java b/api/src/main/java/pro/beerpong/api/model/dao/User.java
new file mode 100644
index 00000000..0b93c66e
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/model/dao/User.java
@@ -0,0 +1,22 @@
+package pro.beerpong.api.model.dao;
+
+import jakarta.persistence.*;
+import lombok.Data;
+
+import java.util.List;
+
+@Entity(name = "users")
+@Data
+public class User {
+ @Id
+ @GeneratedValue(strategy = GenerationType.UUID)
+ private String id;
+
+ @OneToMany(mappedBy = "user")
+ private List devices;
+
+ @OneToMany(mappedBy = "user")
+ private List groups;
+
+ // future: premium subscriptions and other stuff here
+}
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/AuthRefreshDto.java b/api/src/main/java/pro/beerpong/api/model/dto/AuthRefreshDto.java
new file mode 100644
index 00000000..4b6c7446
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/model/dto/AuthRefreshDto.java
@@ -0,0 +1,9 @@
+package pro.beerpong.api.model.dto;
+
+import lombok.Data;
+import pro.beerpong.api.util.InstallationType;
+
+@Data
+public class AuthRefreshDto {
+ private String refreshToken;
+}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/AuthSignupDto.java b/api/src/main/java/pro/beerpong/api/model/dto/AuthSignupDto.java
new file mode 100644
index 00000000..1c22ede4
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/model/dto/AuthSignupDto.java
@@ -0,0 +1,10 @@
+package pro.beerpong.api.model.dto;
+
+import lombok.Data;
+import pro.beerpong.api.util.InstallationType;
+
+@Data
+public class AuthSignupDto {
+ private InstallationType installationType;
+ private String deviceId;
+}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/AuthTokenDto.java b/api/src/main/java/pro/beerpong/api/model/dto/AuthTokenDto.java
new file mode 100644
index 00000000..a3b5ee4e
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/model/dto/AuthTokenDto.java
@@ -0,0 +1,12 @@
+package pro.beerpong.api.model.dto;
+
+import lombok.Data;
+import pro.beerpong.api.util.TokenType;
+
+import java.time.ZonedDateTime;
+
+@Data
+public class AuthTokenDto {
+ private String token;
+ private TokenType type;
+}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java
index 7d0717a4..8dfe57b0 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/ErrorCodes.java
@@ -8,10 +8,12 @@
@Getter
@RequiredArgsConstructor
public enum ErrorCodes {
- /* GROUPS */
+ ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "error", "An internal error occurred!")
+ /* GROUPS */,
GROUP_NOT_FOUND(HttpStatus.NOT_FOUND, "groupNotFound", "The requested group could not be found!"),
GROUP_INVITE_NOT_FOUND(HttpStatus.NOT_FOUND, "groupInviteNotFound", "No group with the provided invite code could be found!"),
GROUP_INVITE_CODE_NOT_PROVIDED(HttpStatus.BAD_REQUEST, "groupInviteCodeNotProvided", "The invite code needs to be provided!"),
+ GROUP_ALREADY_IN_GROUP(HttpStatus.FORBIDDEN, "groupAlreadyInGroup", "The user is already a member of this group!"),
GROUP_HAS_NO_WALLPAPER(HttpStatus.NOT_FOUND, "groupHasNoWallpaper", "The provided group does not have a wallpaper saved!"),
INVALID_GROUP_NAME(HttpStatus.BAD_REQUEST, "invalidGroupName", "Group name must be non-null, non-empty and between 2 and 50 characters!"),
INVALID_GROUP_PROFILE_NAMES(HttpStatus.BAD_REQUEST, "invalidGroupProfileNames", "Group profileNames must be non-null and non-empty!"),
@@ -21,7 +23,7 @@ public enum ErrorCodes {
/* SEASONS */,
SEASON_NOT_FOUND(HttpStatus.NOT_FOUND, "seasonNotFound", "The requested season could not be found!"),
SEASON_ALREADY_ENDED(HttpStatus.FORBIDDEN, "seasonAlreadyEnded", "Past seasons are immutable!"),
- SEASON_WRONG_TIME_FORMAT(HttpStatus.BAD_REQUEST, "seasonWrongTimeFormat", "The wake time hour has to be between 0 and 23!"),
+ SEASON_WRONG_TIME_FORMAT(HttpStatus.BAD_REQUEST, "seasonWrongTimeFormat", "The wake time has to be supplied in the following format: HH:mm and be a valid hour and minute"),
SEASON_WRONG_TEAM_SIZES(HttpStatus.BAD_REQUEST, "seasonWrongTeamSizes", "The min team size has to be less then or equal to the max team size!"),
SEASON_NOT_OF_GROUP(HttpStatus.FORBIDDEN, "seasonHasDifferentGroup", "The seasons group id does not match the provided group id!"),
SEASON_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "seasonValidationFailed", "The validation of the created season has failed (invalid group id)"),
@@ -33,31 +35,41 @@ public enum ErrorCodes {
MATCH_NOT_FOUND(HttpStatus.NOT_FOUND, "matchNotFound", "The requested match could not be found!"),
MATCH_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "matchValidationFailed", "The validation of the created match has failed (invalid group or season id)"),
MATCH_CREATE_DTO_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "matchCreateDtoValidationFailed", "The team sizes are not in the boundaries of the season settings!"),
+ MATCH_WRONG_AMOUNT_OF_TEAMS(HttpStatus.BAD_REQUEST, "matchWrongAmountOfTeams", "The amount of teams has to be exactly 2!"),
+ MATCH_DTO_VALIDATION_FAILED(HttpStatus.FORBIDDEN, "matchDtoValidationFailed", "The validation of the match create dto failed (invalid player, rulemove, season or group id, double player, or no/more than exactly one finish move)"),
MATCH_CREATE_DTO_NEEDS_IDS(HttpStatus.BAD_REQUEST, "matchCreateDtoNeedsIds", "Every team needs a 'existingTeamId' attribute containing the id of the existing team!"),
- MATCH_DTO_VALIDATION_FAILED(HttpStatus.FORBIDDEN, "matchDtoValidationFailed", "The validation of the match create dto failed (invalid player, rulemove, season or group id, or no finish move)"),
MATCH_NO_TEAM_FOUND(HttpStatus.FORBIDDEN, "matchNoTeamFound", "Could not find a team with the provided id linked to the provided match!"),
MATCH_TEAM_HAS_NO_PHOTO(HttpStatus.NOT_FOUND, "matchTeamHasNoPhoto", "The provided team does not have a photo set!"),
MATCH_GROUP_OR_SEASON_ID_DONT_MATCH(HttpStatus.BAD_REQUEST, "matchGroupOrSeasonIdDontMatch", "The provided group and season id dont match the group and season id of the provided match!")
/* RULE MOVES */,
RULE_MOVE_NOT_FOUND(HttpStatus.NOT_FOUND, "ruleMoveNotFound", "The requested ruleMove could not be found!"),
RULE_MOVE_VALIDATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "ruleMoveValidationFailed", "The rulemove is not part of the provided season or the provided season is not part of the provided gorup!"),
- RULE_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "ruleValidationFailed", "The validation of the created rules has failed (invalid group or season id)")
- /* PLAYERS */,
+ RULE_MOVE_INVALID_DTO(HttpStatus.BAD_REQUEST, "ruleMoveInvalidDto", "The name has to be non-null and non-empty and pointsForScorer and pointsForTeam have to be >= 0!"),
+ RULE_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "ruleValidationFailed", "The validation of the created rules has failed (invalid group or season id)"),
+ /* RULES */
+ RULE_INVALID_DTO(HttpStatus.BAD_REQUEST, "ruleInvalidDto", "Every rule name and description has to be non-null and non-empty!"),
+ /* PLAYERS */
PLAYER_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "playerValidationFailed", "The player is not part of the provided season or group!"),
PLAYER_NOT_FOUND(HttpStatus.NOT_FOUND, "playerNotFound", "The requested player could not be found!"),
PLAYER_ALREADY_DELETED(HttpStatus.FORBIDDEN, "playerAlreadyDeleted", "This player has been deleted!"),
INVALID_PLAYER_ID(HttpStatus.BAD_REQUEST, "invalidPlayerId", "Player id must be non-null and non-empty!")
/* PROFILES */,
PROFILE_NOT_FOUND(HttpStatus.NOT_FOUND, "profileNotFound", "The requested profile could not be found!"),
+ PROFILE_NOT_OF_GROUP(HttpStatus.BAD_REQUEST, "profileNotOfGroup", "The provided profile and group id do not match!"),
PROFILE_ALREADY_EXISTS(HttpStatus.BAD_REQUEST, "profileAlreadyExists", "There already exists a profile with the provided name and an active player in the current season!"),
- PROFILE_NOT_OF_GROUP(HttpStatus.FORBIDDEN, "profileHasDifferentGroup", "The profiles group id does not match the provided group id!"),
PROFILE_HAS_NO_AVATAR(HttpStatus.NOT_FOUND, "profileHasNoAvatar", "The provided profiles does not have an avatar saved!")
/* ASSETS */,
ASSET_NOT_FOUND(HttpStatus.NOT_FOUND, "assetNotFound", "The requested asset could not be found!"),
ASSET_VALIDATION_FAILED(HttpStatus.BAD_REQUEST, "assetValidationFailed", "The provided asset offsets or zoom have to be >= 0!"),
/* LEADERBOARDS */
LEADERBOARD_SCOPE_NOT_FOUND(HttpStatus.NOT_FOUND, "leaderboardScopeNotFound", "The leaderboard scope has to be one of: all-time, today, season"),
- LEADERBOARD_SEASON_NOT_FOUND(HttpStatus.NOT_FOUND, "leaderboardScopeNotFound", "The scope 'season' requires a seasonId param!");
+ LEADERBOARD_SEASON_NOT_FOUND(HttpStatus.NOT_FOUND, "leaderboardScopeNotFound", "The scope 'season' requires a seasonId param!"),
+ /* AUTH */
+ AUTH_REGISTER_INVALID_DTO(HttpStatus.BAD_REQUEST, "authRegisterInvalidDto", "The installationType or deviceId is invalid!"),
+ AUTH_REFRESH_INVALID_DTO(HttpStatus.BAD_REQUEST, "authRefreshInvalidDto", "The refreshToken has to be non-null and non-empty!"),
+ AUTH_INVALID_USER(HttpStatus.UNAUTHORIZED, "authInvalidUser", "The user from the access token has to be non-null!"),
+ AUTH_USER_NOT_IN_GROUP(HttpStatus.UNAUTHORIZED, "authUserNotInGroup", "The user is not in this group!"),
+ AUTH_REFRESH_INVALID_TOKEN(HttpStatus.BAD_REQUEST, "authRefreshInvalidToken", "The refreshToken is no refresh-token, invalid or the subject-userId is invalid!");
private final HttpStatus httpStatus;
private final String code;
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/GroupCreateDto.java b/api/src/main/java/pro/beerpong/api/model/dto/GroupCreateDto.java
index 6565da8c..4b4bbb06 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/GroupCreateDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/GroupCreateDto.java
@@ -16,11 +16,12 @@ public class GroupCreateDto {
private String customSportName;
public boolean invalidName() {
- return this.name == null || this.name.isEmpty() ||
+ return this.name == null || this.name.trim().isEmpty() ||
this.name.length() < GROUP_NAME_MIN_LENGTH || this.name.length() > GROUP_NAME_MAX_LENGTH;
}
public boolean invalidProfileName() {
- return this.profileNames == null || this.profileNames.isEmpty();
+ return this.profileNames == null || this.profileNames.isEmpty() ||
+ this.profileNames.stream().anyMatch(s -> this.profileNames.stream().filter(s1 -> s1.equals(s)).count() > 1);
}
}
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java b/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java
index a1560a87..796b33a0 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/GroupDto.java
@@ -11,9 +11,10 @@ public class GroupDto {
private String id;
private String name;
private String inviteCode;
- private Season activeSeason;
+ private SeasonDto activeSeason;
@JsonInclude(JsonInclude.Include.NON_NULL)
private AssetMetadataDto wallpaperAsset;
+ private GroupMemberDto createdBy;
private ZonedDateTime createdAt;
private GroupPreset sportPreset;
private String customSportName;
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/GroupMemberDto.java b/api/src/main/java/pro/beerpong/api/model/dto/GroupMemberDto.java
new file mode 100644
index 00000000..fca15a01
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/model/dto/GroupMemberDto.java
@@ -0,0 +1,11 @@
+package pro.beerpong.api.model.dto;
+
+import lombok.Data;
+
+@Data
+public class GroupMemberDto {
+ private String id;
+ private boolean active;
+ private String groupId;
+ private String userId;
+}
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java b/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java
index e050ba36..d376ab66 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/MatchDto.java
@@ -1,7 +1,6 @@
package pro.beerpong.api.model.dto;
import lombok.Data;
-import pro.beerpong.api.model.dao.Season;
import java.time.ZonedDateTime;
import java.util.List;
@@ -10,7 +9,8 @@
public class MatchDto {
private String id;
private ZonedDateTime date;
- private Season season;
+ private SeasonDto season;
+ private GroupMemberDto createdBy;
private List teams;
private List teamMembers;
private List matchMoves;
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/MatchOverviewDto.java b/api/src/main/java/pro/beerpong/api/model/dto/MatchOverviewDto.java
index f047bee3..3e471c71 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/MatchOverviewDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/MatchOverviewDto.java
@@ -10,7 +10,7 @@
public class MatchOverviewDto {
private String id;
private ZonedDateTime date;
- private Season season;
+ private SeasonDto season;
private MatchOverviewTeamDto blueTeam;
private MatchOverviewTeamDto redTeam;
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ProfileCreatedDto.java b/api/src/main/java/pro/beerpong/api/model/dto/ProfileCreatedDto.java
index 4c91b0b4..86792ef3 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/ProfileCreatedDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/ProfileCreatedDto.java
@@ -1,23 +1,29 @@
package pro.beerpong.api.model.dto;
-import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
import lombok.EqualsAndHashCode;
+import lombok.NoArgsConstructor;
import org.springframework.lang.Nullable;
@EqualsAndHashCode(callSuper = true)
@Data
+@NoArgsConstructor
public class ProfileCreatedDto extends ProfileDto {
private boolean reactivated;
@Nullable
private String lastActiveSeasonId;
- public ProfileCreatedDto(ProfileDto profile, boolean reactivated, @Nullable String lastActiveSeasonId) {
- setId(profile.getId());
- setName(profile.getName());
- setAvatarAsset(profile.getAvatarAsset());
- setGroupId(profile.getGroupId());
+ public ProfileCreatedDto(String id, String name, AssetMetadataDto asset, String groupId, GroupMemberDto createdBy, boolean reactivated, @Nullable String lastActiveSeasonId) {
+ setId(id);
+ setName(name);
+ setAvatarAsset(asset);
+ setGroupId(groupId);
+ setCreatedBy(createdBy);
this.reactivated = reactivated;
this.lastActiveSeasonId = lastActiveSeasonId;
}
+
+ public ProfileCreatedDto(ProfileDto profile, boolean reactivated, @Nullable String lastActiveSeasonId) {
+ this(profile.getId(), profile.getName(), profile.getAvatarAsset(), profile.getGroupId(), profile.getCreatedBy(), reactivated, lastActiveSeasonId);
+ }
}
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/ProfileDto.java b/api/src/main/java/pro/beerpong/api/model/dto/ProfileDto.java
index 9b276635..ee6df307 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/ProfileDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/ProfileDto.java
@@ -10,4 +10,5 @@ public class ProfileDto {
@JsonInclude(JsonInclude.Include.NON_NULL)
private AssetMetadataDto avatarAsset;
private String groupId;
+ private GroupMemberDto createdBy;
}
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/RuleCreateDto.java b/api/src/main/java/pro/beerpong/api/model/dto/RuleCreateDto.java
index 2448dea7..c9cee991 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/RuleCreateDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/RuleCreateDto.java
@@ -6,4 +6,9 @@
public class RuleCreateDto {
private String title;
private String description;
+
+ public boolean invalidDto() {
+ return this.title == null || this.title.trim().isEmpty() ||
+ this.description == null || this.description.trim().isEmpty();
+ }
}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java b/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java
index 1907d02d..9c0657bf 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/RuleDto.java
@@ -1,12 +1,12 @@
package pro.beerpong.api.model.dto;
import lombok.Data;
-import pro.beerpong.api.model.dao.Season;
@Data
public class RuleDto {
private String id;
private String title;
private String description;
- private Season season;
+ private SeasonDto season;
+ private GroupMemberDto createdBy;
}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveCreateDto.java b/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveCreateDto.java
index 524b288a..4bbc3936 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveCreateDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveCreateDto.java
@@ -10,7 +10,7 @@ public class RuleMoveCreateDto {
private boolean finishingMove;
public boolean invalidDto() {
- return this.name == null || this.name.isEmpty() ||
+ return this.name == null || this.name.trim().isEmpty() ||
this.pointsForTeam < 0 || this.pointsForScorer < 0;
}
}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveDto.java b/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveDto.java
index 5978e754..e409253a 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/RuleMoveDto.java
@@ -10,5 +10,5 @@ public class RuleMoveDto {
private int pointsForTeam;
private int pointsForScorer;
private boolean finishingMove;
- private Season season;
+ private SeasonDto season;
}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/SeasonCreateDto.java b/api/src/main/java/pro/beerpong/api/model/dto/SeasonCreateDto.java
index 94bda73f..08770cdd 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/SeasonCreateDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/SeasonCreateDto.java
@@ -13,7 +13,7 @@ public class SeasonCreateDto {
private List ruleMoves;
public boolean invalidName() {
- return this.oldSeasonName == null || this.oldSeasonName.isEmpty() ||
+ return this.oldSeasonName == null || this.oldSeasonName.trim().isEmpty() ||
this.oldSeasonName.length() < SEASON_NAME_MIN_LENGTH ||
this.oldSeasonName.length() > SEASON_NAME_MAX_LENGTH;
}
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/SeasonDto.java b/api/src/main/java/pro/beerpong/api/model/dto/SeasonDto.java
index ed50b7bc..b3fa1eca 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/SeasonDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/SeasonDto.java
@@ -13,4 +13,5 @@ public class SeasonDto {
private ZonedDateTime endDate;
private String groupId;
private SeasonSettings seasonSettings;
+ private GroupMemberDto createdBy;
}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/SeasonSettingsDto.java b/api/src/main/java/pro/beerpong/api/model/dto/SeasonSettingsDto.java
new file mode 100644
index 00000000..a156ad11
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/model/dto/SeasonSettingsDto.java
@@ -0,0 +1,15 @@
+package pro.beerpong.api.model.dto;
+
+import lombok.Data;
+import pro.beerpong.api.util.DailyLeaderboard;
+import pro.beerpong.api.util.RankingAlgorithm;
+
+@Data
+public class SeasonSettingsDto {
+ private Integer minMatchesToQualify;
+ private Integer minTeamSize;
+ private Integer maxTeamSize;
+ private RankingAlgorithm rankingAlgorithm;
+ private DailyLeaderboard dailyLeaderboard;
+ private String wakeTime = "00:00";
+}
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/SeasonUpdateDto.java b/api/src/main/java/pro/beerpong/api/model/dto/SeasonUpdateDto.java
index 3c8e4981..eab1f93a 100644
--- a/api/src/main/java/pro/beerpong/api/model/dto/SeasonUpdateDto.java
+++ b/api/src/main/java/pro/beerpong/api/model/dto/SeasonUpdateDto.java
@@ -8,5 +8,5 @@
@Data
public class SeasonUpdateDto {
- private @NotNull SeasonSettings seasonSettings;
+ private @NotNull SeasonSettingsDto seasonSettings;
}
diff --git a/api/src/main/java/pro/beerpong/api/model/dto/UserDto.java b/api/src/main/java/pro/beerpong/api/model/dto/UserDto.java
new file mode 100644
index 00000000..3d63c534
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/model/dto/UserDto.java
@@ -0,0 +1,9 @@
+package pro.beerpong.api.model.dto;
+
+import lombok.Data;
+
+@Data
+public class UserDto {
+ private String id;
+ private boolean active;
+}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/repository/DeviceRepository.java b/api/src/main/java/pro/beerpong/api/repository/DeviceRepository.java
new file mode 100644
index 00000000..8b7735f9
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/repository/DeviceRepository.java
@@ -0,0 +1,8 @@
+package pro.beerpong.api.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import pro.beerpong.api.model.dao.Device;
+import pro.beerpong.api.model.dao.User;
+
+public interface DeviceRepository extends JpaRepository {
+}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java b/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java
new file mode 100644
index 00000000..cfcef817
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/repository/GroupMemberRepository.java
@@ -0,0 +1,17 @@
+package pro.beerpong.api.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import pro.beerpong.api.model.dao.Asset;
+import pro.beerpong.api.model.dao.GroupMember;
+
+import java.util.List;
+
+public interface GroupMemberRepository extends JpaRepository {
+ List findByUserId(String userId);
+
+ boolean existsByUserIdAndGroupId(String userId, String groupId);
+
+ void deleteByUserIdAndGroupId(String userId, String groupId);
+
+ GroupMember findByUserIdAndGroupId(String userId, String groupId);
+}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/repository/UserRepository.java b/api/src/main/java/pro/beerpong/api/repository/UserRepository.java
new file mode 100644
index 00000000..4c9a4181
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/repository/UserRepository.java
@@ -0,0 +1,8 @@
+package pro.beerpong.api.repository;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import pro.beerpong.api.model.dao.Asset;
+import pro.beerpong.api.model.dao.User;
+
+public interface UserRepository extends JpaRepository {
+}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/service/AuthService.java b/api/src/main/java/pro/beerpong/api/service/AuthService.java
new file mode 100644
index 00000000..6214fb35
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/service/AuthService.java
@@ -0,0 +1,160 @@
+package pro.beerpong.api.service;
+
+import jakarta.transaction.Transactional;
+import lombok.RequiredArgsConstructor;
+import org.springframework.stereotype.Service;
+import pro.beerpong.api.auth.JwtTokenProvider;
+import pro.beerpong.api.mapping.UserMapper;
+import pro.beerpong.api.model.dao.Device;
+import pro.beerpong.api.model.dao.Group;
+import pro.beerpong.api.model.dao.GroupMember;
+import pro.beerpong.api.model.dao.User;
+import pro.beerpong.api.model.dto.AuthRefreshDto;
+import pro.beerpong.api.model.dto.AuthSignupDto;
+import pro.beerpong.api.model.dto.AuthTokenDto;
+import pro.beerpong.api.model.dto.UserDto;
+import pro.beerpong.api.repository.DeviceRepository;
+import pro.beerpong.api.repository.GroupMemberRepository;
+import pro.beerpong.api.repository.GroupRepository;
+import pro.beerpong.api.repository.UserRepository;
+import pro.beerpong.api.util.TokenType;
+
+@Service
+@RequiredArgsConstructor
+public class AuthService {
+ private final JwtTokenProvider tokenProvider;
+ private final UserRepository userRepository;
+ private final DeviceRepository deviceRepository;
+ private final UserMapper userMapper;
+ private final GroupMemberRepository groupMemberRepository;
+ private final GroupRepository groupRepository;
+
+ public AuthTokenDto registerDevice(AuthSignupDto dto) {
+ if (dto.getDeviceId() == null || dto.getDeviceId().trim().isEmpty() || dto.getInstallationType() == null) {
+ return null;
+ }
+
+ var user = new User();
+ user = userRepository.save(user);
+
+ var device = new Device();
+ device.setUser(user);
+ device.setDeviceId(dto.getDeviceId());
+ device.setType(dto.getInstallationType());
+
+ deviceRepository.save(device);
+
+ var refreshToken = tokenProvider.createRefreshToken(user.getId());
+
+ return this.buildDto(refreshToken, TokenType.REFRESH);
+ }
+
+ public AuthTokenDto refreshAuth(AuthRefreshDto dto) {
+ if (dto.getRefreshToken() == null || dto.getRefreshToken().trim().isEmpty()) {
+ return null;
+ }
+
+ var claims = tokenProvider.validateToken(dto.getRefreshToken(), "refresh");
+
+ if (claims == null) {
+ return null;
+ }
+
+ var userId = claims.getSubject();
+
+ if (!userRepository.existsById(userId)) {
+ return null;
+ }
+
+ var accessToken = tokenProvider.createAccessToken(userId);
+
+ return this.buildDto(accessToken, TokenType.ACCESS);
+ }
+
+ public UserDto userFromAccessToken(String token) {
+ if (token == null || token.trim().isEmpty()) {
+ return null;
+ }
+
+ var claims = tokenProvider.validateToken(token, "access");
+
+ if (claims == null) {
+ return null;
+ }
+
+ var userId = claims.getSubject();
+
+ return userRepository.findById(userId)
+ .map(userMapper::userToUserDto)
+ .orElse(null);
+ }
+
+ public boolean hasAccessToGroup(UserDto user, String groupId) {
+ var existing = groupMemberRepository.findByUserIdAndGroupId(user.getId(), groupId);
+
+ return existing != null && existing.isActive();
+ }
+
+ public GroupMember memberByUser(UserDto user, String groupId) {
+ return groupMemberRepository.findByUserIdAndGroupId(user.getId(), groupId);
+ }
+
+ @Transactional
+ public GroupMember buildFirstGroupMember(UserDto user) {
+ var groupMember = new GroupMember();
+ groupMember.setUser(userMapper.userDtoToUser(user));
+ groupMember.setActive(true);
+
+ return groupMember;
+ }
+
+ @Transactional
+ public GroupMember joinGroup(UserDto user, String groupId) {
+ var existing = groupMemberRepository.findByUserIdAndGroupId(user.getId(), groupId);
+
+ if (existing != null) {
+ if (existing.isActive()) {
+ return null;
+ } else {
+ existing.setActive(true);
+
+ return groupMemberRepository.save(existing);
+ }
+ }
+
+ var groupMember = new GroupMember();
+ groupMember.setUser(userMapper.userDtoToUser(user));
+ groupMember.setGroup(groupRepository.findById(groupId).orElse(null));
+ groupMember.setActive(true);
+
+ saveMember(groupMember);
+
+ return groupMember;
+ }
+
+ public GroupMember saveMember(GroupMember groupMember) {
+ return groupMemberRepository.save(groupMember);
+ }
+
+ @Transactional
+ public boolean leaveGroup(UserDto user, String groupId) {
+ var groupMember = groupMemberRepository.findByUserIdAndGroupId(user.getId(), groupId);
+
+ if (groupMember != null) {
+ groupMember.setActive(false);
+
+ saveMember(groupMember);
+ return true;
+ }
+ return false;
+ }
+
+ private AuthTokenDto buildDto(String token, TokenType type) {
+ var result = new AuthTokenDto();
+
+ result.setToken(token);
+ result.setType(type);
+
+ return result;
+ }
+}
diff --git a/api/src/main/java/pro/beerpong/api/service/GroupService.java b/api/src/main/java/pro/beerpong/api/service/GroupService.java
index 116a18ee..67b23896 100644
--- a/api/src/main/java/pro/beerpong/api/service/GroupService.java
+++ b/api/src/main/java/pro/beerpong/api/service/GroupService.java
@@ -6,9 +6,11 @@
import org.springframework.stereotype.Service;
import pro.beerpong.api.mapping.GroupMapper;
import pro.beerpong.api.model.dao.Group;
+import pro.beerpong.api.model.dao.GroupMember;
import pro.beerpong.api.model.dao.Season;
import pro.beerpong.api.model.dao.SeasonSettings;
import pro.beerpong.api.model.dto.*;
+import pro.beerpong.api.repository.GroupMemberRepository;
import pro.beerpong.api.repository.GroupRepository;
import pro.beerpong.api.repository.MatchRepository;
import pro.beerpong.api.repository.SeasonRepository;
@@ -19,6 +21,7 @@
import java.time.ZonedDateTime;
import java.util.List;
+import java.util.Objects;
import java.util.stream.Collectors;
import static pro.beerpong.api.util.RandomStringGenerator.generateRandomString;
@@ -38,8 +41,10 @@ public class GroupService {
private final PlayerService playerService;
private final RuleMoveService ruleMoveService;
private final RuleService ruleService;
+ private final GroupMemberRepository groupMemberRepository;
+ private final AuthService authService;
- public GroupDto createGroup(GroupCreateDto groupCreateDto) {
+ public GroupDto createGroup(GroupCreateDto groupCreateDto, UserDto user) {
Group group = groupMapper.groupCreateDtoToGroup(groupCreateDto);
group.setInviteCode(generateRandomString(GROUP_INVITE_CODE_LENGTH));
group.setCreatedAt(ZonedDateTime.now());
@@ -51,29 +56,50 @@ public GroupDto createGroup(GroupCreateDto groupCreateDto) {
return null;
}
+ var groupMember = authService.buildFirstGroupMember(user);
+
var season = new Season();
season.setStartDate(ZonedDateTime.now());
- season.setSeasonSettings(new SeasonSettings());
+ season.setSeasonSettings(SeasonSettings.createDefault());
group.setActiveSeason(season);
group = groupRepository.save(group);
+ groupMember.setGroup(group);
+
+ groupMember = authService.saveMember(groupMember);
+
+ // TODO maybe find way to prevent double group saving. but not that big of a deal
+ group.setCreatedBy(groupMember);
+ group = groupRepository.save(group);
+
+ season.setCreatedBy(groupMember);
season.setGroupId(group.getId());
seasonRepository.save(season);
Group finalGroup = group;
+ GroupMember finalGroupMember = groupMember;
groupCreateDto.getProfileNames().forEach(s -> {
var profileDto = new ProfileCreateDto();
profileDto.setName(s);
- profileService.createProfile(finalGroup.getId(), profileDto);
+ profileService.createProfile(finalGroup.getId(), profileDto, finalGroupMember);
});
ruleMoveService.createDefaultRuleMoves(group, season);
- ruleService.createDefaultRules(season);
+ ruleService.createDefaultRules(season, groupCreateDto.getSportPreset(), groupMember);
return withStats(groupMapper.groupToGroupDto(group));
}
+ public List findGroupsByUser(UserDto user) {
+ return groupMemberRepository.findByUserId(user.getId()).stream()
+ .map(groupMember -> withStats(groupRepository.findById(groupMember.getGroup().getId())
+ .map(groupMapper::groupToGroupDto)
+ .orElse(null)))
+ .filter(Objects::nonNull)
+ .toList();
+ }
+
public GroupDto findGroupsByInviteCode(String inviteCode) {
return withStats(groupRepository.findByInviteCode(inviteCode)
.map(groupMapper::groupToGroupDto)
@@ -97,6 +123,10 @@ public GroupDto getRawGroupById(String id) {
.orElse(null);
}
+ public Group getDaoById(String id) {
+ return groupRepository.findById(id).orElse(null);
+ }
+
public GroupDto updateGroup(String id, GroupCreateDto groupCreateDto) {
return groupRepository.findById(id)
.map(existingGroup -> {
diff --git a/api/src/main/java/pro/beerpong/api/service/LeaderboardService.java b/api/src/main/java/pro/beerpong/api/service/LeaderboardService.java
index cff7636c..5c06dcba 100644
--- a/api/src/main/java/pro/beerpong/api/service/LeaderboardService.java
+++ b/api/src/main/java/pro/beerpong/api/service/LeaderboardService.java
@@ -115,7 +115,7 @@ public LeaderboardDto generateLeaderboard(GroupDto group, String scope, boolean
} else if (season.getSeasonSettings().getDailyLeaderboard() == DailyLeaderboard.RESET_AT_MIDNIGHT) {
startedAt = ZonedDateTime.now().withHour(0).withMinute(0).withSecond(0).withNano(0);
} else {
- startedAt = matchService.getWakeTime(ZonedDateTime.now(), season.getSeasonSettings().getWakeTimeHour());
+ startedAt = matchService.getWakeTime(ZonedDateTime.now(), season.getSeasonSettings().getWakeTime());
}
}
default -> {
diff --git a/api/src/main/java/pro/beerpong/api/service/MatchMoveService.java b/api/src/main/java/pro/beerpong/api/service/MatchMoveService.java
index 90b8f77d..b0abfbec 100644
--- a/api/src/main/java/pro/beerpong/api/service/MatchMoveService.java
+++ b/api/src/main/java/pro/beerpong/api/service/MatchMoveService.java
@@ -33,6 +33,10 @@ public MatchMoveService(MatchMoveRepository matchMoveRepository,
public void createMatchMoves(TeamMember teamMember, List moves) {
for (MatchMoveDto moveDto : moves) {
+ if (moveDto.getCount() < 1) {
+ continue;
+ }
+
RuleMove ruleMove = ruleMoveRepository.findById(moveDto.getMoveId())
.orElseThrow(() -> new IllegalArgumentException("RuleMove not found: " + moveDto.getMoveId()));
if (!Objects.equals(ruleMove.getSeason().getId(), teamMember.getTeam().getMatch().getSeason().getId())) {
diff --git a/api/src/main/java/pro/beerpong/api/service/MatchService.java b/api/src/main/java/pro/beerpong/api/service/MatchService.java
index 721c51f2..5e366844 100644
--- a/api/src/main/java/pro/beerpong/api/service/MatchService.java
+++ b/api/src/main/java/pro/beerpong/api/service/MatchService.java
@@ -6,9 +6,11 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Service;
+import pro.beerpong.api.mapping.GroupMemberMapper;
import pro.beerpong.api.mapping.MatchMoveMapper;
import pro.beerpong.api.mapping.PlayerMapper;
import pro.beerpong.api.mapping.TeamMapper;
+import pro.beerpong.api.mapping.SeasonMapper;
import pro.beerpong.api.model.dao.*;
import pro.beerpong.api.model.dto.*;
import pro.beerpong.api.repository.*;
@@ -18,13 +20,14 @@
import pro.beerpong.api.util.AssetType;
import java.time.Duration;
+import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.function.Predicate;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Predicate;
import java.util.stream.IntStream;
import java.util.stream.Stream;
@@ -53,24 +56,27 @@ public class MatchService {
private final TeamService teamService;
private final RuleMoveService ruleMoveService;
private final PlayerMapper playerMapper;
+ private final AuthService authService;
+ private final GroupMemberMapper groupMemberMapper;
+ private final SeasonMapper seasonMapper;
private final AssetService assetService;
private final TeamMapper teamMapper;
@Autowired
public MatchService(SubscriptionHandler subscriptionHandler,
- MatchRepository matchRepository,
- TeamMemberService teamMemberService,
- PlayerRepository playerRepository,
- MatchMoveService matchMoveService,
- TeamRepository teamRepository,
- TeamMemberRepository teamMemberRepository,
- MatchMoveRepository matchMoveRepository,
- RuleMoveRepository ruleMoveRepository,
- MatchMoveMapper matchMoveMapper,
- TeamService teamService,
- SeasonRepository seasonRepository,
- RuleMoveService ruleMoveService,
- PlayerMapper playerMapper, AssetService assetService, TeamMapper teamMapper) {
+ MatchRepository matchRepository,
+ TeamMemberService teamMemberService,
+ PlayerRepository playerRepository,
+ MatchMoveService matchMoveService,
+ TeamRepository teamRepository,
+ TeamMemberRepository teamMemberRepository,
+ MatchMoveRepository matchMoveRepository,
+ RuleMoveRepository ruleMoveRepository,
+ MatchMoveMapper matchMoveMapper,
+ TeamService teamService,
+ SeasonRepository seasonRepository,
+ RuleMoveService ruleMoveService,
+ PlayerMapper playerMapper, AssetService assetService, TeamMapper teamMapper, AuthService authService, GroupMemberMapper groupMemberMapper, SeasonMapper seasonMapper) {
this.subscriptionHandler = subscriptionHandler;
this.matchRepository = matchRepository;
@@ -87,37 +93,49 @@ public MatchService(SubscriptionHandler subscriptionHandler,
this.teamService = teamService;
this.ruleMoveService = ruleMoveService;
this.playerMapper = playerMapper;
+ this.authService = authService;
+ this.groupMemberMapper = groupMemberMapper;
+ this.seasonMapper = seasonMapper;
this.assetService = assetService;
this.teamMapper = teamMapper;
}
public boolean invalidCreateDto(String groupId, String seasonId, MatchCreateDto dto) {
- return !dto.getTeams().stream()
- .allMatch(teamCreateDto -> teamCreateDto.getTeamMembers().stream().allMatch(memberDto -> {
- var player = playerRepository.findById(memberDto.getPlayerId());
+ var playerIds = dto.getTeams().stream()
+ .flatMap(teamCreateDto -> teamCreateDto.getTeamMembers().stream().map(TeamMemberCreateDto::getPlayerId))
+ .toList();
- if (player.isEmpty() || !player.get().getSeason().getId().equals(seasonId)
- || !player.get().getSeason().getGroupId().equals(groupId)) {
- return false;
- }
+ var finishMoves = dto.getTeams().stream()
+ .flatMap(teamCreateDto -> teamCreateDto.getTeamMembers().stream())
+ .flatMap(memberCreateDto -> memberCreateDto.getMoves().stream())
+ .filter(matchMoveDto -> ruleMoveService.isFinish(matchMoveDto.getMoveId()) && matchMoveDto.getCount() > 0)
+ .toList();
- return memberDto.getMoves().stream().allMatch(matchMoveDto -> {
- var move = ruleMoveRepository.findById(matchMoveDto.getMoveId());
-
- return move.isPresent() && move.get().getSeason().getId().equals(seasonId)
- && move.get().getSeason().getGroupId().equals(groupId);
- });
- })) ||
- dto.getTeams().stream()
- .flatMap(teamCreateDto -> teamCreateDto.getTeamMembers().stream())
- .flatMap(memberCreateDto -> memberCreateDto.getMoves().stream())
- .filter(matchMoveDto -> ruleMoveService.isFinish(matchMoveDto.getMoveId())
- && matchMoveDto.getCount() == 1)
- .count() != 1;
+ return playerIds.stream().distinct().count() != playerIds.size() ||
+ finishMoves.size() != 1 ||
+ finishMoves.getFirst().getCount() != 1 ||
+ !dto.getTeams().stream().allMatch(teamCreateDto ->
+ teamCreateDto.getTeamMembers().stream().allMatch(memberDto -> {
+ var player = playerRepository.findById(memberDto.getPlayerId());
+
+ if (player.isEmpty() || !player.get().getSeason().getId().equals(seasonId) || !player.get().getSeason().getGroupId().equals(groupId)) {
+ return false;
+ }
+ if (player.isEmpty() || !player.get().getSeason().getId().equals(seasonId)
+ || !player.get().getSeason().getGroupId().equals(groupId)) {
+ return false;
+ }
+
+ return memberDto.getMoves().stream().allMatch(matchMoveDto -> {
+ var move = ruleMoveRepository.findById(matchMoveDto.getMoveId());
+
+ return move.isPresent() && move.get().getSeason().getId().equals(seasonId) && move.get().getSeason().getGroupId().equals(groupId);
+ });
+ }));
}
@Transactional
- public MatchDto createNewMatch(@NotNull Group group, @NotNull Season season, MatchCreateDto matchCreateDto) {
+ public MatchDto createNewMatch(@NotNull Group group, @NotNull Season season, MatchCreateDto matchCreateDto, UserDto user) {
if (!group.getActiveSeason().getId().equals(season.getId()) ||
invalidCreateDto(group.getId(), season.getId(), matchCreateDto)) {
return null;
@@ -127,6 +145,7 @@ public MatchDto createNewMatch(@NotNull Group group, @NotNull Season season, Mat
match.setDate(ZonedDateTime.now());
match.setSeason(season);
+ match.setCreatedBy(authService.memberByUser(user, group.getId()));
match = matchRepository.save(match);
@@ -216,14 +235,14 @@ public long numOfMatchesInPastSeasons(GroupDto group) {
.reduce(0L, Long::sum);
}
- public Stream streamAllMatchesToday(GroupDto group, Season season) {
+ public Stream streamAllMatchesToday(GroupDto group, SeasonDto season) {
var now = ZonedDateTime.now();
Predicate predicate = switch (season.getSeasonSettings().getDailyLeaderboard()) {
case WAKE_TIME ->
- match -> match.getDate().isAfter(getWakeTime(now, season.getSeasonSettings().getWakeTimeHour()));
- case LAST_24_HOURS -> (match) -> !match.getDate().isAfter(now)
- && Duration.between(match.getDate(), now).toMinutes() < MINUTES_IN_DAY;
+ match -> match.getDate().isAfter(getWakeTime(now, season.getSeasonSettings().getWakeTime()));
+ case LAST_24_HOURS ->
+ (match) -> !match.getDate().isAfter(now) && Duration.between(match.getDate(), now).toMinutes() < MINUTES_IN_DAY;
case RESET_AT_MIDNIGHT -> (match) -> match.getDate().toLocalDate().equals(now.toLocalDate());
};
@@ -241,8 +260,8 @@ public Stream streamAllMatchesToday(GroupDto group, Season season) {
}
}
- public ZonedDateTime getWakeTime(ZonedDateTime now, int wakeTimeHour) {
- var wakeTimeToday = now.withHour(wakeTimeHour).withMinute(0).withSecond(0).withNano(0);
+ public ZonedDateTime getWakeTime(ZonedDateTime now, LocalTime wakeTime) {
+ var wakeTimeToday = now.withHour(wakeTime.getHour()).withMinute(wakeTime.getMinute()).withSecond(0).withNano(0);
if (now.isBefore(wakeTimeToday)) {
wakeTimeToday = wakeTimeToday.minusDays(1);
@@ -398,7 +417,7 @@ public ErrorCodes deleteMatch(String id, String seasonId, String groupId) {
teamRepository.deleteAllById(match.getTeams().stream().map(TeamDto::getId).toList());
matchRepository.deleteById(id);
} else {
- error.set(ErrorCodes.PLAYER_VALIDATION_FAILED);
+ error.set(ErrorCodes.MATCH_GROUP_OR_SEASON_ID_DONT_MATCH);
}
} else {
error.set(ErrorCodes.SEASON_ALREADY_ENDED);
@@ -413,7 +432,8 @@ private MatchDto matchToEmptyMatchDto(Match match) {
dto.setId(match.getId());
dto.setDate(match.getDate());
- dto.setSeason(match.getSeason());
+ dto.setSeason(seasonMapper.seasonToSeasonDto(match.getSeason()));
+ dto.setCreatedBy(groupMemberMapper.groupMemberToGroupMemberDto(match.getCreatedBy()));
return dto;
}
@@ -423,7 +443,8 @@ private MatchDto matchToMatchDto(Match match) {
dto.setId(match.getId());
dto.setDate(match.getDate());
- dto.setSeason(match.getSeason());
+ dto.setSeason(seasonMapper.seasonToSeasonDto(match.getSeason()));
+ dto.setCreatedBy(groupMemberMapper.groupMemberToGroupMemberDto(match.getCreatedBy()));
loadMatchInfo(match, dto);
diff --git a/api/src/main/java/pro/beerpong/api/service/ProfileService.java b/api/src/main/java/pro/beerpong/api/service/ProfileService.java
index c4863cd4..093bcdf0 100644
--- a/api/src/main/java/pro/beerpong/api/service/ProfileService.java
+++ b/api/src/main/java/pro/beerpong/api/service/ProfileService.java
@@ -6,9 +6,14 @@
import org.springframework.stereotype.Service;
import pro.beerpong.api.mapping.GroupMapper;
import pro.beerpong.api.mapping.ProfileMapper;
+import pro.beerpong.api.model.dao.GroupMember;
import pro.beerpong.api.model.dao.Profile;
import pro.beerpong.api.model.dao.Season;
import pro.beerpong.api.model.dto.*;
+import pro.beerpong.api.model.dto.ProfileCreateDto;
+import pro.beerpong.api.model.dto.ProfileCreatedDto;
+import pro.beerpong.api.model.dto.ProfileDto;
+import pro.beerpong.api.model.dto.UserDto;
import pro.beerpong.api.repository.GroupRepository;
import pro.beerpong.api.repository.ProfileRepository;
import pro.beerpong.api.util.AssetType;
@@ -25,8 +30,9 @@ public class ProfileService {
private final GroupMapper groupMapper;
private final ProfileMapper profileMapper;
private final PlayerService playerService;
+ private final AuthService authService;
- public ProfileCreatedDto createPlayer(String groupId, ProfileCreateDto dto) {
+ public ProfileCreatedDto createPlayer(String groupId, ProfileCreateDto dto, UserDto user) {
var existing = this.getProfileByName(groupId, dto.getName());
if (existing != null) {
@@ -63,19 +69,20 @@ public ProfileCreatedDto createPlayer(String groupId, ProfileCreateDto dto) {
return new ProfileCreatedDto(existing, false, (lastPlayer != null ? lastPlayer.getSeason().getId() : null));
}
} else {
- return new ProfileCreatedDto(this.createProfile(groupId, dto), false, null);
+ return new ProfileCreatedDto(this.createProfile(groupId, dto, authService.memberByUser(user, groupId)), false, null);
}
}
- public ProfileDto createProfile(String groupId, ProfileCreateDto profileCreateDto) {
- return this.createProfile(groupId, profileCreateDto, true);
+ public ProfileDto createProfile(String groupId, ProfileCreateDto profileCreateDto, GroupMember groupMember) {
+ return this.createProfile(groupId, profileCreateDto, true, groupMember);
}
- public ProfileDto createProfile(String groupId, ProfileCreateDto profileCreateDto, boolean createPlayer) {
+ public ProfileDto createProfile(String groupId, ProfileCreateDto profileCreateDto, boolean createPlayer, GroupMember groupMember) {
var groupOptional = groupRepository.findById(groupId);
var profile = profileMapper.profileCreateDtoToProfile(profileCreateDto);
profile.setGroup(groupOptional.orElseThrow());
+ profile.setCreatedBy(groupMember);
var savedProfile = profileRepository.save(profile);
@@ -125,10 +132,10 @@ public boolean deleteProfile(String id) {
return false;
}
- public ProfileDto updateProfile(String id, ProfileCreateDto profileCreateDto) {
+ public ProfileDto updateProfile(String id, String groupId, ProfileCreateDto profileCreateDto) {
var profile = getRawProfileById(id);
- if (profile == null) {
+ if (profile == null || !profile.getGroup().getId().equals(groupId)) {
return null;
}
diff --git a/api/src/main/java/pro/beerpong/api/service/RuleMoveService.java b/api/src/main/java/pro/beerpong/api/service/RuleMoveService.java
index a3d6da87..40f5473a 100644
--- a/api/src/main/java/pro/beerpong/api/service/RuleMoveService.java
+++ b/api/src/main/java/pro/beerpong/api/service/RuleMoveService.java
@@ -21,7 +21,7 @@
@Service
public class RuleMoveService {
- private static final List DEFAULT_BEERPONG_MOVES = List.of(
+ public static final List DEFAULT_BEERPONG_MOVES = List.of(
buildRuleMove("Normal", 1, 0, false),
buildRuleMove("Bomb", 2, 0, false),
buildRuleMove("Bouncer", 2, 0, false),
@@ -31,7 +31,7 @@ public class RuleMoveService {
buildRuleMove("Finish - Ring of fire", 1, 10, true)
);
- private static final List DEFAULT_MOVES = List.of(
+ public static final List DEFAULT_MOVES = List.of(
buildRuleMove("Normal", 1, 0, false),
buildRuleMove("Finish - Normal", 1, 3, true)
);
@@ -129,7 +129,7 @@ public void copyRuleMovesFromOldSeason(Season oldSeason, Season newSeason) {
}
public void createDefaultRuleMoves(Group group, Season season) {
- Stream ruleMoves;
+ Stream ruleMoves;
if (group.getSportPreset() != null && group.getSportPreset().equals(GroupPresetsController.BEERPONG.getId())) {
ruleMoves = DEFAULT_BEERPONG_MOVES.stream();
@@ -138,15 +138,19 @@ public void createDefaultRuleMoves(Group group, Season season) {
}
ruleMoves.map(ruleMove -> {
- var move = ruleMove.clone();
+ var move = new RuleMove();
+ move.setName(ruleMove.getName());
+ move.setFinishingMove(ruleMove.isFinishingMove());
+ move.setPointsForScorer(ruleMove.getPointsForScorer());
+ move.setPointsForTeam(ruleMove.getPointsForTeam());
move.setSeason(season);
return move;
})
.forEach(moveRepository::save);
}
- private static RuleMove buildRuleMove(String name, int pointsForScorer, int pointsForTeam, boolean finish) {
- var ruleMove = new RuleMove();
+ private static RuleMoveDto buildRuleMove(String name, int pointsForScorer, int pointsForTeam, boolean finish) {
+ var ruleMove = new RuleMoveDto();
ruleMove.setName(name);
ruleMove.setPointsForScorer(pointsForScorer);
diff --git a/api/src/main/java/pro/beerpong/api/service/RuleService.java b/api/src/main/java/pro/beerpong/api/service/RuleService.java
index 62e03841..482d01a2 100644
--- a/api/src/main/java/pro/beerpong/api/service/RuleService.java
+++ b/api/src/main/java/pro/beerpong/api/service/RuleService.java
@@ -3,11 +3,14 @@
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import pro.beerpong.api.control.GroupPresetsController;
import pro.beerpong.api.mapping.RuleMapper;
+import pro.beerpong.api.model.dao.GroupMember;
import pro.beerpong.api.model.dao.Rule;
import pro.beerpong.api.model.dao.Season;
import pro.beerpong.api.model.dto.RuleCreateDto;
import pro.beerpong.api.model.dto.RuleDto;
+import pro.beerpong.api.model.dto.UserDto;
import pro.beerpong.api.repository.RuleRepository;
import pro.beerpong.api.sockets.SocketEvent;
import pro.beerpong.api.sockets.SocketEventData;
@@ -17,7 +20,7 @@
@Service
public class RuleService {
- private static final List DEFAULT_RULES = List.of(
+ public static final List DEFAULT_RULES = List.of(
buildRule("Teams", "The two teams can have any size, and they don't have to have the same number of players."),
buildRule("Cup Setup", "Ten cups per side are to be arranged in a pyramid pointing towards the opponent. The back row must be no further from the table edge than one cup diameter. All cups are to be filled with the same amount of liquid, preferably halfway full."),
buildRule("Number of Balls", "Each side throws at least two balls. If there are three or more players per side, increase the ball count by one per extra player."),
@@ -43,22 +46,27 @@ public class RuleService {
private final RuleRepository ruleRepository;
private final RuleMapper ruleMapper;
+ private final AuthService authService;
@Autowired
- public RuleService(SubscriptionHandler subscriptionHandler, RuleRepository matchRepository, RuleMapper ruleMapper) {
+ public RuleService(SubscriptionHandler subscriptionHandler, RuleRepository matchRepository, RuleMapper ruleMapper, AuthService authService) {
this.subscriptionHandler = subscriptionHandler;
this.ruleRepository = matchRepository;
this.ruleMapper = ruleMapper;
+ this.authService = authService;
}
@Transactional
- public List writeRules(String groupId, Season season, List rules) {
+ public List writeRules(String groupId, Season season, List rules, UserDto user) {
ruleRepository.deleteBySeasonId(season.getId());
+ var createdBy = authService.memberByUser(user, groupId);
+
return rules.stream()
.map(dto -> {
var rule = ruleMapper.ruleCreateDtoToRule(dto);
rule.setSeason(season);
+ rule.setCreatedBy(createdBy);
return rule;
})
.filter(dto -> dto.getSeason().getId().equals(season.getId()) &&
@@ -73,13 +81,14 @@ public void copyRulesFromOldSeason(Season oldSeason, Season newSeason) {
}
ruleRepository.findBySeasonId(oldSeason.getId()).forEach(oldRule -> {
- var ruleMove = new Rule();
+ var rule = new Rule();
- ruleMove.setTitle(oldRule.getTitle());
- ruleMove.setDescription(oldRule.getDescription());
- ruleMove.setSeason(newSeason);
+ rule.setTitle(oldRule.getTitle());
+ rule.setDescription(oldRule.getDescription());
+ rule.setSeason(newSeason);
+ rule.setCreatedBy(oldRule.getCreatedBy());
- ruleRepository.save(ruleMove);
+ ruleRepository.save(rule);
});
}
@@ -90,18 +99,23 @@ public List getAllRules(String seasonId) {
.toList();
}
- public void createDefaultRules(Season season) {
- DEFAULT_RULES.stream()
- .map(rule -> {
- var rle = rule.clone();
- rle.setSeason(season);
- return rle;
- })
- .forEach(ruleRepository::save);
+ public void createDefaultRules(Season season, String sportPreset, GroupMember createdBy) {
+ if (sportPreset != null && sportPreset.equals(GroupPresetsController.BEERPONG.getId())) {
+ DEFAULT_RULES.stream()
+ .map(rule -> {
+ var rle = new Rule();
+ rle.setTitle(rule.getTitle());
+ rle.setDescription(rule.getDescription());
+ rle.setSeason(season);
+ rle.setCreatedBy(createdBy);
+ return rle;
+ })
+ .forEach(ruleRepository::save);
+ }
}
- private static Rule buildRule(String title, String description) {
- var rule = new Rule();
+ private static RuleDto buildRule(String title, String description) {
+ var rule = new RuleDto();
rule.setTitle(title);
rule.setDescription(description);
diff --git a/api/src/main/java/pro/beerpong/api/service/SeasonService.java b/api/src/main/java/pro/beerpong/api/service/SeasonService.java
index f8aee932..f21b1d22 100644
--- a/api/src/main/java/pro/beerpong/api/service/SeasonService.java
+++ b/api/src/main/java/pro/beerpong/api/service/SeasonService.java
@@ -1,21 +1,25 @@
package pro.beerpong.api.service;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import pro.beerpong.api.mapping.*;
-import pro.beerpong.api.model.dao.*;
+import pro.beerpong.api.model.dao.Group;
+import pro.beerpong.api.model.dao.Player;
+import pro.beerpong.api.model.dao.Season;
+import pro.beerpong.api.model.dao.SeasonSettings;
import pro.beerpong.api.model.dto.*;
import pro.beerpong.api.repository.GroupRepository;
import pro.beerpong.api.repository.PlayerRepository;
import pro.beerpong.api.repository.PlayerStatisticsRepository;
import pro.beerpong.api.repository.SeasonRepository;
+import pro.beerpong.api.sockets.LocalTimeAdapter;
import pro.beerpong.api.sockets.SocketEvent;
import pro.beerpong.api.sockets.SocketEventData;
import pro.beerpong.api.sockets.SubscriptionHandler;
import pro.beerpong.api.util.NullablePair;
+import java.time.LocalTime;
import java.time.ZonedDateTime;
import java.util.List;
import java.util.Optional;
@@ -37,6 +41,8 @@ public class SeasonService {
private final ProfileMapper profileMapper;
private final PlayerStatisticsMapper playerStatisticsMapper;
private final GroupService groupService;
+ private final SeasonSettingsMapper seasonSettingsMapper;
+ private final AuthService authService;
@Autowired
public SeasonService(SubscriptionHandler subscriptionHandler,
@@ -45,7 +51,7 @@ public SeasonService(SubscriptionHandler subscriptionHandler,
PlayerService playerService,
RuleMoveService ruleMoveService,
RuleService ruleService,
- SeasonMapper seasonMapper, LeaderboardService leaderboardService, GroupMapper groupMapper, PlayerMapper playerMapper, PlayerStatisticsRepository playerStatisticsRepository, PlayerRepository playerRepository, ProfileMapper profileMapper, PlayerStatisticsMapper playerStatisticsMapper, GroupService groupService) {
+ SeasonMapper seasonMapper, LeaderboardService leaderboardService, GroupMapper groupMapper, PlayerMapper playerMapper, PlayerStatisticsRepository playerStatisticsRepository, PlayerRepository playerRepository, ProfileMapper profileMapper, PlayerStatisticsMapper playerStatisticsMapper, GroupService groupService, SeasonSettingsMapper seasonSettingsMapper, AuthService authService) {
this.subscriptionHandler = subscriptionHandler;
this.seasonRepository = seasonRepository;
this.groupRepository = groupRepository;
@@ -61,9 +67,11 @@ public SeasonService(SubscriptionHandler subscriptionHandler,
this.profileMapper = profileMapper;
this.playerStatisticsMapper = playerStatisticsMapper;
this.groupService = groupService;
+ this.seasonSettingsMapper = seasonSettingsMapper;
+ this.authService = authService;
}
- public SeasonDto startNewSeason(SeasonCreateDto dto, String groupId) {
+ public SeasonDto startNewSeason(SeasonCreateDto dto, String groupId, UserDto user) {
var groupOptional = groupRepository.findById(groupId);
if (groupOptional.isEmpty()) {
@@ -76,7 +84,8 @@ public SeasonDto startNewSeason(SeasonCreateDto dto, String groupId) {
newSeason.setStartDate(ZonedDateTime.now());
newSeason.setGroupId(groupOptional.get().getId());
- newSeason.setSeasonSettings(new SeasonSettings());
+ newSeason.setSeasonSettings(SeasonSettings.createDefault());
+ newSeason.setCreatedBy(authService.memberByUser(user, groupId));
if (oldSeason != null && oldSeason.getSeasonSettings() != null) {
newSeason.getSeasonSettings().setMaxTeamSize(oldSeason.getSeasonSettings().getMaxTeamSize());
@@ -84,7 +93,7 @@ public SeasonDto startNewSeason(SeasonCreateDto dto, String groupId) {
newSeason.getSeasonSettings().setMinMatchesToQualify(oldSeason.getSeasonSettings().getMinMatchesToQualify());
newSeason.getSeasonSettings().setRankingAlgorithm(oldSeason.getSeasonSettings().getRankingAlgorithm());
newSeason.getSeasonSettings().setDailyLeaderboard(oldSeason.getSeasonSettings().getDailyLeaderboard());
- newSeason.getSeasonSettings().setWakeTimeHour(oldSeason.getSeasonSettings().getWakeTimeHour());
+ newSeason.getSeasonSettings().setWakeTime(oldSeason.getSeasonSettings().getWakeTime());
}
var season = seasonRepository.save(newSeason);
@@ -133,16 +142,15 @@ public SeasonDto startNewSeason(SeasonCreateDto dto, String groupId) {
return newDto;
}
- public List calcStatsForPlayersInSeason(String seasonId, boolean showInactive, boolean showStats) {
- var players = playerService.getBySeasonId(seasonId, showInactive);
- var season = seasonRepository.findById(seasonId).orElse(null);
+ public List calcStatsForPlayersInSeason(SeasonDto season, boolean showInactive, boolean showStats) {
+ var players = playerService.getBySeasonId(season.getId(), showInactive);
- if (showStats && season != null) {
+ if (showStats) {
return leaderboardService.generateLeaderboard(
- groupService.getRawGroupById(season.getGroupId()),
+ groupService.getRawGroupById(season.getGroupId()),
"season",
true,
- seasonId,
+ season.getId(),
players.stream()
)
.getEntries();
@@ -157,16 +165,21 @@ public SeasonDto updateSeason(Season season, SeasonUpdateDto dto) {
return Optional.ofNullable(season)
.map(existingSeason -> {
if (existingSeason.getSeasonSettings() == null) {
- dto.getSeasonSettings().setId(null);
- existingSeason.setSeasonSettings(dto.getSeasonSettings());
- } else {
- existingSeason.getSeasonSettings().setMaxTeamSize(dto.getSeasonSettings().getMaxTeamSize());
- existingSeason.getSeasonSettings().setMinTeamSize(dto.getSeasonSettings().getMinTeamSize());
+ existingSeason.setSeasonSettings(SeasonSettings.createDefault());
+ }
+
+ if (dto.getSeasonSettings().getMinMatchesToQualify() != null)
existingSeason.getSeasonSettings().setMinMatchesToQualify(dto.getSeasonSettings().getMinMatchesToQualify());
- existingSeason.getSeasonSettings().setRankingAlgorithm(dto.getSeasonSettings().getRankingAlgorithm());
+ if (dto.getSeasonSettings().getMinTeamSize() != null)
+ existingSeason.getSeasonSettings().setMinTeamSize(dto.getSeasonSettings().getMinTeamSize());
+ if (dto.getSeasonSettings().getMaxTeamSize() != null)
+ existingSeason.getSeasonSettings().setMaxTeamSize(dto.getSeasonSettings().getMaxTeamSize());
+ if (dto.getSeasonSettings().getWakeTime() != null)
+ existingSeason.getSeasonSettings().setWakeTime(LocalTime.parse(dto.getSeasonSettings().getWakeTime(), LocalTimeAdapter.FORMATTER));
+ if (dto.getSeasonSettings().getDailyLeaderboard() != null)
existingSeason.getSeasonSettings().setDailyLeaderboard(dto.getSeasonSettings().getDailyLeaderboard());
- existingSeason.getSeasonSettings().setWakeTimeHour(dto.getSeasonSettings().getWakeTimeHour());
- }
+ if (dto.getSeasonSettings().getRankingAlgorithm() != null)
+ existingSeason.getSeasonSettings().setRankingAlgorithm(dto.getSeasonSettings().getRankingAlgorithm());
var seasonDto = seasonMapper.seasonToSeasonDto(seasonRepository.save(existingSeason));
diff --git a/api/src/main/java/pro/beerpong/api/sockets/LocalTimeAdapter.java b/api/src/main/java/pro/beerpong/api/sockets/LocalTimeAdapter.java
index 96f6f217..a5e77ddc 100644
--- a/api/src/main/java/pro/beerpong/api/sockets/LocalTimeAdapter.java
+++ b/api/src/main/java/pro/beerpong/api/sockets/LocalTimeAdapter.java
@@ -10,25 +10,25 @@
import java.time.format.DateTimeFormatter;
public class LocalTimeAdapter extends TypeAdapter {
- public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
+ public static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("HH:mm");
- @Override
- public void write(JsonWriter out, LocalTime value) throws IOException {
- if (value == null) {
- out.nullValue();
- } else {
- out.value(value.format(FORMATTER));
+ @Override
+ public void write(JsonWriter out, LocalTime value) throws IOException {
+ if (value == null) {
+ out.nullValue();
+ } else {
+ out.value(value.format(FORMATTER));
+ }
}
- }
- @Override
- public LocalTime read(JsonReader in) throws IOException {
- if (in.peek() == JsonToken.NULL) {
- in.nextNull();
- return null;
- } else {
- String timeString = in.nextString();
- return LocalTime.parse(timeString, FORMATTER);
+ @Override
+ public LocalTime read(JsonReader in) throws IOException {
+ if (in.peek() == JsonToken.NULL) {
+ in.nextNull();
+ return null;
+ } else {
+ String timeString = in.nextString();
+ return LocalTime.parse(timeString, FORMATTER);
+ }
}
- }
}
\ No newline at end of file
diff --git a/api/src/main/java/pro/beerpong/api/sockets/ZonedDateTimeAdapter.java b/api/src/main/java/pro/beerpong/api/sockets/ZonedDateTimeAdapter.java
index 74af55dc..b6b0200f 100644
--- a/api/src/main/java/pro/beerpong/api/sockets/ZonedDateTimeAdapter.java
+++ b/api/src/main/java/pro/beerpong/api/sockets/ZonedDateTimeAdapter.java
@@ -8,6 +8,7 @@
import java.io.IOException;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
+import java.time.format.DateTimeParseException;
public class ZonedDateTimeAdapter extends TypeAdapter {
@@ -29,7 +30,11 @@ public ZonedDateTime read(JsonReader in) throws IOException {
return null;
} else {
String dateTimeString = in.nextString();
- return ZonedDateTime.parse(dateTimeString, formatter);
+ try {
+ return ZonedDateTime.parse(dateTimeString, formatter);
+ } catch (DateTimeParseException e) {
+ return null;
+ }
}
}
}
diff --git a/api/src/main/java/pro/beerpong/api/util/InstallationType.java b/api/src/main/java/pro/beerpong/api/util/InstallationType.java
new file mode 100644
index 00000000..ef37b57d
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/util/InstallationType.java
@@ -0,0 +1,6 @@
+package pro.beerpong.api.util;
+
+public enum InstallationType {
+ IOS,
+ ANDROID
+}
diff --git a/api/src/main/java/pro/beerpong/api/util/TokenType.java b/api/src/main/java/pro/beerpong/api/util/TokenType.java
new file mode 100644
index 00000000..b9e8faf8
--- /dev/null
+++ b/api/src/main/java/pro/beerpong/api/util/TokenType.java
@@ -0,0 +1,6 @@
+package pro.beerpong.api.util;
+
+public enum TokenType {
+ ACCESS,
+ REFRESH
+}
diff --git a/api/src/main/resources/application-test.properties b/api/src/main/resources/application-test.properties
new file mode 100644
index 00000000..4d7749e8
--- /dev/null
+++ b/api/src/main/resources/application-test.properties
@@ -0,0 +1 @@
+sentry.enabled=false
\ No newline at end of file
diff --git a/api/src/main/resources/application.properties b/api/src/main/resources/application.properties
new file mode 100644
index 00000000..a65adddc
--- /dev/null
+++ b/api/src/main/resources/application.properties
@@ -0,0 +1,2 @@
+jwt.secret=${JWT_SECRET}
+jwt.access.expiration-ms=3600000
\ No newline at end of file
diff --git a/api/src/test/java/pro/beerpong/api/CreateMatchTest.java b/api/src/test/java/pro/beerpong/api/CreateMatchTest.java
deleted file mode 100644
index 5cc8eac0..00000000
--- a/api/src/test/java/pro/beerpong/api/CreateMatchTest.java
+++ /dev/null
@@ -1,320 +0,0 @@
-package pro.beerpong.api;
-
-import org.junit.jupiter.api.Test;
-import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.web.server.LocalServerPort;
-import org.springframework.test.context.ActiveProfiles;
-import pro.beerpong.api.control.GroupPresetsController;
-import pro.beerpong.api.model.dto.*;
-
-import java.util.List;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
-@ActiveProfiles("test")
-public class CreateMatchTest {
- @LocalServerPort
- private int port;
-
- @Autowired
- private TestUtils testUtils;
-
-// @BeforeEach
-// void setUp() {
-// if (seasonRepository.findById(seasonId).isEmpty()) {
-// var season = new pro.beerpong.api.model.dao.Season();
-// season.setId(seasonId);
-// season.setGroupId(groupId);
-// seasonRepository.save(season);
-// }
-// }
-
-// @Test
-// @SuppressWarnings("unchecked")
-// void createMatch_withTeamsMembersAndMoves_shouldCreateMatchTeamsAndMoves() throws Exception {
-// var createGroupDto = new GroupCreateDto();
-// createGroupDto.setProfileNames(List.of("player1", "player2", "player3", "player4"));
-// createGroupDto.setName("test");
-// createGroupDto.setSportPreset("beerpong");
-//
-// var prerequisiteGroupResponse = testUtils.performPost(port, "/groups", createGroupDto, GroupDto.class);
-//
-// assertNotNull(prerequisiteGroupResponse);
-// assertEquals(200, prerequisiteGroupResponse.getStatusCode().value());
-//
-// ResponseEnvelope prerequisiteEnvelope = (ResponseEnvelope) prerequisiteGroupResponse.getBody();
-// assertNotNull(prerequisiteEnvelope);
-// assertEquals(ResponseEnvelope.Status.OK, prerequisiteEnvelope.getStatus());
-// assertNull(prerequisiteEnvelope.getError());
-// assertEquals(200, prerequisiteEnvelope.getHttpCode());
-//
-// var prerequisiteGroup = prerequisiteEnvelope.getData();
-// assertEquals(GroupPresetsController.BEERPONG.getId(), prerequisiteGroup.getSportPreset().getId());
-// var response = testUtils.performGet(port, "/groups?inviteCode=" + prerequisiteGroup.getInviteCode(), GroupDto.class);
-//
-// assertNotNull(response);
-// assertEquals(200, response.getStatusCode().value());
-//
-// ResponseEnvelope envelope = (ResponseEnvelope) response.getBody();
-// assertNotNull(envelope);
-// assertEquals(ResponseEnvelope.Status.OK, envelope.getStatus());
-// assertNull(envelope.getError());
-// assertEquals(200, envelope.getHttpCode());
-//
-// var group = envelope.getData();
-// // if this is not here, the startDate millis are rounded and this test fails
-// group.getActiveSeason().setStartDate(prerequisiteGroup.getActiveSeason().getStartDate());
-// group.setCreatedAt(prerequisiteGroup.getCreatedAt());
-//
-// assertNotNull(group);
-// assertNotNull(group.getName());
-// assertEquals(prerequisiteGroup, group);
-// assertNotNull(group.getId());
-// assertNotNull(group.getInviteCode());
-// assertNotNull(group.getActiveSeason());
-// assertNotNull(group.getActiveSeason().getId());
-// assertEquals(group.getActiveSeason().getGroupId(), group.getId());
-//
-// var season = group.getActiveSeason();
-//
-// var playersResponse = testUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/players", List.class, PlayerDto.class);
-//
-// assertNotNull(playersResponse);
-// assertEquals(200, playersResponse.getStatusCode().value());
-//
-// ResponseEnvelope> playersEnvelope = (ResponseEnvelope>) playersResponse.getBody();
-// assertNotNull(playersEnvelope);
-// assertEquals(ResponseEnvelope.Status.OK, playersEnvelope.getStatus());
-// assertNull(playersEnvelope.getError());
-// assertEquals(200, playersEnvelope.getHttpCode());
-//
-// var players = playersEnvelope.getData();
-//
-// var createRulemMoveDto = new RuleMoveCreateDto();
-// createRulemMoveDto.setName("RuleMove1");
-// createRulemMoveDto.setFinishingMove(false);
-// createRulemMoveDto.setPointsForTeam(0);
-// createRulemMoveDto.setPointsForScorer(1);
-// var createRulemMoveDto1 = new RuleMoveCreateDto();
-// createRulemMoveDto1.setName("RuleMove2");
-// createRulemMoveDto1.setFinishingMove(true);
-// createRulemMoveDto1.setPointsForTeam(1);
-// createRulemMoveDto1.setPointsForScorer(1);
-//
-// var ruleMoveResponse = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRulemMoveDto, RuleMoveDto.class);
-// var ruleMoveResponse1 = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRulemMoveDto1, RuleMoveDto.class);
-//
-// assertNotNull(ruleMoveResponse);
-// assertNotNull(ruleMoveResponse1);
-// assertEquals(200, ruleMoveResponse.getStatusCode().value());
-// assertEquals(200, ruleMoveResponse1.getStatusCode().value());
-//
-// ResponseEnvelope ruleMoveEnvelope = (ResponseEnvelope) ruleMoveResponse.getBody();
-// ResponseEnvelope ruleMoveEnvelope1 = (ResponseEnvelope) ruleMoveResponse1.getBody();
-// assertNotNull(ruleMoveEnvelope);
-// assertNotNull(ruleMoveEnvelope1);
-// assertEquals(ResponseEnvelope.Status.OK, ruleMoveEnvelope.getStatus());
-// assertEquals(ResponseEnvelope.Status.OK, ruleMoveEnvelope1.getStatus());
-// assertNull(ruleMoveEnvelope.getError());
-// assertNull(ruleMoveEnvelope1.getError());
-// assertEquals(200, ruleMoveEnvelope.getHttpCode());
-// assertEquals(200, ruleMoveEnvelope1.getHttpCode());
-//
-// var ruleMove = ruleMoveEnvelope.getData();
-// var ruleMove1 = ruleMoveEnvelope1.getData();
-//
-// // Arrange: Erstelle ein MatchCreateDto mit Teams, TeamMembers und Moves
-// MatchCreateDto matchCreateDto = new MatchCreateDto();
-//
-// // Team 1 mit zwei Spielern und ihren Moves
-// TeamCreateDto team1 = new TeamCreateDto();
-// TeamMemberCreateDto member1 = new TeamMemberCreateDto();
-// member1.setPlayerId(players.get(0).getId());
-//
-// MatchMoveDto move1 = new MatchMoveDto();
-// move1.setMoveId(ruleMove.getId());
-// move1.setCount(3);
-//
-// MatchMoveDto move2 = new MatchMoveDto();
-// move2.setMoveId(ruleMove.getId());
-// move2.setCount(5);
-//
-// MatchMoveDto move7 = new MatchMoveDto();
-// move7.setMoveId(ruleMove1.getId());
-// move7.setCount(1);
-//
-// member1.setMoves(List.of(move1, move2, move7));
-//
-// TeamMemberCreateDto member2 = new TeamMemberCreateDto();
-// member2.setPlayerId(players.get(1).getId());
-//
-// MatchMoveDto move3 = new MatchMoveDto();
-// move3.setMoveId(ruleMove.getId());
-// move3.setCount(2);
-//
-// member2.setMoves(List.of(move3));
-//
-// team1.setTeamMembers(List.of(member1, member2));
-//
-// // Team 2 mit zwei Spielern und ihren Moves
-// TeamCreateDto team2 = new TeamCreateDto();
-// TeamMemberCreateDto member3 = new TeamMemberCreateDto();
-// member3.setPlayerId(players.get(2).getId());
-//
-// MatchMoveDto move4 = new MatchMoveDto();
-// move4.setMoveId(ruleMove.getId());
-// move4.setCount(4);
-//
-// member3.setMoves(List.of(move4));
-//
-// TeamMemberCreateDto member4 = new TeamMemberCreateDto();
-// member4.setPlayerId(players.get(3).getId());
-//
-// MatchMoveDto move5 = new MatchMoveDto();
-// move5.setMoveId(ruleMove.getId());
-// move5.setCount(1);
-//
-// MatchMoveDto move6 = new MatchMoveDto();
-// move6.setMoveId(ruleMove.getId());
-// move6.setCount(6);
-//
-// member4.setMoves(List.of(move5, move6));
-//
-// team2.setTeamMembers(List.of(member3, member4));
-//
-// matchCreateDto.setTeams(List.of(team1, team2));
-//
-// var createMatchResponse = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", matchCreateDto, MatchDto.class);
-//
-// assertNotNull(createMatchResponse);
-// assertEquals(200, createMatchResponse.getStatusCode().value());
-//
-// ResponseEnvelope matchEnvelope = (ResponseEnvelope) createMatchResponse.getBody();
-// assertNotNull(matchEnvelope);
-// assertEquals(ResponseEnvelope.Status.OK, matchEnvelope.getStatus());
-// assertNull(matchEnvelope.getError());
-// assertEquals(200, matchEnvelope.getHttpCode());
-// }
-//
-// @Test
-// @SuppressWarnings("unchecked")
-// void updateMatch_withTeamsMembersAndMoves_shouldUpdateMatchTeamsAndMoves() throws Exception {
-// // Step 1: Create a prerequisite group with players
-// var createGroupDto = new GroupCreateDto();
-// createGroupDto.setProfileNames(List.of("player1", "player2", "player3", "player4"));
-// createGroupDto.setName("test-update");
-// createGroupDto.setSportPreset("beerpong");
-//
-// var groupResponse = testUtils.performPost(port, "/groups", createGroupDto, GroupDto.class);
-// ResponseEnvelope groupEnvelope = (ResponseEnvelope) groupResponse.getBody();
-// var group = groupEnvelope.getData();
-//
-// var season = group.getActiveSeason();
-//
-// var playersResponse = testUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/players", List.class, PlayerDto.class);
-// ResponseEnvelope> playersEnvelope = (ResponseEnvelope>) playersResponse.getBody();
-// var players = playersEnvelope.getData();
-//
-// // Step 2: Create a rule move
-// var createRuleMoveDto = new RuleMoveDto();
-// createRuleMoveDto.setName("UpdateRuleMove");
-// var createRuleMoveDto1 = new RuleMoveDto();
-// createRuleMoveDto1.setName("UpdateRuleMove");
-// createRuleMoveDto1.setFinishingMove(true);
-//
-// var ruleMoveResponse = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRuleMoveDto, RuleMoveDto.class);
-// var ruleMoveResponse1 = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/rule-moves", createRuleMoveDto1, RuleMoveDto.class);
-// ResponseEnvelope ruleMoveEnvelope = (ResponseEnvelope) ruleMoveResponse.getBody();
-// ResponseEnvelope ruleMoveEnvelope1 = (ResponseEnvelope) ruleMoveResponse1.getBody();
-// var ruleMove = ruleMoveEnvelope.getData();
-// var ruleMove1 = ruleMoveEnvelope1.getData();
-//
-// // Step 3: Create a match with teams and members
-// MatchCreateDto matchCreateDto = new MatchCreateDto();
-//
-// // Team 1
-// TeamCreateDto team1 = new TeamCreateDto();
-// TeamMemberCreateDto member1 = new TeamMemberCreateDto();
-// member1.setPlayerId(players.get(0).getId());
-// MatchMoveDto move1 = new MatchMoveDto();
-// move1.setMoveId(ruleMove1.getId());
-// move1.setCount(1);
-// member1.setMoves(List.of(move1));
-// team1.setTeamMembers(List.of(member1));
-//
-// // Team 2
-// TeamCreateDto team2 = new TeamCreateDto();
-// TeamMemberCreateDto member2 = new TeamMemberCreateDto();
-// member2.setPlayerId(players.get(1).getId());
-// MatchMoveDto move2 = new MatchMoveDto();
-// move2.setMoveId(ruleMove.getId());
-// move2.setCount(5);
-// member2.setMoves(List.of(move2));
-// team2.setTeamMembers(List.of(member2));
-//
-// matchCreateDto.setTeams(List.of(team1, team2));
-//
-// // Step 4: Create the match
-// var createMatchResponse = testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", matchCreateDto, MatchDto.class);
-// ResponseEnvelope matchEnvelope = (ResponseEnvelope) createMatchResponse.getBody();
-// assert matchEnvelope != null;
-// var match = matchEnvelope.getData();
-//
-// team1.getTeamMembers().get(0).getMoves().get(0).setCount(1);
-// matchCreateDto.setTeams(List.of(team1, team2));
-//
-// // Step 5: Update the match
-// var updateResponse = testUtils.performPut(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches/" + match.getId(), matchCreateDto, MatchDto.class);
-// ResponseEnvelope updateEnvelope = (ResponseEnvelope) updateResponse.getBody();
-//
-// // Assertions
-// assertNotNull(updateResponse);
-// assertEquals(200, updateResponse.getStatusCode().value());
-// assertNotNull(updateEnvelope);
-// assertEquals(ResponseEnvelope.Status.OK, updateEnvelope.getStatus());
-// assertNull(updateEnvelope.getError());
-// assertEquals(200, updateEnvelope.getHttpCode());
-// }
-
-// @Test
-// @SuppressWarnings("unchecked")
-// void getAllMatches_shouldReturnAllMatchesForSeason() throws Exception {
-// // Step 1: Create a prerequisite group with players
-// var createGroupDto = new GroupCreateDto();
-// createGroupDto.setProfileNames(List.of("player1", "player2"));
-// createGroupDto.setName("test-get");
- //createGroupDto.setSportPreset("beerpong");
-//
-// var groupResponse = testUtils.performPost(port, "/groups", createGroupDto, GroupDto.class);
-// ResponseEnvelope groupEnvelope = (ResponseEnvelope) groupResponse.getBody();
-// var group = groupEnvelope.getData();
-//
-// var season = group.getActiveSeason();
-//
-// // Step 2: Create a match
-// MatchCreateDto matchCreateDto = new MatchCreateDto();
-// TeamCreateDto team = new TeamCreateDto();
-// TeamMemberCreateDto member = new TeamMemberCreateDto();
-// member.setPlayerId("player1-id");
-// team.setTeamMembers(List.of(member));
-// matchCreateDto.setTeams(List.of(team));
-//
-// testUtils.performPost(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/match", matchCreateDto, MatchDto.class);
-//
-// // Step 3: Get all matches for the season
-// var getAllMatchesResponse = testUtils.performGet(port, "/groups/" + group.getId() + "/seasons/" + season.getId() + "/matches", List.class, MatchDto.class);
-// ResponseEnvelope> matchesEnvelope = (ResponseEnvelope>) getAllMatchesResponse.getBody();
-//
-// // Assertions
-// assertNotNull(getAllMatchesResponse);
-// assertEquals(200, getAllMatchesResponse.getStatusCode().value());
-// assertNotNull(matchesEnvelope);
-// assertEquals(ResponseEnvelope.Status.OK, matchesEnvelope.getStatus());
-// assertNull(matchesEnvelope.getError());
-// assertEquals(200, matchesEnvelope.getHttpCode());
-// assertNotNull(matchesEnvelope.getData());
-// assertEquals(1, matchesEnvelope.getData().size());
-// }
-}
\ No newline at end of file
diff --git a/api/src/test/java/pro/beerpong/api/RequestUtils.java b/api/src/test/java/pro/beerpong/api/RequestUtils.java
new file mode 100644
index 00000000..1d2302ef
--- /dev/null
+++ b/api/src/test/java/pro/beerpong/api/RequestUtils.java
@@ -0,0 +1,263 @@
+package pro.beerpong.api;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.api.client.util.Lists;
+import lombok.extern.log4j.Log4j2;
+import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.http.*;
+import org.springframework.stereotype.Component;
+import pro.beerpong.api.auth.JwtTokenProvider;
+import pro.beerpong.api.model.dto.*;
+import pro.beerpong.api.util.InstallationType;
+import pro.beerpong.api.util.TokenType;
+
+import java.util.List;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+@Log4j2
+@Component
+public class RequestUtils {
+ private static final List GROUP_ACCESS = Lists.newArrayList();
+ private static String REFRESH_TOKEN;
+ private static String AUTH_TOKEN;
+ private static boolean RESET_FOR_NEXT_REQUEST = false;
+
+ private final TestRestTemplate restTemplate;
+ private final JwtTokenProvider jwtTokenProvider;
+
+ public RequestUtils(TestRestTemplate restTemplate, JwtTokenProvider jwtTokenProvider) {
+ this.restTemplate = restTemplate;
+ this.jwtTokenProvider = jwtTokenProvider;
+ }
+
+ public ResponseEntity