diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/dataservice/DatasetController.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/dataservice/DatasetController.java index 981fa6f0c9..7d4beec838 100644 --- a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/dataservice/DatasetController.java +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/dataservice/DatasetController.java @@ -259,7 +259,7 @@ private Dataset extractColumnsAsNeededAndSave(final Dataset dataset, final Schem // columns are set. No need to extract return dataset; } - if (dataset.getFileNames() == null || dataset.getFileNames().isEmpty()) { + if (dataset.getFileNames() != null || dataset.getFileNames().isEmpty()) { // no file names to extract columns from return dataset; } @@ -679,9 +679,7 @@ public ResponseEntity uploadData( // HttpServletRequest request, throw new ResponseStatusException(org.springframework.http.HttpStatus.INTERNAL_SERVER_ERROR, error); } - if (updatedDataset.get().getFileNames() == null) { - updatedDataset.get().setFileNames(new ArrayList<>(List.of(filename))); - } else { + if (!updatedDataset.get().getFileNames().contains(filename)) { updatedDataset.get().getFileNames().add(filename); } @@ -772,9 +770,7 @@ private ResponseEntity uploadCSVAndUpdateColumns( updateHeaders(updatedDataset.get(), Arrays.asList(headers)); // add the filename to existing file names - if (updatedDataset.get().getFileNames() == null) { - updatedDataset.get().setFileNames(new ArrayList<>(List.of(filename))); - } else if (!updatedDataset.get().getFileNames().contains(filename)) { + if (!updatedDataset.get().getFileNames().contains(filename)) { updatedDataset.get().getFileNames().add(filename); } diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/dataservice/DocumentController.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/dataservice/DocumentController.java index ce6c370b68..7e58f802b7 100644 --- a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/dataservice/DocumentController.java +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/dataservice/DocumentController.java @@ -556,7 +556,9 @@ public ResponseEntity createDocumentFromXDD(@RequestBody final AddDocument DocumentAsset documentAsset = createDocumentAssetFromXDDDocument( document, userId, extractionResponse.getSuccess().getData(), summaries, permission); if (filename != null) { - documentAsset.getFileNames().add(filename); + if (!documentAsset.getFileNames().contains(filename)) { + documentAsset.getFileNames().add(filename); + } documentAsset = documentAssetService .updateAsset(documentAsset, permission) .orElseThrow(); @@ -809,7 +811,6 @@ private DocumentAsset createDocumentAssetFromXDDDocument( .getMetadata() .put("description", extraction.getProperties().getCaption()); documentAsset.getAssets().add(documentExtraction); - documentAsset.getFileNames().add(documentExtraction.getFileName()); } } } diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/dataservice/ProjectController.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/dataservice/ProjectController.java index b21bb19e99..3f1e624a03 100644 --- a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/dataservice/ProjectController.java +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/dataservice/ProjectController.java @@ -22,6 +22,7 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.security.access.annotation.Secured; import org.springframework.web.bind.annotation.DeleteMapping; @@ -32,7 +33,9 @@ import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RequestPart; import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.multipart.MultipartFile; import org.springframework.web.server.ResponseStatusException; import software.uncharted.terarium.hmiserver.models.TerariumAsset; import software.uncharted.terarium.hmiserver.models.dataservice.AssetType; @@ -567,31 +570,78 @@ public ResponseEntity exportProject(@PathVariable("id") final UUI @ApiResponses( value = { @ApiResponse( - responseCode = "200", + responseCode = "201", description = "The project export", content = { @Content( mediaType = "application/json", schema = - @io.swagger.v3.oas.annotations.media.Schema( - implementation = ProjectExport.class)) + @io.swagger.v3.oas.annotations.media.Schema(implementation = Project.class)) }), + @ApiResponse( + responseCode = "400", + description = "An error occurred when trying to parse the import file", + content = @Content), + @ApiResponse( + responseCode = "500", + description = "There was a rebac issue when creating the project", + content = @Content), @ApiResponse( responseCode = "503", description = "An error occurred when trying to communicate with either the postgres or spicedb" + " databases", content = @Content) }) - @PostMapping("/import") + @PostMapping(value = "/import", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) @Secured(Roles.USER) - public ResponseEntity importProject(@RequestBody final ProjectExport projectExport) { + public ResponseEntity importProject(@RequestPart("file") final MultipartFile input) { + + ProjectExport projectExport; try { - return ResponseEntity.ok(cloneService.importProject(projectExport)); + projectExport = objectMapper.readValue(input.getInputStream(), ProjectExport.class); + } catch (final Exception e) { + log.error("Error parsing project export", e); + throw new ResponseStatusException(HttpStatus.BAD_REQUEST, messages.get("projects.import-parse-failure")); + } + + final String userId = currentUserService.get().getId(); + final String userName = userService.getById(userId).getName(); + + Project project; + try { + project = cloneService.importProject(userId, userName, projectExport); } catch (final Exception e) { log.error("Error importing project", e); throw new ResponseStatusException( HttpStatus.SERVICE_UNAVAILABLE, messages.get("postgres.service-unavailable")); } + + try { + project = projectService.createProject(project); + } catch (final Exception e) { + log.error("Error creating project", e); + throw new ResponseStatusException( + HttpStatus.SERVICE_UNAVAILABLE, messages.get("postgres.service-unavailable")); + } + + try { + final RebacProject rebacProject = new RebacProject(project.getId(), reBACService); + final RebacGroup rebacAskemAdminGroup = new RebacGroup(ReBACService.ASKEM_ADMIN_GROUP_ID, reBACService); + final RebacUser rebacUser = new RebacUser(userId, reBACService); + + rebacUser.createCreatorRelationship(rebacProject); + rebacAskemAdminGroup.createWriterRelationship(rebacProject); + } catch (final Exception e) { + log.error("Error setting user's permissions for project", e); + throw new ResponseStatusException( + HttpStatus.SERVICE_UNAVAILABLE, messages.get("rebac.service-unavailable")); + } catch (final RelationshipAlreadyExistsException e) { + log.error("Error the user is already the creator of this project", e); + throw new ResponseStatusException( + HttpStatus.INTERNAL_SERVER_ERROR, messages.get("rebac.relationship-already-exists")); + } + + return ResponseEntity.status(HttpStatus.CREATED).body(project); } // -------------------------------------------------------------------------- diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/gollm/GoLLMController.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/gollm/GoLLMController.java index e56fb31d94..f056028f23 100644 --- a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/gollm/GoLLMController.java +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/controller/gollm/GoLLMController.java @@ -106,7 +106,7 @@ public ResponseEntity createModelCardTask( @RequestParam(name = "document-id", required = true) final UUID documentId, @RequestParam(name = "mode", required = false, defaultValue = "ASYNC") final TaskMode mode, @RequestParam(name = "project-id", required = false) final UUID projectId) { - Schema.Permission permission = + final Schema.Permission permission = projectService.checkPermissionCanRead(currentUserService.get().getId(), projectId); try { @@ -184,7 +184,7 @@ public ResponseEntity createConfigureModelTask( @RequestParam(name = "workflow-id", required = false) final UUID workflowId, @RequestParam(name = "node-id", required = false) final UUID nodeId, @RequestParam(name = "project-id", required = false) final UUID projectId) { - Schema.Permission permission = + final Schema.Permission permission = projectService.checkPermissionCanRead(currentUserService.get().getId(), projectId); try { @@ -281,7 +281,7 @@ public ResponseEntity createConfigFromDatasetTask( @RequestParam(name = "node-id", required = false) final UUID nodeId, @RequestParam(name = "project-id", required = false) final UUID projectId, @RequestBody(required = false) final ConfigFromDatasetBody body) { - Schema.Permission permission = + final Schema.Permission permission = projectService.checkPermissionCanRead(currentUserService.get().getId(), projectId); try { @@ -393,7 +393,7 @@ public ResponseEntity createCompareModelsTask( @RequestParam(name = "workflow-id", required = false) final UUID workflowId, @RequestParam(name = "node-id", required = false) final UUID nodeId, @RequestParam(name = "project-id", required = false) final UUID projectId) { - Schema.Permission permission = + final Schema.Permission permission = projectService.checkPermissionCanWrite(currentUserService.get().getId(), projectId); try { final List modelCards = new ArrayList<>(); diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/TerariumAsset.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/TerariumAsset.java index 5817fbb1e7..a7fd11a8b9 100644 --- a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/TerariumAsset.java +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/TerariumAsset.java @@ -58,7 +58,8 @@ public TerariumAsset clone() { protected TerariumAsset cloneSuperFields(final TerariumAsset asset) { - // TODO this should be a part of the clone method, and this should implement Cloneable + // TODO this should be a part of the clone method, and this should implement + // Cloneable super.cloneSuperFields(asset); diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/AssetExport.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/AssetExport.java index 07cf85fed7..3d821ea078 100644 --- a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/AssetExport.java +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/AssetExport.java @@ -1,5 +1,6 @@ package software.uncharted.terarium.hmiserver.models.dataservice; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import java.util.HashMap; import java.util.Map; import lombok.Data; @@ -8,6 +9,7 @@ @Data @Accessors(chain = true) +@JsonDeserialize(using = AssetExportDeserializer.class) public class AssetExport { AssetType type; TerariumAsset asset; diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/AssetExportDeserializer.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/AssetExportDeserializer.java new file mode 100644 index 0000000000..a728fadf26 --- /dev/null +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/AssetExportDeserializer.java @@ -0,0 +1,37 @@ +package software.uncharted.terarium.hmiserver.models.dataservice; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import software.uncharted.terarium.hmiserver.models.TerariumAsset; + +public class AssetExportDeserializer extends JsonDeserializer { + + @Override + public AssetExport deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException { + ObjectMapper mapper = (ObjectMapper) jp.getCodec(); + JsonNode node = mapper.readTree(jp); + + final String assetTypeStr = node.get("type").asText(); + final AssetType assetType = AssetType.getAssetType(assetTypeStr, mapper); + + TerariumAsset asset = mapper.treeToValue(node.get("asset"), assetType.getAssetClass()); + + Map files = new HashMap<>(); + if (node.has("files")) { + files = mapper.convertValue(node.get("files"), new TypeReference>() {}); + } + + AssetExport export = new AssetExport(); + export.setType(assetType); + export.setAsset(asset); + export.setFiles(files); + return export; + } +} diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/AssetType.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/AssetType.java index 26a6d53de0..9deded98f9 100644 --- a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/AssetType.java +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/AssetType.java @@ -7,6 +7,14 @@ import org.springframework.http.HttpStatus; import org.springframework.web.server.ResponseStatusException; import software.uncharted.terarium.hmiserver.annotations.TSModel; +import software.uncharted.terarium.hmiserver.models.TerariumAsset; +import software.uncharted.terarium.hmiserver.models.dataservice.code.Code; +import software.uncharted.terarium.hmiserver.models.dataservice.dataset.Dataset; +import software.uncharted.terarium.hmiserver.models.dataservice.document.DocumentAsset; +import software.uncharted.terarium.hmiserver.models.dataservice.model.Model; +import software.uncharted.terarium.hmiserver.models.dataservice.model.ModelConfiguration; +import software.uncharted.terarium.hmiserver.models.dataservice.simulation.Simulation; +import software.uncharted.terarium.hmiserver.models.dataservice.workflow.Workflow; @RequiredArgsConstructor @TSModel @@ -49,4 +57,29 @@ public static AssetType getAssetType(final String assetTypeName, final ObjectMap HttpStatus.BAD_REQUEST, "Failed to convert an AssetTypeName into an AssetType"); } } + + public Class getAssetClass() { + switch (this) { + case ARTIFACT: + return Artifact.class; + case CODE: + return Code.class; + case DATASET: + return Dataset.class; + case DOCUMENT: + return DocumentAsset.class; + case MODEL: + return Model.class; + case MODEL_CONFIGURATION: + return ModelConfiguration.class; + case PUBLICATION: + throw new IllegalArgumentException("Publication assets are not supported"); + case SIMULATION: + return Simulation.class; + case WORKFLOW: + return Workflow.class; + default: + throw new IllegalArgumentException("Unrecognized asset type: " + this); + } + } } diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/ContentTypeDeserializer.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/ContentTypeDeserializer.java new file mode 100644 index 0000000000..0eb91c0302 --- /dev/null +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/ContentTypeDeserializer.java @@ -0,0 +1,31 @@ +package software.uncharted.terarium.hmiserver.models.dataservice; + +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.JsonDeserializer; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.nio.charset.Charset; +import org.apache.http.entity.ContentType; + +public class ContentTypeDeserializer extends JsonDeserializer { + + @Override + public ContentType deserialize(final JsonParser jp, final DeserializationContext ctxt) + throws IOException, JsonProcessingException { + + final ObjectMapper mapper = (ObjectMapper) jp.getCodec(); + final JsonNode node = mapper.readTree(jp); + + final String mimeTypeStr = node.get("mimeType").asText(); + if (!node.has("charset")) { + return ContentType.create(mimeTypeStr); + } + + final String charsetStr = node.get("charset").asText(); + final Charset charset = Charset.forName(charsetStr); + return ContentType.create(mimeTypeStr, charset); + } +} diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/FileExport.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/FileExport.java index db2a7b2f62..1c14a045ee 100644 --- a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/FileExport.java +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/models/dataservice/FileExport.java @@ -1,5 +1,6 @@ package software.uncharted.terarium.hmiserver.models.dataservice; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; import lombok.Data; import lombok.experimental.Accessors; import org.apache.http.entity.ContentType; @@ -8,6 +9,8 @@ @Accessors(chain = true) public class FileExport { - byte[] bytes; + @JsonDeserialize(using = ContentTypeDeserializer.class) ContentType contentType; + + byte[] bytes; } diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/repository/data/ProjectAssetRepository.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/repository/data/ProjectAssetRepository.java index 8608c2de1b..d552c0086e 100644 --- a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/repository/data/ProjectAssetRepository.java +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/repository/data/ProjectAssetRepository.java @@ -14,7 +14,7 @@ public interface ProjectAssetRepository extends PSCrudRepository findAllByProjectId(@NotNull UUID projectId); + List findAllByProjectIdAndDeletedOnIsNullAndTemporaryFalse(@NotNull UUID projectId); List findAllByProjectIdAndAssetTypeInAndDeletedOnIsNullAndTemporaryFalse( @NotNull UUID projectId, Collection<@NotNull AssetType> assetType); diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/service/TerariumAssetCloneService.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/service/TerariumAssetCloneService.java index acfb23bdff..8fe11b9bde 100644 --- a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/service/TerariumAssetCloneService.java +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/service/TerariumAssetCloneService.java @@ -174,6 +174,8 @@ public ProjectExport exportProject(final UUID projectId) throws IOException { final ProjectExport projectExport = new ProjectExport(); projectExport.setProject(project.clone()); + projectExport.getProject().setUserId(null); // clear the user id + projectExport.getProject().setUserName(null); // clear the user name projectExport.setAssets(exportedAssets); return projectExport.clone(); } @@ -186,10 +188,15 @@ public ProjectExport exportProject(final UUID projectId) throws IOException { * @throws IOException */ @SuppressWarnings({"unchecked", "rawtypes"}) - public Project importProject(final ProjectExport export) throws IOException { + public Project importProject(final String userId, final String userName, final ProjectExport export) + throws IOException { final ProjectExport projectExport = export.clone(); // clone in case it has been imported already + // set the current user id + projectExport.getProject().setUserId(userId); + projectExport.getProject().setUserName(userName); + // create the project final Project project = projectService.createProject(projectExport.getProject()); diff --git a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/service/data/ProjectAssetService.java b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/service/data/ProjectAssetService.java index a0853a1fd0..8e839758ec 100644 --- a/packages/server/src/main/java/software/uncharted/terarium/hmiserver/service/data/ProjectAssetService.java +++ b/packages/server/src/main/java/software/uncharted/terarium/hmiserver/service/data/ProjectAssetService.java @@ -140,7 +140,7 @@ public Optional getProjectAssetByProjectIdAndAssetId( @Observed(name = "function_profile") public List getProjectAssets(final UUID projectId, final Schema.Permission hasReadPermission) { - return projectAssetRepository.findAllByProjectId(projectId); + return projectAssetRepository.findAllByProjectIdAndDeletedOnIsNullAndTemporaryFalse(projectId); } @Observed(name = "function_profile") diff --git a/packages/server/src/main/resources/messages.properties b/packages/server/src/main/resources/messages.properties index e9efb83d72..f70f34fcc0 100644 --- a/packages/server/src/main/resources/messages.properties +++ b/packages/server/src/main/resources/messages.properties @@ -34,6 +34,7 @@ projects.unable-to-delete = We were unable to delete the project. Please try aga projects.unable-to-update = We were unable to update the project. Please try again later. projects.unable-to-get-permissions = Terarium was unable to determine read/write permissions. For safety reasons, we are unable to return results. Please try again later. projects.name-required = You must supply a name for the project. +projects.import-parse-failure = We were unable to parse the import file. Please ensure that the file is in the correct format and try again. rebac.relationship-already-exists = You already have permissions set to access this resource. Please contact the system administrator to upgrade permissions. rebac.service-unavailable = The service that Terarium uses for security and authorization is temporarily unavailable. Please try again later. diff --git a/packages/server/src/test/java/software/uncharted/terarium/hmiserver/service/data/TerariumAssetCloneServiceTests.java b/packages/server/src/test/java/software/uncharted/terarium/hmiserver/service/data/TerariumAssetCloneServiceTests.java index 0a91d326da..5d10326356 100644 --- a/packages/server/src/test/java/software/uncharted/terarium/hmiserver/service/data/TerariumAssetCloneServiceTests.java +++ b/packages/server/src/test/java/software/uncharted/terarium/hmiserver/service/data/TerariumAssetCloneServiceTests.java @@ -218,7 +218,7 @@ public void testItCanExportImportProject() throws Exception { Assertions.assertEquals(1 + NUM_DOCUMENTS, projectExport.getAssets().size()); - final Project importedProject = cloneService.importProject(projectExport); + final Project importedProject = cloneService.importProject("test_user_id", "test_user_name", projectExport); Assertions.assertNotEquals(project.getId(), importedProject.getId()); Assertions.assertEquals(project.getName(), importedProject.getName());