mirror of
https://codeberg.org/beerbrawl/beerbrawl.git
synced 2024-09-23 05:40:51 +02:00
feat(#14): add button to copy tournament team self-registration link
This commit is contained in:
parent
413fee2936
commit
0e1f70aaf1
|
@ -62,10 +62,6 @@
|
|||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-actuator</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-test</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
|
|
|
@ -2,15 +2,18 @@ package at.ac.tuwien.sepr.groupphase.backend.endpoint;
|
|||
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.CreateTeamDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.CreateTournamentDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.SignupTeamResponseDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TeamDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TournamentCreateTeamDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TournamentDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TournamentListDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TournamentQualificationMatchDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TournamentSignupTeamResponseDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.mapper.TeamMapper;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.exception.BadTournamentSignupTokenException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.http.HttpStatus;
|
||||
|
@ -35,6 +38,7 @@ import org.springframework.web.bind.annotation.PathVariable;
|
|||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestParam;
|
||||
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
|
@ -133,8 +137,6 @@ public class TournamentEndpoint {
|
|||
}
|
||||
|
||||
// region Team
|
||||
|
||||
|
||||
@Secured("ROLE_USER")
|
||||
@ResponseStatus(HttpStatus.OK)
|
||||
@GetMapping(value = "{id}/teams")
|
||||
|
@ -168,20 +170,36 @@ public class TournamentEndpoint {
|
|||
return ResponseEntity.noContent().build();
|
||||
}
|
||||
|
||||
|
||||
@PermitAll
|
||||
@ResponseStatus(HttpStatus.OK) // No location header, thus 200
|
||||
@PostMapping("{tournamentId}/teams")
|
||||
@Operation(summary = "Create a new team")
|
||||
public ResponseEntity<SignupTeamResponseDto> signupTeamForTournament(
|
||||
public ResponseEntity<TournamentSignupTeamResponseDto> signupTeamForTournament(
|
||||
@PathVariable("tournamentId") long tournamentId,
|
||||
@Valid @RequestBody CreateTeamDto messageDto) {
|
||||
LOG.info("POST {} body: {}", BASE_ENDPOINT, messageDto);
|
||||
final var signupResult = tournamentService.signupTeamForTournament(tournamentId, messageDto.name());
|
||||
if (signupResult != SignupTeamResult.SUCCESS) {
|
||||
return ResponseEntity.badRequest().body(new SignupTeamResponseDto(signupResult));
|
||||
@RequestParam("token") Optional<UUID> selfRegistrationToken,
|
||||
@Valid @RequestBody TournamentCreateTeamDto createTeamDto
|
||||
) {
|
||||
LOG.info("POST {}/{}/teams?token={}", BASE_ENDPOINT, tournamentId, selfRegistrationToken);
|
||||
LOG.debug("request body: {}", createTeamDto);
|
||||
|
||||
// Explicitly use an `Optional<>` and check it here, so we can return
|
||||
// the appropriate error
|
||||
// Otherwise, Spring Boot would just return a 400.
|
||||
if (selfRegistrationToken.isEmpty()) {
|
||||
throw new BadTournamentSignupTokenException();
|
||||
}
|
||||
return ResponseEntity.ok(new SignupTeamResponseDto(SignupTeamResult.SUCCESS));
|
||||
|
||||
final var signupResult = tournamentService.signupTeamForTournament(
|
||||
tournamentId, selfRegistrationToken.get(), createTeamDto.name()
|
||||
);
|
||||
|
||||
if (signupResult != SignupTeamResult.SUCCESS) {
|
||||
return ResponseEntity.badRequest()
|
||||
.body(new TournamentSignupTeamResponseDto(signupResult));
|
||||
}
|
||||
|
||||
return ResponseEntity
|
||||
.ok(new TournamentSignupTeamResponseDto(SignupTeamResult.SUCCESS));
|
||||
}
|
||||
// endregion team
|
||||
}
|
|
@ -3,5 +3,9 @@ package at.ac.tuwien.sepr.groupphase.backend.endpoint.dto;
|
|||
import jakarta.validation.constraints.NotBlank;
|
||||
import jakarta.validation.constraints.Size;
|
||||
|
||||
public record CreateTeamDto(@NotBlank @Size(min = 3, max = 20) String name) {
|
||||
}
|
||||
public record TournamentCreateTeamDto(
|
||||
@NotBlank
|
||||
@Size(min = 3, max = 20)
|
||||
String name
|
||||
) {
|
||||
}
|
|
@ -3,11 +3,16 @@ package at.ac.tuwien.sepr.groupphase.backend.endpoint.dto;
|
|||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import jakarta.validation.constraints.NotNull;
|
||||
import jakarta.validation.constraints.Size;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public class TournamentListDto {
|
||||
|
||||
@NotNull(message = "ID must not be null")
|
||||
private Long id;
|
||||
|
||||
|
@ -25,51 +30,12 @@ public class TournamentListDto {
|
|||
@Size(max = 1024)
|
||||
private String description;
|
||||
|
||||
// Getters
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public LocalDateTime getRegistrationEnd() {
|
||||
return registrationEnd;
|
||||
}
|
||||
|
||||
public Long getMaxParticipants() {
|
||||
return maxParticipants;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
// Setters
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public void setRegistrationEnd(LocalDateTime registrationEnd) {
|
||||
this.registrationEnd = registrationEnd;
|
||||
}
|
||||
|
||||
public void setMaxParticipants(Long maxParticipants) {
|
||||
this.maxParticipants = maxParticipants;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
@NotNull
|
||||
private UUID selfRegistrationToken;
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, name, registrationEnd, maxParticipants, description);
|
||||
return Objects.hash(id, name, registrationEnd, maxParticipants, description, selfRegistrationToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -85,7 +51,8 @@ public class TournamentListDto {
|
|||
&& Objects.equals(name, that.name)
|
||||
&& Objects.equals(registrationEnd, that.registrationEnd)
|
||||
&& Objects.equals(maxParticipants, that.maxParticipants)
|
||||
&& Objects.equals(description, that.description);
|
||||
&& Objects.equals(description, that.description)
|
||||
&& Objects.equals(selfRegistrationToken, that.selfRegistrationToken);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -102,6 +69,9 @@ public class TournamentListDto {
|
|||
+ ", description='"
|
||||
+ description
|
||||
+ '\''
|
||||
+ ", selfRegistrationToken='"
|
||||
+ selfRegistrationToken
|
||||
+ '\''
|
||||
+ '}';
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
package at.ac.tuwien.sepr.groupphase.backend.endpoint.dto;
|
||||
|
||||
|
||||
import at.ac.tuwien.sepr.groupphase.backend.entity.Tournament;
|
||||
|
||||
/**
|
||||
* Response for team signup. Compared to enum, has openapi-generator support.
|
||||
*/
|
||||
public record SignupTeamResponseDto(Tournament.SignupTeamResult signupTeamResult) {
|
||||
public record TournamentSignupTeamResponseDto(
|
||||
Tournament.SignupTeamResult signupTeamResult
|
||||
) {
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package at.ac.tuwien.sepr.groupphase.backend.endpoint.exceptionhandler;
|
||||
|
||||
import at.ac.tuwien.sepr.groupphase.backend.exception.BadTournamentSignupTokenException;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.exception.NotFoundException;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.exception.PreconditionFailedException;
|
||||
|
||||
|
@ -103,4 +104,16 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
|
|||
HttpStatus.CONFLICT,
|
||||
request);
|
||||
}
|
||||
|
||||
@ExceptionHandler({BadTournamentSignupTokenException.class})
|
||||
protected ResponseEntity<Object> handleBadTournamentSignupTokenException(RuntimeException ex, WebRequest request) {
|
||||
LOGGER.debug(ex.getMessage());
|
||||
|
||||
return handleExceptionInternal(
|
||||
ex,
|
||||
"self registration token missing or incorrect",
|
||||
new HttpHeaders(),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
request);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import org.mapstruct.Mapper;
|
|||
import org.mapstruct.Mapping;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TournamentQualificationMatchDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TournamentQualificationMatchParticipantDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.entity.QualificationMatch;
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package at.ac.tuwien.sepr.groupphase.backend.entity;
|
||||
|
||||
import jakarta.persistence.CascadeType;
|
||||
import jakarta.persistence.Column;
|
||||
import jakarta.persistence.Entity;
|
||||
import jakarta.persistence.GeneratedValue;
|
||||
import jakarta.persistence.Id;
|
||||
|
@ -14,6 +15,7 @@ import java.time.LocalDateTime;
|
|||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.hibernate.annotations.OnDelete;
|
||||
import org.hibernate.annotations.OnDeleteAction;
|
||||
|
@ -21,24 +23,38 @@ import org.hibernate.annotations.OnDeleteAction;
|
|||
/**
|
||||
* The tournament entity.
|
||||
* Owns the relation with teams for consistency reasons.
|
||||
*
|
||||
*
|
||||
*/
|
||||
@Entity
|
||||
@Getter(value = AccessLevel.PUBLIC)
|
||||
@Setter(value = AccessLevel.PRIVATE)
|
||||
public class Tournament {
|
||||
@Getter
|
||||
@Id
|
||||
@GeneratedValue
|
||||
private Long id;
|
||||
@Setter
|
||||
@Getter
|
||||
private String name;
|
||||
@Setter
|
||||
@Getter
|
||||
private LocalDateTime registrationEnd;
|
||||
@Getter
|
||||
private Long maxParticipants;
|
||||
@Setter
|
||||
@Getter
|
||||
private String description;
|
||||
@Getter
|
||||
@Column(nullable = false, updatable = false)
|
||||
private UUID selfRegistrationToken;
|
||||
@Setter
|
||||
@Getter
|
||||
@ManyToOne
|
||||
private ApplicationUser organizer;
|
||||
@OneToMany(mappedBy = Team_.TOURNAMENT, cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||
private List<Team> teams = new LinkedList<>();
|
||||
@Getter
|
||||
@OneToMany(mappedBy = BeerPongTable_.TOURNAMENT, cascade = CascadeType.ALL, orphanRemoval = true)
|
||||
@OnDelete(action = OnDeleteAction.CASCADE)
|
||||
private List<BeerPongTable> tables = new LinkedList<>();
|
||||
|
@ -59,31 +75,18 @@ public class Tournament {
|
|||
this.maxParticipants = maxParticipants;
|
||||
this.description = description;
|
||||
this.organizer = organizer;
|
||||
this.selfRegistrationToken = UUID.randomUUID();
|
||||
}
|
||||
|
||||
/**
|
||||
* This object should be able to enforce its validity. It is not able to do that
|
||||
* if it isn't even initialized
|
||||
* if it isn't even initialized.
|
||||
* Unfortunately required due to Spring Boot doing some things automagically in
|
||||
* the background and needs this ...
|
||||
*/
|
||||
@Deprecated
|
||||
public Tournament() {
|
||||
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public Long getMaxParticipants() {
|
||||
return maxParticipants;
|
||||
this.selfRegistrationToken = UUID.randomUUID();
|
||||
}
|
||||
|
||||
// requires careful validation if updated retrospectively, please just use
|
||||
|
@ -93,30 +96,6 @@ public class Tournament {
|
|||
this.maxParticipants = maxParticipants;
|
||||
}
|
||||
|
||||
public String getDescription() {
|
||||
return description;
|
||||
}
|
||||
|
||||
public void setDescription(String description) {
|
||||
this.description = description;
|
||||
}
|
||||
|
||||
public ApplicationUser getOrganizer() {
|
||||
return organizer;
|
||||
}
|
||||
|
||||
public void setOrganizer(ApplicationUser organizer) {
|
||||
this.organizer = organizer;
|
||||
}
|
||||
|
||||
public LocalDateTime getRegistrationEnd() {
|
||||
return registrationEnd;
|
||||
}
|
||||
|
||||
public void setRegistrationEnd(LocalDateTime registrationEnd) {
|
||||
this.registrationEnd = registrationEnd;
|
||||
}
|
||||
|
||||
public List<Team> getTeams() {
|
||||
return Collections.unmodifiableList(teams);
|
||||
}
|
||||
|
@ -156,8 +135,4 @@ public class Tournament {
|
|||
teams.add(team);
|
||||
return SignupTeamResult.SUCCESS;
|
||||
}
|
||||
|
||||
public List<BeerPongTable> getTables() {
|
||||
return tables;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
package at.ac.tuwien.sepr.groupphase.backend.exception;
|
||||
|
||||
public class BadTournamentSignupTokenException extends RuntimeException {
|
||||
}
|
|
@ -2,6 +2,7 @@ package at.ac.tuwien.sepr.groupphase.backend.service;
|
|||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import at.ac.tuwien.sepr.groupphase.backend.entity.Team;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
@ -66,7 +67,9 @@ public interface TournamentService {
|
|||
*
|
||||
* @return created tournament entity
|
||||
*/
|
||||
SignupTeamResult signupTeamForTournament(long tournamentId, String name);
|
||||
SignupTeamResult signupTeamForTournament(
|
||||
long tournamentId, UUID selfRegistrationToken, String name
|
||||
);
|
||||
|
||||
/**
|
||||
* Find a single tournament entity by id.
|
||||
|
|
|
@ -5,10 +5,13 @@ import java.util.ArrayList;
|
|||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import at.ac.tuwien.sepr.groupphase.backend.entity.Team;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.exception.TournamentAlreadyStartedException;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.exception.BadTournamentSignupTokenException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
|
@ -143,12 +146,18 @@ public class TournamentServiceImpl implements TournamentService {
|
|||
|
||||
@Override
|
||||
@Transactional
|
||||
public SignupTeamResult signupTeamForTournament(long tournamentId, String name) {
|
||||
public SignupTeamResult signupTeamForTournament(
|
||||
long tournamentId, UUID selfRegistrationToken, String name
|
||||
) {
|
||||
LOGGER.debug("Create new team {} for tournament {}", name, tournamentId);
|
||||
|
||||
final var tournament = tournamentRepository.findById(tournamentId)
|
||||
.orElseThrow(() -> new IllegalArgumentException("Tournament not found"));
|
||||
|
||||
if (!Objects.equals(tournament.getSelfRegistrationToken(), selfRegistrationToken)) {
|
||||
throw new BadTournamentSignupTokenException();
|
||||
}
|
||||
|
||||
final var newTeam = new Team(name, tournament);
|
||||
var result = tournament.signupTeam(newTeam);
|
||||
if (result != SignupTeamResult.SUCCESS) {
|
||||
|
@ -289,7 +298,7 @@ public class TournamentServiceImpl implements TournamentService {
|
|||
if (!tournament.getId().equals(tournamentId)) {
|
||||
throw new NotFoundException("Team not found in tournament");
|
||||
}
|
||||
var test = tournament.getQualificationMatches();
|
||||
|
||||
if ((long) tournament.getQualificationMatches().size() > 0) {
|
||||
throw new TournamentAlreadyStartedException();
|
||||
}
|
||||
|
|
|
@ -106,10 +106,10 @@ public class BeerPongTableEndpointTest extends TestUserData implements TestData
|
|||
var user = new ApplicationUser("TestUser", "Password", false);
|
||||
userRepository.save(user);
|
||||
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TEST_TOURNAMENT");
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
tournament.setMaxParticipants(64l);
|
||||
var tournament = new Tournament(
|
||||
"TEST_TOURNAMENT", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", user
|
||||
);
|
||||
tournament = tournamentService.create(tournament, user.getUsername());
|
||||
|
||||
var tableDto = new CreateBeerPongTableDto();
|
||||
|
@ -142,10 +142,10 @@ public class BeerPongTableEndpointTest extends TestUserData implements TestData
|
|||
var user = new ApplicationUser("TestUser", "Password", false);
|
||||
userRepository.save(user);
|
||||
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TEST_TOURNAMENT");
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
tournament.setMaxParticipants(64l);
|
||||
var tournament = new Tournament(
|
||||
"TEST_TOURNAMENT", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", user
|
||||
);
|
||||
tournament = tournamentService.create(tournament, user.getUsername());
|
||||
|
||||
var tableDto = new CreateBeerPongTableDto();
|
||||
|
@ -185,11 +185,11 @@ public class BeerPongTableEndpointTest extends TestUserData implements TestData
|
|||
@Test
|
||||
public void updateBeerPongTableForExistingTournamentThatWasCreatedByTheCurrentUser() throws Exception {
|
||||
// setup
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TEST_TOURNAMENT");
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
tournament.setMaxParticipants(64l);
|
||||
tournament = tournamentService.create(tournament, TestDataGenerator.TEST_USER);
|
||||
var tournament = new Tournament(
|
||||
"TEST_TOURNAMENT", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", userRepository.findByUsername(TEST_USER)
|
||||
);
|
||||
tournament = tournamentService.create(tournament, TEST_USER);
|
||||
|
||||
var beerPongTable = new BeerPongTable("TEST");
|
||||
beerPongTable.setTournament(tournament);
|
||||
|
@ -221,11 +221,11 @@ public class BeerPongTableEndpointTest extends TestUserData implements TestData
|
|||
@Test
|
||||
public void updateBeerPongTableForExistingTournamentThatWasntCreatedByTheCurrentUser() throws Exception {
|
||||
// setup
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TEST_TOURNAMENT");
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
tournament.setMaxParticipants(64l);
|
||||
tournament = tournamentService.create(tournament, TestDataGenerator.TEST_USER);
|
||||
var tournament = new Tournament(
|
||||
"TEST_TOURNAMENT", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", userRepository.findByUsername(TEST_USER)
|
||||
);
|
||||
tournament = tournamentService.create(tournament, TEST_USER);
|
||||
|
||||
var beerPongTable = new BeerPongTable("TEST");
|
||||
beerPongTable.setTournament(tournament);
|
||||
|
|
|
@ -5,6 +5,7 @@ import at.ac.tuwien.sepr.groupphase.backend.basetest.TestUserData;
|
|||
import at.ac.tuwien.sepr.groupphase.backend.config.properties.SecurityProperties;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.datagenerator.TestDataGenerator;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.CreateTournamentDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TournamentCreateTeamDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TournamentDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.endpoint.dto.TournamentListDto;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.entity.ApplicationUser;
|
||||
|
@ -18,6 +19,7 @@ import at.ac.tuwien.sepr.groupphase.backend.repository.UserRepository;
|
|||
import at.ac.tuwien.sepr.groupphase.backend.security.JwtTokenizer;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
|
||||
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
|
||||
|
@ -126,12 +128,10 @@ public class TournamentEndpointTest extends TestUserData implements TestData {
|
|||
@Test
|
||||
public void successfullyGetTournaments() throws Exception {
|
||||
// setup
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TOURNAMENT_WITHOUT_TEAMS");
|
||||
tournament.setMaxParticipants(64l);
|
||||
tournament.setOrganizer(userRepository.findByUsername(TestDataGenerator.TEST_USER));
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
|
||||
var tournament = new Tournament(
|
||||
"TEST_TOURNAMENT", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", userRepository.findByUsername(TEST_USER)
|
||||
);
|
||||
tournamentRepository.saveAndFlush(tournament);
|
||||
|
||||
var mvcResult = this.mockMvc.perform(get(TOURNAMENT_BASE_URI)
|
||||
|
@ -186,12 +186,10 @@ public class TournamentEndpointTest extends TestUserData implements TestData {
|
|||
@Test
|
||||
public void generateQualificationMatchesForTournamentWithEnoughTeams() throws Exception {
|
||||
// setup
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TOURNAMENT_WITH_TEAMS");
|
||||
tournament.setMaxParticipants(64l);
|
||||
tournament.setOrganizer(userRepository.findByUsername(TestDataGenerator.TEST_USER));
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
|
||||
var tournament = new Tournament(
|
||||
"TOURNAMENT_WITHOUT_TEAMS", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", userRepository.findByUsername(TEST_USER)
|
||||
);
|
||||
|
||||
var numberOfTeams = 16;
|
||||
for (int i = 0; i < numberOfTeams; i++) {
|
||||
|
@ -234,12 +232,10 @@ public class TournamentEndpointTest extends TestUserData implements TestData {
|
|||
@Test
|
||||
public void generateQualificationMatchesForTournamentWithoutEnoughTeams() throws Exception {
|
||||
// setup
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TOURNAMENT_WITHOUT_TEAMS");
|
||||
tournament.setMaxParticipants(64l);
|
||||
tournament.setOrganizer(userRepository.findByUsername(TestDataGenerator.TEST_USER));
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
|
||||
var tournament = new Tournament(
|
||||
"TOURNAMENT_WITHOUT_TEAMS", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", userRepository.findByUsername(TEST_USER)
|
||||
);
|
||||
tournamentRepository.saveAndFlush(tournament);
|
||||
|
||||
var mvcResult = this.mockMvc
|
||||
|
@ -260,12 +256,10 @@ public class TournamentEndpointTest extends TestUserData implements TestData {
|
|||
@Test
|
||||
public void generateQualificationMatchesForTournamentFromAnotherOrganizerWhenItIsntAllowed() throws Exception {
|
||||
// setup
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TOURNAMENT_WITHOUT_TEAMS");
|
||||
tournament.setMaxParticipants(64l);
|
||||
tournament.setOrganizer(userRepository.findByUsername(TestDataGenerator.TEST_USER));
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
|
||||
var tournament = new Tournament(
|
||||
"TOURNAMENT_WITHOUT_TEAMS", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", userRepository.findByUsername(TEST_USER)
|
||||
);
|
||||
tournamentRepository.saveAndFlush(tournament);
|
||||
|
||||
var mvcResult = this.mockMvc
|
||||
|
@ -514,4 +508,77 @@ public class TournamentEndpointTest extends TestUserData implements TestData {
|
|||
.andReturn();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotSelfSignupTeamForTournamentWithMissingToken() throws Exception {
|
||||
final var tournament = new Tournament(
|
||||
"FOOBAR", LocalDateTime.now().plusDays(5), 64L, "test tournament",
|
||||
userRepository.findByUsername(TestDataGenerator.TEST_USER));
|
||||
|
||||
tournamentRepository.saveAndFlush(tournament);
|
||||
|
||||
var mvcResult = this.mockMvc.perform(get(TOURNAMENT_BASE_URI)
|
||||
.header(securityProperties.getAuthHeader(), jwtTokenizer.getAuthToken(TEST_USER, TEST_USER_ROLES))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andReturn();
|
||||
MockHttpServletResponse response = mvcResult.getResponse();
|
||||
|
||||
final var tournaments = objectMapper.readValue(response.getContentAsString(), TournamentListDto[].class);
|
||||
|
||||
assertThat(tournaments)
|
||||
.isNotNull()
|
||||
.hasSize(1)
|
||||
.extracting("name")
|
||||
.containsExactly(tournament.getName());
|
||||
|
||||
final var listDto = tournaments[0];
|
||||
final var createDto = new TournamentCreateTeamDto("baz");
|
||||
|
||||
mvcResult = this.mockMvc.perform(post(TOURNAMENT_BASE_URI + "/{tournamentId}/teams", listDto.getId())
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(createDto)))
|
||||
.andDo(print())
|
||||
.andReturn();
|
||||
|
||||
response = mvcResult.getResponse();
|
||||
assertEquals(response.getStatus(), HttpStatus.UNAUTHORIZED.value());
|
||||
assertEquals(response.getContentAsString(), "self registration token missing or incorrect");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cannotSelfSignupTeamForTournamentWithInvalidToken() throws Exception {
|
||||
final var tournament = new Tournament(
|
||||
"FOOBAR", LocalDateTime.now().plusDays(5), 64L, "test tournament",
|
||||
userRepository.findByUsername(TestDataGenerator.TEST_USER));
|
||||
|
||||
tournamentRepository.saveAndFlush(tournament);
|
||||
|
||||
var mvcResult = this.mockMvc.perform(get(TOURNAMENT_BASE_URI)
|
||||
.header(securityProperties.getAuthHeader(), jwtTokenizer.getAuthToken(TEST_USER, TEST_USER_ROLES))
|
||||
.contentType(MediaType.APPLICATION_JSON))
|
||||
.andReturn();
|
||||
MockHttpServletResponse response = mvcResult.getResponse();
|
||||
|
||||
final var tournaments = objectMapper.readValue(response.getContentAsString(), TournamentListDto[].class);
|
||||
|
||||
assertThat(tournaments)
|
||||
.isNotNull()
|
||||
.hasSize(1)
|
||||
.extracting("name")
|
||||
.containsExactly(tournament.getName());
|
||||
|
||||
final var listDto = tournaments[0];
|
||||
final var createDto = new TournamentCreateTeamDto("baz");
|
||||
|
||||
mvcResult = this.mockMvc.perform(post(TOURNAMENT_BASE_URI + "/{tournamentId}/teams", listDto.getId())
|
||||
.param("token", "11111111-2222-3333-4444-555555555555")
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(objectMapper.writeValueAsString(createDto)))
|
||||
.andDo(print())
|
||||
.andReturn();
|
||||
|
||||
response = mvcResult.getResponse();
|
||||
assertEquals(response.getStatus(), HttpStatus.UNAUTHORIZED.value());
|
||||
assertEquals(response.getContentAsString(), "self registration token missing or incorrect");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
|||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
import at.ac.tuwien.sepr.groupphase.backend.basetest.TestData;
|
||||
import at.ac.tuwien.sepr.groupphase.backend.repository.UserRepository;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
|
@ -29,10 +31,12 @@ import at.ac.tuwien.sepr.groupphase.backend.service.TournamentService;
|
|||
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
|
||||
@ActiveProfiles("test")
|
||||
@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)
|
||||
public class BeerPongTableServiceTest extends TestUserData {
|
||||
public class BeerPongTableServiceTest extends TestUserData implements TestData {
|
||||
@Autowired
|
||||
private TournamentService tournamentService;
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
@Autowired
|
||||
private BeerPongTableService beerPongTableService;
|
||||
@Autowired
|
||||
private BeerPongTableRepository beerPongTableRepository;
|
||||
|
@ -40,11 +44,11 @@ public class BeerPongTableServiceTest extends TestUserData {
|
|||
@Test
|
||||
public void getSingleBeerPongTableById() {
|
||||
// setup
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TEST_TOURNAMENT");
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
tournament.setMaxParticipants(64l);
|
||||
tournament = tournamentService.create(tournament, TestDataGenerator.TEST_USER);
|
||||
var tournament = new Tournament(
|
||||
"TEST_TOURNAMENT", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", userRepository.findByUsername(TEST_USER)
|
||||
);
|
||||
tournament = tournamentService.create(tournament, TEST_USER);
|
||||
|
||||
var beerPongTable = new BeerPongTable("TEST");
|
||||
beerPongTable.setTournament(tournament);
|
||||
|
@ -68,11 +72,11 @@ public class BeerPongTableServiceTest extends TestUserData {
|
|||
@Test
|
||||
public void createNewBeerPongTableForExistingTournamentThatWasCreatedByTheCurrentUser() {
|
||||
// setup
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TEST_TOURNAMENT");
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
tournament.setMaxParticipants(64l);
|
||||
tournament = tournamentService.create(tournament, TestDataGenerator.TEST_USER);
|
||||
var tournament = new Tournament(
|
||||
"TEST_TOURNAMENT", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", userRepository.findByUsername(TEST_USER)
|
||||
);
|
||||
tournament = tournamentService.create(tournament, TEST_USER);
|
||||
|
||||
var tableDto = new CreateBeerPongTableDto();
|
||||
tableDto.setName("TEST_NAME");
|
||||
|
@ -89,11 +93,11 @@ public class BeerPongTableServiceTest extends TestUserData {
|
|||
@Test
|
||||
public void createNewBeerPongTableForExistingTournamentThatWasntCreatedByTheCurrentUser() {
|
||||
// setup
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TEST_TOURNAMENT");
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
tournament.setMaxParticipants(64l);
|
||||
tournament = tournamentService.create(tournament, TestDataGenerator.TEST_USER);
|
||||
var tournament = new Tournament(
|
||||
"TEST_TOURNAMENT", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", userRepository.findByUsername(TEST_USER)
|
||||
);
|
||||
tournament = tournamentService.create(tournament, TEST_USER);
|
||||
|
||||
var tableDto = new CreateBeerPongTableDto();
|
||||
tableDto.setName("TEST_NAME");
|
||||
|
@ -115,11 +119,11 @@ public class BeerPongTableServiceTest extends TestUserData {
|
|||
@Test
|
||||
public void updateBeerPongTableForExistingTournamentThatWasCreatedByTheCurrentUser() {
|
||||
// setup
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TEST_TOURNAMENT");
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
tournament.setMaxParticipants(64l);
|
||||
tournament = tournamentService.create(tournament, TestDataGenerator.TEST_USER);
|
||||
var tournament = new Tournament(
|
||||
"TEST_TOURNAMENT", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", userRepository.findByUsername(TEST_USER)
|
||||
);
|
||||
tournament = tournamentService.create(tournament, TEST_USER);
|
||||
|
||||
var beerPongTable = new BeerPongTable("TEST");
|
||||
beerPongTable.setTournament(tournament);
|
||||
|
@ -139,11 +143,11 @@ public class BeerPongTableServiceTest extends TestUserData {
|
|||
@Test
|
||||
public void updateBeerPongTableForExistingTournamentThatWasntCreatedByTheCurrentUser() {
|
||||
// setup
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TEST_TOURNAMENT");
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
tournament.setMaxParticipants(64l);
|
||||
tournament = tournamentService.create(tournament, TestDataGenerator.TEST_USER);
|
||||
var tournament = new Tournament(
|
||||
"TEST_TOURNAMENT", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", userRepository.findByUsername(TEST_USER)
|
||||
);
|
||||
tournament = tournamentService.create(tournament, TEST_USER);
|
||||
|
||||
var beerPongTable = new BeerPongTable("TEST");
|
||||
beerPongTable.setTournament(tournament);
|
||||
|
|
|
@ -53,10 +53,10 @@ public class TournamentServiceTest extends TestUserData implements TestData {
|
|||
|
||||
@Test
|
||||
public void createNewTournamentWithTestUserAsOrganizer() {
|
||||
var tournament = new Tournament();
|
||||
tournament.setName("TEST_TOURNAMENT");
|
||||
tournament.setRegistrationEnd(LocalDateTime.now().plusDays(1));
|
||||
tournament.setMaxParticipants(64L);
|
||||
var tournament = new Tournament(
|
||||
"TEST_TOURNAMENT", LocalDateTime.now().plusDays(1), 64L,
|
||||
"testdescription", userRepository.findByUsername(TEST_USER)
|
||||
);
|
||||
tournament = tournamentService.create(tournament, TestDataGenerator.TEST_USER);
|
||||
|
||||
assertNotNull(tournament);
|
||||
|
@ -152,15 +152,18 @@ public class TournamentServiceTest extends TestUserData implements TestData {
|
|||
}
|
||||
|
||||
Tournament generateTournamentWithFinishedQualiPhase() {
|
||||
|
||||
var tournament = new Tournament("testname", LocalDateTime.now().plusMinutes(1), 64L, "testdescription",
|
||||
userRepository.findByUsername(TEST_USER));
|
||||
tournamentService
|
||||
.create(tournament, TEST_USER);
|
||||
var tournament = new Tournament(
|
||||
"testname", LocalDateTime.now().plusMinutes(1), 64L, "testdescription",
|
||||
userRepository.findByUsername(TEST_USER));
|
||||
tournamentService.create(tournament, TEST_USER);
|
||||
|
||||
IntStream.rangeClosed(1, 32).forEach(i -> {
|
||||
assertEquals(SignupTeamResult.SUCCESS,
|
||||
tournamentService.signupTeamForTournament(tournament.getId(), "team" + i));
|
||||
assertEquals(
|
||||
SignupTeamResult.SUCCESS,
|
||||
tournamentService.signupTeamForTournament(
|
||||
tournament.getId(), tournament.getSelfRegistrationToken(), "team" + i
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
tournamentService.generateQualificationMatchesForTournament(tournament.getId(), TEST_USER);
|
||||
|
|
|
@ -13,6 +13,20 @@ context('Get Tournaments', () => {
|
|||
cy.get('[data-cy="tournaments-list"]').should('exist');
|
||||
cy.get('[data-cy="tournaments-list-item"]').should('have.length.at.least', 1);
|
||||
});
|
||||
|
||||
it('successfully copied team self-registration link', () => {
|
||||
cy.fixture('settings').then(settings => {
|
||||
cy.intercept('GET', '/api/v1/tournaments', { fixture: 'tournaments.json' }).as(
|
||||
'getTournaments',
|
||||
);
|
||||
cy.visit('/#/tournaments');
|
||||
cy.wait('@getTournaments');
|
||||
cy.get('[data-cy="open-copy-team-self-reg-link-dialog"]').first().click();
|
||||
|
||||
cy.get('[data-cy="copy-button"]').first().click();
|
||||
cy.assertClipboardContents(`http://${settings.baseUrl}/#/tournaments/1/signup`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
context('Delete Tournament', () => {
|
||||
|
|
|
@ -82,3 +82,11 @@ Cypress.Commands.add('fillTournamentCreateFormWithInvalidData', () => {
|
|||
cy.contains('At least 16 participants are required.');
|
||||
});
|
||||
});
|
||||
|
||||
Cypress.Commands.add('assertClipboardContents', value => {
|
||||
cy.window().then(window => {
|
||||
window.navigator.clipboard.readText().then(text => {
|
||||
expect(text).to.eq(value);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -14,18 +14,18 @@ git_push.sh
|
|||
index.ts
|
||||
model/beerPongTableDto.ts
|
||||
model/createBeerPongTableDto.ts
|
||||
model/createTeamDto.ts
|
||||
model/createTournamentDto.ts
|
||||
model/detailedMessageDto.ts
|
||||
model/messageInquiryDto.ts
|
||||
model/models.ts
|
||||
model/signupTeamResponseDto.ts
|
||||
model/simpleMessageDto.ts
|
||||
model/teamDto.ts
|
||||
model/tournamentCreateTeamDto.ts
|
||||
model/tournamentDto.ts
|
||||
model/tournamentListDto.ts
|
||||
model/tournamentQualificationMatchDto.ts
|
||||
model/tournamentQualificationMatchParticipantDto.ts
|
||||
model/tournamentSignupTeamResponseDto.ts
|
||||
model/updateBeerPongTableDto.ts
|
||||
model/userDetailDto.ts
|
||||
model/userLoginDto.ts
|
||||
|
|
|
@ -18,20 +18,20 @@ import { HttpClient, HttpHeaders, HttpParams,
|
|||
import { CustomHttpParameterCodec } from '../encoder';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
// @ts-ignore
|
||||
import { CreateTeamDto } from '../model/createTeamDto';
|
||||
// @ts-ignore
|
||||
import { CreateTournamentDto } from '../model/createTournamentDto';
|
||||
// @ts-ignore
|
||||
import { SignupTeamResponseDto } from '../model/signupTeamResponseDto';
|
||||
// @ts-ignore
|
||||
import { TeamDto } from '../model/teamDto';
|
||||
// @ts-ignore
|
||||
import { TournamentCreateTeamDto } from '../model/tournamentCreateTeamDto';
|
||||
// @ts-ignore
|
||||
import { TournamentDto } from '../model/tournamentDto';
|
||||
// @ts-ignore
|
||||
import { TournamentListDto } from '../model/tournamentListDto';
|
||||
// @ts-ignore
|
||||
import { TournamentQualificationMatchDto } from '../model/tournamentQualificationMatchDto';
|
||||
// @ts-ignore
|
||||
import { TournamentSignupTeamResponseDto } from '../model/tournamentSignupTeamResponseDto';
|
||||
|
||||
// @ts-ignore
|
||||
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
|
||||
|
@ -565,19 +565,29 @@ export class TournamentEndpointService {
|
|||
/**
|
||||
* Create a new team
|
||||
* @param tournamentId
|
||||
* @param createTeamDto
|
||||
* @param token
|
||||
* @param tournamentCreateTeamDto
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public signupTeamForTournament(tournamentId: number, createTeamDto: CreateTeamDto, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<SignupTeamResponseDto>;
|
||||
public signupTeamForTournament(tournamentId: number, createTeamDto: CreateTeamDto, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<SignupTeamResponseDto>>;
|
||||
public signupTeamForTournament(tournamentId: number, createTeamDto: CreateTeamDto, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<SignupTeamResponseDto>>;
|
||||
public signupTeamForTournament(tournamentId: number, createTeamDto: CreateTeamDto, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<any> {
|
||||
public signupTeamForTournament(tournamentId: number, token: string, tournamentCreateTeamDto: TournamentCreateTeamDto, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<TournamentSignupTeamResponseDto>;
|
||||
public signupTeamForTournament(tournamentId: number, token: string, tournamentCreateTeamDto: TournamentCreateTeamDto, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpResponse<TournamentSignupTeamResponseDto>>;
|
||||
public signupTeamForTournament(tournamentId: number, token: string, tournamentCreateTeamDto: TournamentCreateTeamDto, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<HttpEvent<TournamentSignupTeamResponseDto>>;
|
||||
public signupTeamForTournament(tournamentId: number, token: string, tournamentCreateTeamDto: TournamentCreateTeamDto, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json', context?: HttpContext, transferCache?: boolean}): Observable<any> {
|
||||
if (tournamentId === null || tournamentId === undefined) {
|
||||
throw new Error('Required parameter tournamentId was null or undefined when calling signupTeamForTournament.');
|
||||
}
|
||||
if (createTeamDto === null || createTeamDto === undefined) {
|
||||
throw new Error('Required parameter createTeamDto was null or undefined when calling signupTeamForTournament.');
|
||||
if (token === null || token === undefined) {
|
||||
throw new Error('Required parameter token was null or undefined when calling signupTeamForTournament.');
|
||||
}
|
||||
if (tournamentCreateTeamDto === null || tournamentCreateTeamDto === undefined) {
|
||||
throw new Error('Required parameter tournamentCreateTeamDto was null or undefined when calling signupTeamForTournament.');
|
||||
}
|
||||
|
||||
let localVarQueryParameters = new HttpParams({encoder: this.encoder});
|
||||
if (token !== undefined && token !== null) {
|
||||
localVarQueryParameters = this.addToHttpParams(localVarQueryParameters,
|
||||
<any>token, 'token');
|
||||
}
|
||||
|
||||
let localVarHeaders = this.defaultHeaders;
|
||||
|
@ -626,10 +636,11 @@ export class TournamentEndpointService {
|
|||
}
|
||||
|
||||
let localVarPath = `/api/v1/tournaments/${this.configuration.encodeParam({name: "tournamentId", value: tournamentId, in: "path", style: "simple", explode: false, dataType: "number", dataFormat: "int64"})}/teams`;
|
||||
return this.httpClient.request<SignupTeamResponseDto>('post', `${this.configuration.basePath}${localVarPath}`,
|
||||
return this.httpClient.request<TournamentSignupTeamResponseDto>('post', `${this.configuration.basePath}${localVarPath}`,
|
||||
{
|
||||
context: localVarHttpContext,
|
||||
body: createTeamDto,
|
||||
body: tournamentCreateTeamDto,
|
||||
params: localVarQueryParameters,
|
||||
responseType: <any>responseType_,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: localVarHeaders,
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
export * from './beerPongTableDto';
|
||||
export * from './createBeerPongTableDto';
|
||||
export * from './createTeamDto';
|
||||
export * from './createTournamentDto';
|
||||
export * from './detailedMessageDto';
|
||||
export * from './messageInquiryDto';
|
||||
export * from './signupTeamResponseDto';
|
||||
export * from './simpleMessageDto';
|
||||
export * from './teamDto';
|
||||
export * from './tournamentCreateTeamDto';
|
||||
export * from './tournamentDto';
|
||||
export * from './tournamentListDto';
|
||||
export * from './tournamentQualificationMatchDto';
|
||||
export * from './tournamentQualificationMatchParticipantDto';
|
||||
export * from './tournamentSignupTeamResponseDto';
|
||||
export * from './updateBeerPongTableDto';
|
||||
export * from './userDetailDto';
|
||||
export * from './userLoginDto';
|
||||
|
|
17
frontend/openapi-generated/model/tournamentCreateTeamDto.ts
Normal file
17
frontend/openapi-generated/model/tournamentCreateTeamDto.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/**
|
||||
* OpenAPI definition
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: v0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
export interface TournamentCreateTeamDto {
|
||||
name: string;
|
||||
}
|
||||
|
|
@ -17,5 +17,6 @@ export interface TournamentListDto {
|
|||
registrationEnd: string;
|
||||
maxParticipants: number;
|
||||
description?: string;
|
||||
selfRegistrationToken: string;
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
/**
|
||||
* OpenAPI definition
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: v0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
|
||||
|
||||
export interface TournamentSignupTeamResponseDto {
|
||||
signupTeamResult?: TournamentSignupTeamResponseDto.SignupTeamResultEnum;
|
||||
}
|
||||
export namespace TournamentSignupTeamResponseDto {
|
||||
export type SignupTeamResultEnum = 'SUCCESS' | 'REGISTRATION_CLOSED' | 'MAX_PARTICIPANTS_REACHED' | 'TEAM_ALREADY_EXISTS';
|
||||
export const SignupTeamResultEnum = {
|
||||
Success: 'SUCCESS' as SignupTeamResultEnum,
|
||||
RegistrationClosed: 'REGISTRATION_CLOSED' as SignupTeamResultEnum,
|
||||
MaxParticipantsReached: 'MAX_PARTICIPANTS_REACHED' as SignupTeamResultEnum,
|
||||
TeamAlreadyExists: 'TEAM_ALREADY_EXISTS' as SignupTeamResultEnum
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -26,6 +26,11 @@ const routes: Routes = [
|
|||
{ path: 'details/:username', component: UserDetailComponent },
|
||||
],
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: MainLayoutComponent,
|
||||
children: [{ path: 'tournaments/:tournamentId/signup', component: TeamSignupComponent }],
|
||||
},
|
||||
{
|
||||
path: '',
|
||||
component: MainLayoutComponent,
|
||||
|
@ -40,10 +45,7 @@ const routes: Routes = [
|
|||
{ path: 'create', component: TournamentCreateComponent },
|
||||
{
|
||||
path: ':tournamentId',
|
||||
children: [
|
||||
{ path: 'signup', component: TeamSignupComponent },
|
||||
{ path: 'teams', component: TournamentTeamsComponent },
|
||||
],
|
||||
children: [{ path: 'teams', component: TournamentTeamsComponent }],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -27,6 +27,7 @@ import { MainLayoutComponent } from './layouts/main-layout/main-layout.component
|
|||
import { MatListItem, MatNavList } from '@angular/material/list';
|
||||
import { MatDivider } from '@angular/material/divider';
|
||||
import { SidebarComponent } from './components/sidebar/sidebar.component';
|
||||
import { ClipboardModule } from '@angular/cdk/clipboard';
|
||||
import localeDe from '@angular/common/locales/de';
|
||||
import { registerLocaleData } from '@angular/common';
|
||||
registerLocaleData(localeDe);
|
||||
|
@ -36,6 +37,7 @@ import { CommonModule } from '@angular/common';
|
|||
import { TeamSignupComponent } from './components/tournament/team-signup/team-signup.component';
|
||||
import { TournamentsComponent } from './components/tournament/tournaments/tournaments.component';
|
||||
import { TournamentCardComponent } from './components/tournament/tournament-card/tournament-card.component';
|
||||
import { CopyLinkDialogComponent } from './components/copy-link-dialog/copy-link-dialog.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -56,6 +58,7 @@ import { TournamentCardComponent } from './components/tournament/tournament-card
|
|||
UpdateUserComponent,
|
||||
TeamSignupComponent,
|
||||
ConfirmDialogComponent,
|
||||
CopyLinkDialogComponent,
|
||||
],
|
||||
imports: [
|
||||
AppRoutingModule,
|
||||
|
@ -79,6 +82,7 @@ import { TournamentCardComponent } from './components/tournament/tournament-card
|
|||
MatButtonModule,
|
||||
MatIconModule,
|
||||
CommonModule,
|
||||
ClipboardModule,
|
||||
],
|
||||
providers: [
|
||||
httpInterceptorProviders,
|
||||
|
|
|
@ -0,0 +1,16 @@
|
|||
<h2 matDialogTitle>Copy {{ title }}</h2>
|
||||
<mat-dialog-content>
|
||||
<mat-form-field>
|
||||
<input matInput readonly="true" [value]="link" />
|
||||
</mat-form-field>
|
||||
<button
|
||||
data-cy="copy-button"
|
||||
mat-icon-button
|
||||
aria-label="Copy link"
|
||||
matTooltip="Copy link"
|
||||
[cdkCopyToClipboard]="link"
|
||||
(click)="showNotification()"
|
||||
>
|
||||
<mat-icon>insert_link</mat-icon>
|
||||
</button>
|
||||
</mat-dialog-content>
|
|
@ -0,0 +1,11 @@
|
|||
mat-dialog-content {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
mat-form-field {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-left: 16px;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { CopyLinkDialogComponent } from './copy-link-dialog.component';
|
||||
|
||||
describe('CopyLinkDialogComponent', () => {
|
||||
let component: CopyLinkDialogComponent;
|
||||
let fixture: ComponentFixture<CopyLinkDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [CopyLinkDialogComponent],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(CopyLinkDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
import { Component, Inject } from '@angular/core';
|
||||
import { MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
export interface CopyLinkDialogData {
|
||||
title: string;
|
||||
link: string;
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'app-copy-link-dialog',
|
||||
templateUrl: './copy-link-dialog.component.html',
|
||||
styleUrl: './copy-link-dialog.component.scss',
|
||||
})
|
||||
export class CopyLinkDialogComponent {
|
||||
title: string;
|
||||
link: string;
|
||||
|
||||
constructor(
|
||||
@Inject(MAT_DIALOG_DATA) data: CopyLinkDialogData,
|
||||
private snackBar: MatSnackBar,
|
||||
) {
|
||||
this.title = data.title;
|
||||
this.link = data.link;
|
||||
}
|
||||
|
||||
showNotification(): void {
|
||||
this.snackBar.open('Copied link to clipboard!', 'OK', {
|
||||
duration: 2500,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<mat-nav-list>
|
||||
<mat-list-item>
|
||||
<h1>Beer Brawl</h1>
|
||||
<a [routerLink]="'/'"><h1>Beer Brawl</h1></a>
|
||||
</mat-list-item>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-list-item>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { ChangeDetectionStrategy, Component } from '@angular/core';
|
||||
import { TournamentEndpointService } from '../../../../../openapi-generated/api/tournamentEndpoint.service';
|
||||
import { Component } from '@angular/core';
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { FormControl, FormGroup, Validators } from '@angular/forms';
|
||||
import { SignupTeamResponseDto, TournamentDto } from 'openapi-generated';
|
||||
import { catchError, tap, firstValueFrom, of } from 'rxjs';
|
||||
import { extend } from 'lodash';
|
||||
import { TournamentDto, TournamentSignupTeamResponseDto } from 'openapi-generated';
|
||||
import { catchError, of } from 'rxjs';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { TournamentEndpointService } from '@api';
|
||||
import { HttpErrorResponse } from '@angular/common/http';
|
||||
|
||||
@Component({
|
||||
selector: 'app-team-signup',
|
||||
|
@ -13,6 +14,8 @@ import { extend } from 'lodash';
|
|||
})
|
||||
export class TeamSignupComponent {
|
||||
tournament: TournamentDto | undefined;
|
||||
token: string | null | undefined;
|
||||
|
||||
success = false;
|
||||
nameFormControl = new FormControl('', [
|
||||
Validators.minLength(3),
|
||||
|
@ -33,41 +36,48 @@ export class TeamSignupComponent {
|
|||
constructor(
|
||||
private tournamentService: TournamentEndpointService,
|
||||
private route: ActivatedRoute,
|
||||
private snackBar: MatSnackBar,
|
||||
) {
|
||||
const tournamentId = Number(this.route.snapshot.paramMap.get('tournamentId'));
|
||||
const token = this.route.snapshot.queryParamMap.get('token');
|
||||
this.tournamentService.get(tournamentId).subscribe(tournament => {
|
||||
this.tournament = tournament;
|
||||
this.token = token;
|
||||
});
|
||||
}
|
||||
|
||||
async createTeam() {
|
||||
const request = this.tournamentService.signupTeamForTournament(
|
||||
this.tournament?.id!,
|
||||
this.token!,
|
||||
{ name: this.newTeamNameForm.controls.name.value! },
|
||||
'response',
|
||||
);
|
||||
request
|
||||
.pipe(
|
||||
catchError(e => {
|
||||
if (e.status !== 400) {
|
||||
catchError((e: HttpErrorResponse) => {
|
||||
if (!Object.values(TournamentSignupTeamResponseDto).includes(e.error)) {
|
||||
const errorMessage =
|
||||
typeof e.error === 'string' ? e.error : 'Failed to register team; invalid request';
|
||||
this.snackBar.open(errorMessage, 'Dismiss', {
|
||||
duration: 5000,
|
||||
});
|
||||
return [];
|
||||
}
|
||||
const response = e.error as SignupTeamResponseDto;
|
||||
|
||||
const response = e.error as TournamentSignupTeamResponseDto;
|
||||
switch (response.signupTeamResult) {
|
||||
case SignupTeamResponseDto.SignupTeamResultEnum.TeamAlreadyExists:
|
||||
case TournamentSignupTeamResponseDto.SignupTeamResultEnum.TeamAlreadyExists:
|
||||
this.nameFormControl.setErrors({ teamAlreadyExists: true });
|
||||
return of();
|
||||
case SignupTeamResponseDto.SignupTeamResultEnum.MaxParticipantsReached:
|
||||
case TournamentSignupTeamResponseDto.SignupTeamResultEnum.MaxParticipantsReached:
|
||||
this.nameFormControl.setErrors({ maxParticipantsReached: true });
|
||||
return of();
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
this.nameFormControl.setErrors({ nameAlreadyTaken: true });
|
||||
return of();
|
||||
}),
|
||||
)
|
||||
.subscribe(response => (this.success = true));
|
||||
.subscribe(() => (this.success = true));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,19 +6,26 @@
|
|||
<button mat-icon-button>
|
||||
<mat-icon>edit</mat-icon>
|
||||
</button>
|
||||
<button
|
||||
data-cy="open-copy-team-self-reg-link-dialog"
|
||||
mat-icon-button
|
||||
aria-label="Copy team self registration link"
|
||||
matTooltip="Copy team self registration link"
|
||||
(click)="openCopyTeamRegistrationLink()"
|
||||
>
|
||||
<mat-icon>insert_link</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<p class="card-date">
|
||||
registration ends at {{ tournament?.registrationEnd | date: 'short' }}
|
||||
</p>
|
||||
<div class="card-icons">
|
||||
<button mat-icon-button>
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button data-cy="delete-tournament-btn" (click)="openConfirmDialog()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<button mat-icon-button>
|
||||
<mat-icon>play_arrow</mat-icon>
|
||||
</button>
|
||||
<button mat-icon-button data-cy="delete-tournament-btn" (click)="openConfirmDialog()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</mat-card>
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
.card-title {
|
||||
font-size: 1.25rem; /* Larger font size for the title */
|
||||
font-weight: 500; /* Medium font weight for better readability */
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.card-footer {
|
||||
|
@ -44,14 +45,10 @@
|
|||
.card-date {
|
||||
font-size: 0.8rem; /* Default font size for the date */
|
||||
color: #757575; /* Subtle color for the date */
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.card-icons {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.card-icons button {
|
||||
button {
|
||||
color: #757575; /* Default color for icons */
|
||||
}
|
||||
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import { Component, Input, Output, EventEmitter } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { TournamentEndpointService, TournamentListDto } from '@api';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { ConfirmationService } from '../../../services/confirmation.service';
|
||||
import { DatePipe } from '@angular/common';
|
||||
import { Location, LocationStrategy, PathLocationStrategy } from '@angular/common';
|
||||
import { CopyLinkService } from 'src/app/services/copy-link.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tournament-card',
|
||||
templateUrl: './tournament-card.component.html',
|
||||
styleUrls: ['./tournament-card.component.scss'],
|
||||
providers: [Location, { provide: LocationStrategy, useClass: PathLocationStrategy }],
|
||||
})
|
||||
export class TournamentCardComponent {
|
||||
@Input() tournament: TournamentListDto | undefined;
|
||||
|
@ -18,6 +19,8 @@ export class TournamentCardComponent {
|
|||
private tournamentService: TournamentEndpointService,
|
||||
private snackBar: MatSnackBar,
|
||||
private confirmationService: ConfirmationService,
|
||||
private copyLinkService: CopyLinkService,
|
||||
private location: Location,
|
||||
) {}
|
||||
|
||||
async openConfirmDialog(): Promise<void> {
|
||||
|
@ -44,4 +47,12 @@ export class TournamentCardComponent {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
openCopyTeamRegistrationLink(): void {
|
||||
const link = this.location.prepareExternalUrl(
|
||||
`#/tournaments/${this.tournament?.id}/signup?token=${this.tournament?.selfRegistrationToken}`,
|
||||
);
|
||||
|
||||
this.copyLinkService.openDialog({ title: 'Team self-registration link', link });
|
||||
}
|
||||
}
|
||||
|
|
25
frontend/src/app/services/copy-link.service.ts
Normal file
25
frontend/src/app/services/copy-link.service.ts
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { inject, Injectable } from '@angular/core';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import {
|
||||
CopyLinkDialogComponent,
|
||||
CopyLinkDialogData,
|
||||
} from '../components/copy-link-dialog/copy-link-dialog.component';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class CopyLinkService {
|
||||
matDialog = inject(MatDialog);
|
||||
|
||||
/*
|
||||
* Opens a dialog and prompts the user to copy a given link.
|
||||
* @param data dialog data to use
|
||||
*/
|
||||
openDialog(data: CopyLinkDialogData): void {
|
||||
this.matDialog.open(CopyLinkDialogComponent, {
|
||||
width: '750px',
|
||||
maxWidth: '900px',
|
||||
data,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ConfirmationService } from './confirmation.service';
|
||||
|
||||
describe('DeleteConfirmationService', () => {
|
||||
let service: ConfirmationService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(ConfirmationService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue