Skip to content

Commit

Permalink
Attachments implementation (#1343)
Browse files Browse the repository at this point in the history
Co-authored-by: alexeykozyurov <[email protected]>
  • Loading branch information
damirabdul and Leshe4ka authored May 25, 2023
1 parent 413a1d7 commit 6bb7624
Show file tree
Hide file tree
Showing 104 changed files with 3,252 additions and 377 deletions.
4 changes: 4 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ logback = '1.2.11'
easy-random-core = '5.0.0'
protobuf-java = '3.21.12'
snappy-java = '1.1.9.1'
minio = '8.4.6'
okhttp = '4.10.0'

[libraries]
spring-starter-webflux = { module = 'org.springframework.boot:spring-boot-starter-webflux' }
Expand Down Expand Up @@ -104,6 +106,8 @@ testcontainers-junit-jupiter = { module = 'org.testcontainers:junit-jupiter', ve
easy-random-core = { module = 'org.jeasy:easy-random-core', version.ref = 'easy-random-core' }
protobuf-java = { module = 'com.google.protobuf:protobuf-java', version.ref = 'protobuf-java' }
snappy-java = { module = 'org.xerial.snappy:snappy-java', version.ref = 'snappy-java' }
minio = { module = 'io.minio:minio', version.ref = 'minio' }
okhttp = { module = 'com.squareup.okhttp3:okhttp', version.ref = 'okhttp' }

[bundles]
spring = [
Expand Down
1 change: 1 addition & 0 deletions odd-platform-api-contract/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ openApiGenerate {

inputSpec = "$projectDir/../odd-platform-specification/openapi.yaml"
outputDir = "$buildDir/generated"
templateDir = "$projectDir/../odd-platform-specification/templates"

apiPackage = "org.opendatadiscovery.oddplatform.api.contract.api"
invokerPackage = "org.opendatadiscovery.oddplatform.api.contract"
Expand Down
2 changes: 2 additions & 0 deletions odd-platform-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ dependencies {
implementation libs.caffeine
implementation libs.protobuf.java
implementation libs.snappy.java
implementation libs.minio
implementation libs.okhttp

compileOnly libs.lombok

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
import java.util.List;
import lombok.experimental.UtilityClass;
import org.opendatadiscovery.oddplatform.auth.manager.AuthorizationManagerType;
import org.springframework.http.HttpMethod;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;

import static org.opendatadiscovery.oddplatform.auth.manager.AuthorizationManagerType.ALERT;
Expand All @@ -23,6 +22,7 @@
import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.DATA_ENTITY_ADD_TO_GROUP;
import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.DATA_ENTITY_ALERT_CONFIG_UPDATE;
import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.DATA_ENTITY_ALERT_RESOLVE;
import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.DATA_ENTITY_ATTACHMENT_MANAGE;
import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.DATA_ENTITY_CUSTOM_METADATA_CREATE;
import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.DATA_ENTITY_CUSTOM_METADATA_DELETE;
import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.DATA_ENTITY_CUSTOM_METADATA_UPDATE;
Expand Down Expand Up @@ -68,6 +68,7 @@
import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.TERM_TAGS_UPDATE;
import static org.opendatadiscovery.oddplatform.dto.policy.PolicyPermissionDto.TERM_UPDATE;
import static org.springframework.http.HttpMethod.DELETE;
import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;
import static org.springframework.http.HttpMethod.PUT;

Expand Down Expand Up @@ -128,7 +129,7 @@ public final class SecurityConstants {
new SecurityRule(NO_CONTEXT, new PathPatternParserServerWebExchangeMatcher("/api/owners/{owner_id}", DELETE),
OWNER_DELETE),
new SecurityRule(
NO_CONTEXT, new PathPatternParserServerWebExchangeMatcher("/api/owner_association_request", HttpMethod.GET),
NO_CONTEXT, new PathPatternParserServerWebExchangeMatcher("/api/owner_association_request", GET),
OWNER_ASSOCIATION_MANAGE),
new SecurityRule(
NO_CONTEXT, new PathPatternParserServerWebExchangeMatcher(
Expand Down Expand Up @@ -211,6 +212,36 @@ DATA_ENTITY, new PathPatternParserServerWebExchangeMatcher(
DATA_ENTITY, new PathPatternParserServerWebExchangeMatcher(
"/api/datasets/{data_entity_id}/dataqatests/{dataqa_test_id}/severity", PUT),
DATASET_TEST_RUN_SET_SEVERITY),
new SecurityRule(
DATA_ENTITY,
new PathPatternParserServerWebExchangeMatcher("/api/dataentities/{data_entity_id}/files/**", POST),
DATA_ENTITY_ATTACHMENT_MANAGE
),
new SecurityRule(
DATA_ENTITY,
new PathPatternParserServerWebExchangeMatcher("/api/dataentities/{data_entity_id}/files/**", PUT),
DATA_ENTITY_ATTACHMENT_MANAGE
),
new SecurityRule(
DATA_ENTITY,
new PathPatternParserServerWebExchangeMatcher("/api/dataentities/{data_entity_id}/files/{file_id}", DELETE),
DATA_ENTITY_ATTACHMENT_MANAGE
),
new SecurityRule(
DATA_ENTITY,
new PathPatternParserServerWebExchangeMatcher("/api/dataentities/{data_entity_id}/links", POST),
DATA_ENTITY_ATTACHMENT_MANAGE
),
new SecurityRule(
DATA_ENTITY,
new PathPatternParserServerWebExchangeMatcher("/api/dataentities/{data_entity_id}/links/{link_id}", PUT),
DATA_ENTITY_ATTACHMENT_MANAGE
),
new SecurityRule(
DATA_ENTITY,
new PathPatternParserServerWebExchangeMatcher("/api/dataentities/{data_entity_id}/links/{link_id}", DELETE),
DATA_ENTITY_ATTACHMENT_MANAGE
),
new SecurityRule(DATASET_FIELD,
new PathPatternParserServerWebExchangeMatcher("/api/datasetfields/{dataset_field_id}/description", PUT),
DATASET_FIELD_DESCRIPTION_UPDATE),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.opendatadiscovery.oddplatform.config;

import io.minio.MinioAsyncClient;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConditionalOnProperty(value = "attachment.storage", havingValue = "REMOTE")
public class MinioConfig {
@Value("${attachment.remote.url}")
private String url;
@Value("${attachment.remote.access-key}")
private String accessKey;
@Value("${attachment.remote.secret-key}")
private String secretKey;

@Bean
public MinioAsyncClient minioClient() {
return MinioAsyncClient.builder()
.endpoint(url)
.credentials(accessKey, secretKey)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package org.opendatadiscovery.oddplatform.controller;

import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.opendatadiscovery.oddplatform.api.contract.api.DataEntityAttachmentApi;
import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityAttachments;
import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityFile;
import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityLink;
import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityLinkFormData;
import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityLinkListFormData;
import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityUpload;
import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityUploadFormData;
import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityUploadOptions;
import org.opendatadiscovery.oddplatform.service.attachment.AttachmentService;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.codec.multipart.Part;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

@RestController
@RequiredArgsConstructor
public class DataEntityAttachmentController implements DataEntityAttachmentApi {
private final AttachmentService attachmentService;

@Override
public Mono<ResponseEntity<DataEntityAttachments>> getAttachments(final Long dataEntityId,
final ServerWebExchange exchange) {
return attachmentService.getDataEntityAttachments(dataEntityId)
.map(ResponseEntity::ok);
}

@Override
public Mono<ResponseEntity<DataEntityUploadOptions>> getUploadOptions(final Long dataEntityId,
final ServerWebExchange exchange) {
return attachmentService.getUploadOptions()
.map(ResponseEntity::ok);
}

@Override
public Mono<ResponseEntity<DataEntityUpload>> initiateFileUpload(final Long dataEntityId,
final Mono<DataEntityUploadFormData> formDataMono,
final ServerWebExchange exchange) {
return formDataMono
.flatMap(formData -> attachmentService.initiateFileUpload(formData, dataEntityId))
.map(ResponseEntity::ok);
}

@Override
public Mono<ResponseEntity<Void>> uploadFileChunk(final Long dataEntityId,
final UUID uploadId,
final Mono<Part> fileMono,
final String index,
final ServerWebExchange exchange) {
return fileMono
.flatMap(file -> attachmentService.uploadFileChunk(uploadId, file, Integer.parseInt(index)))
.map(ResponseEntity::ok);
}

@Override
public Mono<ResponseEntity<DataEntityFile>> completeFileUpload(final Long dataEntityId,
final UUID uploadId,
final ServerWebExchange exchange) {
return attachmentService.completeFileUpload(uploadId)
.map(ResponseEntity::ok);
}

@Override
public Mono<ResponseEntity<Resource>> downloadFile(final Long dataEntityId,
final Long fileId,
final ServerWebExchange exchange) {
return attachmentService.downloadFile(fileId).map(dto -> ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment;filename=" + dto.fileName())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(dto.resource()));
}

@Override
public Mono<ResponseEntity<Void>> deleteFile(final Long dataEntityId,
final Long fileId,
final ServerWebExchange exchange) {
return attachmentService.deleteFile(fileId)
.thenReturn(ResponseEntity.noContent().build());
}

@Override
public Mono<ResponseEntity<Flux<DataEntityLink>>> saveLinks(final Long dataEntityId,
final Mono<DataEntityLinkListFormData> formData,
final ServerWebExchange exchange) {
return formData
.map(fd -> attachmentService.saveLinks(dataEntityId, fd))
.map(ResponseEntity::ok);
}

@Override
public Mono<ResponseEntity<DataEntityLink>> updateLink(final Long dataEntityId,
final Long linkId,
final Mono<DataEntityLinkFormData> dataEntityLinkFormData,
final ServerWebExchange exchange) {
return dataEntityLinkFormData
.flatMap(fd -> attachmentService.updateLink(linkId, fd))
.map(ResponseEntity::ok);
}

@Override
public Mono<ResponseEntity<Void>> deleteLink(final Long dataEntityId,
final Long linkId,
final ServerWebExchange exchange) {
return attachmentService.deleteLink(linkId)
.thenReturn(ResponseEntity.noContent().build());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package org.opendatadiscovery.oddplatform.dto;

import org.springframework.core.io.Resource;

public record FileDto(Resource resource, String fileName) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package org.opendatadiscovery.oddplatform.dto;

import java.util.Arrays;
import java.util.Optional;
import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public enum FileUploadStatus {
PROCESSING(1),
COMPLETED(2);

@Getter
private final short code;

FileUploadStatus(final int code) {
this.code = ((short) code);
}

public static Optional<FileUploadStatus> fromCode(final short code) {
return Arrays.stream(FileUploadStatus.values())
.filter(s -> s.getCode() == code)
.findFirst();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ public enum PolicyPermissionDto {
DATA_ENTITY_GROUP_CREATE(MANAGEMENT),
DATA_ENTITY_GROUP_UPDATE(DATA_ENTITY),
DATA_ENTITY_GROUP_DELETE(DATA_ENTITY),
DATA_ENTITY_ATTACHMENT_MANAGE(DATA_ENTITY),
TERM_CREATE(MANAGEMENT),
TERM_UPDATE(TERM),
TERM_DELETE(TERM),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.opendatadiscovery.oddplatform.exception;

public class MinioException extends RuntimeException {

public MinioException(final Throwable cause) {
super(cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package org.opendatadiscovery.oddplatform.mapper;

import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityFile;
import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityUploadFormData;
import org.opendatadiscovery.oddplatform.model.tables.pojos.FilePojo;
import org.opendatadiscovery.oddplatform.service.attachment.FilePathConstructor;
import org.springframework.stereotype.Component;

import static org.opendatadiscovery.oddplatform.dto.FileUploadStatus.PROCESSING;

@Component
@RequiredArgsConstructor
public class FileMapper {
private final FilePathConstructor pathConstructor;

public DataEntityFile mapToDto(final FilePojo pojo) {
final DataEntityFile file = new DataEntityFile();
file.setId(pojo.getId());
file.setName(pojo.getName());
return file;
}

public FilePojo mapToProcessingPojo(final DataEntityUploadFormData fileMetadata,
final UUID uploadId,
final long dataEntityId) {
final FilePojo filePojo = new FilePojo();
filePojo.setDataEntityId(dataEntityId);
filePojo.setName(fileMetadata.getFileName());
filePojo.setPath(pathConstructor.getFilePath(fileMetadata.getFileName(), dataEntityId));
filePojo.setUploadId(uploadId);
filePojo.setStatus(PROCESSING.getCode());
return filePojo;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.opendatadiscovery.oddplatform.mapper;

import org.mapstruct.Mapper;
import org.mapstruct.MappingTarget;
import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityLink;
import org.opendatadiscovery.oddplatform.api.contract.model.DataEntityLinkFormData;
import org.opendatadiscovery.oddplatform.model.tables.pojos.LinkPojo;

@Mapper(config = MapperConfig.class)
public interface LinkMapper {
DataEntityLink mapToDto(final LinkPojo pojo);

LinkPojo mapToPojo(final DataEntityLinkFormData formData, final long dataEntityId);

LinkPojo applyToPojo(final DataEntityLinkFormData formData,
@MappingTarget final LinkPojo pojo);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import org.jooq.impl.DSL;
import org.opendatadiscovery.oddplatform.model.tables.pojos.MetricFamilyPojo;
import org.opendatadiscovery.oddplatform.model.tables.records.MetricFamilyRecord;
import org.opendatadiscovery.oddplatform.repository.util.JooqQueryHelper;
import org.opendatadiscovery.oddplatform.repository.util.JooqReactiveOperations;
import org.springframework.stereotype.Repository;
import reactor.core.publisher.Flux;
Expand All @@ -21,7 +20,6 @@
@RequiredArgsConstructor
public class MetricFamilyRepositoryImpl implements MetricFamilyRepository {
private final JooqReactiveOperations jooqReactiveOperations;
private final JooqQueryHelper jooqQueryHelper;

@Override
public Flux<MetricFamilyPojo> createOrUpdateMetricFamilies(final Collection<MetricFamilyPojo> metricFamilies) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.opendatadiscovery.oddplatform.repository.reactive;

import java.util.UUID;
import org.opendatadiscovery.oddplatform.model.tables.pojos.FilePojo;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

public interface FileRepository extends ReactiveCRUDRepository<FilePojo> {
Mono<FilePojo> getFileByDataEntityAndName(final long dataEntityId, final String fileName);

Mono<FilePojo> getFileByUploadId(final UUID uploadId);

Flux<FilePojo> getDataEntityFiles(final long dataEntityId);
}
Loading

0 comments on commit 6bb7624

Please sign in to comment.