Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 44 additions & 3 deletions src/common/utils/deep-mapper.util.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { get, trim } from "lodash";
import { get, merge, set, trim } from "lodash";

function mapDeep<T, U>(
type MappingFn<S> = (source: S) => unknown;

type FieldsMap<S> = Partial<Record<keyof S & string, string | MappingFn<S>>>;

function getDeep<T, U>(
source: T,
key: keyof U & string,
fieldsMap: Partial<Record<keyof U & string, string>>,
Expand All @@ -22,6 +26,43 @@ export function createDeepMapper<T, U>(
fieldsMap: Partial<Record<keyof U & string, string>>,
) {
return (source: T, key: string) => {
return mapDeep<T, U>(source, key as keyof U & string, fieldsMap);
return getDeep<T, U>(source, key as keyof U & string, fieldsMap);
};
}

function setDeep<S>(
source: S,
key: keyof S & string,
fieldsMap: FieldsMap<S>,
): Record<string, unknown> {
if (!source) return {};
const instruction = fieldsMap[key];
if (typeof instruction === "function") return { [key]: instruction(source) };
const path = (instruction as string) || key;
const value = get(source, key);
if (value === undefined) return {};
const fragment = {};
if (!path.includes("[]")) return set(fragment, path, value);
const [arrayRootPath, rawLeafPath] = path.split("[]");
const leafPath = trim(rawLeafPath, ".");
const arrayResult: Record<string, unknown>[] = [];

if (Array.isArray(value))
value.forEach((itemValue, index) => {
arrayResult[index] = {};
set(arrayResult[index], leafPath, itemValue);
});
else {
arrayResult[0] = {};
set(arrayResult[0], leafPath, value);
}
return set(fragment, trim(arrayRootPath, "."), arrayResult);
}

export function createDeepSetter<S, T>(fieldsMap: FieldsMap<S>) {
return (source: S): T =>
Object.keys(source as keyof S).reduce((acc, key) => {
const fragment = setDeep<S>(source, key as keyof S & string, fieldsMap);
return merge(acc, fragment);
}, {}) as T;
}
2 changes: 1 addition & 1 deletion src/published-data/dto/published-data.obsolete.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export const publishedDataV3toV4FieldMap: Partial<
downloadLink: "metadata.downloadLink",
};

const mapPublishedDataV3toV4Field = createDeepMapper<
export const mapPublishedDataV3toV4Field = createDeepMapper<
PublishedData,
PublishedDataObsoleteDto
>(publishedDataV3toV4FieldMap);
Expand Down
28 changes: 28 additions & 0 deletions src/published-data/pipes/body-dto.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Injectable, PipeTransform } from "@nestjs/common";
import { createDeepSetter } from "src/common/utils/deep-mapper.util";
import { CreatePublishedDataDto } from "../dto/create-published-data.dto";
import { CreatePublishedDataV4Dto } from "../dto/create-published-data.v4.dto";
import { publishedDataV3toV4FieldMap } from "../dto/published-data.obsolete.dto";
import { PublishedDataStatus } from "../interfaces/published-data.interface";

const dtoV3toV4 = createDeepSetter<
CreatePublishedDataDto,
CreatePublishedDataV4Dto
>({
...publishedDataV3toV4FieldMap,
status: (src: CreatePublishedDataDto): PublishedDataStatus =>
src.status === "registered"
? PublishedDataStatus.REGISTERED
: PublishedDataStatus.PRIVATE,
});

@Injectable()
export class V3ToV4MigrationPipe<S, T> implements PipeTransform {
constructor(private readonly mapper: (source: S) => T) {}

transform(value: S): T {
return this.mapper(value);
}
}

export const V3_TO_V4_DTO_BODY_PIPE = new V3ToV4MigrationPipe(dtoV3toV4);
17 changes: 0 additions & 17 deletions src/published-data/published-data.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { AttachmentsService } from "src/attachments/attachments.service";
import { CaslAbilityFactory } from "src/casl/casl-ability.factory";
import { DatasetsService } from "src/datasets/datasets.service";
import { ProposalsService } from "src/proposals/proposals.service";
import { PublishedDataStatus } from "./interfaces/published-data.interface";
import { PublishedDataController } from "./published-data.controller";
import { PublishedDataService } from "./published-data.service";

Expand Down Expand Up @@ -44,20 +43,4 @@ describe("PublishedDataController", () => {
it("should be defined", () => {
expect(controller).toBeDefined();
});

it("should result in PublishedDataStatus.REGISTERED", () => {
expect(controller.convertObsoleteStatusToCurrent("registered")).toEqual(
PublishedDataStatus.REGISTERED,
);
});
it("should result in PublishedDataStatus.PRIVATE", () => {
expect(
controller.convertObsoleteStatusToCurrent("pending_registration"),
).toEqual(PublishedDataStatus.PRIVATE);
});
it("should result in PublishedDataStatus.PRIVATE", () => {
expect(controller.convertObsoleteStatusToCurrent("invalid_status")).toEqual(
PublishedDataStatus.PRIVATE,
);
});
});
187 changes: 14 additions & 173 deletions src/published-data/published-data.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import {
Get,
HttpException,
HttpStatus,
Logger,
NotFoundException,
Param,
Patch,
Expand Down Expand Up @@ -48,16 +47,12 @@ import {
PartialUpdatePublishedDataDto,
UpdatePublishedDataDto,
} from "./dto/update-published-data.dto";
import {
PartialUpdatePublishedDataV4Dto,
UpdatePublishedDataV4Dto,
} from "./dto/update-published-data.v4.dto";
import { UpdatePublishedDataV4Dto } from "./dto/update-published-data.v4.dto";
import {
FormPopulateData,
ICount,
IPublishedDataFilters,
IRegister,
PublishedDataStatus,
} from "./interfaces/published-data.interface";
import {
IdToDoiPipe,
Expand All @@ -68,6 +63,7 @@ import { PublishedDataService } from "./published-data.service";
import { PublishedData } from "./schemas/published-data.schema";
import { V3_FILTER_PIPE } from "./pipes/filter.pipe";
import { Filter } from "src/datasets/decorators/filter.decorator";
import { V3_TO_V4_DTO_BODY_PIPE } from "./pipes/body-dto.pipe";

@ApiBearerAuth()
@ApiTags("published data")
Expand All @@ -83,152 +79,6 @@ export class PublishedDataController {
private readonly publishedDataService: PublishedDataService,
) {}

convertObsoleteStatusToCurrent(obsoleteStatus: string): PublishedDataStatus {
switch (obsoleteStatus) {
case "registered":
return PublishedDataStatus.REGISTERED;
case "pending_registration":
return PublishedDataStatus.PRIVATE;
default:
Logger.error(
`Unknown PublishedData.status '${obsoleteStatus}' defaulting to PublishedDataStatus.PRIVATE`,
);
return PublishedDataStatus.PRIVATE;
}
}

convertObsoleteToCurrentSchema(
inputObsoletePublishedData:
| CreatePublishedDataDto
| UpdatePublishedDataDto
| PartialUpdatePublishedDataDto,
):
| CreatePublishedDataV4Dto
| UpdatePublishedDataV4Dto
| PartialUpdatePublishedDataV4Dto {
const propertiesModifier: Record<string, any> = {
metadata: {},
title: inputObsoletePublishedData.title,
abstract: inputObsoletePublishedData.abstract,
datasetPids: inputObsoletePublishedData.pidArray,
};

if ("affiliation" in inputObsoletePublishedData) {
propertiesModifier.metadata.affiliation =
inputObsoletePublishedData.affiliation;
}

if ("publisher" in inputObsoletePublishedData) {
propertiesModifier.metadata.publisher = {
name: inputObsoletePublishedData.publisher,
};
}

if ("publicationYear" in inputObsoletePublishedData) {
propertiesModifier.metadata.publicationYear =
inputObsoletePublishedData.publicationYear;
}

if ("creator" in inputObsoletePublishedData) {
propertiesModifier.metadata.creators =
inputObsoletePublishedData.creator?.map((creator) => ({
name: creator.trim(),
affiliation: [
{ name: inputObsoletePublishedData.affiliation?.trim() || "" },
],
}));
}

if ("dataDescription" in inputObsoletePublishedData) {
propertiesModifier.metadata.dataDescription =
inputObsoletePublishedData.dataDescription;
}

if ("resourceType" in inputObsoletePublishedData) {
propertiesModifier.metadata.resourceType =
inputObsoletePublishedData.resourceType;
}

if ("numberOfFiles" in inputObsoletePublishedData) {
propertiesModifier.metadata.numberOfFiles =
inputObsoletePublishedData.numberOfFiles;
}

if ("sizeOfArchive" in inputObsoletePublishedData) {
propertiesModifier.metadata.sizeOfArchive =
inputObsoletePublishedData.sizeOfArchive;
}

if ("url" in inputObsoletePublishedData) {
propertiesModifier.metadata.url = inputObsoletePublishedData.url;
}

if ("thumbnail" in inputObsoletePublishedData) {
propertiesModifier.metadata.thumbnail =
inputObsoletePublishedData.thumbnail;
}

if ("scicatUser" in inputObsoletePublishedData) {
propertiesModifier.metadata.scicatUser =
inputObsoletePublishedData.scicatUser;
}

if ("downloadLink" in inputObsoletePublishedData) {
propertiesModifier.metadata.downloadLink =
inputObsoletePublishedData.downloadLink;
}

if ("authors" in inputObsoletePublishedData) {
propertiesModifier.metadata.contributors =
inputObsoletePublishedData.authors?.map((author) => ({
name: author.trim(),
}));
}

if ("relatedPublications" in inputObsoletePublishedData) {
propertiesModifier.metadata.relatedIdentifiers =
inputObsoletePublishedData.relatedPublications?.map((publication) => ({
relatedIdentifier: publication,
}));
}

if ("pidArray" in inputObsoletePublishedData) {
propertiesModifier.datasetPids = inputObsoletePublishedData.pidArray;
}

if (
"status" in inputObsoletePublishedData &&
typeof inputObsoletePublishedData.status === "string"
) {
propertiesModifier.status = this.convertObsoleteStatusToCurrent(
inputObsoletePublishedData.status,
);
}

let outputPublishedData:
| CreatePublishedDataV4Dto
| UpdatePublishedDataV4Dto
| PartialUpdatePublishedDataV4Dto = {};

if (inputObsoletePublishedData instanceof CreatePublishedDataDto) {
outputPublishedData = {
...propertiesModifier,
} as CreatePublishedDataV4Dto;
} else if (inputObsoletePublishedData instanceof UpdatePublishedDataDto) {
outputPublishedData = {
...propertiesModifier,
} as UpdatePublishedDataV4Dto;
} else if (
inputObsoletePublishedData instanceof PartialUpdatePublishedDataDto
) {
outputPublishedData = {
...propertiesModifier,
} as PartialUpdatePublishedDataV4Dto;
}

return outputPublishedData;
}

// POST /publisheddata
@UseGuards(PoliciesGuard)
@CheckPolicies("publisheddata", (ability: AppAbility) =>
Expand All @@ -245,14 +95,12 @@ export class PublishedDataController {
})
@Post()
async create(
@Body() createPublishedDataDto: CreatePublishedDataDto,
@Body(V3_TO_V4_DTO_BODY_PIPE)
createPublishedDataDto: CreatePublishedDataDto,
): Promise<PublishedDataObsoleteDto> {
const publishedDataDto = this.convertObsoleteToCurrentSchema(
createPublishedDataDto,
) as CreatePublishedDataV4Dto;

const createdPublishedData =
await this.publishedDataService.create(publishedDataDto);
const createdPublishedData = await this.publishedDataService.create(
createPublishedDataDto as unknown as CreatePublishedDataV4Dto,
);

return createdPublishedData as unknown as PublishedDataObsoleteDto;
}
Expand Down Expand Up @@ -456,14 +304,12 @@ export class PublishedDataController {
@Patch("/:id")
async update(
@Param("id") id: string,
@Body() updatePublishedDataDto: PartialUpdatePublishedDataDto,
@Body(V3_TO_V4_DTO_BODY_PIPE)
updatePublishedDataDto: PartialUpdatePublishedDataDto,
): Promise<PublishedDataObsoleteDto | null> {
const updateData = this.convertObsoleteToCurrentSchema(
updatePublishedDataDto,
);
const updatedData = await this.publishedDataService.update(
{ doi: id },
updateData,
updatePublishedDataDto as unknown as PublishedData,
);

return updatedData as unknown as PublishedDataObsoleteDto;
Expand Down Expand Up @@ -721,27 +567,22 @@ export class PublishedDataController {
@Post("/:id/resync")
async resync(
@Param("id") id: string,
@Body() data: UpdatePublishedDataDto,
@Body(V3_TO_V4_DTO_BODY_PIPE)
data: UpdatePublishedDataDto,
): Promise<IRegister | null> {
const { ...obsolettePublishedData } = data;

const publishedData = this.convertObsoleteToCurrentSchema(
obsolettePublishedData,
);

const OAIServerUri = this.configService.get<string>("oaiProviderRoute");

let returnValue = null;
if (OAIServerUri) {
returnValue = await this.publishedDataService.resyncOAIPublication(
id,
publishedData as UpdatePublishedDataV4Dto,
data as unknown as UpdatePublishedDataV4Dto,
OAIServerUri,
);
}

try {
await this.publishedDataService.update({ doi: id }, publishedData);
await this.publishedDataService.update({ doi: id }, data);
} catch (error: any) {
throw new HttpException(
`Error occurred: ${error}`,
Expand Down
Loading