From 8d5e4b3378557df217f628f5a672ce363d47e399 Mon Sep 17 00:00:00 2001 From: Romuald Lemesle Date: Wed, 25 Sep 2024 20:10:11 +0200 Subject: [PATCH] [backend/frontend] Fix removing a team from the context doesn't work --- .../io/openbas/rest/exercise/ExerciseApi.java | 9 +-- .../rest/exercise/ExerciseService.java | 26 +++++-- .../io/openbas/service/ScenarioService.java | 6 +- .../openbas/service/ExerciseServiceTest.java | 70 ++++++++++++++++--- .../openbas/service/ScenarioServiceTest.java | 66 ++++++++++++++--- .../repository/ExerciseRepository.java | 15 +++- .../ExerciseTeamUserRepository.java | 2 + .../database/repository/InjectRepository.java | 17 +++-- .../repository/ScenarioRepository.java | 15 +++- .../ScenarioTeamUserRepository.java | 12 +--- .../database/repository/TeamRepository.java | 5 +- .../database/repository/UserRepository.java | 9 +-- 12 files changed, 194 insertions(+), 58 deletions(-) diff --git a/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseApi.java b/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseApi.java index c9c11f04ef..e21bc4395d 100644 --- a/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseApi.java @@ -230,14 +230,7 @@ public Iterable addExerciseTeams( @PreAuthorize("isExercisePlanner(#exerciseId)") public Iterable removeExerciseTeams(@PathVariable String exerciseId, @Valid @RequestBody ExerciseUpdateTeamsInput input) { - Exercise exercise = this.exerciseService.exercise(exerciseId); - // Remove teams from exercise - List teams = exercise.getTeams().stream().filter(team -> !input.getTeamIds().contains(team.getId())).toList(); - exercise.setTeams(new ArrayList<>(teams)); - this.exerciseService.updateExercise(exercise); - // Remove all association between users / exercises / teams - input.getTeamIds().forEach(exerciseTeamUserRepository::deleteTeamFromAllReferences); - return teamRepository.findAllById(input.getTeamIds()); + return this.exerciseService.removeTeams(exerciseId, input.getTeamIds()); } @Transactional(rollbackOn = Exception.class) diff --git a/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseService.java b/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseService.java index 46b36c8f25..a8d46c3d0d 100644 --- a/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseService.java +++ b/openbas-api/src/main/java/io/openbas/rest/exercise/ExerciseService.java @@ -4,10 +4,8 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import io.openbas.config.OpenBASConfig; import io.openbas.database.model.*; -import io.openbas.database.repository.ArticleRepository; -import io.openbas.database.repository.ExerciseRepository; +import io.openbas.database.repository.*; import io.openbas.rest.exception.ElementNotFoundException; -import io.openbas.database.repository.TeamRepository; import io.openbas.rest.exercise.form.ExerciseSimple; import io.openbas.rest.inject.service.InjectDuplicateService; import io.openbas.service.GrantService; @@ -20,7 +18,6 @@ import jakarta.persistence.Tuple; import jakarta.persistence.TypedQuery; import jakarta.persistence.criteria.*; -import jakarta.transaction.Transactional; import jakarta.validation.constraints.NotBlank; import lombok.RequiredArgsConstructor; import org.jetbrains.annotations.NotNull; @@ -30,6 +27,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import java.time.Instant; @@ -65,6 +63,8 @@ public class ExerciseService { private final ArticleRepository articleRepository; private final ExerciseRepository exerciseRepository; private final TeamRepository teamRepository; + private final ExerciseTeamUserRepository exerciseTeamUserRepository; + private final InjectRepository injectRepository; // region properties @Value("${openbas.mail.imap.enabled}") @@ -195,7 +195,7 @@ private List execution(TypedQuery query) { // -- CREATION -- - @Transactional(rollbackOn = Exception.class) + @Transactional(rollbackFor = Exception.class) public Exercise createExercise(@NotNull final Exercise exercise){ if (imapEnabled) { exercise.setFrom(imapUsername); @@ -407,4 +407,20 @@ private void getObjectives(Exercise duplicatedExercise, Exercise originalExercis duplicatedExercise.setObjectives(duplicatedObjectives); } + // -- TEAMS -- + + @Transactional(rollbackFor = Exception.class) + public Iterable removeTeams(@NotBlank final String exerciseId, @NotNull final List teamIds) { + Exercise exercise = exerciseRepository.findById(exerciseId).orElseThrow(ElementNotFoundException::new); + // Remove teams from exercise + List teams = this.exerciseRepository.teams(exercise.getId(), teamIds); + exercise.setTeams(fromIterable(teams)); + this.exerciseRepository.save(exercise); + // Remove all association between users / exercises / teams + teamIds.forEach(exerciseTeamUserRepository::deleteTeamFromAllReferences); + // Remove all association between injects and teams + this.injectRepository.removeTeams(teamIds); + return teamRepository.findAllById(teamIds); + } + } diff --git a/openbas-api/src/main/java/io/openbas/service/ScenarioService.java b/openbas-api/src/main/java/io/openbas/service/ScenarioService.java index 21bbf2be17..2d17770677 100644 --- a/openbas-api/src/main/java/io/openbas/service/ScenarioService.java +++ b/openbas-api/src/main/java/io/openbas/service/ScenarioService.java @@ -97,6 +97,7 @@ public class ScenarioService { private final TeamService teamService; private final FileService fileService; private final InjectDuplicateService injectDuplicateService; + private final InjectRepository injectRepository; @Transactional public Scenario createScenario(@NotNull final Scenario scenario) { @@ -440,13 +441,16 @@ public Iterable addTeams(@NotBlank final String scenarioId, @NotNull final return teamsToAdd; } + @Transactional(rollbackFor = Exception.class) public Iterable removeTeams(@NotBlank final String scenarioId, @NotNull final List teamIds) { Scenario scenario = this.scenario(scenarioId); - List teams = scenario.getTeams().stream().filter(team -> !teamIds.contains(team.getId())).toList(); + List teams = this.scenarioRepository.teams(scenario.getId(), teamIds); scenario.setTeams(new ArrayList<>(teams)); this.updateScenario(scenario); // Remove all association between users / exercises / teams teamIds.forEach(this.scenarioTeamUserRepository::deleteTeamFromAllReferences); + // Remove all association between injects and teams + this.injectRepository.removeTeams(teamIds); return teamRepository.findAllById(teamIds); } diff --git a/openbas-api/src/test/java/io/openbas/service/ExerciseServiceTest.java b/openbas-api/src/test/java/io/openbas/service/ExerciseServiceTest.java index 35466b8959..a89df7d283 100644 --- a/openbas-api/src/test/java/io/openbas/service/ExerciseServiceTest.java +++ b/openbas-api/src/test/java/io/openbas/service/ExerciseServiceTest.java @@ -1,15 +1,12 @@ package io.openbas.service; import io.openbas.database.model.*; -import io.openbas.database.repository.ArticleRepository; -import io.openbas.database.repository.ExerciseRepository; -import io.openbas.database.repository.TeamRepository; +import io.openbas.database.repository.*; import io.openbas.rest.exercise.ExerciseService; import io.openbas.rest.inject.service.InjectDuplicateService; +import io.openbas.utils.fixtures.ExerciseFixture; import jakarta.transaction.Transactional; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.mockito.InjectMocks; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; @@ -18,12 +15,16 @@ import java.util.ArrayList; import java.util.List; -import static io.openbas.utils.fixtures.TeamFixture.getTeam; +import static io.openbas.injectors.email.EmailContract.EMAIL_DEFAULT; import static io.openbas.utils.fixtures.ExerciseFixture.getExercise; +import static io.openbas.utils.fixtures.InjectFixture.getInjectForEmailContract; +import static io.openbas.utils.fixtures.TeamFixture.getTeam; +import static io.openbas.utils.fixtures.UserFixture.getUser; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @SpringBootTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class ExerciseServiceTest { @Mock GrantService grantService; @@ -43,12 +44,33 @@ public class ExerciseServiceTest { @Autowired private TeamRepository teamRepository; + @Autowired + private UserRepository userRepository; + @Autowired + private InjectRepository injectRepository; + @Autowired + private ExerciseTeamUserRepository exerciseTeamUserRepository; + @Autowired + private InjectorContractRepository injectorContractRepository; + + private static String USER_ID; + private static String TEAM_ID; + private static String INJECT_ID; + @InjectMocks private ExerciseService exerciseService; @BeforeEach void setUp() { exerciseService = new ExerciseService(grantService, injectService, injectDuplicateService, - teamService,variableService, articleRepository,exerciseRepository,teamRepository); + teamService,variableService, articleRepository,exerciseRepository,teamRepository, + exerciseTeamUserRepository, injectRepository); + } + + @AfterAll + public void teardown() { + this.userRepository.deleteById(USER_ID); + this.teamRepository.deleteById(TEAM_ID); + this.injectRepository.deleteById(INJECT_ID); } @DisplayName("Should create new contextual teams while exercise duplication") @@ -78,4 +100,36 @@ void createNewContextualTeamsWhileExerciseDuplication(){ } }); } + + @DisplayName("Should remove team from exercise") + @Test + void testRemoveTeams() { + // -- PREPARE -- + User user = getUser(); + User userSaved = this.userRepository.saveAndFlush(user); + USER_ID = userSaved.getId(); + Team team = getTeam(user); + Team teamSaved = this.teamRepository.saveAndFlush(team); + TEAM_ID = teamSaved.getId(); + Exercise exercise = ExerciseFixture.getExercise(); + exercise.setTeams(List.of(teamSaved)); + exercise.setFrom(user.getEmail()); + Exercise exerciseSaved = this.exerciseRepository.saveAndFlush(exercise); + + InjectorContract injectorContract = this.injectorContractRepository.findById(EMAIL_DEFAULT).orElseThrow(); + Inject injectDefaultEmail = getInjectForEmailContract(injectorContract); + injectDefaultEmail.setExercise(exerciseSaved); + injectDefaultEmail.setTeams(List.of(teamSaved)); + Inject injectDefaultEmailSaved = this.injectRepository.saveAndFlush(injectDefaultEmail); + INJECT_ID = injectDefaultEmailSaved.getId(); + + // -- EXECUTE -- + this.exerciseService.removeTeams(exerciseSaved.getId(), List.of(teamSaved.getId())); + + // -- ASSERT -- + List teams = this.exerciseRepository.teams(exerciseSaved.getId(), List.of(TEAM_ID)); + assertEquals(0, teams.size()); + Inject injectAssert = this.injectRepository.findById(INJECT_ID).orElseThrow(); + assertEquals(0, injectAssert.getTeams().size()); + } } diff --git a/openbas-api/src/test/java/io/openbas/service/ScenarioServiceTest.java b/openbas-api/src/test/java/io/openbas/service/ScenarioServiceTest.java index dcd840852c..c78b3b49e2 100644 --- a/openbas-api/src/test/java/io/openbas/service/ScenarioServiceTest.java +++ b/openbas-api/src/test/java/io/openbas/service/ScenarioServiceTest.java @@ -4,23 +4,26 @@ import io.openbas.database.repository.*; import io.openbas.rest.inject.service.InjectDuplicateService; import io.openbas.utils.fixtures.ScenarioFixture; -import jakarta.transaction.Transactional; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.*; import org.mockito.InjectMocks; import org.mockito.Mock; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; -import static io.openbas.utils.fixtures.TeamFixture.getTeam; -import static io.openbas.utils.fixtures.ScenarioFixture.getScenario; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import static io.openbas.injectors.email.EmailContract.EMAIL_DEFAULT; +import static io.openbas.utils.fixtures.InjectFixture.getInjectForEmailContract; +import static io.openbas.utils.fixtures.TeamFixture.getTeam; +import static io.openbas.utils.fixtures.UserFixture.getUser; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotEquals; @SpringBootTest +@TestInstance(TestInstance.Lifecycle.PER_CLASS) public class ScenarioServiceTest { @Autowired @@ -51,22 +54,33 @@ public class ScenarioServiceTest { FileService fileService; @Autowired private InjectDuplicateService injectDuplicateService; - + @Autowired + private InjectorContractRepository injectorContractRepository; @InjectMocks private ScenarioService scenarioService; + private static String USER_ID; + private static String TEAM_ID; + private static String INJECT_ID; + @BeforeEach void setUp() { scenarioService = new ScenarioService(scenarioRepository, teamRepository, userRepository, documentRepository, scenarioTeamUserRepository, articleRepository, grantService, variableService, challengeService, - teamService, fileService, injectDuplicateService + teamService, fileService, injectDuplicateService, injectRepository ); } + @AfterAll + public void teardown() { + this.userRepository.deleteById(USER_ID); + this.teamRepository.deleteById(TEAM_ID); + this.injectRepository.deleteById(INJECT_ID); + } + @DisplayName("Should create new contextual teams during scenario duplication") @Test - @Transactional(rollbackOn = Exception.class) void createNewContextualTeamsDuringScenarioDuplication(){ // -- PREPARE -- List scenarioTeams = new ArrayList<>();; @@ -110,4 +124,36 @@ void createNewContextualTeamsDuringScenarioDuplication(){ } }); } + + @DisplayName("Should remove team from scenario") + @Test + void testRemoveTeams() { + // -- PREPARE -- + User user = getUser(); + User userSaved = this.userRepository.saveAndFlush(user); + USER_ID = userSaved.getId(); + Team team = getTeam(user); + Team teamSaved = this.teamRepository.saveAndFlush(team); + TEAM_ID = teamSaved.getId(); + Scenario scenario = ScenarioFixture.getScenario(); + scenario.setTeams(List.of(teamSaved)); + Scenario scenarioSaved = this.scenarioRepository.saveAndFlush(scenario); + + InjectorContract injectorContract = this.injectorContractRepository.findById(EMAIL_DEFAULT).orElseThrow(); + Inject injectDefaultEmail = getInjectForEmailContract(injectorContract); + injectDefaultEmail.setScenario(scenarioSaved); + injectDefaultEmail.setTeams(List.of(teamSaved)); + Inject injectDefaultEmailSaved = this.injectRepository.saveAndFlush(injectDefaultEmail); + INJECT_ID = injectDefaultEmailSaved.getId(); + + // -- EXECUTE -- + this.scenarioService.removeTeams(scenarioSaved.getId(), List.of(teamSaved.getId())); + + // -- ASSERT -- + List teams = this.scenarioRepository.teams(scenarioSaved.getId(), List.of(TEAM_ID)); + assertEquals(0, teams.size()); + Inject injectAssert = this.injectRepository.findById(INJECT_ID).orElseThrow(); + assertEquals(0, injectAssert.getTeams().size()); + } + } diff --git a/openbas-model/src/main/java/io/openbas/database/repository/ExerciseRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/ExerciseRepository.java index 8a88ecd961..1db60407f0 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/ExerciseRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/ExerciseRepository.java @@ -1,13 +1,14 @@ package io.openbas.database.repository; import io.openbas.database.model.Exercise; +import io.openbas.database.model.Team; import io.openbas.database.raw.RawExercise; import io.openbas.database.raw.RawGlobalInjectExpectation; import io.openbas.database.raw.RawInjectExpectation; import jakarta.validation.constraints.NotNull; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -16,7 +17,7 @@ import java.util.Optional; @Repository -public interface ExerciseRepository extends CrudRepository, +public interface ExerciseRepository extends JpaRepository, StatisticRepository, JpaSpecificationExecutor { @@ -186,4 +187,14 @@ public interface ExerciseRepository extends CrudRepository, "WHERE ex.exercise_id = :exerciseId " + "GROUP BY ex.exercise_id, inj.inject_scenario, se.scenario_id ;", nativeQuery = true) RawExercise rawDetailsById(@Param("exerciseId") String exerciseId); + + // -- TEAM -- + + @Query(value = "SELECT t.* " + + "FROM teams t " + + "JOIN exercises_teams et ON t.team_id = et.team_id " + + "WHERE et.exercise_id = :exerciseId " + + " AND t.team_id NOT IN (:teamIds) ", nativeQuery = true) + List teams(@Param("exerciseId") final String exerciseId, @Param("teamIds") final List teamIds); + } diff --git a/openbas-model/src/main/java/io/openbas/database/repository/ExerciseTeamUserRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/ExerciseTeamUserRepository.java index 6982c84d7a..b5871c0e2a 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/ExerciseTeamUserRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/ExerciseTeamUserRepository.java @@ -10,6 +10,7 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import java.util.List; import java.util.Optional; @@ -26,6 +27,7 @@ public interface ExerciseTeamUserRepository extends CrudRepository, JpaSpecificationExecutor, +public interface InjectRepository extends + JpaRepository, JpaSpecificationExecutor, StatisticRepository { @NotNull @@ -148,4 +150,11 @@ void importSaveForScenario(@Param("id") String id, "GROUP BY org.organization_id", nativeQuery = true) List rawAll(); + + // -- TEAM -- + + @Modifying + @Query(value = "delete from injects_teams i where i.team_id in :teamIds", nativeQuery = true) + @Transactional + void removeTeams(@Param("teamIds") final List teamIds); } diff --git a/openbas-model/src/main/java/io/openbas/database/repository/ScenarioRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/ScenarioRepository.java index c699011b83..3f19aac57a 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/ScenarioRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/ScenarioRepository.java @@ -1,15 +1,16 @@ package io.openbas.database.repository; import io.openbas.database.model.Scenario; +import io.openbas.database.model.Team; import io.openbas.database.raw.RawScenario; import org.jetbrains.annotations.NotNull; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -17,7 +18,8 @@ import java.util.List; @Repository -public interface ScenarioRepository extends CrudRepository, +public interface ScenarioRepository extends + JpaRepository, StatisticRepository, JpaSpecificationExecutor { @@ -86,4 +88,13 @@ public interface ScenarioRepository extends CrudRepository, @EntityGraph(value = "Scenario.tags-injects", type = EntityGraph.EntityGraphType.LOAD) Page findAll(@NotNull Specification spec, @NotNull Pageable pageable); + // -- TEAM -- + + @Query(value = "SELECT t.* " + + "FROM teams t " + + "JOIN scenarios_teams st ON t.team_id = st.team_id " + + "WHERE st.scenario_id = :scenarioId " + + " AND t.team_id NOT IN (:teamIds) ", nativeQuery = true) + List teams(@Param("scenarioId") final String scenarioId, @Param("teamIds") final List teamIds); + } diff --git a/openbas-model/src/main/java/io/openbas/database/repository/ScenarioTeamUserRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/ScenarioTeamUserRepository.java index 62244a3d54..ebff9d085e 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/ScenarioTeamUserRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/ScenarioTeamUserRepository.java @@ -9,6 +9,7 @@ import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Transactional; import java.util.Optional; @@ -18,18 +19,9 @@ public interface ScenarioTeamUserRepository extends CrudRepository findById(@NotNull final ScenarioTeamUserId id); - @Modifying - @Query(value = "delete from scenarios_teams_users i where i.user_id = :userId", nativeQuery = true) - void deleteUserFromAllReferences(@Param("userId") final String userId); - @Modifying @Query(value = "delete from scenarios_teams_users i where i.team_id = :teamId", nativeQuery = true) + @Transactional void deleteTeamFromAllReferences(@Param("teamId") final String teamId); - @Modifying - @Query(value = "insert into scenarios_teams_users (exercise_id, team_id, user_id) " + - "values (:exerciseId, :teamId, :userId)", nativeQuery = true) - void addExerciseTeamUser(@Param("exerciseId") final String exerciseId, - @Param("teamId") final String teamId, - @Param("userId") final String userId); } diff --git a/openbas-model/src/main/java/io/openbas/database/repository/TeamRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/TeamRepository.java index 3da83c0310..fb0cef68db 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/TeamRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/TeamRepository.java @@ -7,9 +7,9 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; import org.springframework.data.jpa.repository.EntityGraph; +import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -18,7 +18,8 @@ import java.util.Optional; @Repository -public interface TeamRepository extends CrudRepository, +public interface TeamRepository extends + JpaRepository, StatisticRepository, JpaSpecificationExecutor { diff --git a/openbas-model/src/main/java/io/openbas/database/repository/UserRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/UserRepository.java index 8e9095ba38..fce6045d8b 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/UserRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/UserRepository.java @@ -7,11 +7,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.domain.Specification; -import org.springframework.data.jpa.repository.EntityGraph; -import org.springframework.data.jpa.repository.JpaSpecificationExecutor; -import org.springframework.data.jpa.repository.Modifying; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.CrudRepository; +import org.springframework.data.jpa.repository.*; import org.springframework.data.repository.query.Param; import org.springframework.stereotype.Repository; @@ -20,7 +16,8 @@ import java.util.Optional; @Repository -public interface UserRepository extends CrudRepository, JpaSpecificationExecutor, +public interface UserRepository extends + JpaRepository, JpaSpecificationExecutor, StatisticRepository { @NotNull