-
Notifications
You must be signed in to change notification settings - Fork 4
feat: Collection pseudo variables antérieures #251
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 9 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
99e0ea9
feat: Edited Previous Responses storage
alexisszmundy e6c10cb
Merge branch 'main' into devEditedPreviousResponses
alexisszmundy 11bf7f4
feat: edited previous unit tests
alexisszmundy 05dd2de
feat: error on double interrogationId
alexisszmundy 2ffe759
feat: add index on questionnaireId
alexisszmundy de598f0
fix: add repository mockito bean
alexisszmundy be2433f
fix: restore backup overwriting
alexisszmundy b09f35e
fix: use double not float for mongo
alexisszmundy 23848cc
feat: authentication
alexisszmundy 382b8f1
refactor: extract methods
alexisszmundy 550c27d
Merge branch 'main' into devEditedPreviousResponses
alexisszmundy 9d8bf0c
Merge branch 'main' into devEditedPreviousResponses
alexisszmundy f28dea0
Merge branch 'main' into devEditedPreviousResponses
alexisszmundy 46184d9
Merge branch 'main' into devEditedPreviousResponses
alexisszmundy File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
70 changes: 70 additions & 0 deletions
70
...ain/java/fr/insee/genesis/controller/rest/responses/EditedPreviousResponseController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,70 @@ | ||
| package fr.insee.genesis.controller.rest.responses; | ||
|
|
||
| import fr.insee.genesis.configuration.Config; | ||
| import fr.insee.genesis.domain.model.surveyunit.Mode; | ||
| import fr.insee.genesis.domain.ports.api.EditedPreviousResponseApiPort; | ||
| import fr.insee.genesis.exceptions.GenesisException; | ||
| import fr.insee.genesis.infrastructure.utils.FileUtils; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.security.access.prepost.PreAuthorize; | ||
| import org.springframework.stereotype.Controller; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
|
|
||
| import java.io.FileInputStream; | ||
| import java.io.FileNotFoundException; | ||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.nio.file.Path; | ||
|
|
||
| @RequestMapping(path = "/edited-previous" ) | ||
| @Controller | ||
| @Slf4j | ||
| public class EditedPreviousResponseController { | ||
| private final EditedPreviousResponseApiPort editedPreviousResponseApiPort; | ||
|
|
||
| private final Config config; | ||
|
|
||
| @Autowired | ||
| public EditedPreviousResponseController(EditedPreviousResponseApiPort editedPreviousResponseApiPort, Config config) { | ||
| this.editedPreviousResponseApiPort = editedPreviousResponseApiPort; | ||
| this.config = config; | ||
| } | ||
|
|
||
| @Operation(summary = "Add edited previous json file") | ||
| @PostMapping(path = "/json") | ||
| @PreAuthorize("hasRole('USER_KRAFTWERK')") | ||
| public ResponseEntity<Object> readJson( | ||
| @RequestParam("questionnaireId") String questionnaireId, | ||
| @RequestParam("mode") Mode mode, | ||
| @RequestParam(value = "sourceState", required = false) String sourceState, | ||
| @RequestParam(value = "jsonFileName") String jsonFileName | ||
| ) throws GenesisException { | ||
| FileUtils fileUtils = new FileUtils(config); | ||
|
|
||
| String filePath = "%s/%s".formatted( | ||
| fileUtils.getDataFolder(questionnaireId, mode.getFolder(), null), | ||
| jsonFileName | ||
| ); | ||
| if(!jsonFileName.toLowerCase().endsWith(".json")){ | ||
| throw new GenesisException(400, "File must be a JSON file !"); | ||
| } | ||
| try(InputStream inputStream = new FileInputStream(filePath)){ | ||
| editedPreviousResponseApiPort.readEditedPreviousFile(inputStream, questionnaireId, sourceState); | ||
| } catch (FileNotFoundException e) { | ||
| throw new GenesisException(404, "File %s not found".formatted(filePath)); | ||
| } catch (IOException e) { | ||
| throw new GenesisException(500, e.toString()); | ||
| } | ||
| try { | ||
| fileUtils.moveFiles(Path.of(filePath), fileUtils.getDoneFolder(questionnaireId, mode.getFolder())); | ||
| } catch (IOException e) { | ||
| throw new GenesisException(500, "Error while moving file to done : %s".formatted(e.toString())); | ||
| } | ||
| return ResponseEntity.ok("Edited previous variable file %s saved !".formatted(filePath)); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
src/main/java/fr/insee/genesis/domain/model/editedprevious/EditedPreviousResponseModel.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package fr.insee.genesis.domain.model.editedprevious; | ||
|
|
||
| import lombok.Builder; | ||
| import lombok.Data; | ||
|
|
||
| import java.util.Map; | ||
|
|
||
| @Builder | ||
| @Data | ||
| public class EditedPreviousResponseModel { | ||
| String id; | ||
| String questionnaireId; | ||
| String interrogationId; | ||
| Map<String,Object> variables; | ||
| String sourceState; | ||
| } | ||
9 changes: 9 additions & 0 deletions
9
src/main/java/fr/insee/genesis/domain/ports/api/EditedPreviousResponseApiPort.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package fr.insee.genesis.domain.ports.api; | ||
|
|
||
| import fr.insee.genesis.exceptions.GenesisException; | ||
|
|
||
| import java.io.InputStream; | ||
|
|
||
| public interface EditedPreviousResponseApiPort { | ||
| void readEditedPreviousFile(InputStream inputStream, String questionnaireId, String sourceState) throws GenesisException; | ||
| } |
13 changes: 13 additions & 0 deletions
13
src/main/java/fr/insee/genesis/domain/ports/spi/EditedPreviousResponsePersistancePort.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| package fr.insee.genesis.domain.ports.spi; | ||
|
|
||
| import fr.insee.genesis.domain.model.editedprevious.EditedPreviousResponseModel; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public interface EditedPreviousResponsePersistancePort { | ||
| void backup(String questionnaireId); | ||
| void deleteBackup(String questionnaireId); | ||
| void restoreBackup(String questionnaireId); | ||
| void saveAll(List<EditedPreviousResponseModel> editedPreviousResponseModelList); | ||
| void delete(String questionnaireId); | ||
| } |
167 changes: 167 additions & 0 deletions
167
...ava/fr/insee/genesis/domain/service/editedprevious/EditedPreviousResponseJsonService.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| package fr.insee.genesis.domain.service.editedprevious; | ||
|
|
||
| import com.fasterxml.jackson.core.JsonFactory; | ||
| import com.fasterxml.jackson.core.JsonParseException; | ||
| import com.fasterxml.jackson.core.JsonParser; | ||
| import com.fasterxml.jackson.core.JsonToken; | ||
| import fr.insee.genesis.domain.model.editedprevious.EditedPreviousResponseModel; | ||
| import fr.insee.genesis.domain.ports.api.EditedPreviousResponseApiPort; | ||
| import fr.insee.genesis.domain.ports.spi.EditedPreviousResponsePersistancePort; | ||
| import fr.insee.genesis.exceptions.GenesisException; | ||
| import lombok.extern.slf4j.Slf4j; | ||
| import org.springframework.beans.factory.annotation.Autowired; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import java.io.IOException; | ||
| import java.io.InputStream; | ||
| import java.util.ArrayList; | ||
| import java.util.HashMap; | ||
| import java.util.HashSet; | ||
| import java.util.List; | ||
| import java.util.Set; | ||
|
|
||
| @Service | ||
| @Slf4j | ||
| public class EditedPreviousResponseJsonService implements EditedPreviousResponseApiPort { | ||
| private final EditedPreviousResponsePersistancePort editedPreviousResponsePersistancePort; | ||
|
|
||
| private static final int BLOCK_SIZE = 1000; | ||
|
|
||
| @Autowired | ||
| public EditedPreviousResponseJsonService(EditedPreviousResponsePersistancePort editedPreviousResponsePersistancePort) { | ||
| this.editedPreviousResponsePersistancePort = editedPreviousResponsePersistancePort; | ||
| } | ||
|
|
||
| @Override | ||
| public void readEditedPreviousFile(InputStream inputStream, | ||
|
alexisszmundy marked this conversation as resolved.
|
||
| String questionnaireId, | ||
| String sourceState) throws GenesisException { | ||
| if(sourceState != null && sourceState.length() > 15){ | ||
| throw new GenesisException(400, "Source state is too long (>15 characters)"); | ||
| } | ||
|
|
||
| JsonFactory jsonFactory = new JsonFactory(); | ||
| editedPreviousResponsePersistancePort.backup(questionnaireId); | ||
| editedPreviousResponsePersistancePort.delete(questionnaireId); | ||
| try(JsonParser jsonParser = jsonFactory.createParser(inputStream)){ | ||
| List<EditedPreviousResponseModel> toSave = new ArrayList<>(); | ||
| boolean isTokenFound = false; | ||
| while (!isTokenFound){ | ||
| jsonParser.nextToken(); | ||
| if(jsonParser.currentToken().equals(JsonToken.FIELD_NAME) && jsonParser.currentName() != null){ | ||
| if (jsonParser.currentName().equals("editedPrevious")) { | ||
| isTokenFound = true; | ||
| } | ||
| } | ||
| } | ||
| long savedCount = 0; | ||
| Set<String> savedInterrogationIds = new HashSet<>(); | ||
| jsonParser.nextToken(); //skip field name | ||
| jsonParser.nextToken(); //skip [ | ||
| while (jsonParser.currentToken() != JsonToken.END_ARRAY) { | ||
| EditedPreviousResponseModel editedPreviousResponseModel = readNextEditedPrevious( | ||
| jsonParser, | ||
| questionnaireId, | ||
| sourceState | ||
| ); | ||
|
|
||
| if(editedPreviousResponseModel.getInterrogationId() == null){ | ||
| throw new GenesisException(400, | ||
| "Missing interrogationId on the object that ends on line %d" | ||
| .formatted(jsonParser.currentLocation().getLineNr()) | ||
| ); | ||
| } | ||
| if(savedInterrogationIds.contains(editedPreviousResponseModel.getInterrogationId())){ | ||
| throw new GenesisException(400, | ||
| "Double interrogationId : %s".formatted(editedPreviousResponseModel.getInterrogationId())); | ||
| } | ||
|
|
||
| toSave.add(editedPreviousResponseModel); | ||
| savedInterrogationIds.add(editedPreviousResponseModel.getInterrogationId()); | ||
|
|
||
| if(toSave.size() >= BLOCK_SIZE){ | ||
| editedPreviousResponsePersistancePort.saveAll(toSave); | ||
| savedCount += toSave.size(); | ||
| toSave.clear(); | ||
| } | ||
| jsonParser.nextToken(); //skip } | ||
| } | ||
| editedPreviousResponsePersistancePort.saveAll(toSave); | ||
| savedCount += toSave.size(); | ||
| log.info("Reached end of edited previous file, saved %d interrogations".formatted(savedCount)); | ||
| editedPreviousResponsePersistancePort.deleteBackup(questionnaireId); | ||
| }catch (JsonParseException jpe){ | ||
| editedPreviousResponsePersistancePort.restoreBackup(questionnaireId); | ||
| throw new GenesisException(400, "JSON Parsing exception : %s".formatted(jpe.toString())); | ||
| }catch (IOException ioe){ | ||
| editedPreviousResponsePersistancePort.restoreBackup(questionnaireId); | ||
| throw new GenesisException(500, ioe.toString()); | ||
| } | ||
| } | ||
|
|
||
| private EditedPreviousResponseModel readNextEditedPrevious(JsonParser jsonParser, | ||
| String questionnaireId, | ||
| String sourceState | ||
| ) throws IOException { | ||
| if(jsonParser.currentToken() != JsonToken.START_OBJECT){ | ||
| throw new JsonParseException("Expected { on line %d, got token %s".formatted(jsonParser.currentLocation().getLineNr(), jsonParser.currentToken())); | ||
| } | ||
| EditedPreviousResponseModel editedPreviousResponseModel = EditedPreviousResponseModel.builder() | ||
| .questionnaireId(questionnaireId) | ||
| .sourceState(sourceState) | ||
| .variables(new HashMap<>()) | ||
| .build(); | ||
| jsonParser.nextToken(); // read { | ||
| while (!jsonParser.currentToken().equals(JsonToken.END_OBJECT)){ | ||
| if(jsonParser.currentToken().equals(JsonToken.FIELD_NAME) && jsonParser.currentName().equals("interrogationId")){ | ||
| jsonParser.nextToken(); | ||
| editedPreviousResponseModel.setInterrogationId(jsonParser.getText()); | ||
| jsonParser.nextToken(); | ||
| continue; | ||
| } | ||
| jsonParser.nextToken(); | ||
| editedPreviousResponseModel.getVariables().put( | ||
| jsonParser.currentName(), | ||
| readValue(jsonParser) | ||
| ); | ||
| jsonParser.nextToken(); | ||
| } | ||
| return editedPreviousResponseModel; | ||
| } | ||
|
|
||
| private Object readValue(JsonParser jsonParser) throws IOException{ | ||
| switch (jsonParser.currentToken()){ | ||
| case VALUE_STRING -> { | ||
| return jsonParser.getText(); | ||
| } | ||
| case VALUE_NUMBER_INT -> { | ||
| return jsonParser.getIntValue(); | ||
| } | ||
| case VALUE_NUMBER_FLOAT -> { | ||
| return jsonParser.getDoubleValue(); | ||
| } | ||
| case VALUE_TRUE, VALUE_FALSE -> { | ||
| return jsonParser.getBooleanValue(); | ||
| } | ||
| case VALUE_NULL -> { | ||
| return null; | ||
| } | ||
| case START_ARRAY -> { | ||
| return readArray(jsonParser); | ||
| } | ||
| case null, default -> throw new JsonParseException("Unexpected token %s on line %d".formatted( | ||
| jsonParser.currentToken(), jsonParser.currentLocation().getLineNr()) | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| private List<Object> readArray(JsonParser jsonParser) throws IOException { | ||
| List<Object> list = new ArrayList<>(); | ||
| jsonParser.nextToken(); //Read [ | ||
| while(!jsonParser.currentToken().equals(JsonToken.END_ARRAY)){ | ||
| list.add(readValue(jsonParser)); | ||
| jsonParser.nextToken(); | ||
| } | ||
| return list; | ||
| } | ||
| } | ||
79 changes: 79 additions & 0 deletions
79
...main/java/fr/insee/genesis/infrastructure/adapter/EditedPreviousResponseMongoAdapter.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| package fr.insee.genesis.infrastructure.adapter; | ||
|
|
||
| import fr.insee.genesis.domain.model.editedprevious.EditedPreviousResponseModel; | ||
| import fr.insee.genesis.domain.ports.spi.EditedPreviousResponsePersistancePort; | ||
| import fr.insee.genesis.infrastructure.document.editedprevious.EditedPreviousResponseDocument; | ||
| import fr.insee.genesis.infrastructure.mappers.EditedPreviousResponseDocumentMapper; | ||
| import fr.insee.genesis.infrastructure.repository.EditedPreviousResponseMongoDBRepository; | ||
| import org.springframework.beans.factory.annotation.Qualifier; | ||
| import org.springframework.data.mongodb.core.MongoTemplate; | ||
| import org.springframework.data.mongodb.core.aggregation.Aggregation; | ||
| import org.springframework.data.mongodb.core.aggregation.MatchOperation; | ||
| import org.springframework.data.mongodb.core.aggregation.MergeOperation; | ||
| import org.springframework.data.mongodb.core.query.Criteria; | ||
| import org.springframework.stereotype.Service; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Service | ||
| @Qualifier("editedPreviousResponseMongoAdapter") | ||
| public class EditedPreviousResponseMongoAdapter implements EditedPreviousResponsePersistancePort { | ||
| private final MongoTemplate mongoTemplate; | ||
| private final EditedPreviousResponseMongoDBRepository repository; | ||
|
|
||
| public EditedPreviousResponseMongoAdapter(EditedPreviousResponseMongoDBRepository repository, MongoTemplate mongoTemplate) { | ||
| this.repository = repository; | ||
| this.mongoTemplate = mongoTemplate; | ||
| } | ||
|
|
||
| @Override | ||
| public void backup(String questionnaireId) { | ||
| deleteBackup(questionnaireId); | ||
| MatchOperation match = Aggregation.match(Criteria.where("questionnaireId").is(questionnaireId)); | ||
| MergeOperation merge = Aggregation | ||
| .merge() | ||
| .intoCollection("editedPreviousResponses_%s_backup".formatted(questionnaireId)) | ||
| .whenMatched(MergeOperation.WhenDocumentsMatch.replaceDocument()) | ||
| .whenDocumentsDontMatch(MergeOperation.WhenDocumentsDontMatch.insertNewDocument()) | ||
| .build(); | ||
|
|
||
| Aggregation aggregation = Aggregation.newAggregation(match, merge); | ||
|
|
||
| mongoTemplate.aggregate(aggregation, "editedPreviousResponses", EditedPreviousResponseDocument.class); | ||
| } | ||
|
|
||
| @Override | ||
| public void deleteBackup(String questionnaireId) { | ||
| if (mongoTemplate.collectionExists("editedPreviousResponses_%s_backup".formatted(questionnaireId))){ | ||
| mongoTemplate.dropCollection("editedPreviousResponses_%s_backup".formatted(questionnaireId)); | ||
| } | ||
| } | ||
|
|
||
| @Override | ||
| public void restoreBackup(String questionnaireId) { | ||
| delete(questionnaireId); | ||
| MergeOperation merge = Aggregation | ||
| .merge() | ||
| .intoCollection("editedPreviousResponses") | ||
| .whenMatched(MergeOperation.WhenDocumentsMatch.replaceDocument()) | ||
| .whenDocumentsDontMatch(MergeOperation.WhenDocumentsDontMatch.insertNewDocument()) | ||
| .build(); | ||
|
|
||
| Aggregation aggregation = Aggregation.newAggregation(merge); | ||
|
|
||
| mongoTemplate.aggregate(aggregation, "editedPreviousResponses_%s_backup".formatted(questionnaireId), | ||
| EditedPreviousResponseDocument.class); | ||
| } | ||
|
|
||
| @Override | ||
| public void saveAll(List<EditedPreviousResponseModel> editedPreviousResponseModelList) { | ||
| repository.saveAll(EditedPreviousResponseDocumentMapper.INSTANCE.listModelToListDocument( | ||
| editedPreviousResponseModelList) | ||
| ); | ||
| } | ||
|
|
||
| @Override | ||
| public void delete(String questionnaireId) { | ||
| repository.deleteByQuestionnaireId(questionnaireId); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.