diff --git a/openbas-api/src/main/java/io/openbas/importer/V1_DataImporter.java b/openbas-api/src/main/java/io/openbas/importer/V1_DataImporter.java index a2030484f0..aee2b4da27 100644 --- a/openbas-api/src/main/java/io/openbas/importer/V1_DataImporter.java +++ b/openbas-api/src/main/java/io/openbas/importer/V1_DataImporter.java @@ -13,16 +13,16 @@ import jakarta.activation.MimetypesFileTypeMap; import jakarta.annotation.Resource; import jakarta.validation.constraints.NotNull; +import lombok.extern.java.Log; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; import java.time.Instant; import java.util.*; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import static io.openbas.helper.StreamHelper.fromIterable; import static io.openbas.helper.StreamHelper.iterableToSet; import static io.openbas.injectors.challenge.ChallengeContract.CHALLENGE_PUBLISH; import static io.openbas.injectors.channel.ChannelContract.CHANNEL_PUBLISH; @@ -32,6 +32,7 @@ import static org.springframework.util.StringUtils.hasText; @Component +@Log public class V1_DataImporter implements Importer { // region variables @@ -183,77 +184,18 @@ private String handleInjectContent(Map baseIds, String contract, J return content; } - private void importInjects(Map baseIds, String exerciseId, String scenarioId, List injects) { - List injected = new ArrayList<>(); - injects.forEach(injectNode -> { - String injectId = UUID.randomUUID().toString(); - injected.add(injectId); - String id = injectNode.get("inject_id").textValue(); - String title = injectNode.get("inject_title").textValue(); - String description = injectNode.get("inject_description").textValue(); - String country = injectNode.get("inject_country").textValue(); - String city = injectNode.get("inject_city").textValue(); - String injectorContractId = null; - JsonNode injectContractNode = injectNode.get("inject_injector_contract"); - if (injectContractNode != null) { - injectorContractId = injectContractNode.get("injector_contract_id").textValue(); - } - // If contract is not know, inject can't be imported - String content = handleInjectContent(baseIds, injectorContractId, injectNode); - JsonNode dependsOnNode = injectNode.get("inject_depends_on"); - String dependsOn = !dependsOnNode.isNull() ? baseIds.get(dependsOnNode.asText()).getId() : null; - Long dependsDuration = injectNode.get("inject_depends_duration").asLong(); - boolean allTeams = injectNode.get("inject_all_teams").booleanValue(); - if (hasText(exerciseId)) { - injectRepository.importSaveForExercise(injectId, title, description, country, city, injectorContractId, - allTeams, - true, exerciseId, dependsOn, dependsDuration, content); - } else if (hasText(scenarioId)) { - injectRepository.importSaveForScenario(injectId, title, description, country, city, injectorContractId, - allTeams, - true, scenarioId, dependsOn, dependsDuration, content); - } - baseIds.put(id, new BaseHolder(injectId)); - // Tags - List injectTagIds = resolveJsonIds(injectNode, "inject_tags"); - injectTagIds.forEach(tagId -> { - String remappedId = baseIds.get(tagId).getId(); - injectRepository.addTag(injectId, remappedId); - }); - // Teams - List injectTeamIds = resolveJsonIds(injectNode, "inject_teams"); - injectTeamIds.forEach(teamId -> { - String remappedId = baseIds.get(teamId).getId(); - injectRepository.addTeam(injectId, remappedId); - }); - // Documents - List injectDocuments = resolveJsonElements(injectNode, "inject_documents").toList(); - injectDocuments.forEach(jsonNode -> { - String docId = jsonNode.get("document_id").textValue(); - String documentId = baseIds.get(docId).getId(); - boolean docAttached = jsonNode.get("document_attached").booleanValue(); - injectDocumentRepository.addInjectDoc(injectId, documentId, docAttached); - }); - }); - // Looking for child of created injects - List childInjects = injects.stream().filter(jsonNode -> { - String injectDependsOn = jsonNode.get("inject_depends_on").asText(); - return injected.contains(injectDependsOn); - }).toList(); - if (!childInjects.isEmpty()) { - importInjects(baseIds, exerciseId, scenarioId, childInjects); - } - } - private Set computeTagsCompletion(Set existingTags, List lookingIds, Map baseIds) { Set tags = new HashSet<>(existingTags); - Set tagsForOrganization = lookingIds.stream().map(baseIds::get).map(base -> (Tag) base) + Set tagsForOrganization = lookingIds.stream() + .map(baseIds::get) + .map(Tag.class::cast) .collect(Collectors.toSet()); tags.addAll(tagsForOrganization); return tags; } @Override + @Transactional public void importData(JsonNode importNode, Map docReferences) { Map baseIds = new HashMap<>(); final String prefix = importNode.has("exercise_information") ? "exercise_" : "scenario_"; @@ -271,22 +213,27 @@ public void importData(JsonNode importNode, Map docReferenc importObjectives(importNode, prefix, savedExercise, savedScenario, baseIds); importLessons(importNode, prefix, savedExercise, savedScenario, baseIds); importInjects(importNode, prefix, savedExercise, savedScenario, baseIds); - importVariables(importNode, prefix, savedExercise, savedScenario, baseIds); + importVariables(importNode, savedExercise, savedScenario, baseIds); } // -- TAGS -- private void importTags(JsonNode importNode, String prefix, Map baseIds) { - Stream tagsStream = resolveJsonElements(importNode, prefix + "tags"); - Map existingTagsByName = fromIterable(tagRepository.findAll()) - .stream() - .collect(Collectors.toMap(Tag::getName, Function.identity())); + resolveJsonElements(importNode, prefix + "tags").forEach(nodeTag -> { + String id = nodeTag.get("tag_id").textValue(); + if (baseIds.get(id) != null) { + // Already import + return; + } + String name = nodeTag.get("tag_name").textValue(); - tagsStream.map(this::createTag) - .forEach(tag -> baseIds.put( - tag.getId(), - existingTagsByName.getOrDefault(tag.getName(), this.tagRepository.save(tag)) - )); + List existingTags = this.tagRepository.findByNameIgnoreCase(name); + if (!existingTags.isEmpty()) { + baseIds.put(id, existingTags.getFirst()); + } else { + baseIds.put(id, this.tagRepository.save(createTag(nodeTag))); + } + }); } private Tag createTag(JsonNode jsonNode) { @@ -301,7 +248,9 @@ private Tag createTag(JsonNode jsonNode) { private Exercise importExercise(JsonNode importNode, Map baseIds) { JsonNode exerciseNode = importNode.get("exercise_information"); - if (exerciseNode == null) return null; + if (exerciseNode == null) { + return null; + } Exercise exercise = new Exercise(); exercise.setName(exerciseNode.get("exercise_name").textValue() + " (Import)"); @@ -325,7 +274,9 @@ private Exercise importExercise(JsonNode importNode, Map baseIds) private Scenario importScenario(JsonNode importNode, Map baseIds) { JsonNode scenarioNode = importNode.get("scenario_information"); - if (scenarioNode == null) return null; + if (scenarioNode == null) { + return null; + } Scenario scenario = new Scenario(); scenario.setName(scenarioNode.get("scenario_name").textValue() + " (Import)"); @@ -364,20 +315,22 @@ private Scenario importScenario(JsonNode importNode, Map baseIds) return scenarioService.createScenario(scenario); } - private void importDocuments(JsonNode importNode, String prefix, Map docReferences, Exercise savedExercise, Scenario savedScenario, Map baseIds) { + private void importDocuments(JsonNode importNode, String prefix, Map docReferences, + Exercise savedExercise, Scenario savedScenario, Map baseIds) { Stream documentsStream = resolveJsonElements(importNode, prefix + "documents"); documentsStream.forEach(nodeDoc -> { - String id = nodeDoc.get("document_id").textValue(); String target = nodeDoc.get("document_target").textValue(); ImportEntry entry = docReferences.get(target); if (entry != null) { - handleDocumentWithEntry(nodeDoc, entry, target, savedExercise, savedScenario, baseIds, id); + handleDocumentWithEntry(nodeDoc, entry, target, savedExercise, savedScenario, baseIds); } }); } - private void handleDocumentWithEntry(JsonNode nodeDoc, ImportEntry entry, String target, Exercise savedExercise, Scenario savedScenario, Map baseIds, String id) { + private void handleDocumentWithEntry( + JsonNode nodeDoc, ImportEntry entry, String target, Exercise savedExercise, + Scenario savedScenario, Map baseIds) { String contentType = new MimetypesFileTypeMap().getContentType(entry.getEntry().getName()); Optional targetDocument = this.documentRepository.findByTarget(target); @@ -388,7 +341,9 @@ private void handleDocumentWithEntry(JsonNode nodeDoc, ImportEntry entry, String } } - private void updateExistingDocument(JsonNode nodeDoc, Document document, Exercise savedExercise, Scenario savedScenario, Map baseIds) { + private void updateExistingDocument( + JsonNode nodeDoc, Document document, Exercise savedExercise, + Scenario savedScenario, Map baseIds) { if (savedExercise != null) { document.getExercises().add(savedExercise); } else if (savedScenario != null) { @@ -399,7 +354,8 @@ private void updateExistingDocument(JsonNode nodeDoc, Document document, Exercis baseIds.put(nodeDoc.get("document_id").textValue(), savedDocument); } - private void uploadNewDocument(JsonNode nodeDoc, ImportEntry entry, String target, Exercise savedExercise, Scenario savedScenario, String contentType, Map baseIds) { + private void uploadNewDocument(JsonNode nodeDoc, ImportEntry entry, String target, Exercise savedExercise, + Scenario savedScenario, String contentType, Map baseIds) { try { this.documentService.uploadFile(target, entry.getData(), entry.getEntry().getSize(), contentType); } catch (Exception e) { @@ -421,64 +377,94 @@ private void uploadNewDocument(JsonNode nodeDoc, ImportEntry entry, String targe baseIds.put(nodeDoc.get("document_id").textValue(), savedDocument); } + // -- ORGANIZATION -- + private void importOrganizations(JsonNode importNode, String prefix, Map baseIds) { - if (importNode.has(prefix + "organizations")) { - Map existingOrganizationsByName = fromIterable(organizationRepository.findAll()) - .stream() - .collect(Collectors.toMap(Organization::getName, Function.identity())); - - resolveJsonElements(importNode, prefix + "organizations") - .forEach(nodeOrganization -> { - String id = nodeOrganization.get("organization_id").textValue(); - String name = nodeOrganization.get("organization_name").textValue(); - - Organization organization = existingOrganizationsByName.getOrDefault(name, new Organization()); - if (!hasText(organization.getId())) { // new entity - organization.setDescription(nodeOrganization.get("organization_description").textValue()); - } - organization.setName(name); - organization.setTags(computeTagsCompletion(organization.getTags(), resolveJsonIds(nodeOrganization, "organization_tags"), baseIds)); - - baseIds.put(id, this.organizationRepository.save(organization)); - }); - } + resolveJsonElements(importNode, prefix + "organizations") + .forEach(nodeOrganization -> { + String id = nodeOrganization.get("organization_id").textValue(); + if (baseIds.get(id) != null) { + // Already import + return; + } + String name = nodeOrganization.get("organization_name").textValue(); + + List existingOrganizations = this.organizationRepository.findByNameIgnoreCase(name); + + if (!existingOrganizations.isEmpty()) { + baseIds.put(id, existingOrganizations.getFirst()); + } else { + baseIds.put(id, this.organizationRepository.save(createOrganization(nodeOrganization, baseIds))); + } + }); } + private Organization createOrganization(JsonNode importNode, Map baseIds) { + Organization organization = new Organization(); + organization.setName(importNode.get("organization_name").textValue()); + organization.setDescription(getNodeValue(importNode.get("organization_description"))); + organization.setTags( + resolveJsonIds(importNode, "organization_tags") + .stream() + .map(baseIds::get) + .map(Tag.class::cast) + .collect(Collectors.toSet()) + ); + return organization; + } + + // -- USERS -- + private void importUsers(JsonNode importNode, String prefix, Map baseIds) { - if (importNode.has(prefix + "users")) { - Map existingUsersByEmail = fromIterable(userRepository.findAll()) - .stream() - .collect(Collectors.toMap(User::getEmail, Function.identity())); - - resolveJsonElements(importNode, prefix + "users") - .forEach(nodeUser -> { - String id = nodeUser.get("user_id").textValue(); - String email = nodeUser.get("user_email").textValue(); - User user = existingUsersByEmail.getOrDefault(email, new User()); - if (!hasText(user.getId())) { // new entity - user.setEmail(email); - user.setFirstname(nodeUser.get("user_firstname").textValue()); - user.setLastname(nodeUser.get("user_lastname").textValue()); - user.setLang(nodeUser.get("user_lang").textValue()); - user.setPhone(nodeUser.get("user_phone").textValue()); - user.setPgpKey(nodeUser.get("user_pgp_key").textValue()); - user.setCountry(nodeUser.get("user_country").textValue()); - user.setCity(nodeUser.get("user_city").textValue()); - Base userOrganization = baseIds.get(nodeUser.get("user_organization").textValue()); - if (userOrganization != null) { - user.setOrganization((Organization) userOrganization); - } - } - user.setTags(computeTagsCompletion(user.getTags(), resolveJsonIds(nodeUser, "user_tags"), baseIds)); - - baseIds.put(id, this.userRepository.save(user)); - }); + resolveJsonElements(importNode, prefix + "users") + .forEach(nodeUser -> { + String id = nodeUser.get("user_id").textValue(); + if (baseIds.get(id) != null) { + // Already import + return; + } + String email = nodeUser.get("user_email").textValue(); + + User existingUser = this.userRepository.findByEmailIgnoreCase(email).orElse(null); + + if (existingUser != null) { + baseIds.put(id, existingUser); + } else { + baseIds.put(id, this.userRepository.save(createUser(nodeUser, baseIds))); + } + }); + } + + private User createUser(JsonNode jsonNode, Map baseIds) { + User user = new User(); + user.setEmail(jsonNode.get("user_email").textValue()); + user.setFirstname(jsonNode.get("user_firstname").textValue()); + user.setLastname(jsonNode.get("user_lastname").textValue()); + user.setLang(getNodeValue(jsonNode.get("user_lang"))); + user.setPhone(getNodeValue(jsonNode.get("user_phone"))); + user.setPgpKey(getNodeValue(jsonNode.get("user_pgp_key"))); + user.setCountry(getNodeValue(jsonNode.get("user_country"))); + user.setCity(getNodeValue(jsonNode.get("user_city"))); + Base userOrganization = baseIds.get(jsonNode.get("user_organization").textValue()); + if (userOrganization != null) { + user.setOrganization((Organization) userOrganization); } + user.setTags( + resolveJsonIds(jsonNode, "user_tags") + .stream() + .map(baseIds::get) + .map(Tag.class::cast) + .collect(Collectors.toSet()) + ); + return user; } - private void importTeams(JsonNode importNode, String prefix, Exercise savedExercise, Scenario savedScenario, Map baseIds) { - Stream teamsStream = resolveJsonElements(importNode, prefix + "teams"); - Map baseTeams = handlingTeams(teamsStream, baseIds); + // -- TEAMS -- + + private void importTeams( + JsonNode importNode, String prefix, Exercise savedExercise, Scenario savedScenario, Map baseIds + ) { + Map baseTeams = handlingTeams(importNode, prefix, baseIds); baseTeams.values().forEach((team) -> { if (savedExercise != null) { team.getExercises().add(savedExercise); @@ -490,27 +476,32 @@ private void importTeams(JsonNode importNode, String prefix, Exercise savedExerc } private Map handlingTeams( - Stream teamsStream, + JsonNode importNode, + String prefix, Map baseIds) { Map baseTeams = new HashMap<>(); - teamsStream.forEach(nodeTeam -> { - String teamId = nodeTeam.get("team_id").textValue(); - String teamName = nodeTeam.get("team_name").textValue(); + + resolveJsonElements(importNode, prefix + "teams").forEach(nodeTeam -> { + String id = nodeTeam.get("team_id").textValue(); + if (baseIds.get(id) != null) { + // Already import + return; + } + String name = nodeTeam.get("team_name").textValue(); + // Prevent duplication of team, based on the team name and not contextual - List existingTeams = teamRepository.findByNameIgnoreCase(teamName) - .stream() - .filter(t -> !t.getContextual()) - .toList(); - if (existingTeams.size() == 1) { - Team existingTeam = existingTeams.get(0); - baseTeams.put(teamId, existingTeam); + List existingTeams = this.teamRepository.findByNameIgnoreCaseAndNotContextual(name); + + if (!existingTeams.isEmpty()) { + baseTeams.put(id, existingTeams.getFirst()); } else { - Team team = new Team(); - team.setName(nodeTeam.get("team_name").textValue()); - team.setDescription(nodeTeam.get("team_description").textValue()); + Team team = createTeam(nodeTeam, baseIds); // Tags List teamTagIds = resolveJsonIds(nodeTeam, "team_tags"); - Set tagsForTeam = teamTagIds.stream().map(baseIds::get).map(base -> (Tag) base) + Set tagsForTeam = teamTagIds.stream() + .map(baseIds::get) + .filter(Objects::nonNull) + .map(Tag.class::cast) .collect(Collectors.toSet()); team.setTags(tagsForTeam); // Users @@ -518,25 +509,44 @@ private Map handlingTeams( List usersForTeam = teamUserIds.stream() .map(baseIds::get) .filter(Objects::nonNull) - .map(base -> (User) base) + .map(User.class::cast) .toList(); team.setUsers(usersForTeam); - Team savedTeam = teamRepository.save(team); - baseTeams.put(teamId, savedTeam); + Team savedTeam = this.teamRepository.save(team); + baseTeams.put(id, savedTeam); } }); return baseTeams; } + private Team createTeam(JsonNode jsonNode, Map baseIds) { + Team team = new Team(); + team.setName(jsonNode.get("team_name").textValue()); + team.setDescription(jsonNode.get("team_description").textValue()); + if (jsonNode.get("team_organization") != null) { + Base teamOrganization = baseIds.get(jsonNode.get("team_organization").textValue()); + if (teamOrganization != null) { + team.setOrganization((Organization) teamOrganization); + } + } + return team; + } + + // -- CHALLENGES -- + private void importChallenges(JsonNode importNode, String prefix, Map baseIds) { resolveJsonElements(importNode, prefix + "challenges") .forEach(nodeChallenge -> { String id = nodeChallenge.get("challenge_id").textValue(); - String challengeName = nodeChallenge.get("challenge_name").textValue(); + if (baseIds.get(id) != null) { + // Already import + return; + } + String name = nodeChallenge.get("challenge_name").textValue(); - List existingChallenges = this.challengeRepository.findByNameIgnoreCase(challengeName); - if (existingChallenges.size() == 1) { - baseIds.put(id, existingChallenges.get(0)); + List existingChallenges =this.challengeRepository.findByNameIgnoreCase(name); + if (!existingChallenges.isEmpty()) { + baseIds.put(id, existingChallenges.getFirst()); } else { baseIds.put(id, this.challengeRepository.save(createChallenge(nodeChallenge, baseIds))); } @@ -581,15 +591,21 @@ private ChallengeFlag createChallengeFlag(JsonNode flagNode, Challenge challenge return flag; } + // -- CHANNELS -- + private void importChannels(JsonNode importNode, String prefix, Map baseIds) { resolveJsonElements(importNode, prefix + "channels") .forEach(nodeChannel -> { String id = nodeChannel.get("channel_id").textValue(); + if (baseIds.get(id) != null) { + // Already import + return; + } String channelName = nodeChannel.get("channel_name").textValue(); List existingChannels = this.channelRepository.findByNameIgnoreCase(channelName); - if (existingChannels.size() == 1) { - baseIds.put(id, existingChannels.get(0)); + if (!existingChannels.isEmpty()) { + baseIds.put(id, existingChannels.getFirst()); } else { baseIds.put(id, this.channelRepository.save(createChannel(nodeChannel, baseIds))); } @@ -619,7 +635,8 @@ private Channel createChannel(JsonNode nodeChannel, Map baseIds) { return channel; } - private void importArticles(JsonNode importNode, String prefix, Exercise savedExercise, Scenario savedScenario, Map baseIds) { + private void importArticles(JsonNode importNode, String prefix, Exercise savedExercise, Scenario savedScenario, + Map baseIds) { resolveJsonElements(importNode, prefix + "articles") .forEach(nodeArticle -> { String id = nodeArticle.get("article_id").textValue(); @@ -628,7 +645,8 @@ private void importArticles(JsonNode importNode, String prefix, Exercise savedEx }); } - private Article createArticle(JsonNode nodeArticle, Exercise savedExercise, Scenario savedScenario, Map baseIds) { + private Article createArticle(JsonNode nodeArticle, Exercise savedExercise, Scenario savedScenario, + Map baseIds) { Article article = new Article(); article.setName(nodeArticle.get("article_name").textValue()); article.setContent(nodeArticle.get("article_content").textValue()); @@ -653,7 +671,8 @@ private Article createArticle(JsonNode nodeArticle, Exercise savedExercise, Scen return article; } - private void importObjectives(JsonNode importNode, String prefix, Exercise savedExercise, Scenario savedScenario, Map baseIds) { + private void importObjectives(JsonNode importNode, String prefix, Exercise savedExercise, Scenario savedScenario, + Map baseIds) { resolveJsonElements(importNode, prefix + "objectives") .forEach(nodeObjective -> { String id = nodeObjective.get("objective_id").textValue(); @@ -676,11 +695,13 @@ private Objective createObjective(JsonNode nodeObjective, Exercise savedExercise return objective; } - private void importLessons(JsonNode importNode, String prefix, Exercise savedExercise, Scenario savedScenario, Map baseIds) { + private void importLessons(JsonNode importNode, String prefix, Exercise savedExercise, Scenario savedScenario, + Map baseIds) { resolveJsonElements(importNode, prefix + "lessons_categories") .forEach(nodeLessonCategory -> { String id = nodeLessonCategory.get("lessonscategory_id").textValue(); - LessonsCategory lessonsCategory = createLessonsCategory(nodeLessonCategory, savedExercise, savedScenario, baseIds); + LessonsCategory lessonsCategory = createLessonsCategory(nodeLessonCategory, savedExercise, savedScenario, + baseIds); baseIds.put(id, this.lessonsCategoryRepository.save(lessonsCategory)); }); resolveJsonElements(importNode, prefix + "lessons_questions") @@ -691,7 +712,8 @@ private void importLessons(JsonNode importNode, String prefix, Exercise savedExe }); } - private LessonsCategory createLessonsCategory(JsonNode nodeLessonCategory, Exercise savedExercise, Scenario savedScenario, Map baseIds) { + private LessonsCategory createLessonsCategory(JsonNode nodeLessonCategory, Exercise savedExercise, + Scenario savedScenario, Map baseIds) { LessonsCategory lessonsCategory = new LessonsCategory(); lessonsCategory.setName(nodeLessonCategory.get("lessons_category_name").textValue()); lessonsCategory.setDescription(nodeLessonCategory.get("lessons_category_description").textValue()); @@ -717,7 +739,8 @@ private LessonsQuestion createLessonsQuestion(JsonNode nodeLessonQuestion, Map baseIds) { + private void importInjects(JsonNode importNode, String prefix, Exercise savedExercise, Scenario savedScenario, + Map baseIds) { Stream injectsStream = resolveJsonElements(importNode, prefix + "injects"); Stream injectsNoParent = injectsStream.filter(jsonNode -> jsonNode.get("inject_depends_on").isNull()); @@ -736,7 +760,76 @@ private void importInjects(JsonNode importNode, String prefix, Exercise savedExe } } - private void importVariables(JsonNode importNode, String prefix, Exercise savedExercise, Scenario savedScenario, Map baseIds) { + private void importInjects(Map baseIds, String exerciseId, String scenarioId, List injects) { + List injected = new ArrayList<>(); + injects.forEach(injectNode -> { + String injectId = UUID.randomUUID().toString(); + injected.add(injectId); + String id = injectNode.get("inject_id").textValue(); + String title = injectNode.get("inject_title").textValue(); + String description = injectNode.get("inject_description").textValue(); + String country = injectNode.get("inject_country").textValue(); + String city = injectNode.get("inject_city").textValue(); + String injectorContractId = null; + JsonNode injectContractNode = injectNode.get("inject_injector_contract"); + if (injectContractNode != null) { + injectorContractId = injectContractNode.get("injector_contract_id").textValue(); + } + // If contract is not know, inject can't be imported + String content = handleInjectContent(baseIds, injectorContractId, injectNode); + JsonNode dependsOnNode = injectNode.get("inject_depends_on"); + String dependsOn = !dependsOnNode.isNull() ? baseIds.get(dependsOnNode.asText()).getId() : null; + Long dependsDuration = injectNode.get("inject_depends_duration").asLong(); + boolean allTeams = injectNode.get("inject_all_teams").booleanValue(); + if (hasText(exerciseId)) { + injectRepository.importSaveForExercise( + injectId, title, description, country, city, injectorContractId, allTeams, + true, exerciseId, dependsOn, dependsDuration, content + ); + } else if (hasText(scenarioId)) { + injectRepository.importSaveForScenario( + injectId, title, description, country, city, injectorContractId, + allTeams, true, scenarioId, dependsOn, dependsDuration, content + ); + } + baseIds.put(id, new BaseHolder(injectId)); + // Tags + List injectTagIds = resolveJsonIds(injectNode, "inject_tags"); + injectTagIds.forEach(tagId -> { + String remappedId = baseIds.get(tagId).getId(); + injectRepository.addTag(injectId, remappedId); + }); + // Teams + List injectTeamIds = resolveJsonIds(injectNode, "inject_teams"); + injectTeamIds.forEach(teamId -> { + String remappedId = baseIds.get(teamId).getId(); + injectRepository.addTeam(injectId, remappedId); + }); + // Documents + List injectDocuments = resolveJsonElements(injectNode, "inject_documents").toList(); + injectDocuments.forEach(jsonNode -> { + String docId = jsonNode.get("document_id").textValue(); + if (!hasText(docId)) { + String documentId = baseIds.get(docId).getId(); + boolean docAttached = jsonNode.get("document_attached").booleanValue(); + injectDocumentRepository.addInjectDoc(injectId, documentId, docAttached); + } else { + log.warning("Missing document in the exercise_documents property"); + } + }); + }); + // Looking for child of created injects + List childInjects = injects.stream().filter(jsonNode -> { + String injectDependsOn = jsonNode.get("inject_depends_on").asText(); + return injected.contains(injectDependsOn); + }).toList(); + if (!childInjects.isEmpty()) { + importInjects(baseIds, exerciseId, scenarioId, childInjects); + } + } + + private void importVariables(JsonNode importNode, Exercise savedExercise, Scenario savedScenario, + Map baseIds) { Optional> variableNodesOpt = Optional.empty(); if (ofNullable(importNode.get(EXERCISE_VARIABLES)).isPresent()) { variableNodesOpt = ofNullable(importNode.get(EXERCISE_VARIABLES)).map(JsonNode::elements); @@ -756,6 +849,11 @@ private void importVariables(JsonNode importNode, String prefix, Exercise savedE })); } + private String getNodeValue(JsonNode importNode) { + return Optional.ofNullable(importNode) + .map(JsonNode::textValue) + .orElse(null); + } private static class BaseHolder implements Base { diff --git a/openbas-api/src/main/java/io/openbas/migration/V3_16__Add_array_union_agg_method.java b/openbas-api/src/main/java/io/openbas/migration/V3_16__Add_array_union_agg_method.java index d331470c53..682c668d31 100644 --- a/openbas-api/src/main/java/io/openbas/migration/V3_16__Add_array_union_agg_method.java +++ b/openbas-api/src/main/java/io/openbas/migration/V3_16__Add_array_union_agg_method.java @@ -1,32 +1,45 @@ package io.openbas.migration; +import lombok.extern.java.Log; import org.flywaydb.core.api.migration.BaseJavaMigration; import org.flywaydb.core.api.migration.Context; import org.springframework.stereotype.Component; import java.sql.Connection; +import java.sql.ResultSet; import java.sql.Statement; +@Log @Component public class V3_16__Add_array_union_agg_method extends BaseJavaMigration { @Override public void migrate(Context context) throws Exception { Connection connection = context.getConnection(); - Statement select = connection.createStatement(); - select.execute("CREATE FUNCTION array_union(a ANYARRAY, b ANYARRAY)" - + " RETURNS ANYARRAY AS" - + " $$" - + "SELECT array_agg(DISTINCT x)" - + "FROM (" - + " SELECT unnest(a) x" - + " UNION ALL SELECT unnest(b)" - + " ) AS u" - + " $$ LANGUAGE SQL;" - + "CREATE AGGREGATE array_union_agg(ANYARRAY) (" - + " SFUNC = array_union," - + " STYPE = ANYARRAY," - + " INITCOND = '{}'" - + ");"); + try (Statement statement = connection.createStatement()) { + ResultSet rs = statement.executeQuery( + "SELECT EXISTS (SELECT 1 FROM pg_proc WHERE proname = 'array_union');"); + + boolean functionExists = false; + if (rs.next()) { + functionExists = rs.getBoolean(1); + } + if (!functionExists) { + statement.execute("CREATE FUNCTION array_union(a ANYARRAY, b ANYARRAY)" + + " RETURNS ANYARRAY AS" + + " $$" + + "SELECT array_agg(DISTINCT x)" + + "FROM (" + + " SELECT unnest(a) x" + + " UNION ALL SELECT unnest(b)" + + " ) AS u" + + " $$ LANGUAGE SQL;" + + "CREATE AGGREGATE array_union_agg(ANYARRAY) (" + + " SFUNC = array_union," + + " STYPE = ANYARRAY," + + " INITCOND = '{}'" + + ");"); + } + } } } 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 c00419a7b7..c0afd71bec 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 @@ -800,7 +800,11 @@ public void exerciseExport(@NotBlank @PathVariable final String exerciseId, importExport.setUsers(players); objectMapper.addMixIn(User.class, ExerciseExportMixins.User.class); // organizations - List organizations = players.stream().map(User::getOrganization).filter(Objects::nonNull).toList(); + List organizations = players.stream() + .map(User::getOrganization) + .filter(Objects::nonNull) + .distinct() + .toList(); exerciseTags.addAll(organizations.stream().flatMap(org -> org.getTags().stream()).toList()); importExport.setOrganizations(organizations); objectMapper.addMixIn(Organization.class, ExerciseExportMixins.Organization.class); diff --git a/openbas-api/src/main/java/io/openbas/rest/exercise/exports/ExerciseExportMixins.java b/openbas-api/src/main/java/io/openbas/rest/exercise/exports/ExerciseExportMixins.java index 101910f33d..89cfcbe90d 100644 --- a/openbas-api/src/main/java/io/openbas/rest/exercise/exports/ExerciseExportMixins.java +++ b/openbas-api/src/main/java/io/openbas/rest/exercise/exports/ExerciseExportMixins.java @@ -5,185 +5,203 @@ public class ExerciseExportMixins { - @JsonIgnoreProperties(value = {"exercise_users", "exercise_organizations"}) - public static class ExerciseFileExport { - } - - @JsonIncludeProperties(value = { - "exercise_id", - "exercise_name", - "exercise_description", - "exercise_subtitle", - "exercise_image", - "exercise_message_header", - "exercise_message_footer", - "exercise_mail_from", - "exercise_tags", - "exercise_documents", - }) - public static class Exercise { - } - - @JsonIncludeProperties(value = { - "document_id", - "document_name", - "document_target", - "document_description", - "document_tags", - }) - public static class Document { - } - - @JsonIncludeProperties(value = { - "organization_id", - "organization_name", - "organization_description", - "organization_tags", - }) - public static class Organization { - } - - @JsonIncludeProperties(value = { - "team_id", - "team_name", - "team_description", - "team_tags", - "team_users", - }) - public static class Team { - } - - @JsonIncludeProperties(value = { - "team_id", - "team_name", - "team_description", - "team_tags", - }) - public static class EmptyTeam { - } - - @JsonIncludeProperties(value = { - "inject_id", - "inject_title", - "inject_description", - "inject_country", - "inject_city", - "inject_injector_contract", - "inject_all_teams", - "inject_depends_on", - "inject_depends_duration", - "inject_tags", - "inject_documents", - "inject_teams", - "inject_content", - }) - public static class Inject { - } - - @JsonIncludeProperties(value = { - "user_id", - "user_firstname", - "user_lastname", - "user_lang", - "user_email", - "user_phone", - "user_pgp_key", - "user_organization", - "user_country", - "user_city", - "user_tags", - }) - public static class User { - } - - @JsonIncludeProperties(value = { - "objective_id", - "objective_title", - "objective_description", - "objective_priority", - }) - public static class Objective { - } - - @JsonIncludeProperties(value = { - "poll_id", - "poll_question", - }) - public static class Poll { - } - - @JsonIncludeProperties(value = { - "tag_id", - "tag_name", - "tag_color", - }) - public static class Tag { - } - - @JsonIncludeProperties(value = { - "channel_id", - "channel_type", - "channel_name", - "channel_description", - "channel_mode", - "channel_primary_color_dark", - "channel_primary_color_light", - "channel_secondary_color_dark", - "channel_secondary_color_light", - "channel_logo_dark", - "channel_logo_light", - }) - public static class Channel { - } - - @JsonIncludeProperties(value = { - "article_id", - "article_name", - "article_content", - "article_author", - "article_shares", - "article_likes", - "article_comments", - "article_channel", - "article_documents", - "article_exercise", - }) - public static class Article { - } - @JsonIncludeProperties(value = { - "challenge_id", - "challenge_name", - "challenge_category", - "challenge_content", - "challenge_score", - "challenge_max_attempts", - "challenge_flags", - "challenge_tags", - "challenge_documents", - }) - public static class Challenge { - } - - @JsonIncludeProperties(value = { - "lessonscategory_id", - "lessons_category_name", - "lessons_category_description", - "lessons_category_order", - "lessons_category_questions", - "lessons_category_teams", - }) - public static class LessonsCategory { - } - - @JsonIncludeProperties(value = { - "lessonsquestion_id", - "lessons_question_category", - "lessons_question_content", - "lessons_question_explanation", - "lessons_question_order", - }) - public static class LessonsQuestion { - } + @JsonIgnoreProperties(value = {"exercise_users", "exercise_organizations"}) + public static class ExerciseFileExport { + + } + + @JsonIncludeProperties(value = { + "exercise_id", + "exercise_name", + "exercise_description", + "exercise_subtitle", + "exercise_image", + "exercise_message_header", + "exercise_message_footer", + "exercise_mail_from", + "exercise_tags", + "exercise_documents", + }) + public static class Exercise { + + } + + @JsonIncludeProperties(value = { + "document_id", + "document_name", + "document_target", + "document_description", + "document_tags", + }) + public static class Document { + + } + + @JsonIncludeProperties(value = { + "organization_id", + "organization_name", + "organization_description", + "organization_tags", + }) + public static class Organization { + + } + + @JsonIncludeProperties(value = { + "team_id", + "team_name", + "team_description", + "team_tags", + "team_organization", + "team_users", + }) + public static class Team { + + } + + @JsonIncludeProperties(value = { + "team_id", + "team_name", + "team_description", + "team_tags", + }) + public static class EmptyTeam { + + } + + @JsonIncludeProperties(value = { + "inject_id", + "inject_title", + "inject_description", + "inject_country", + "inject_city", + "inject_injector_contract", + "inject_all_teams", + "inject_depends_on", + "inject_depends_duration", + "inject_tags", + "inject_documents", + "inject_teams", + "inject_content", + }) + public static class Inject { + + } + + @JsonIncludeProperties(value = { + "user_id", + "user_firstname", + "user_lastname", + "user_lang", + "user_email", + "user_phone", + "user_pgp_key", + "user_organization", + "user_country", + "user_city", + "user_tags", + }) + public static class User { + + } + + @JsonIncludeProperties(value = { + "objective_id", + "objective_title", + "objective_description", + "objective_priority", + }) + public static class Objective { + + } + + @JsonIncludeProperties(value = { + "poll_id", + "poll_question", + }) + public static class Poll { + + } + + @JsonIncludeProperties(value = { + "tag_id", + "tag_name", + "tag_color", + }) + public static class Tag { + + } + + @JsonIncludeProperties(value = { + "channel_id", + "channel_type", + "channel_name", + "channel_description", + "channel_mode", + "channel_primary_color_dark", + "channel_primary_color_light", + "channel_secondary_color_dark", + "channel_secondary_color_light", + "channel_logo_dark", + "channel_logo_light", + }) + public static class Channel { + + } + + @JsonIncludeProperties(value = { + "article_id", + "article_name", + "article_content", + "article_author", + "article_shares", + "article_likes", + "article_comments", + "article_channel", + "article_documents", + "article_exercise", + }) + public static class Article { + + } + + @JsonIncludeProperties(value = { + "challenge_id", + "challenge_name", + "challenge_category", + "challenge_content", + "challenge_score", + "challenge_max_attempts", + "challenge_flags", + "challenge_tags", + "challenge_documents", + }) + public static class Challenge { + + } + + @JsonIncludeProperties(value = { + "lessonscategory_id", + "lessons_category_name", + "lessons_category_description", + "lessons_category_order", + "lessons_category_questions", + "lessons_category_teams", + }) + public static class LessonsCategory { + + } + + @JsonIncludeProperties(value = { + "lessonsquestion_id", + "lessons_question_category", + "lessons_question_content", + "lessons_question_explanation", + "lessons_question_order", + }) + public static class LessonsQuestion { + + } } diff --git a/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java b/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java index bbac7e7686..c5872577e0 100644 --- a/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/inject/InjectApi.java @@ -336,7 +336,7 @@ public Inject createInject(@PathVariable String exerciseId, @Valid @RequestBody inject.setTeams(fromIterable(teamRepository.findAllById(input.getTeams()))); inject.setAssets(fromIterable(assetService.assets(input.getAssets()))); inject.setAssetGroups(fromIterable(assetGroupService.assetGroups(input.getAssetGroups()))); - inject.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds()))); + inject.setTags(iterableToSet(tagRepository.findAllById(input.getTagIds()))); List injectDocuments = input.getDocuments().stream() .map(i -> { InjectDocument injectDocument = new InjectDocument(); @@ -346,6 +346,12 @@ public Inject createInject(@PathVariable String exerciseId, @Valid @RequestBody return injectDocument; }).toList(); inject.setDocuments(injectDocuments); + // Linked documents directly to the exercise + inject.getDocuments().forEach(document -> { + if (!document.getDocument().getExercises().contains(exercise)) { + exercise.getDocuments().add(document.getDocument()); + } + }); return injectRepository.save(inject); } @@ -498,6 +504,12 @@ public Inject createInjectForScenario( return injectDocument; }).toList(); inject.setDocuments(injectDocuments); + // Linked documents directly to the exercise + inject.getDocuments().forEach(document -> { + if (!document.getDocument().getExercises().contains(scenario)) { + scenario.getDocuments().add(document.getDocument()); + } + }); return injectRepository.save(inject); } diff --git a/openbas-api/src/main/java/io/openbas/rest/team/TeamApi.java b/openbas-api/src/main/java/io/openbas/rest/team/TeamApi.java index 1f27194424..aca78e224e 100644 --- a/openbas-api/src/main/java/io/openbas/rest/team/TeamApi.java +++ b/openbas-api/src/main/java/io/openbas/rest/team/TeamApi.java @@ -15,7 +15,6 @@ import io.openbas.rest.team.form.TeamUpdateInput; import io.openbas.rest.team.form.UpdateUsersTeamInput; import io.openbas.utils.pagination.SearchPaginationInput; -import jakarta.transaction.Transactional; import jakarta.validation.Valid; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; @@ -23,6 +22,7 @@ import org.springframework.data.jpa.domain.Specification; import org.springframework.security.access.annotation.Secured; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.transaction.annotation.Transactional; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -124,6 +124,7 @@ public Iterable getTeams() { @PostMapping("/api/teams/search") @PreAuthorize("isObserver()") + @Transactional(readOnly = true) public Page teams(@RequestBody @Valid SearchPaginationInput searchPaginationInput) { BiFunction, Pageable, Page> teamsFunction; OpenBASPrincipal currentUser = currentUser(); @@ -160,7 +161,7 @@ public Iterable getTeamPlayers(@PathVariable String teamId) { @PostMapping("/api/teams") @PreAuthorize("isPlanner()") - @Transactional(rollbackOn = Exception.class) + @Transactional(rollbackFor = Exception.class) public Team createTeam(@Valid @RequestBody TeamCreateInput input) { if (TRUE.equals(input.getContextual()) && input.getExerciseIds().toArray().length > 1) { throw new UnsupportedOperationException("Contextual team can only be associated to one exercise"); @@ -180,7 +181,7 @@ public Team createTeam(@Valid @RequestBody TeamCreateInput input) { @PostMapping("/api/teams/upsert") @PreAuthorize("isPlanner()") - @Transactional(rollbackOn = Exception.class) + @Transactional(rollbackFor = Exception.class) public Team upsertTeam(@Valid @RequestBody TeamCreateInput input) { if (input.getContextual() && input.getExerciseIds().toArray().length > 1) { throw new UnsupportedOperationException("Contextual team can only be associated to one exercise"); diff --git a/openbas-api/src/test/java/io/openbas/importer/V1_DataImporterTest.java b/openbas-api/src/test/java/io/openbas/importer/V1_DataImporterTest.java new file mode 100644 index 0000000000..ee662ebfb6 --- /dev/null +++ b/openbas-api/src/test/java/io/openbas/importer/V1_DataImporterTest.java @@ -0,0 +1,106 @@ +package io.openbas.importer; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.openbas.IntegrationTest; +import io.openbas.database.model.*; +import io.openbas.database.repository.*; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.mockito.MockitoAnnotations; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.jpa.domain.Specification; +import org.springframework.transaction.annotation.Transactional; + +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.TestInstance.Lifecycle.PER_CLASS; + +@TestInstance(PER_CLASS) +class V1_DataImporterTest extends IntegrationTest { + + @Autowired + private V1_DataImporter importer; + + @Autowired + private ExerciseRepository exerciseRepository; + + @Autowired + private TeamRepository teamRepository; + + @Autowired + private UserRepository userRepository; + + @Autowired + private OrganizationRepository organizationRepository; + + @Autowired + private TagRepository tagRepository; + + private JsonNode importNode; + + public static final String EXERCISE_NAME = "Test Exercise (Import)"; + public static final String TEAM_NAME = "Animation team"; + public static final String USER_EMAIL = "Romuald.Lemesle@openbas.io"; + public static final String ORGANIZATION_NAME = "Filigran"; + public static final String TAG_NAME = "crisis exercise"; + + @BeforeAll + public void setUp() throws Exception { + MockitoAnnotations.openMocks(this); + ObjectMapper mapper = new ObjectMapper(); + String jsonContent = new String(Files.readAllBytes(Paths.get("src/test/resources/importer-v1/import-data.json"))); + this.importNode = mapper.readTree(jsonContent); + } + + @Test + @Transactional + void testImportData() { + // -- EXECUTE -- + this.importer.importData(this.importNode, Map.of()); + + // -- ASSERT -- + Optional exercise = this.exerciseRepository.findOne(exerciseByName(EXERCISE_NAME)); + assertTrue(exercise.isPresent()); + + Optional team = this.teamRepository.findByName(TEAM_NAME); + assertTrue(team.isPresent()); + assertEquals(1, team.get().getUsersNumber()); + assertEquals(ORGANIZATION_NAME, team.get().getOrganization().getName()); + assertEquals(1, team.get().getTags().size()); + + Optional user = this.userRepository.findByEmailIgnoreCase(USER_EMAIL); + assertTrue(user.isPresent()); + assertEquals(ORGANIZATION_NAME, user.get().getOrganization().getName()); + assertEquals(1, user.get().getTags().size()); + + List organization = this.organizationRepository.findByNameIgnoreCase(ORGANIZATION_NAME); + assertFalse(organization.isEmpty()); + assertEquals(ORGANIZATION_NAME, organization.getFirst().getName()); + + List tag = this.tagRepository.findByNameIgnoreCase(TAG_NAME); + assertFalse(tag.isEmpty()); + assertEquals(TAG_NAME, tag.getFirst().getName()); + + // -- CLEAN -- + this.tagRepository.delete(tag.getFirst()); + this.organizationRepository.delete(organization.getFirst()); + this.userRepository.delete(user.get()); + this.teamRepository.delete(team.get()); + this.exerciseRepository.delete(exercise.get()); + } + + // -- UTILS -- + + private static Specification exerciseByName(@NotNull final String name) { + return (root, query, cb) -> cb.equal(root.get("name"), name); + } + +} diff --git a/openbas-api/src/test/resources/importer-v1/import-data.json b/openbas-api/src/test/resources/importer-v1/import-data.json new file mode 100644 index 0000000000..8f48f3d574 --- /dev/null +++ b/openbas-api/src/test/resources/importer-v1/import-data.json @@ -0,0 +1,64 @@ +{ + "exercise_information": { + "exercise_name": "Test Exercise", + "exercise_description": "Description", + "exercise_subtitle": "Subtitle", + "exercise_message_header": "Header", + "exercise_message_footer": "Footer", + "exercise_mail_from": "mail@example.com", + "exercise_tags": [] + }, + "exercise_teams": [ + { + "team_id": "0801be02-2aef-472e-a0b2-29f464a61c55", + "team_name": "Animation team", + "team_description": "", + "team_tags": [ + "5783bfdf-aaf4-4818-976e-a8ad3e46e8eb" + ], + "team_organization": "e2a24b52-fd3e-4678-8f9e-c44d6d6a39c3", + "team_users": [ + "984f481b-73c6-407a-9036-d48e306d409d" + ] + }, + { + "team_id": "0801be02-2aef-472e-a0b2-29f464a61c55", + "team_name": "Animation team", + "team_description": "", + "team_tags": [ + "5783bfdf-aaf4-4818-976e-a8ad3e46e8eb" + ], + "team_organization": "e2a24b52-fd3e-4678-8f9e-c44d6d6a39c3", + "team_users": [ + "984f481b-73c6-407a-9036-d48e306d409d" + ] + } + ], + "exercise_users": [ + { + "user_id": "984f481b-73c6-407a-9036-d48e306d409d", + "user_firstname": "Romuald", + "user_lastname": "Lemesle", + "user_email": "Romuald.Lemesle@openbas.io", + "user_organization": "e2a24b52-fd3e-4678-8f9e-c44d6d6a39c3", + "user_tags": [ + "5783bfdf-aaf4-4818-976e-a8ad3e46e8eb" + ] + } + ], + "exercise_organizations": [ + { + "organization_id": "e2a24b52-fd3e-4678-8f9e-c44d6d6a39c3", + "organization_name": "Filigran" + }, + { + "organization_id": "e2a24b52-fd3e-4678-8f9e-c44d6d6a39c3", + "organization_name": "Filigran" + } + ], + "exercise_tags" : [ { + "tag_id" : "5783bfdf-aaf4-4818-976e-a8ad3e46e8eb", + "tag_name" : "Crisis exercise", + "tag_color" : "#bc42d4" + } ] +} diff --git a/openbas-front/src/admin/components/common/articles/Articles.tsx b/openbas-front/src/admin/components/common/articles/Articles.tsx index 8736031ac4..2f4f4a575e 100644 --- a/openbas-front/src/admin/components/common/articles/Articles.tsx +++ b/openbas-front/src/admin/components/common/articles/Articles.tsx @@ -239,7 +239,7 @@ const Articles: FunctionComponent = ({ articles }) => {
- + = ({ articles }) => { ), }} variant="outlined" - label={article.article_fullchannel.channel_name} + label={article.article_fullchannel?.channel_name} />
diff --git a/openbas-front/src/admin/components/components/teams/TeamPopover.tsx b/openbas-front/src/admin/components/components/teams/TeamPopover.tsx index 6e66acd88b..7b4371eb3d 100644 --- a/openbas-front/src/admin/components/components/teams/TeamPopover.tsx +++ b/openbas-front/src/admin/components/components/teams/TeamPopover.tsx @@ -196,7 +196,7 @@ const TeamPopover: FunctionComponent = ({ initialValues={initialValues} handleClose={handleCloseEdit} onSubmit={onSubmitEdit} - editing={true} + editing /> { const dispatch = useAppDispatch(); return { - previewArticleUrl: (article: FullArticleStore) => `/channels/${exerciseId}/${article.article_fullchannel.channel_id}?preview=true`, + previewArticleUrl: (article: FullArticleStore) => `/channels/${exerciseId}/${article.article_fullchannel?.channel_id}?preview=true`, onAddArticle: (data: ArticleCreateInput) => dispatch(addExerciseArticle(exerciseId, data)), onUpdateArticle: (article: ArticleStore, data: ArticleUpdateInput) => dispatch( updateExerciseArticle(exerciseId, article.article_id, data), diff --git a/openbas-model/src/main/java/io/openbas/database/raw/RawPaginationTeam.java b/openbas-model/src/main/java/io/openbas/database/raw/RawPaginationTeam.java index d0eb6efae4..7d7a436628 100644 --- a/openbas-model/src/main/java/io/openbas/database/raw/RawPaginationTeam.java +++ b/openbas-model/src/main/java/io/openbas/database/raw/RawPaginationTeam.java @@ -1,11 +1,13 @@ package io.openbas.database.raw; +import io.openbas.database.model.Organization; import io.openbas.database.model.Tag; import io.openbas.database.model.Team; import lombok.Data; import java.time.Instant; import java.util.List; +import java.util.Optional; @Data public class RawPaginationTeam { @@ -14,6 +16,7 @@ public class RawPaginationTeam { String team_name; String team_description; long team_users_number; + String team_organization; List team_tags; boolean team_contextual; Instant team_updated_at; @@ -23,6 +26,7 @@ public RawPaginationTeam(final Team team) { this.team_name = team.getName(); this.team_description = team.getDescription(); this.team_users_number = team.getUsersNumber(); + this.team_organization = Optional.ofNullable(team.getOrganization()).map(Organization::getId).orElse(null); this.team_tags = team.getTags().stream().map(Tag::getId).toList(); this.team_contextual = team.getContextual(); this.team_updated_at = team.getUpdatedAt(); diff --git a/openbas-model/src/main/java/io/openbas/database/repository/ChallengeRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/ChallengeRepository.java index e9003209ff..b6150b155f 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/ChallengeRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/ChallengeRepository.java @@ -1,19 +1,20 @@ package io.openbas.database.repository; import io.openbas.database.model.Challenge; +import org.jetbrains.annotations.NotNull; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; -import jakarta.validation.constraints.NotNull; import java.util.List; import java.util.Optional; @Repository public interface ChallengeRepository extends CrudRepository, JpaSpecificationExecutor { - @NotNull - Optional findById(@NotNull String id); + @NotNull + Optional findById(@NotNull final String id); - List findByNameIgnoreCase(String name); + @NotNull + List findByNameIgnoreCase(@NotNull final String name); } diff --git a/openbas-model/src/main/java/io/openbas/database/repository/OrganizationRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/OrganizationRepository.java index 503b2e18c7..7dd8ba166b 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/OrganizationRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/OrganizationRepository.java @@ -2,7 +2,7 @@ import io.openbas.database.model.Organization; import io.openbas.database.raw.RawOrganization; -import jakarta.validation.constraints.NotNull; +import org.jetbrains.annotations.NotNull; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; @@ -18,6 +18,9 @@ public interface OrganizationRepository extends CrudRepository findById(@NotNull String id); + @NotNull + List findByNameIgnoreCase(@NotNull final String name); + @Query(value = "SELECT org.*, " + "array_agg(DISTINCT org_tags.tag_id) FILTER (WHERE org_tags.tag_id IS NOT NULL) AS organization_tags, " + diff --git a/openbas-model/src/main/java/io/openbas/database/repository/TagRepository.java b/openbas-model/src/main/java/io/openbas/database/repository/TagRepository.java index b10f378cc6..a122c858e6 100644 --- a/openbas-model/src/main/java/io/openbas/database/repository/TagRepository.java +++ b/openbas-model/src/main/java/io/openbas/database/repository/TagRepository.java @@ -1,18 +1,23 @@ package io.openbas.database.repository; import io.openbas.database.model.Tag; -import jakarta.validation.constraints.NotNull; +import org.jetbrains.annotations.NotNull; import org.springframework.data.jpa.repository.JpaSpecificationExecutor; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Repository; +import java.util.List; import java.util.Optional; @Repository public interface TagRepository extends CrudRepository, JpaSpecificationExecutor { - @NotNull - Optional findById(@NotNull String id); + @NotNull + Optional findById(@NotNull String id); - Optional findByName(@NotNull String name); + @NotNull + Optional findByName(@NotNull final String name); + + @NotNull + List findByNameIgnoreCase(@NotNull final String name); } 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 172f761117..e34f8c26e1 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 @@ -25,9 +25,11 @@ public interface TeamRepository extends CrudRepository, @NotNull Optional findById(@NotNull String id); - Optional findByName(String name); + @NotNull + Optional findByName(@NotNull final String name); - List findByNameIgnoreCase(String name); + @Query("SELECT team FROM Team team where lower(team.name) = lower(:name) and team.contextual = false") + List findByNameIgnoreCaseAndNotContextual(@NotNull final String name); @Query("select team from Team team where team.organization is null or team.organization.id in :organizationIds") List teamsAccessibleFromOrganizations(@Param("organizationIds") List organizationIds);