diff --git a/backend/pom.xml b/backend/pom.xml
index 2d8463657b..ace13685c7 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -71,8 +71,8 @@
com.tngtech.archunit
- archunit
- 1.0.1
+ archunit-junit5
+ 1.2.0
test
diff --git a/backend/src/main/java/ch/puzzle/okr/controller/TeamController.java b/backend/src/main/java/ch/puzzle/okr/controller/TeamController.java
index 11dacc3707..a465d59e24 100644
--- a/backend/src/main/java/ch/puzzle/okr/controller/TeamController.java
+++ b/backend/src/main/java/ch/puzzle/okr/controller/TeamController.java
@@ -35,7 +35,7 @@ public TeamController(TeamAuthorizationService teamAuthorizationService, TeamMap
@Content(mediaType = "application/json", schema = @Schema(implementation = TeamDto.class)) }), })
@GetMapping
public List getAllTeams(@RequestParam(value = "quarterId", required = false) Long quarterId) {
- return teamAuthorizationService.getEntities().stream().map(team -> teamMapper.toDto(team, quarterId)).toList();
+ return teamAuthorizationService.getAllTeams().stream().map(team -> teamMapper.toDto(team, quarterId)).toList();
}
@Operation(summary = "Create Team", description = "Create a new Team")
diff --git a/backend/src/main/java/ch/puzzle/okr/controller/UserController.java b/backend/src/main/java/ch/puzzle/okr/controller/UserController.java
index 67083fed9d..2a45432e3c 100644
--- a/backend/src/main/java/ch/puzzle/okr/controller/UserController.java
+++ b/backend/src/main/java/ch/puzzle/okr/controller/UserController.java
@@ -2,7 +2,7 @@
import ch.puzzle.okr.dto.UserDto;
import ch.puzzle.okr.mapper.UserMapper;
-import ch.puzzle.okr.service.business.UserBusinessService;
+import ch.puzzle.okr.service.authorization.UserAuthorizationService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
@@ -18,11 +18,11 @@
@RequestMapping("api/v1/users")
public class UserController {
- private final UserBusinessService userBusinessService;
+ private final UserAuthorizationService userAuthorizationService;
private final UserMapper userMapper;
- public UserController(UserBusinessService userBusinessService, UserMapper userMapper) {
- this.userBusinessService = userBusinessService;
+ public UserController(UserAuthorizationService userAuthorizationService, UserMapper userMapper) {
+ this.userAuthorizationService = userAuthorizationService;
this.userMapper = userMapper;
}
@@ -31,7 +31,7 @@ public UserController(UserBusinessService userBusinessService, UserMapper userMa
@Content(mediaType = "application/json", schema = @Schema(implementation = UserDto.class)) }), })
@GetMapping
public List getAllUsers() {
- return userBusinessService.getAllUsers().stream().map(userMapper::toDto).toList();
+ return userAuthorizationService.getAllUsers().stream().map(userMapper::toDto).toList();
}
}
diff --git a/backend/src/main/java/ch/puzzle/okr/converter/JwtConverterFactory.java b/backend/src/main/java/ch/puzzle/okr/converter/JwtConverterFactory.java
new file mode 100644
index 0000000000..febe219644
--- /dev/null
+++ b/backend/src/main/java/ch/puzzle/okr/converter/JwtConverterFactory.java
@@ -0,0 +1,35 @@
+package ch.puzzle.okr.converter;
+
+import ch.puzzle.okr.models.User;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.oauth2.jwt.Jwt;
+import org.springframework.stereotype.Component;
+
+import java.util.List;
+
+@Component
+public class JwtConverterFactory {
+
+ @Autowired
+ private ApplicationContext appContext;
+ private Converter> jwtOrganisationConverter;
+ private Converter jwtUserConverter;
+
+ public synchronized Converter> getJwtOrganisationConverter() {
+ if (jwtOrganisationConverter == null) {
+ // place to load configured converter instead of default converter
+ jwtOrganisationConverter = appContext.getBean(JwtOrganisationConverter.class);
+ }
+ return jwtOrganisationConverter;
+ }
+
+ public synchronized Converter getJwtUserConverter() {
+ if (jwtUserConverter == null) {
+ // place to load configured converter instead of default converter
+ jwtUserConverter = appContext.getBean(JwtUserConverter.class);
+ }
+ return jwtUserConverter;
+ }
+}
diff --git a/backend/src/main/java/ch/puzzle/okr/converter/JwtOrganisationConverter.java b/backend/src/main/java/ch/puzzle/okr/converter/JwtOrganisationConverter.java
index eeaa0d394b..4ef9d275f8 100644
--- a/backend/src/main/java/ch/puzzle/okr/converter/JwtOrganisationConverter.java
+++ b/backend/src/main/java/ch/puzzle/okr/converter/JwtOrganisationConverter.java
@@ -34,6 +34,7 @@ public List convert(Jwt token) {
return organisations.stream().filter(o -> o.startsWith(organisationNamePrefix)).toList();
}
}
+ logger.warn("empty list of realms or organisations {}", realmAccess);
return List.of();
}
}
diff --git a/backend/src/main/java/ch/puzzle/okr/converter/JwtUserConverter.java b/backend/src/main/java/ch/puzzle/okr/converter/JwtUserConverter.java
index 7da4734cd1..05da5c023b 100644
--- a/backend/src/main/java/ch/puzzle/okr/converter/JwtUserConverter.java
+++ b/backend/src/main/java/ch/puzzle/okr/converter/JwtUserConverter.java
@@ -37,7 +37,7 @@ public User convert(Jwt token) {
.withFirstname(claims.get(firstname).toString()).withLastname(claims.get(lastname).toString())
.withEmail(claims.get(email).toString()).build();
} catch (Exception e) {
- logger.error("can not convert user from claims {}", claims);
+ logger.warn("can not convert user from claims {}", claims);
throw new ResponseStatusException(BAD_REQUEST, "can not convert user from token");
}
}
diff --git a/backend/src/main/java/ch/puzzle/okr/dto/TeamDto.java b/backend/src/main/java/ch/puzzle/okr/dto/TeamDto.java
index 3ab618d1a7..fd9a338034 100644
--- a/backend/src/main/java/ch/puzzle/okr/dto/TeamDto.java
+++ b/backend/src/main/java/ch/puzzle/okr/dto/TeamDto.java
@@ -2,5 +2,5 @@
import java.util.List;
-public record TeamDto(Long id, String name, List organisations) {
+public record TeamDto(Long id, int version, String name, List organisations) {
}
diff --git a/backend/src/main/java/ch/puzzle/okr/dto/UserDto.java b/backend/src/main/java/ch/puzzle/okr/dto/UserDto.java
index 78a1bfede2..3d25fc02eb 100644
--- a/backend/src/main/java/ch/puzzle/okr/dto/UserDto.java
+++ b/backend/src/main/java/ch/puzzle/okr/dto/UserDto.java
@@ -1,4 +1,5 @@
package ch.puzzle.okr.dto;
-public record UserDto(Long id, String username, String firstname, String lastname, String email) {
+public record UserDto(Long id, int version, String username, String firstname, String lastname, String email,
+ boolean isWriteable) {
}
diff --git a/backend/src/main/java/ch/puzzle/okr/dto/overview/OverviewTeamDto.java b/backend/src/main/java/ch/puzzle/okr/dto/overview/OverviewTeamDto.java
index 41a46bfdcc..4a1b7f65cb 100644
--- a/backend/src/main/java/ch/puzzle/okr/dto/overview/OverviewTeamDto.java
+++ b/backend/src/main/java/ch/puzzle/okr/dto/overview/OverviewTeamDto.java
@@ -1,4 +1,4 @@
package ch.puzzle.okr.dto.overview;
-public record OverviewTeamDto(Long id, String name, boolean writable, boolean hasInActiveOrganisations) {
+public record OverviewTeamDto(Long id, int version, String name, boolean writable, boolean hasInActiveOrganisations) {
}
diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java
index bc01cf187c..35b09a2870 100644
--- a/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java
+++ b/backend/src/main/java/ch/puzzle/okr/mapper/OverviewMapper.java
@@ -67,8 +67,8 @@ private OverviewDto createOverviewDto(Overview overview) {
objectives.add(createObjectiveDto(overview));
}
return new OverviewDto(
- new OverviewTeamDto(overview.getOverviewId().getTeamId(), overview.getTeamName(),
- overview.isWriteable(),
+ new OverviewTeamDto(overview.getOverviewId().getTeamId(), overview.getTeamVersion(),
+ overview.getTeamName(), overview.isWriteable(),
organisationBusinessService.teamHasInActiveOrganisations(overview.getOverviewId().getTeamId())),
objectives);
}
diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/TeamMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/TeamMapper.java
index 8389a980ad..a439542aff 100644
--- a/backend/src/main/java/ch/puzzle/okr/mapper/TeamMapper.java
+++ b/backend/src/main/java/ch/puzzle/okr/mapper/TeamMapper.java
@@ -28,13 +28,13 @@ public TeamDto toDto(Team team, Long quarterId) {
long chosenQuarterId = quarterId == null ? quarterBusinessService.getCurrentQuarter().getId() : quarterId;
List organisationDTOs = team.getAuthorizationOrganisation().stream()
.map(organisationMapper::toDto).toList();
- return new TeamDto(team.getId(), team.getName(), organisationDTOs);
+ return new TeamDto(team.getId(), team.getVersion(), team.getName(), organisationDTOs);
}
public Team toTeam(TeamDto teamDto) {
List organisations = teamDto.organisations().stream().map(organisationMapper::toOrganisation)
.toList();
- return Team.Builder.builder().withId(teamDto.id()).withName(teamDto.name())
+ return Team.Builder.builder().withId(teamDto.id()).withVersion(teamDto.version()).withName(teamDto.name())
.withAuthorizationOrganisation(organisations).build();
}
}
diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/UserMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/UserMapper.java
index 6af7624f13..b35e2b6a36 100644
--- a/backend/src/main/java/ch/puzzle/okr/mapper/UserMapper.java
+++ b/backend/src/main/java/ch/puzzle/okr/mapper/UserMapper.java
@@ -7,6 +7,7 @@
@Component
public class UserMapper {
public UserDto toDto(User user) {
- return new UserDto(user.getId(), user.getUsername(), user.getFirstname(), user.getLastname(), user.getEmail());
+ return new UserDto(user.getId(), user.getVersion(), user.getUsername(), user.getFirstname(), user.getLastname(),
+ user.getEmail(), user.isWriteable());
}
}
diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/RoleMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/role/DefaultRoleMapper.java
similarity index 89%
rename from backend/src/main/java/ch/puzzle/okr/mapper/RoleMapper.java
rename to backend/src/main/java/ch/puzzle/okr/mapper/role/DefaultRoleMapper.java
index 3ce825024d..64d567f68a 100644
--- a/backend/src/main/java/ch/puzzle/okr/mapper/RoleMapper.java
+++ b/backend/src/main/java/ch/puzzle/okr/mapper/role/DefaultRoleMapper.java
@@ -1,4 +1,4 @@
-package ch.puzzle.okr.mapper;
+package ch.puzzle.okr.mapper.role;
import ch.puzzle.okr.models.User;
import ch.puzzle.okr.models.authorization.AuthorizationRole;
@@ -13,7 +13,7 @@
import static ch.puzzle.okr.models.authorization.AuthorizationRole.*;
@Component
-public class RoleMapper {
+public class DefaultRoleMapper implements RoleMapper {
private static final String DELIMITER = ",";
@Value("${okr.organisation.name.1stLevel}")
@@ -23,7 +23,8 @@ public class RoleMapper {
@Value("${okr.user.champion.usernames}")
private String okrChampionUsernames;
- public List mapOrganisationNames(List organisationNames, User user) {
+ @Override
+ public List mapAuthorizationRoles(List organisationNames, User user) {
List roles = new ArrayList<>();
if (hasFirstLevelOrganisationName(organisationNames) || isOkrChampion(user)) {
roles.addAll(List.of(READ_ALL_DRAFT, WRITE_ALL));
diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/role/RoleMapper.java b/backend/src/main/java/ch/puzzle/okr/mapper/role/RoleMapper.java
new file mode 100644
index 0000000000..6517342c06
--- /dev/null
+++ b/backend/src/main/java/ch/puzzle/okr/mapper/role/RoleMapper.java
@@ -0,0 +1,10 @@
+package ch.puzzle.okr.mapper.role;
+
+import ch.puzzle.okr.models.User;
+import ch.puzzle.okr.models.authorization.AuthorizationRole;
+
+import java.util.List;
+
+public interface RoleMapper {
+ List mapAuthorizationRoles(List organisationNames, User user);
+}
diff --git a/backend/src/main/java/ch/puzzle/okr/mapper/role/RoleMapperFactory.java b/backend/src/main/java/ch/puzzle/okr/mapper/role/RoleMapperFactory.java
new file mode 100644
index 0000000000..f3b31e2bef
--- /dev/null
+++ b/backend/src/main/java/ch/puzzle/okr/mapper/role/RoleMapperFactory.java
@@ -0,0 +1,20 @@
+package ch.puzzle.okr.mapper.role;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.stereotype.Component;
+
+@Component
+public class RoleMapperFactory {
+ @Autowired
+ private ApplicationContext appContext;
+ private RoleMapper roleMapper;
+
+ public synchronized RoleMapper getRoleMapper() {
+ if (roleMapper == null) {
+ // place to load configured role mapper instead of default role mapper
+ roleMapper = appContext.getBean(DefaultRoleMapper.class);
+ }
+ return roleMapper;
+ }
+}
diff --git a/backend/src/main/java/ch/puzzle/okr/models/Team.java b/backend/src/main/java/ch/puzzle/okr/models/Team.java
index 46cd91387a..27ae00e149 100644
--- a/backend/src/main/java/ch/puzzle/okr/models/Team.java
+++ b/backend/src/main/java/ch/puzzle/okr/models/Team.java
@@ -8,11 +8,14 @@
import java.util.Objects;
@Entity
-public class Team {
+public class Team implements WriteableInterface {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence_team")
private Long id;
+ @Version
+ private int version;
+
@NotBlank(message = "Missing attribute name when saving team")
@NotNull(message = "Attribute name can not be null when saving team")
@Size(min = 2, max = 250, message = "Attribute name must have size between 2 and 250 characters when saving team")
@@ -22,11 +25,14 @@ public class Team {
@JoinTable(name = "team_organisation", joinColumns = @JoinColumn(name = "team_id"), inverseJoinColumns = @JoinColumn(name = "organisation_id"))
private List authorizationOrganisation;
+ private transient boolean writeable;
+
public Team() {
}
private Team(Builder builder) {
id = builder.id;
+ version = builder.version;
setName(builder.name);
setAuthorizationOrganisation(builder.authorizationOrganisation);
}
@@ -35,6 +41,10 @@ public Long getId() {
return id;
}
+ public int getVersion() {
+ return version;
+ }
+
public String getName() {
return name;
}
@@ -51,9 +61,19 @@ public void setAuthorizationOrganisation(List authorizationOrganis
this.authorizationOrganisation = authorizationOrganisation;
}
+ @Override
+ public boolean isWriteable() {
+ return writeable;
+ }
+
+ @Override
+ public void setWriteable(boolean writeable) {
+ this.writeable = writeable;
+ }
+
@Override
public String toString() {
- return "Team{" + "id=" + id + ", name='" + name + '}';
+ return "Team{" + "id=" + id + ", version=" + version + ", name='" + name + ", writeable=" + writeable + '}';
}
@Override
@@ -63,16 +83,18 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass())
return false;
Team team = (Team) o;
- return Objects.equals(id, team.id) && Objects.equals(name, team.name);
+ return Objects.equals(id, team.id) && Objects.equals(version, team.version) && Objects.equals(name, team.name)
+ && Objects.equals(writeable, team.writeable);
}
@Override
public int hashCode() {
- return Objects.hash(id, name);
+ return Objects.hash(id, version, name, writeable);
}
public static final class Builder {
private Long id;
+ private int version;
private String name;
private List authorizationOrganisation;
@@ -89,6 +111,11 @@ public Builder withId(Long id) {
return this;
}
+ public Builder withVersion(int version) {
+ this.version = version;
+ return this;
+ }
+
public Builder withName(String name) {
this.name = name;
return this;
diff --git a/backend/src/main/java/ch/puzzle/okr/models/User.java b/backend/src/main/java/ch/puzzle/okr/models/User.java
index 75092cfe11..3ff112f9af 100644
--- a/backend/src/main/java/ch/puzzle/okr/models/User.java
+++ b/backend/src/main/java/ch/puzzle/okr/models/User.java
@@ -10,11 +10,14 @@
@Entity
// table cannot be named "user" since it is a reserved keyword of Postgres
@Table(name = "person")
-public class User {
+public class User implements WriteableInterface {
@Id
@GeneratedValue(strategy = GenerationType.AUTO, generator = "sequence_person")
private Long id;
+ @Version
+ private int version;
+
@Column(unique = true)
@NotBlank(message = "Missing attribute username when saving user")
@NotNull(message = "Attribute username can not be null when saving user")
@@ -37,11 +40,14 @@ public class User {
@Size(min = 2, max = 250, message = "Attribute email must have size between 2 and 250 characters when saving user")
private String email;
+ private transient boolean writeable;
+
public User() {
}
private User(Builder builder) {
id = builder.id;
+ version = builder.version;
setUsername(builder.username);
setFirstname(builder.firstname);
setLastname(builder.lastname);
@@ -52,6 +58,10 @@ public Long getId() {
return id;
}
+ public int getVersion() {
+ return version;
+ }
+
public String getUsername() {
return username;
}
@@ -84,10 +94,21 @@ public void setEmail(String email) {
this.email = email;
}
+ @Override
+ public boolean isWriteable() {
+ return writeable;
+ }
+
+ @Override
+ public void setWriteable(boolean writeable) {
+ this.writeable = writeable;
+ }
+
@Override
public String toString() {
- return "User{" + "id=" + id + ", username='" + username + '\'' + ", firstname='" + firstname + '\''
- + ", lastname='" + lastname + '\'' + ", email='" + email + '\'' + '}';
+ return "User{" + "id=" + id + ", version=" + version + ", username='" + username + '\'' + ", firstname='"
+ + firstname + '\'' + ", lastname='" + lastname + '\'' + ", email='" + email + '\'' + ", writeable="
+ + writeable + '}';
}
@Override
@@ -97,18 +118,20 @@ public boolean equals(Object o) {
if (o == null || getClass() != o.getClass())
return false;
User user = (User) o;
- return Objects.equals(id, user.id) && Objects.equals(username, user.username)
- && Objects.equals(firstname, user.firstname) && Objects.equals(lastname, user.lastname)
- && Objects.equals(email, user.email);
+ return Objects.equals(id, user.id) && Objects.equals(version, user.version)
+ && Objects.equals(username, user.username) && Objects.equals(firstname, user.firstname)
+ && Objects.equals(lastname, user.lastname) && Objects.equals(email, user.email)
+ && Objects.equals(writeable, user.writeable);
}
@Override
public int hashCode() {
- return Objects.hash(id, username, firstname, lastname, email);
+ return Objects.hash(id, version, username, firstname, lastname, email, writeable);
}
public static final class Builder {
private Long id;
+ private int version;
private String username;
private String firstname;
private String lastname;
@@ -126,6 +149,11 @@ public Builder withId(Long id) {
return this;
}
+ public Builder withVersion(int version) {
+ this.version = version;
+ return this;
+ }
+
public Builder withUsername(String username) {
this.username = username;
return this;
diff --git a/backend/src/main/java/ch/puzzle/okr/models/overview/Overview.java b/backend/src/main/java/ch/puzzle/okr/models/overview/Overview.java
index ae5a5b7489..65707048b8 100644
--- a/backend/src/main/java/ch/puzzle/okr/models/overview/Overview.java
+++ b/backend/src/main/java/ch/puzzle/okr/models/overview/Overview.java
@@ -17,6 +17,7 @@ public class Overview implements WriteableInterface {
@EmbeddedId
private OverviewId overviewId;
private String teamName;
+ private int teamVersion;
private String objectiveTitle;
@Enumerated(EnumType.STRING)
private State objectiveState;
@@ -43,6 +44,7 @@ public Overview() {
private Overview(Builder builder) {
overviewId = builder.overviewId;
+ teamVersion = builder.teamVersion;
teamName = builder.teamName;
objectiveTitle = builder.objectiveTitle;
objectiveState = builder.objectiveState;
@@ -67,6 +69,10 @@ public OverviewId getOverviewId() {
return overviewId;
}
+ public int getTeamVersion() {
+ return teamVersion;
+ }
+
public String getTeamName() {
return teamName;
}
@@ -151,12 +157,12 @@ public void setWriteable(boolean writeable) {
@Override
public String toString() {
- return "Overview{" + "overviewId=" + overviewId + ", teamName='" + teamName + '\'' + ", objectiveTitle='"
- + objectiveTitle + '\'' + ", objectiveState=" + objectiveState + ", objectiveCreatedOn="
- + objectiveCreatedOn + ", quarterId=" + quarterId + ", quarterLabel='" + quarterLabel + '\''
- + ", keyResultTitle='" + keyResultTitle + '\'' + ", keyResultType='" + keyResultType + '\''
- + ", baseline=" + baseline + ", stretchGoal=" + stretchGoal + ", unit='" + unit + '\''
- + ", commitZone='" + commitZone + '\'' + ", targetZone='" + targetZone + '\'' + ", stretchZone='"
+ return "Overview{" + "overviewId=" + overviewId + ", teamVersion='" + teamVersion + ", teamName='" + teamName
+ + '\'' + ", objectiveTitle='" + objectiveTitle + '\'' + ", objectiveState=" + objectiveState
+ + ", objectiveCreatedOn=" + objectiveCreatedOn + ", quarterId=" + quarterId + ", quarterLabel='"
+ + quarterLabel + '\'' + ", keyResultTitle='" + keyResultTitle + '\'' + ", keyResultType='"
+ + keyResultType + '\'' + ", baseline=" + baseline + ", stretchGoal=" + stretchGoal + ", unit='" + unit
+ + '\'' + ", commitZone='" + commitZone + '\'' + ", targetZone='" + targetZone + '\'' + ", stretchZone='"
+ stretchZone + '\'' + ", checkInValue=" + checkInValue + ", checkInZone='" + checkInZone + '\''
+ ", confidence=" + confidence + ", createdOn=" + checkInCreatedOn + ", writeable=" + writeable + '\''
+ '}';
@@ -164,6 +170,7 @@ public String toString() {
public static final class Builder {
private OverviewId overviewId;
+ private int teamVersion;
private String teamName;
private String objectiveTitle;
private State objectiveState;
@@ -195,6 +202,11 @@ public Builder withOverviewId(OverviewId overviewId) {
return this;
}
+ public Builder withTeamVersion(int teamVersion) {
+ this.teamVersion = teamVersion;
+ return this;
+ }
+
public Builder withTeamName(String teamName) {
this.teamName = teamName;
return this;
diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationRegistrationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationRegistrationService.java
index f79b461712..8f95370b2c 100644
--- a/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationRegistrationService.java
+++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationRegistrationService.java
@@ -1,7 +1,7 @@
package ch.puzzle.okr.service.authorization;
-import ch.puzzle.okr.converter.JwtOrganisationConverter;
-import ch.puzzle.okr.mapper.RoleMapper;
+import ch.puzzle.okr.converter.JwtConverterFactory;
+import ch.puzzle.okr.mapper.role.RoleMapperFactory;
import ch.puzzle.okr.models.User;
import ch.puzzle.okr.models.authorization.AuthorizationUser;
import ch.puzzle.okr.service.business.UserBusinessService;
@@ -23,23 +23,24 @@ public class AuthorizationRegistrationService {
private final UserBusinessService userBusinessService;
private final TeamPersistenceService teamPersistenceService;
- private final JwtOrganisationConverter jwtOrganisationConverter;
- private final RoleMapper roleMapper;
+ private final JwtConverterFactory jwtConverterFactory;
+ private final RoleMapperFactory roleMapperFactory;
public AuthorizationRegistrationService(UserBusinessService userBusinessService,
- TeamPersistenceService teamPersistenceService, JwtOrganisationConverter jwtOrganisationConverter,
- RoleMapper roleMapper) {
+ TeamPersistenceService teamPersistenceService, JwtConverterFactory jwtConverterFactory,
+ RoleMapperFactory roleMapperFactory) {
this.userBusinessService = userBusinessService;
this.teamPersistenceService = teamPersistenceService;
- this.jwtOrganisationConverter = jwtOrganisationConverter;
- this.roleMapper = roleMapper;
+ this.jwtConverterFactory = jwtConverterFactory;
+ this.roleMapperFactory = roleMapperFactory;
}
@Cacheable(value = AUTHORIZATION_USER_CACHE, key = "#user.username")
public AuthorizationUser registerAuthorizationUser(User user, Jwt token) {
- List organisationNames = jwtOrganisationConverter.convert(token);
+ List organisationNames = jwtConverterFactory.getJwtOrganisationConverter().convert(token);
return new AuthorizationUser(userBusinessService.getOrCreateUser(user), getTeamIds(organisationNames),
- getFirstLevelTeamIds(), roleMapper.mapOrganisationNames(organisationNames, user));
+ getFirstLevelTeamIds(),
+ roleMapperFactory.getRoleMapper().mapAuthorizationRoles(organisationNames, user));
}
private List getTeamIds(List organisationNames) {
diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java
index 2312ef4cd7..b264e03750 100644
--- a/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java
+++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/AuthorizationService.java
@@ -1,6 +1,6 @@
package ch.puzzle.okr.service.authorization;
-import ch.puzzle.okr.converter.JwtUserConverter;
+import ch.puzzle.okr.converter.JwtConverterFactory;
import ch.puzzle.okr.models.Action;
import ch.puzzle.okr.models.Objective;
import ch.puzzle.okr.models.Team;
@@ -35,15 +35,15 @@ public class AuthorizationService {
private final AuthorizationRegistrationService authorizationRegistrationService;
private final ObjectivePersistenceService objectivePersistenceService;
private final ActionPersistenceService actionPersistenceService;
- private final JwtUserConverter jwtUserConverter;
+ private final JwtConverterFactory jwtConverterFactory;
public AuthorizationService(AuthorizationRegistrationService authorizationRegistrationService,
ObjectivePersistenceService objectivePersistenceService, ActionPersistenceService actionPersistenceService,
- JwtUserConverter jwtUserConverter) {
+ JwtConverterFactory jwtConverterFactory) {
this.authorizationRegistrationService = authorizationRegistrationService;
this.actionPersistenceService = actionPersistenceService;
this.objectivePersistenceService = objectivePersistenceService;
- this.jwtUserConverter = jwtUserConverter;
+ this.jwtConverterFactory = jwtConverterFactory;
}
public static boolean hasRoleReadTeamsDraft(AuthorizationUser user) {
@@ -78,7 +78,7 @@ public AuthorizationUser getAuthorizationUser() {
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication authentication = securityContext.getAuthentication();
Jwt token = (Jwt) authentication.getPrincipal();
- User user = jwtUserConverter.convert(token);
+ User user = jwtConverterFactory.getJwtUserConverter().convert(token);
return authorizationRegistrationService.registerAuthorizationUser(user, token);
}
diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/TeamAuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/TeamAuthorizationService.java
index 76cf2cb8c5..c0101804f3 100644
--- a/backend/src/main/java/ch/puzzle/okr/service/authorization/TeamAuthorizationService.java
+++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/TeamAuthorizationService.java
@@ -24,12 +24,16 @@ public TeamAuthorizationService(TeamBusinessService teamBusinessService,
public Team createEntity(Team entity) {
checkUserAuthorization("not authorized to create team");
- return teamBusinessService.createTeam(entity);
+ Team savedTeam = teamBusinessService.createTeam(entity);
+ savedTeam.setWriteable(true);
+ return savedTeam;
}
public Team updateEntity(Team entity, Long id) {
checkUserAuthorization("not authorized to update team");
- return teamBusinessService.updateTeam(entity, id);
+ Team updatedTeam = teamBusinessService.updateTeam(entity, id);
+ updatedTeam.setWriteable(true);
+ return updatedTeam;
}
public void deleteEntity(Long id) {
@@ -37,14 +41,18 @@ public void deleteEntity(Long id) {
teamBusinessService.deleteTeam(id);
}
- public void checkUserAuthorization(String message) {
+ private void checkUserAuthorization(String message) {
AuthorizationUser authorizationUser = authorizationService.getAuthorizationUser();
if (!hasRoleWriteAll(authorizationUser)) {
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, message);
}
}
- public List getEntities() {
- return teamBusinessService.getAllTeams();
+ public List getAllTeams() {
+ AuthorizationUser authorizationUser = authorizationService.getAuthorizationUser();
+ boolean isWritable = hasRoleWriteAll(authorizationUser);
+ List allTeams = teamBusinessService.getAllTeams();
+ allTeams.forEach(team -> team.setWriteable(isWritable));
+ return allTeams;
}
}
diff --git a/backend/src/main/java/ch/puzzle/okr/service/authorization/UserAuthorizationService.java b/backend/src/main/java/ch/puzzle/okr/service/authorization/UserAuthorizationService.java
new file mode 100644
index 0000000000..24daa70404
--- /dev/null
+++ b/backend/src/main/java/ch/puzzle/okr/service/authorization/UserAuthorizationService.java
@@ -0,0 +1,30 @@
+package ch.puzzle.okr.service.authorization;
+
+import ch.puzzle.okr.models.User;
+import ch.puzzle.okr.models.authorization.AuthorizationUser;
+import ch.puzzle.okr.service.business.UserBusinessService;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+import static ch.puzzle.okr.service.authorization.AuthorizationService.hasRoleWriteAll;
+
+@Service
+public class UserAuthorizationService {
+ private final UserBusinessService userBusinessService;
+ private final AuthorizationService authorizationService;
+
+ public UserAuthorizationService(UserBusinessService userBusinessService,
+ AuthorizationService authorizationService) {
+ this.userBusinessService = userBusinessService;
+ this.authorizationService = authorizationService;
+ }
+
+ public List getAllUsers() {
+ AuthorizationUser authorizationUser = authorizationService.getAuthorizationUser();
+ boolean isWritable = hasRoleWriteAll(authorizationUser);
+ List allUsers = userBusinessService.getAllUsers();
+ allUsers.forEach(user -> user.setWriteable(isWritable));
+ return allUsers;
+ }
+}
diff --git a/backend/src/main/resources/db/data-migration/V2_0_99__newQuarterData.sql b/backend/src/main/resources/db/data-migration/V2_0_99__newQuarterData.sql
index acb632e652..bbaff68591 100644
--- a/backend/src/main/resources/db/data-migration/V2_0_99__newQuarterData.sql
+++ b/backend/src/main/resources/db/data-migration/V2_0_99__newQuarterData.sql
@@ -176,35 +176,8 @@ values (21, 1,
insert into quarter (id, label, start_date, end_date)
values (8, 'GJ 23/24-Q3', '2024-01-01', '2024-03-31');
-insert into organisation (id, version, org_name, state)
-values (1, 1, 'org_bl', 'ACTIVE'),
- (2, 1, 'org_de', 'ACTIVE'),
- (3, 1, 'org_gl', 'ACTIVE'),
- (4, 1, 'org_pl', 'ACTIVE'),
- (5, 1, 'org_pv', 'ACTIVE'),
- (6, 1, 'org_ux', 'ACTIVE'),
- (7, 1, 'org_zh', 'ACTIVE'),
- (8, 1, 'org_sys', 'ACTIVE'),
- (9, 1, 'org_azubi', 'ACTIVE'),
- (10, 1, 'org_de_gl', 'ACTIVE'),
- (11, 1, 'org_de_gs', 'ACTIVE'),
- (12, 1, 'org_devtre', 'ACTIVE'),
- (13, 1, 'org_racoon', 'ACTIVE'),
- (14, 1, 'org_tqm_qm', 'ACTIVE'),
- (15, 1, 'org_devruby', 'ACTIVE'),
- (16, 1, 'org_midcicd', 'ACTIVE'),
- (17, 1, 'org_verkauf', 'ACTIVE'),
- (18, 1, 'org_finanzen', 'ACTIVE'),
- (19, 1, 'org_mobility', 'ACTIVE'),
- (20, 1, 'org_personal', 'ACTIVE'),
- (21, 1, 'org_security', 'ACTIVE'),
- (22, 1, 'org_marketing', 'ACTIVE'),
- (23, 1, 'org_openshift', 'ACTIVE'),
- (24, 1, 'org_ausbildung', 'ACTIVE'),
- (25, 1, 'org_backoffice', 'ACTIVE'),
- (26, 1, 'org_branch_sec', 'ACTIVE'),
- (27, 1, 'org_standort_zh', 'ACTIVE'),
- (28, 1, 'org_midcontainer', 'ACTIVE');
+delete
+from team_organisation;
insert into team_organisation (team_id, organisation_id)
values (5, 3),
diff --git a/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql b/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql
index 8c558326a5..db4d5b0077 100644
--- a/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql
+++ b/backend/src/main/resources/db/h2-db/data-test-h2/V100_0_0__TestData.sql
@@ -12,24 +12,24 @@ truncate table action;
SET REFERENTIAL_INTEGRITY TRUE;
-ALTER SEQUENCE sequence_team RESTART WITH 200;
-ALTER SEQUENCE sequence_person RESTART WITH 200;
-ALTER SEQUENCE sequence_quarter RESTART WITH 200;
-ALTER SEQUENCE sequence_objective RESTART WITH 200;
-ALTER SEQUENCE sequence_key_result RESTART WITH 200;
-ALTER SEQUENCE sequence_check_in RESTART WITH 200;
+ALTER SEQUENCE sequence_action RESTART WITH 200;
ALTER SEQUENCE sequence_alignment RESTART WITH 200;
+ALTER SEQUENCE sequence_check_in RESTART WITH 200;
ALTER SEQUENCE sequence_completed RESTART WITH 200;
+ALTER SEQUENCE sequence_key_result RESTART WITH 200;
+ALTER SEQUENCE sequence_objective RESTART WITH 200;
ALTER SEQUENCE sequence_organisation RESTART WITH 200;
-ALTER SEQUENCE sequence_action RESTART WITH 200;
+ALTER SEQUENCE sequence_person RESTART WITH 200;
+ALTER SEQUENCE sequence_quarter RESTART WITH 200;
+ALTER SEQUENCE sequence_team RESTART WITH 200;
-insert into person (id, email, firstname, lastname, username)
-values (1, 'peggimann@puzzle.ch', 'Paco', 'Eggimann', 'peggimann'),
- (11, 'wunderland@puzzle.ch', 'Alice', 'Wunderland', 'alice'),
- (21, 'baumeister@puzzle.ch', 'Bob', 'Baumeister', 'bob'),
- (31, 'peterson@puzzle.ch', 'Findus', 'Peterson', 'findus'),
- (41, 'egiman@puzzle.ch', 'Paco', 'Egiman', 'paco'),
- (51, 'papierer@puzzle.ch', 'Robin', 'Papierer', 'robin');
+insert into person (id, version, email, firstname, lastname, username)
+values (1, 1, 'peggimann@puzzle.ch', 'Paco', 'Eggimann', 'peggimann'),
+ (11, 1, 'wunderland@puzzle.ch', 'Alice', 'Wunderland', 'alice'),
+ (21, 1, 'baumeister@puzzle.ch', 'Bob', 'Baumeister', 'bob'),
+ (31, 1, 'peterson@puzzle.ch', 'Findus', 'Peterson', 'findus'),
+ (41, 1, 'egiman@puzzle.ch', 'Paco', 'Egiman', 'paco'),
+ (51, 1, 'papierer@puzzle.ch', 'Robin', 'Papierer', 'robin');
insert into quarter (id, label, start_date, end_date)
values (1, 'GJ 22/23-Q4', '2023-04-01', '2023-06-30'),
@@ -41,11 +41,11 @@ values (1, 'GJ 22/23-Q4', '2023-04-01', '2023-06-30'),
(7, 'GJ 23/24-Q2', '2023-10-01', '2023-12-31'),
(8, 'GJ 23/24-Q3', '2024-01-01', '2024-03-31');
-insert into team (id, name)
-values (4, '/BBT'),
- (8, 'we are cube.³'),
- (5, 'Puzzle ITC'),
- (6, 'LoremIpsum');
+insert into team (id, version, name)
+values (4, 1, '/BBT'),
+ (8, 1, 'we are cube.³'),
+ (5, 1, 'Puzzle ITC'),
+ (6, 1, 'LoremIpsum');
insert into objective (id, version, description, modified_on, progress, title, created_by_id, quarter_id, team_id, state,
modified_by_id, created_on)
diff --git a/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql b/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql
index 963c4546f8..f581f26742 100644
--- a/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql
+++ b/backend/src/main/resources/db/h2-db/database-h2-schema/V1_0_0__current-db-schema-for-testing.sql
@@ -12,6 +12,7 @@ create sequence if not exists sequence_action;
create table if not exists person
(
id bigint not null,
+ version int not null,
email varchar(250) not null,
firstname varchar(50) not null,
lastname varchar(50) not null,
@@ -36,8 +37,9 @@ create table if not exists quarter
create table if not exists team
(
- id bigint not null,
- name varchar(250) not null,
+ id bigint not null,
+ version int not null,
+ name varchar(250) not null,
primary key (id)
);
@@ -144,7 +146,7 @@ create table action
(
id bigint not null
primary key,
- version int not null,
+ version int not null,
action varchar(4096) not null,
priority integer not null,
is_checked boolean not null,
@@ -159,6 +161,7 @@ create index if not exists idx_completed_objective
DROP VIEW IF EXISTS OVERVIEW;
CREATE VIEW OVERVIEW AS
SELECT TQ.TEAM_ID AS "TEAM_ID",
+ TQ.TEAM_VERSION AS "TEAM_VERSION",
TQ.NAME AS "TEAM_NAME",
TQ.QUARTER_ID AS "QUARTER_ID",
TQ.LABEL AS "QUARTER_LABEL",
@@ -180,7 +183,7 @@ SELECT TQ.TEAM_ID AS "TEAM_ID",
C.ZONE AS "CHECK_IN_ZONE",
C.CONFIDENCE,
C.CREATED_ON AS "CHECK_IN_CREATED_ON"
-FROM (SELECT T.ID AS TEAM_ID, T.NAME, Q.ID AS QUARTER_ID, Q.LABEL
+FROM (SELECT T.ID AS TEAM_ID, T.VERSION AS TEAM_VERSION, T.NAME, Q.ID AS QUARTER_ID, Q.LABEL
FROM TEAM T,
QUARTER Q) TQ
LEFT JOIN OBJECTIVE O ON TQ.TEAM_ID = O.TEAM_ID AND TQ.QUARTER_ID = O.QUARTER_ID
diff --git a/backend/src/main/resources/db/migration/V2_0_10__createActionTable.sql b/backend/src/main/resources/db/migration/V2_0_10__createActionTable.sql
index c0624cef81..a8a0bf7d1d 100644
--- a/backend/src/main/resources/db/migration/V2_0_10__createActionTable.sql
+++ b/backend/src/main/resources/db/migration/V2_0_10__createActionTable.sql
@@ -1,5 +1,5 @@
create sequence if not exists sequence_action;
-ALTER SEQUENCE sequence_action RESTART WITH 500;
+ALTER SEQUENCE sequence_action RESTART WITH 1000;
create table if not exists action
(
id bigint not null primary key,
diff --git a/backend/src/main/resources/db/migration/V2_0_11__updateSequences.sql b/backend/src/main/resources/db/migration/V2_0_11__updateSequences.sql
new file mode 100644
index 0000000000..ce392b322b
--- /dev/null
+++ b/backend/src/main/resources/db/migration/V2_0_11__updateSequences.sql
@@ -0,0 +1,10 @@
+ALTER SEQUENCE sequence_action RESTART WITH 1000;
+ALTER SEQUENCE sequence_alignment RESTART WITH 1000;
+ALTER SEQUENCE sequence_check_in RESTART WITH 1000;
+ALTER SEQUENCE sequence_completed RESTART WITH 1000;
+ALTER SEQUENCE sequence_key_result RESTART WITH 1000;
+ALTER SEQUENCE sequence_objective RESTART WITH 1000;
+ALTER SEQUENCE sequence_organisation RESTART WITH 1000;
+ALTER SEQUENCE sequence_person RESTART WITH 1000;
+ALTER SEQUENCE sequence_quarter RESTART WITH 1000;
+ALTER SEQUENCE sequence_team RESTART WITH 1000;
diff --git a/backend/src/main/resources/db/migration/V2_0_5__createCompletedTable.sql b/backend/src/main/resources/db/migration/V2_0_5__createCompletedTable.sql
index ee7fb73a9d..6eaf6d3670 100644
--- a/backend/src/main/resources/db/migration/V2_0_5__createCompletedTable.sql
+++ b/backend/src/main/resources/db/migration/V2_0_5__createCompletedTable.sql
@@ -1,5 +1,5 @@
create sequence if not exists sequence_completed;
-alter sequence sequence_completed restart with 500;
+alter sequence sequence_completed restart with 1000;
create table if not exists completed
(
diff --git a/backend/src/main/resources/db/migration/V2_0_7__createAuthorizationOrganisation.sql b/backend/src/main/resources/db/migration/V2_0_7__createAuthorizationOrganisation.sql
index 8e3438ef8c..b309489d78 100644
--- a/backend/src/main/resources/db/migration/V2_0_7__createAuthorizationOrganisation.sql
+++ b/backend/src/main/resources/db/migration/V2_0_7__createAuthorizationOrganisation.sql
@@ -1,5 +1,5 @@
create sequence if not exists sequence_organisation;
-Alter SEQUENCE sequence_organisation RESTART WITH 200;
+Alter SEQUENCE sequence_organisation RESTART WITH 1000;
create table if not exists organisation
(
@@ -22,3 +22,48 @@ create table if not exists team_organisation
create index if not exists idx_team_organisation_team
on team_organisation (team_id);
+
+DO
+$$
+ BEGIN
+ IF NOT EXISTS(SELECT * FROM organisation)
+ THEN
+ insert into organisation (id, org_name, state)
+ values (1, 'org_bl', 'ACTIVE'),
+ (2, 'org_de', 'ACTIVE'),
+ (3, 'org_gl', 'ACTIVE'),
+ (4, 'org_pl', 'ACTIVE'),
+ (5, 'org_pv', 'ACTIVE'),
+ (6, 'org_ux', 'ACTIVE'),
+ (7, 'org_zh', 'ACTIVE'),
+ (8, 'org_sys', 'ACTIVE'),
+ (9, 'org_azubi', 'ACTIVE'),
+ (10, 'org_de_gl', 'ACTIVE'),
+ (11, 'org_de_gs', 'ACTIVE'),
+ (12, 'org_devtre', 'ACTIVE'),
+ (13, 'org_racoon', 'ACTIVE'),
+ (14, 'org_tqm_qm', 'ACTIVE'),
+ (15, 'org_devruby', 'ACTIVE'),
+ (16, 'org_midcicd', 'ACTIVE'),
+ (17, 'org_verkauf', 'ACTIVE'),
+ (18, 'org_finanzen', 'ACTIVE'),
+ (19, 'org_mobility', 'ACTIVE'),
+ (20, 'org_personal', 'ACTIVE'),
+ (21, 'org_security', 'ACTIVE'),
+ (22, 'org_marketing', 'ACTIVE'),
+ (23, 'org_openshift', 'ACTIVE'),
+ (24, 'org_ausbildung', 'ACTIVE'),
+ (25, 'org_backoffice', 'ACTIVE'),
+ (26, 'org_branch_sec', 'ACTIVE'),
+ (27, 'org_standort_zh', 'ACTIVE'),
+ (28, 'org_midcontainer', 'ACTIVE');
+
+ END IF;
+ IF NOT EXISTS(SELECT * FROM team_organisation)
+ THEN
+ insert into team_organisation (team_id, organisation_id)
+ select t.id, 3
+ from team t;
+ END IF;
+ END;
+$$;
diff --git a/backend/src/main/resources/db/migration/V2_0_9__addOptimisticLocking.sql b/backend/src/main/resources/db/migration/V2_0_9__addOptimisticLocking.sql
index 46d8dd2a37..1400dfa6a0 100644
--- a/backend/src/main/resources/db/migration/V2_0_9__addOptimisticLocking.sql
+++ b/backend/src/main/resources/db/migration/V2_0_9__addOptimisticLocking.sql
@@ -10,6 +10,10 @@ alter table objective
add column if not exists version int;
alter table organisation
add column if not exists version int;
+alter table person
+ add column if not exists version int;
+alter table team
+ add column if not exists version int;
update alignment
set version = 1
@@ -29,6 +33,12 @@ where version is null;
update organisation
set version = 1
where version is null;
+update person
+set version = 1
+where version is null;
+update team
+set version = 1
+where version is null;
alter table alignment
alter column version set not null;
@@ -42,3 +52,7 @@ alter table objective
alter column version set not null;
alter table organisation
alter column version set not null;
+alter table person
+ alter column version set not null;
+alter table team
+ alter column version set not null;
diff --git a/backend/src/main/resources/db/migration/V2_1_0__createOverviewView.sql b/backend/src/main/resources/db/migration/V2_1_0__createOverviewView.sql
index 8779d187f2..43bae53589 100644
--- a/backend/src/main/resources/db/migration/V2_1_0__createOverviewView.sql
+++ b/backend/src/main/resources/db/migration/V2_1_0__createOverviewView.sql
@@ -1,6 +1,7 @@
DROP VIEW IF EXISTS OVERVIEW;
CREATE VIEW OVERVIEW AS
SELECT tq.team_id AS "team_id",
+ tq.team_version AS "team_version",
tq.name AS "team_name",
tq.quater_id AS "quarter_id",
tq.label AS "quarter_label",
@@ -22,7 +23,7 @@ SELECT tq.team_id AS "team_id",
c.zone AS "check_in_zone",
c.confidence,
c.created_on AS "check_in_created_on"
-FROM (select t.id as team_id, t.name, q.id as quater_id, q.label
+FROM (select t.id as team_id, t.version as team_version, t.name, q.id as quater_id, q.label
from team t,
quarter q) tq
LEFT JOIN OBJECTIVE O ON TQ.TEAM_ID = O.TEAM_ID AND TQ.QUATER_ID = O.QUARTER_ID
diff --git a/backend/src/test/java/ch/puzzle/okr/controller/TeamControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/TeamControllerIT.java
index bf2fd1bd4c..3984536208 100644
--- a/backend/src/test/java/ch/puzzle/okr/controller/TeamControllerIT.java
+++ b/backend/src/test/java/ch/puzzle/okr/controller/TeamControllerIT.java
@@ -44,8 +44,8 @@ class TeamControllerIT {
static Team teamPuzzle = Team.Builder.builder().withId(5L).withName(PUZZLE).build();
static Team teamOKR = Team.Builder.builder().withId(7L).withName("OKR").build();
static List teamList = Arrays.asList(teamPuzzle, teamOKR);
- static TeamDto teamPuzzleDto = new TeamDto(5L, PUZZLE, new ArrayList<>());
- static TeamDto teamOkrDto = new TeamDto(7L, "OKR", new ArrayList<>());
+ static TeamDto teamPuzzleDto = new TeamDto(5L, 3, PUZZLE, new ArrayList<>());
+ static TeamDto teamOkrDto = new TeamDto(7L, 4, "OKR", new ArrayList<>());
private static final String CREATE_NEW_TEAM = """
{
@@ -58,7 +58,7 @@ class TeamControllerIT {
}
""";
private static final String RESPONSE_NEW_TEAM = """
- {"id":7,"name":"OKR","organisations":[]}""";
+ {"id":7,"version":4,"name":"OKR","organisations":[]}""";
private static final String UPDATE_TEAM = """
{
@@ -84,7 +84,7 @@ void setUp() {
@Test
void shouldGetAllTeams() throws Exception {
- BDDMockito.given(teamAuthorizationService.getEntities()).willReturn(teamList);
+ BDDMockito.given(teamAuthorizationService.getAllTeams()).willReturn(teamList);
mvc.perform(get("/api/v2/teams?quarterId=1").contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk()).andExpect(jsonPath("$", Matchers.hasSize(2)))
@@ -94,7 +94,7 @@ void shouldGetAllTeams() throws Exception {
@Test
void shouldGetAllTeamsWhenNoQuarterParamIsPassed() throws Exception {
- BDDMockito.given(teamAuthorizationService.getEntities()).willReturn(teamList);
+ BDDMockito.given(teamAuthorizationService.getAllTeams()).willReturn(teamList);
mvc.perform(get(BASE_URL).contentType(MediaType.APPLICATION_JSON)).andExpectAll();
BDDMockito.verify(teamMapper).toDto(teamOKR, null);
BDDMockito.verify(teamMapper).toDto(teamPuzzle, null);
@@ -102,7 +102,7 @@ void shouldGetAllTeamsWhenNoQuarterParamIsPassed() throws Exception {
@Test
void shouldGetAllTeamsIfTeamModelIsNull() throws Exception {
- BDDMockito.given(teamAuthorizationService.getEntities()).willReturn(Collections.emptyList());
+ BDDMockito.given(teamAuthorizationService.getAllTeams()).willReturn(Collections.emptyList());
mvc.perform(get("/api/v2/teams?quarterId=1").contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk()).andExpect(jsonPath("$", Matchers.hasSize(0)));
@@ -130,7 +130,7 @@ void shouldReturnResponseStatusExceptionWhenCreatingObjectiveWithNullValues() th
@Test
void shouldReturnUpdatedTeam() throws Exception {
- TeamDto teamDto = new TeamDto(1L, "OKR-Team", new ArrayList<>());
+ TeamDto teamDto = new TeamDto(1L, 0, "OKR-Team", new ArrayList<>());
Team team = Team.Builder.builder().withId(1L).withName("OKR-Team")
.withAuthorizationOrganisation(new ArrayList<>()).build();
@@ -140,6 +140,7 @@ void shouldReturnUpdatedTeam() throws Exception {
mvc.perform(put(URL_TEAM_1).contentType(MediaType.APPLICATION_JSON).content(UPDATE_TEAM)
.with(SecurityMockMvcRequestPostProcessors.csrf())).andExpect(MockMvcResultMatchers.status().isOk())
.andExpect(jsonPath("$.id", Is.is(teamDto.id().intValue())))
+ .andExpect(jsonPath("$.version", Is.is(teamDto.version())))
.andExpect(jsonPath("$.name", Is.is(teamDto.name())));
}
@@ -176,4 +177,4 @@ void throwExceptionWhenOTeamWithIdCantBeFoundWhileDeleting() throws Exception {
mvc.perform(delete(URL_TEAM_1).with(SecurityMockMvcRequestPostProcessors.csrf()))
.andExpect(MockMvcResultMatchers.status().isNotFound());
}
-}
\ No newline at end of file
+}
diff --git a/backend/src/test/java/ch/puzzle/okr/controller/UserControllerIT.java b/backend/src/test/java/ch/puzzle/okr/controller/UserControllerIT.java
index fd66977e4f..10511c2346 100644
--- a/backend/src/test/java/ch/puzzle/okr/controller/UserControllerIT.java
+++ b/backend/src/test/java/ch/puzzle/okr/controller/UserControllerIT.java
@@ -3,7 +3,7 @@
import ch.puzzle.okr.dto.UserDto;
import ch.puzzle.okr.mapper.UserMapper;
import ch.puzzle.okr.models.User;
-import ch.puzzle.okr.service.business.UserBusinessService;
+import ch.puzzle.okr.service.authorization.UserAuthorizationService;
import org.hamcrest.Matchers;
import org.hamcrest.core.Is;
import org.junit.jupiter.api.BeforeEach;
@@ -43,12 +43,12 @@ class UserControllerIT {
static User userBob = User.Builder.builder().withId(9L).withUsername(USERNAME_2).withFirstname(FIRSTNAME_2)
.withLastname(LASTNAME_2).withEmail(EMAIL_2).build();
static List userList = Arrays.asList(userAlice, userBob);
- static UserDto userAliceDto = new UserDto(2L, USERNAME_1, FIRSTNAME_1, LASTNAME_1, EMAIL_1);
- static UserDto userBobDto = new UserDto(9L, USERNAME_2, FIRSTNAME_2, LASTNAME_2, EMAIL_2);
+ static UserDto userAliceDto = new UserDto(2L, 3, USERNAME_1, FIRSTNAME_1, LASTNAME_1, EMAIL_1, true);
+ static UserDto userBobDto = new UserDto(9L, 4, USERNAME_2, FIRSTNAME_2, LASTNAME_2, EMAIL_2, false);
@Autowired
private MockMvc mvc;
@MockBean
- private UserBusinessService userBusinessService;
+ private UserAuthorizationService userAuthorizationService;
@MockBean
private UserMapper userMapper;
@@ -60,7 +60,7 @@ void setUp() {
@Test
void shouldGetAllUsers() throws Exception {
- BDDMockito.given(userBusinessService.getAllUsers()).willReturn(userList);
+ BDDMockito.given(userAuthorizationService.getAllUsers()).willReturn(userList);
mvc.perform(get("/api/v1/users").contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk()).andExpect(jsonPath("$", Matchers.hasSize(2)))
@@ -76,7 +76,7 @@ void shouldGetAllUsers() throws Exception {
@Test
void shouldGetAllUsersIfNoUserExists() throws Exception {
- BDDMockito.given(userBusinessService.getAllUsers()).willReturn(Collections.emptyList());
+ BDDMockito.given(userAuthorizationService.getAllUsers()).willReturn(Collections.emptyList());
mvc.perform(get("/api/v1/users").contentType(MediaType.APPLICATION_JSON))
.andExpect(MockMvcResultMatchers.status().isOk()).andExpect(jsonPath("$", Matchers.hasSize(0)));
diff --git a/backend/src/test/java/ch/puzzle/okr/converter/JwtConverterFactoryIT.java b/backend/src/test/java/ch/puzzle/okr/converter/JwtConverterFactoryIT.java
new file mode 100644
index 0000000000..47e7a5f51b
--- /dev/null
+++ b/backend/src/test/java/ch/puzzle/okr/converter/JwtConverterFactoryIT.java
@@ -0,0 +1,34 @@
+package ch.puzzle.okr.converter;
+
+import ch.puzzle.okr.models.User;
+import ch.puzzle.okr.test.SpringIntegrationTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.security.oauth2.jwt.Jwt;
+
+import java.util.List;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+@SpringIntegrationTest
+public class JwtConverterFactoryIT {
+
+ @Autowired
+ private JwtConverterFactory jwtConverterFactory;
+
+ @Test
+ void getJwtOrganisationConverter() {
+ Converter> converter = jwtConverterFactory.getJwtOrganisationConverter();
+ assertNotNull(converter);
+ assertSame(converter, jwtConverterFactory.getJwtOrganisationConverter());
+ }
+
+ @Test
+ void getJwtUserConverter() {
+ Converter converter = jwtConverterFactory.getJwtUserConverter();
+ assertNotNull(converter);
+ assertSame(converter, jwtConverterFactory.getJwtUserConverter());
+ }
+}
diff --git a/backend/src/test/java/ch/puzzle/okr/mapper/role/RoleMapperFactoryIT.java b/backend/src/test/java/ch/puzzle/okr/mapper/role/RoleMapperFactoryIT.java
new file mode 100644
index 0000000000..d5848d8fad
--- /dev/null
+++ b/backend/src/test/java/ch/puzzle/okr/mapper/role/RoleMapperFactoryIT.java
@@ -0,0 +1,22 @@
+package ch.puzzle.okr.mapper.role;
+
+import ch.puzzle.okr.test.SpringIntegrationTest;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+
+@SpringIntegrationTest
+public class RoleMapperFactoryIT {
+
+ @Autowired
+ private RoleMapperFactory roleMapperFactory;
+
+ @Test
+ void getRoleMapper() {
+ RoleMapper mapper = roleMapperFactory.getRoleMapper();
+ assertNotNull(mapper);
+ assertSame(mapper, roleMapperFactory.getRoleMapper());
+ }
+}
diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java
index b7b3ac2d37..d191420e8c 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/AuthorizationServiceTest.java
@@ -1,5 +1,6 @@
package ch.puzzle.okr.service.authorization;
+import ch.puzzle.okr.converter.JwtConverterFactory;
import ch.puzzle.okr.converter.JwtUserConverter;
import ch.puzzle.okr.models.Objective;
import ch.puzzle.okr.models.Team;
@@ -43,6 +44,8 @@ class AuthorizationServiceTest {
@Mock
ObjectivePersistenceService objectivePersistenceService;
@Mock
+ JwtConverterFactory jwtConverterFactory;
+ @Mock
JwtUserConverter jwtUserConverter;
private final User user = defaultUser(null);
@@ -138,6 +141,7 @@ void getAuthorizationUserShouldReturnAuthorizationUser() {
AuthorizationUser authorizationUser = defaultAuthorizationUser();
setSecurityContext(token);
+ when(jwtConverterFactory.getJwtUserConverter()).thenReturn(jwtUserConverter);
when(jwtUserConverter.convert(token)).thenReturn(user);
when(authorizationRegistrationService.registerAuthorizationUser(user, token)).thenReturn(authorizationUser);
diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationServiceTest.java
index da152b99cc..12090b33c3 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationServiceTest.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/OverviewAuthorizationServiceTest.java
@@ -14,7 +14,8 @@
import java.util.List;
-import static ch.puzzle.okr.TestHelper.defaultAuthorizationUser;
+import static ch.puzzle.okr.TestHelper.*;
+import static ch.puzzle.okr.models.authorization.AuthorizationRole.READ_ALL_PUBLISHED;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.ArgumentMatchers.any;
@@ -66,4 +67,17 @@ void getFilteredOverviewShouldReturnEmptyListWhenNotAuthorized() {
List overviews = overviewAuthorizationService.getFilteredOverview(1L, List.of(5L), "");
assertThat(List.of()).hasSameElementsAs(overviews);
}
+
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ void hasWriteAllAccessShouldReturnHasRoleWriteAll(boolean hasRoleWriteAll) {
+ if (hasRoleWriteAll) {
+ when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser);
+ } else {
+ when(authorizationService.getAuthorizationUser())
+ .thenReturn(mockAuthorizationUser(defaultUser(5L), List.of(), 7L, List.of(READ_ALL_PUBLISHED)));
+ }
+
+ assertEquals(hasRoleWriteAll, overviewAuthorizationService.hasWriteAllAccess());
+ }
}
diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/TeamAuthorizationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/TeamAuthorizationServiceTest.java
index a513c2890c..a736e3cd9b 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/authorization/TeamAuthorizationServiceTest.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/TeamAuthorizationServiceTest.java
@@ -5,6 +5,8 @@
import ch.puzzle.okr.service.business.TeamBusinessService;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;
@@ -37,7 +39,7 @@ class TeamAuthorizationServiceTest {
@Test
void createEntityShouldReturnTeamWhenAuthorized() {
when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser);
- when(teamAuthorizationService.createEntity(newTeam)).thenReturn(newTeam);
+ when(teamBusinessService.createTeam(newTeam)).thenReturn(newTeam);
Team team = teamAuthorizationService.createEntity(newTeam);
assertEquals(newTeam, team);
@@ -93,12 +95,19 @@ void deleteEntityByIdShouldThrowExceptionWhenNotAuthorized() {
assertEquals(reason, exception.getReason());
}
- @Test
- void getEntitiesShouldReturnTeams() {
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ void getAllTeamsShouldReturnAllTeams(boolean isWriteable) {
List teamList = List.of(newTeam, newTeam);
+ if (isWriteable) {
+ when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser);
+ } else {
+ when(authorizationService.getAuthorizationUser()).thenReturn(userWithoutWriteAllRole());
+ }
when(teamBusinessService.getAllTeams()).thenReturn(teamList);
- List teams = teamAuthorizationService.getEntities();
+ List teams = teamAuthorizationService.getAllTeams();
assertEquals(teamList, teams);
+ teams.forEach(team -> assertEquals(isWriteable, team.isWriteable()));
}
}
diff --git a/backend/src/test/java/ch/puzzle/okr/service/authorization/UserAuthorizationServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/authorization/UserAuthorizationServiceTest.java
new file mode 100644
index 0000000000..dadb590a4b
--- /dev/null
+++ b/backend/src/test/java/ch/puzzle/okr/service/authorization/UserAuthorizationServiceTest.java
@@ -0,0 +1,48 @@
+package ch.puzzle.okr.service.authorization;
+
+import ch.puzzle.okr.models.User;
+import ch.puzzle.okr.models.authorization.AuthorizationUser;
+import ch.puzzle.okr.service.business.UserBusinessService;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.mockito.InjectMocks;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
+
+import java.util.List;
+
+import static ch.puzzle.okr.TestHelper.defaultAuthorizationUser;
+import static ch.puzzle.okr.TestHelper.userWithoutWriteAllRole;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.when;
+
+@ExtendWith(MockitoExtension.class)
+public class UserAuthorizationServiceTest {
+ @Mock
+ UserBusinessService userBusinessService;
+ @Mock
+ AuthorizationService authorizationService;
+ @InjectMocks
+ private UserAuthorizationService userAuthorizationService;
+
+ private final AuthorizationUser authorizationUser = defaultAuthorizationUser();
+ User user = User.Builder.builder().withId(5L).withFirstname("firstname").withLastname("lastname")
+ .withUsername("username").withEmail("lastname@puzzle.ch").build();
+
+ @ParameterizedTest
+ @ValueSource(booleans = { true, false })
+ void getAllUsersShouldReturnAllUsers(boolean isWriteable) {
+ List userList = List.of(user, user);
+ if (isWriteable) {
+ when(authorizationService.getAuthorizationUser()).thenReturn(authorizationUser);
+ } else {
+ when(authorizationService.getAuthorizationUser()).thenReturn(userWithoutWriteAllRole());
+ }
+ when(userBusinessService.getAllUsers()).thenReturn(userList);
+
+ List users = userAuthorizationService.getAllUsers();
+ assertEquals(userList, users);
+ users.forEach(user -> assertEquals(isWriteable, user.isWriteable()));
+ }
+}
diff --git a/backend/src/test/java/ch/puzzle/okr/service/business/ObjectiveBusinessServiceTest.java b/backend/src/test/java/ch/puzzle/okr/service/business/ObjectiveBusinessServiceTest.java
index d268668286..eb16d3b652 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/business/ObjectiveBusinessServiceTest.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/business/ObjectiveBusinessServiceTest.java
@@ -23,6 +23,7 @@
import static ch.puzzle.okr.TestHelper.defaultAuthorizationUser;
import static ch.puzzle.okr.models.State.DRAFT;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
@@ -57,15 +58,25 @@ class ObjectiveBusinessServiceTest {
@Test
void getOneObjective() {
- Mockito.when(objectivePersistenceService.findById(5L)).thenReturn(objective);
+ when(objectivePersistenceService.findById(5L)).thenReturn(objective);
+
Objective realObjective = objectiveBusinessService.getEntityById(5L);
assertEquals("Objective 1", realObjective.getTitle());
}
+ @Test
+ void getEntitiesByTeamId() {
+ when(objectivePersistenceService.findObjectiveByTeamId(anyLong())).thenReturn(List.of(objective));
+
+ List entities = objectiveBusinessService.getEntitiesByTeamId(5L);
+
+ assertThat(entities).hasSameElementsAs(List.of(objective));
+ }
+
@Test
void shouldNotFindTheObjective() {
- Mockito.when(objectivePersistenceService.findById(6L))
+ when(objectivePersistenceService.findById(6L))
.thenThrow(new ResponseStatusException(NOT_FOUND, "Objective with id 6 not found"));
ResponseStatusException exception = assertThrows(ResponseStatusException.class,
@@ -94,7 +105,7 @@ void shouldSaveANewObjective() {
void shouldNotThrowResponseStatusExceptionWhenPuttingNullId() {
Objective objective1 = Objective.Builder.builder().withId(null).withTitle("Title")
.withDescription("Description").withModifiedOn(LocalDateTime.now()).build();
- Mockito.when(objectiveBusinessService.createEntity(objective1, authorizationUser)).thenReturn(fullObjective);
+ when(objectiveBusinessService.createEntity(objective1, authorizationUser)).thenReturn(fullObjective);
Objective savedObjective = objectiveBusinessService.createEntity(objective1, authorizationUser);
assertNull(savedObjective.getId());
diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/ActionPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/ActionPersistenceServiceIT.java
index d350b8895f..881516f55a 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/persistence/ActionPersistenceServiceIT.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/ActionPersistenceServiceIT.java
@@ -11,8 +11,8 @@
import java.util.List;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;
@SpringIntegrationTest
class ActionPersistenceServiceIT {
@@ -21,7 +21,12 @@ class ActionPersistenceServiceIT {
private ActionPersistenceService actionPersistenceService;
private static Action createAction(Long id) {
- return Action.Builder.builder().withId(id).withAction("Neue Katze").withPriority(0).withIsChecked(false)
+ return createAction(id, 1);
+ }
+
+ private static Action createAction(Long id, int version) {
+ return Action.Builder.builder().withId(id).withVersion(version).withAction("Neue Katze").withPriority(0)
+ .withIsChecked(false)
.withKeyResult(KeyResultMetric.Builder.builder().withBaseline(1.0).withStretchGoal(13.0).withId(8L)
.withObjective(Objective.Builder.builder().withId(1L).build()).build())
.build();
@@ -68,6 +73,18 @@ void updateActionShouldUpdateAction() {
assertEquals(4, updateAction.getPriority());
}
+ @Test
+ void updateActionShouldThrowExceptionWhenAlreadyUpdated() {
+ createdAction = actionPersistenceService.save(createAction(null));
+ Action changedAction = createAction(createdAction.getId(), 0);
+ changedAction.setAction("Updated Action");
+
+ ResponseStatusException exception = assertThrows(ResponseStatusException.class,
+ () -> actionPersistenceService.save(changedAction));
+ assertEquals(UNPROCESSABLE_ENTITY, exception.getStatus());
+ assertTrue(exception.getReason().contains("updated or deleted by another user"));
+ }
+
@Test
void getAllActionsShouldReturnListOfAllActions() {
List actions = actionPersistenceService.findAll();
diff --git a/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java b/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java
index 2b21ba53bb..f1cfc68165 100644
--- a/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java
+++ b/backend/src/test/java/ch/puzzle/okr/service/persistence/TeamPersistenceServiceIT.java
@@ -21,6 +21,7 @@
import static org.junit.jupiter.api.Assertions.*;
import static org.junit.jupiter.params.provider.Arguments.arguments;
import static org.springframework.http.HttpStatus.NOT_FOUND;
+import static org.springframework.http.HttpStatus.UNPROCESSABLE_ENTITY;
@SpringIntegrationTest
class TeamPersistenceServiceIT {
@@ -92,6 +93,19 @@ void shouldUpdateTeamProperly() {
assertEquals("Updated Team", returnedTeam.getName());
}
+ @Test
+ void updateTeamShouldThrowExceptionWhenAlreadyUpdated() {
+ Team team = Team.Builder.builder().withVersion(1).withName("New Team").build();
+ createdTeam = teamPersistenceService.save(team);
+ Team changedTeam = Team.Builder.builder().withId(createdTeam.getId()).withVersion(0).withName("Changed Team")
+ .build();
+
+ ResponseStatusException exception = assertThrows(ResponseStatusException.class,
+ () -> teamPersistenceService.save(changedTeam));
+ assertEquals(UNPROCESSABLE_ENTITY, exception.getStatus());
+ assertTrue(exception.getReason().contains("updated or deleted by another user"));
+ }
+
@Test
void shouldDeleteTeam() {
Team team = Team.Builder.builder().withName("New Team").build();
diff --git a/backend/src/test/resources/logback.xml b/backend/src/test/resources/logback.xml
new file mode 100644
index 0000000000..0f596d4a21
--- /dev/null
+++ b/backend/src/test/resources/logback.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.spec.ts b/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.spec.ts
index aa6a5b7565..480fc9cfd4 100644
--- a/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.spec.ts
+++ b/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.spec.ts
@@ -53,8 +53,8 @@ const quarterService = {
const teamService = {
getAllTeams(): Observable {
return of([
- { id: 1, name: teamMin1.name, writeable: true, organisations: [] },
- { id: 4, name: 'team2', writeable: true, organisations: [] },
+ { id: 1, version: 2, name: teamMin1.name, writeable: true, organisations: [] },
+ { id: 4, version: 5, name: 'team2', writeable: true, organisations: [] },
]);
},
};
diff --git a/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.ts b/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.ts
index d700eb6322..6e998ae231 100644
--- a/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.ts
+++ b/frontend/src/app/shared/dialog/objective-dialog/objective-form.component.ts
@@ -52,6 +52,7 @@ export class ObjectiveFormComponent implements OnInit {
objective: {
objectiveId?: number;
teamId?: number;
+ teamVersion?: number;
};
},
) {}
diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.spec.ts b/frontend/src/app/shared/dialog/team-management/team-management.component.spec.ts
index c5ad9de7cb..e4e5819190 100644
--- a/frontend/src/app/shared/dialog/team-management/team-management.component.spec.ts
+++ b/frontend/src/app/shared/dialog/team-management/team-management.component.spec.ts
@@ -137,7 +137,11 @@ describe('TeamManagementComponent', () => {
fixture.detectChanges();
component.saveTeam();
expect(teamServiceMock.updateTeam).toHaveBeenCalled();
- expect(teamServiceMock.updateTeam).toHaveBeenCalledWith({ ...teamFormObject, id: teamMin1.id } as Team);
+ expect(teamServiceMock.updateTeam).toHaveBeenCalledWith({
+ ...teamFormObject,
+ id: teamMin1.id,
+ version: teamMin1.version,
+ } as Team);
});
it('should call service method to delete team', async () => {
diff --git a/frontend/src/app/shared/dialog/team-management/team-management.component.ts b/frontend/src/app/shared/dialog/team-management/team-management.component.ts
index c9565a61f4..04db6ff091 100644
--- a/frontend/src/app/shared/dialog/team-management/team-management.component.ts
+++ b/frontend/src/app/shared/dialog/team-management/team-management.component.ts
@@ -33,7 +33,10 @@ export class TeamManagementComponent implements OnInit {
private dialog: MatDialog,
private organisationService: OrganisationService,
private teamService: TeamService,
- @Inject(MAT_DIALOG_DATA) public data: { team: TeamMin },
+ @Inject(MAT_DIALOG_DATA)
+ public data: {
+ team: TeamMin;
+ },
) {}
ngOnInit(): void {
@@ -60,7 +63,11 @@ export class TeamManagementComponent implements OnInit {
this.dialogRef.close(result);
});
} else {
- let updatedTeam: Team = { ...this.teamForm.value, id: this.data.team.id } as Team;
+ let updatedTeam: Team = {
+ ...this.teamForm.value,
+ id: this.data.team.id,
+ version: this.data.team.version,
+ } as Team;
this.teamService.updateTeam(updatedTeam).subscribe((result) => {
this.dialogRef.close(result);
});
@@ -117,6 +124,4 @@ export class TeamManagementComponent implements OnInit {
compareWithFunc(a: Organisation, b: Organisation) {
return a.orgName === b.orgName;
}
-
- protected readonly OrganisationState = OrganisationState;
}
diff --git a/frontend/src/app/shared/testData.ts b/frontend/src/app/shared/testData.ts
index 8d18988959..2bf7cf8d0d 100644
--- a/frontend/src/app/shared/testData.ts
+++ b/frontend/src/app/shared/testData.ts
@@ -43,28 +43,33 @@ export const teamFormObject = {
export const teamMin1: TeamMin = {
id: 1,
+ version: 2,
name: 'Marketing Team',
writable: true,
} as TeamMin;
export const teamMin2: TeamMin = {
id: 1,
+ version: 3,
name: 'Marketing Team',
writable: false,
} as TeamMin;
export const team1: Team = {
id: 1,
+ version: 2,
name: 'Team2',
} as Team;
export const team2: Team = {
id: 2,
+ version: 3,
name: 'Team2',
} as Team;
export const team3: Team = {
id: 3,
+ version: 4,
name: 'Team3',
} as Team;
diff --git a/frontend/src/app/shared/types/model/Team.ts b/frontend/src/app/shared/types/model/Team.ts
index 0fec92d6f5..4a196320b9 100644
--- a/frontend/src/app/shared/types/model/Team.ts
+++ b/frontend/src/app/shared/types/model/Team.ts
@@ -2,6 +2,7 @@ import { Organisation } from './Organisation';
export interface Team {
id: number;
+ version: number;
name: string;
organisations: Organisation[];
}
diff --git a/frontend/src/app/shared/types/model/TeamMin.ts b/frontend/src/app/shared/types/model/TeamMin.ts
index cc3abf978f..6743ff678e 100644
--- a/frontend/src/app/shared/types/model/TeamMin.ts
+++ b/frontend/src/app/shared/types/model/TeamMin.ts
@@ -1,5 +1,6 @@
export interface TeamMin {
id: number;
+ version: number;
name: string;
writable: boolean;
hasInActiveOrganisations: boolean;
diff --git a/frontend/src/app/team/team.component.ts b/frontend/src/app/team/team.component.ts
index b633578d3f..4e4692467e 100644
--- a/frontend/src/app/team/team.component.ts
+++ b/frontend/src/app/team/team.component.ts
@@ -39,7 +39,12 @@ export class TeamComponent {
createObjective() {
const matDialogRef = this.dialog.open(ObjectiveFormComponent, {
- data: { objective: { teamId: this.overviewEntity.value.team.id } },
+ data: {
+ objective: {
+ teamId: this.overviewEntity.value.team.id,
+ teamVersion: this.overviewEntity.value.team.version,
+ },
+ },
width: '45em',
});
matDialogRef.afterClosed().subscribe((result) => {