From 92bde050c5d45ac6a1c37ba75d4d8f795836ea6e Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Thu, 24 Jul 2025 14:48:31 -0700 Subject: [PATCH 01/19] Adding 'external link' Class to old and current "output dataset" API. --- .../dto/output-dataset-obsolete.dto.ts | 14 +++++++++ src/datasets/dto/output-dataset.dto.ts | 24 ++++++++++++-- src/datasets/schemas/externallink.class.ts | 31 +++++++++++++++++++ 3 files changed, 67 insertions(+), 2 deletions(-) create mode 100644 src/datasets/schemas/externallink.class.ts diff --git a/src/datasets/dto/output-dataset-obsolete.dto.ts b/src/datasets/dto/output-dataset-obsolete.dto.ts index d367241a6..e8703d93f 100644 --- a/src/datasets/dto/output-dataset-obsolete.dto.ts +++ b/src/datasets/dto/output-dataset-obsolete.dto.ts @@ -5,6 +5,7 @@ import { IsObject, IsOptional, IsString, + ValidateNested, } from "class-validator"; import { ApiProperty, getSchemaPath } from "@nestjs/swagger"; import { UpdateDatasetObsoleteDto } from "./update-dataset-obsolete.dto"; @@ -13,6 +14,7 @@ import { OrigDatablock } from "src/origdatablocks/schemas/origdatablock.schema"; import { Datablock } from "src/datablocks/schemas/datablock.schema"; import { DatasetType } from "../types/dataset-type.enum"; import { OutputAttachmentV3Dto } from "src/attachments/dto-obsolete/output-attachment.v3.dto"; +import { ExternalLinkClass } from "../schemas/externallink.class"; export class OutputDatasetObsoleteDto extends UpdateDatasetObsoleteDto { @ApiProperty({ @@ -200,6 +202,18 @@ export class OutputDatasetObsoleteDto extends UpdateDatasetObsoleteDto { @Type(() => Datablock) datablocks?: Datablock[]; + @ApiProperty({ + type: [ExternalLinkClass], + required: false, + default: [], + description: "List of external links that involve this data set.", + }) + @IsArray() + @IsOptional() + @ValidateNested({ each: true }) + @Type(() => ExternalLinkClass) + readonly externalLinks?: ExternalLinkClass[]; + @ApiProperty({ type: String, required: true, diff --git a/src/datasets/dto/output-dataset.dto.ts b/src/datasets/dto/output-dataset.dto.ts index 0a507d395..b67deda98 100644 --- a/src/datasets/dto/output-dataset.dto.ts +++ b/src/datasets/dto/output-dataset.dto.ts @@ -1,6 +1,14 @@ -import { ApiProperty, PartialType } from "@nestjs/swagger"; +import { ApiProperty, getSchemaPath, PartialType } from "@nestjs/swagger"; import { CreateDatasetDto } from "./create-dataset.dto"; -import { IsDateString, IsString } from "class-validator"; +import { Type } from "class-transformer"; +import { + IsArray, + IsDateString, + IsOptional, + IsString, + ValidateNested, +} from "class-validator"; +import { ExternalLinkClass } from "../schemas/externallink.class"; export class OutputDatasetDto extends CreateDatasetDto { @ApiProperty({ @@ -39,6 +47,18 @@ export class OutputDatasetDto extends CreateDatasetDto { @IsDateString() updatedAt: Date; + @ApiProperty({ + type: [ExternalLinkClass], + required: false, + default: [], + description: "List of external links that involve this data set.", + }) + @IsArray() + @IsOptional() + @ValidateNested({ each: true }) + @Type(() => ExternalLinkClass) + readonly externalLinks?: ExternalLinkClass[]; + @ApiProperty({ type: String, required: true, diff --git a/src/datasets/schemas/externallink.class.ts b/src/datasets/schemas/externallink.class.ts new file mode 100644 index 000000000..de47ad59c --- /dev/null +++ b/src/datasets/schemas/externallink.class.ts @@ -0,0 +1,31 @@ +import { ApiProperty } from "@nestjs/swagger"; +import { IsString } from "class-validator"; + +// This data is not represented in Mongoose. +// It is generated internally based on the user-defined link templates. + +export class ExternalLinkClass { + @ApiProperty({ + type: String, + required: true, + description: "URL of the external link.", + }) + @IsString() + readonly url: string; + + @ApiProperty({ + type: String, + required: true, + description: "Text to display representing the external link.", + }) + @IsString() + readonly title: string; + + @ApiProperty({ + type: String, + required: false, + description: "Description of the link destination.", + }) + @IsString() + readonly description?: string; +} From 1baa254df239171a60fc811df48909407259a5dc Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Thu, 7 Aug 2025 14:10:29 -0700 Subject: [PATCH 02/19] Test version of virtual (derived) value for external links. --- src/datasets/schemas/dataset.schema.ts | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/src/datasets/schemas/dataset.schema.ts b/src/datasets/schemas/dataset.schema.ts index 0c15e9531..ea434baa9 100644 --- a/src/datasets/schemas/dataset.schema.ts +++ b/src/datasets/schemas/dataset.schema.ts @@ -1,8 +1,9 @@ -import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; +import { Prop, Virtual, Schema, SchemaFactory } from "@nestjs/mongoose"; import { ApiProperty, getSchemaPath } from "@nestjs/swagger"; import { Document } from "mongoose"; import { OwnableClass } from "src/common/schemas/ownable.schema"; import { v4 as uuidv4 } from "uuid"; +import { ExternalLinkClass } from "./externallink.class"; import { HistoryClass, HistorySchema } from "./history.schema"; import { LifecycleClass, LifecycleSchema } from "./lifecycle.schema"; import { RelationshipClass, RelationshipSchema } from "./relationship.schema"; @@ -14,8 +15,12 @@ export type DatasetDocument = DatasetClass & Document; collection: "Dataset", minimize: false, timestamps: true, + toObject: { + virtuals: true, + }, toJSON: { getters: true, + virtuals: true, }, }) export class DatasetClass extends OwnableClass { @@ -265,6 +270,25 @@ export class DatasetClass extends OwnableClass { @Prop({ type: String, required: true }) version: string; + @ApiProperty({ + type: [ExternalLinkClass], + required: false, + default: [], + description: "List of external links that involve this data set.", + }) + @Virtual({ + get: function (this: ExternalLinkClass[]) { + //`${this.firstName} ${this.lastName}`; + return [ + { url: "test_url", + title: "test_title", + description: "test_description" + } + ]; + }, + }) + externalLinks?: ExternalLinkClass[]; + @ApiProperty({ type: "array", items: { $ref: getSchemaPath(HistoryClass) }, From ddeceeb07251dab72b9066e89b967c0afe157f6e Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Wed, 13 Aug 2025 15:57:04 -0700 Subject: [PATCH 03/19] Applying some test templates, in a draft data structure --- src/datasets/schemas/dataset.schema.ts | 29 +++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/src/datasets/schemas/dataset.schema.ts b/src/datasets/schemas/dataset.schema.ts index ea434baa9..9e0c69da2 100644 --- a/src/datasets/schemas/dataset.schema.ts +++ b/src/datasets/schemas/dataset.schema.ts @@ -278,13 +278,32 @@ export class DatasetClass extends OwnableClass { }) @Virtual({ get: function (this: ExternalLinkClass[]) { - //`${this.firstName} ${this.lastName}`; - return [ - { url: "test_url", - title: "test_title", - description: "test_description" + const thisDataSet = this; + const externalLinkDefinitions = [ + { + "title": "Franzviewer", + "url_template": "https://franz.site.com/franzviewer?id=${dataset.pid}", + "description_template": "View ${dataset.numberOfFiles} files in the FranzViewer", + "filter": "(dataset.type == 'derived') && dataset.owner.includes('Franz')", + }, + { + "title": "High Beam-Energy View", + "url_template": "https://beamviewer.beamline.net/highenergy?id=${dataset.pid}", + "description_template": "The high-energy beamviewer (value ${dataset.scientificMetadata?.beamEnergy?.value}) at beamCo", + "filter": "(dataset.scientificMetadata?.beamEnergy?.value > 20)", } ]; + return externalLinkDefinitions.filter((d) => { + const filterFn = new Function("dataset", `return (${d.filter});`); + return filterFn(thisDataSet); + }).map((d) => { + const urlFn = new Function("dataset", `return (\`${d.url_template}\`);`); + const descriptionFn = new Function("dataset", `return (\`${d.description_template}\`);`); + return { url: urlFn(thisDataSet), + title: d.title, + description: descriptionFn(thisDataSet) + }; + }); }, }) externalLinks?: ExternalLinkClass[]; From 1cd8986ffc649e9ec6fb7dc3a4bea7099a552d43 Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Thu, 14 Aug 2025 16:20:28 -0700 Subject: [PATCH 04/19] Adding a config json file and section for link templates. --- .gitignore | 1 + datasetExternalLinkTemplates.example.json | 14 ++++++++++++++ src/config/configuration.ts | 3 +++ 3 files changed, 18 insertions(+) create mode 100644 datasetExternalLinkTemplates.example.json diff --git a/.gitignore b/.gitignore index 3a2b4567e..04171bab6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ /node_modules functionalAccounts.json datasetTypes.json +datasetExternalLinkTemplates.json proposalTypes.json loggers.json jobConfig.json diff --git a/datasetExternalLinkTemplates.example.json b/datasetExternalLinkTemplates.example.json new file mode 100644 index 000000000..3ee23320f --- /dev/null +++ b/datasetExternalLinkTemplates.example.json @@ -0,0 +1,14 @@ +[ + { + "title": "Franzviewer II", + "url_template": "https://franz.site.com/franzviewer?id=${dataset.pid}", + "description_template": "View ${dataset.numberOfFiles} files in Franz' own personal viewer", + "filter": "(dataset.type == 'derived') && dataset.owner.includes('Franz')" + }, + { + "title": "High Beam-Energy View", + "url_template": "https://beamviewer.beamline.net/highenergy?id=${dataset.pid}", + "description_template": "The high-energy beamviewer (value ${dataset.scientificMetadata?.beamEnergy?.value}) at beamCo", + "filter": "(dataset.scientificMetadata?.beamEnergy?.value > 20)" + } +] diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 6975135c0..26823c113 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -50,6 +50,7 @@ const configuration = () => { }; const jsonConfigMap: { [key: string]: object | object[] | boolean } = { datasetTypes: {}, + datasetExternalLinkTemplates: { datasetExternalLinkTemplates: []}, proposalTypes: {}, }; const jsonConfigFileList: { [key: string]: string } = { @@ -59,6 +60,7 @@ const configuration = () => { process.env.FRONTEND_THEME_FILE || "./src/config/frontend.theme.json", loggers: process.env.LOGGERS_CONFIG_FILE || "loggers.json", datasetTypes: process.env.DATASET_TYPES_FILE || "datasetTypes.json", + datasetExternalLinkTemplates: process.env.DATASET_EXTERNAL_LINK_TEMPLATES_FILE || "datasetExternalLinkTemplates.json", proposalTypes: process.env.PROPOSAL_TYPES_FILE || "proposalTypes.json", metricsConfig: process.env.METRICS_CONFIG_FILE || "metricsConfig.json", }; @@ -316,6 +318,7 @@ const configuration = () => { policyRetentionShiftInYears: process.env.POLICY_RETENTION_SHIFT ?? -1, }, datasetTypes: jsonConfigMap.datasetTypes, + datasetExternalLinkTemplates: jsonConfigMap.datasetExternalLinkTemplates, proposalTypes: jsonConfigMap.proposalTypes, frontendConfig: jsonConfigMap.frontendConfig, frontendTheme: jsonConfigMap.frontendTheme, From 58e1b48848d84cc7df1fa312024e4c3351333768 Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Thu, 14 Aug 2025 16:21:09 -0700 Subject: [PATCH 05/19] Moving the virtual field creation up to the module so we can get access to the injected configuration. --- src/datasets/datasets.module.ts | 31 ++++++++++++++++++-- src/datasets/schemas/dataset.schema.ts | 39 +------------------------- 2 files changed, 30 insertions(+), 40 deletions(-) diff --git a/src/datasets/datasets.module.ts b/src/datasets/datasets.module.ts index 8eb8b11c1..075a1913d 100644 --- a/src/datasets/datasets.module.ts +++ b/src/datasets/datasets.module.ts @@ -1,6 +1,7 @@ import { forwardRef, Module } from "@nestjs/common"; import { MongooseModule } from "@nestjs/mongoose"; import { HttpModule } from "@nestjs/axios"; +import { ConfigService } from "@nestjs/config"; import { DatasetClass, DatasetSchema } from "./schemas/dataset.schema"; import { DatasetsController } from "./datasets.controller"; import { DatasetsService } from "./datasets.service"; @@ -30,10 +31,36 @@ import { CaslModule } from "src/casl/casl.module"; { name: DatasetClass.name, imports: [PoliciesModule], - inject: [PoliciesService], - useFactory: (policyService: PoliciesService) => { + inject: [ConfigService, PoliciesService], + useFactory: (configService: ConfigService, policyService: PoliciesService) => { const schema = DatasetSchema; + schema.virtual('externalLinks').get( function() { + const thisDataSet = this; + + interface ExternalLinkTemplateConfig { + title: string; + url_template: string; + description_template: string; + filter: string; + } + + const templates:ExternalLinkTemplateConfig[] | undefined = configService.get("datasetExternalLinkTemplates"); + if (!templates) { return []; } + + return templates.filter((d) => { + const filterFn = new Function("dataset", `return (${d.filter});`); + return filterFn(thisDataSet); + }).map((d) => { + const urlFn = new Function("dataset", `return (\`${d.url_template}\`);`); + const descriptionFn = new Function("dataset", `return (\`${d.description_template}\`);`); + return { url: urlFn(thisDataSet), + title: d.title, + description: descriptionFn(thisDataSet) + }; + }); + }); + schema.pre("save", async function (next) { // if _id is empty or differnet than pid, // set _id to pid diff --git a/src/datasets/schemas/dataset.schema.ts b/src/datasets/schemas/dataset.schema.ts index 9e0c69da2..a617d24cf 100644 --- a/src/datasets/schemas/dataset.schema.ts +++ b/src/datasets/schemas/dataset.schema.ts @@ -1,3 +1,4 @@ +import { ConfigService } from "@nestjs/config"; import { Prop, Virtual, Schema, SchemaFactory } from "@nestjs/mongoose"; import { ApiProperty, getSchemaPath } from "@nestjs/swagger"; import { Document } from "mongoose"; @@ -270,44 +271,6 @@ export class DatasetClass extends OwnableClass { @Prop({ type: String, required: true }) version: string; - @ApiProperty({ - type: [ExternalLinkClass], - required: false, - default: [], - description: "List of external links that involve this data set.", - }) - @Virtual({ - get: function (this: ExternalLinkClass[]) { - const thisDataSet = this; - const externalLinkDefinitions = [ - { - "title": "Franzviewer", - "url_template": "https://franz.site.com/franzviewer?id=${dataset.pid}", - "description_template": "View ${dataset.numberOfFiles} files in the FranzViewer", - "filter": "(dataset.type == 'derived') && dataset.owner.includes('Franz')", - }, - { - "title": "High Beam-Energy View", - "url_template": "https://beamviewer.beamline.net/highenergy?id=${dataset.pid}", - "description_template": "The high-energy beamviewer (value ${dataset.scientificMetadata?.beamEnergy?.value}) at beamCo", - "filter": "(dataset.scientificMetadata?.beamEnergy?.value > 20)", - } - ]; - return externalLinkDefinitions.filter((d) => { - const filterFn = new Function("dataset", `return (${d.filter});`); - return filterFn(thisDataSet); - }).map((d) => { - const urlFn = new Function("dataset", `return (\`${d.url_template}\`);`); - const descriptionFn = new Function("dataset", `return (\`${d.description_template}\`);`); - return { url: urlFn(thisDataSet), - title: d.title, - description: descriptionFn(thisDataSet) - }; - }); - }, - }) - externalLinks?: ExternalLinkClass[]; - @ApiProperty({ type: "array", items: { $ref: getSchemaPath(HistoryClass) }, From a419d1d54c8fb540a81309807934d5b9518eb81c Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Thu, 14 Aug 2025 16:21:18 -0700 Subject: [PATCH 06/19] Minor commentary and cleanup. --- src/datasets/dto/output-dataset.dto.ts | 2 +- src/datasets/schemas/externallink.class.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/datasets/dto/output-dataset.dto.ts b/src/datasets/dto/output-dataset.dto.ts index 5716286fb..8f0aca903 100644 --- a/src/datasets/dto/output-dataset.dto.ts +++ b/src/datasets/dto/output-dataset.dto.ts @@ -1,4 +1,4 @@ -import { ApiProperty, getSchemaPath, PartialType } from "@nestjs/swagger"; +import { ApiProperty, PartialType } from "@nestjs/swagger"; import { CreateDatasetDto } from "./create-dataset.dto"; import { Type } from "class-transformer"; import { diff --git a/src/datasets/schemas/externallink.class.ts b/src/datasets/schemas/externallink.class.ts index de47ad59c..6040146fc 100644 --- a/src/datasets/schemas/externallink.class.ts +++ b/src/datasets/schemas/externallink.class.ts @@ -1,8 +1,10 @@ import { ApiProperty } from "@nestjs/swagger"; import { IsString } from "class-validator"; -// This data is not represented in Mongoose. -// It is generated internally based on the user-defined link templates. +// This class defines the externalLinks field in a dataset. +// That field is not represented in the Mongoose data store, +// so there is no equivalent schema representation for it. +// The content is created in the datasets module as a virtual field. export class ExternalLinkClass { @ApiProperty({ From fa3d6f3fde35e4ca4a652e177f5bc1acd16444ec Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Thu, 14 Aug 2025 17:22:25 -0700 Subject: [PATCH 07/19] Too much nesting. :D --- src/config/configuration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 26823c113..0cb8faca4 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -50,7 +50,7 @@ const configuration = () => { }; const jsonConfigMap: { [key: string]: object | object[] | boolean } = { datasetTypes: {}, - datasetExternalLinkTemplates: { datasetExternalLinkTemplates: []}, + datasetExternalLinkTemplates: [], proposalTypes: {}, }; const jsonConfigFileList: { [key: string]: string } = { From d1d15444343ccd21c921bde6b2d272f2d96cea76 Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Thu, 14 Aug 2025 17:26:38 -0700 Subject: [PATCH 08/19] Lint. --- src/config/configuration.ts | 4 ++- src/datasets/datasets.module.ts | 48 ++++++++++++++++++-------- src/datasets/schemas/dataset.schema.ts | 4 +-- 3 files changed, 37 insertions(+), 19 deletions(-) diff --git a/src/config/configuration.ts b/src/config/configuration.ts index 0cb8faca4..85fc27ea1 100644 --- a/src/config/configuration.ts +++ b/src/config/configuration.ts @@ -60,7 +60,9 @@ const configuration = () => { process.env.FRONTEND_THEME_FILE || "./src/config/frontend.theme.json", loggers: process.env.LOGGERS_CONFIG_FILE || "loggers.json", datasetTypes: process.env.DATASET_TYPES_FILE || "datasetTypes.json", - datasetExternalLinkTemplates: process.env.DATASET_EXTERNAL_LINK_TEMPLATES_FILE || "datasetExternalLinkTemplates.json", + datasetExternalLinkTemplates: + process.env.DATASET_EXTERNAL_LINK_TEMPLATES_FILE || + "datasetExternalLinkTemplates.json", proposalTypes: process.env.PROPOSAL_TYPES_FILE || "proposalTypes.json", metricsConfig: process.env.METRICS_CONFIG_FILE || "metricsConfig.json", }; diff --git a/src/datasets/datasets.module.ts b/src/datasets/datasets.module.ts index 075a1913d..b1eb6c8a2 100644 --- a/src/datasets/datasets.module.ts +++ b/src/datasets/datasets.module.ts @@ -32,10 +32,13 @@ import { CaslModule } from "src/casl/casl.module"; name: DatasetClass.name, imports: [PoliciesModule], inject: [ConfigService, PoliciesService], - useFactory: (configService: ConfigService, policyService: PoliciesService) => { + useFactory: ( + configService: ConfigService, + policyService: PoliciesService, + ) => { const schema = DatasetSchema; - schema.virtual('externalLinks').get( function() { + schema.virtual("externalLinks").get(function () { const thisDataSet = this; interface ExternalLinkTemplateConfig { @@ -45,20 +48,35 @@ import { CaslModule } from "src/casl/casl.module"; filter: string; } - const templates:ExternalLinkTemplateConfig[] | undefined = configService.get("datasetExternalLinkTemplates"); - if (!templates) { return []; } + const templates: ExternalLinkTemplateConfig[] | undefined = + configService.get("datasetExternalLinkTemplates"); + if (!templates) { + return []; + } - return templates.filter((d) => { - const filterFn = new Function("dataset", `return (${d.filter});`); - return filterFn(thisDataSet); - }).map((d) => { - const urlFn = new Function("dataset", `return (\`${d.url_template}\`);`); - const descriptionFn = new Function("dataset", `return (\`${d.description_template}\`);`); - return { url: urlFn(thisDataSet), - title: d.title, - description: descriptionFn(thisDataSet) - }; - }); + return templates + .filter((d) => { + const filterFn = new Function( + "dataset", + `return (${d.filter});`, + ); + return filterFn(thisDataSet); + }) + .map((d) => { + const urlFn = new Function( + "dataset", + `return (\`${d.url_template}\`);`, + ); + const descriptionFn = new Function( + "dataset", + `return (\`${d.description_template}\`);`, + ); + return { + url: urlFn(thisDataSet), + title: d.title, + description: descriptionFn(thisDataSet), + }; + }); }); schema.pre("save", async function (next) { diff --git a/src/datasets/schemas/dataset.schema.ts b/src/datasets/schemas/dataset.schema.ts index a617d24cf..92ac0139f 100644 --- a/src/datasets/schemas/dataset.schema.ts +++ b/src/datasets/schemas/dataset.schema.ts @@ -1,10 +1,8 @@ -import { ConfigService } from "@nestjs/config"; -import { Prop, Virtual, Schema, SchemaFactory } from "@nestjs/mongoose"; +import { Prop, Schema, SchemaFactory } from "@nestjs/mongoose"; import { ApiProperty, getSchemaPath } from "@nestjs/swagger"; import { Document } from "mongoose"; import { OwnableClass } from "src/common/schemas/ownable.schema"; import { v4 as uuidv4 } from "uuid"; -import { ExternalLinkClass } from "./externallink.class"; import { HistoryClass, HistorySchema } from "./history.schema"; import { LifecycleClass, LifecycleSchema } from "./lifecycle.schema"; import { RelationshipClass, RelationshipSchema } from "./relationship.schema"; From 9afc970606a392480050eafccfe766de7cdbc486 Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Thu, 14 Aug 2025 17:28:43 -0700 Subject: [PATCH 09/19] Sorry linter, we actually need this for the anonymous filter/map functions. --- src/datasets/datasets.module.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/datasets/datasets.module.ts b/src/datasets/datasets.module.ts index b1eb6c8a2..c113ef079 100644 --- a/src/datasets/datasets.module.ts +++ b/src/datasets/datasets.module.ts @@ -39,6 +39,7 @@ import { CaslModule } from "src/casl/casl.module"; const schema = DatasetSchema; schema.virtual("externalLinks").get(function () { + // eslint-disable-next-line @typescript-eslint/no-this-alias const thisDataSet = this; interface ExternalLinkTemplateConfig { From 42fee6af8a911b2c215b86030b5d484c8105b70a Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Mon, 18 Aug 2025 12:20:28 -0700 Subject: [PATCH 10/19] Mentioning datasetExternalLinkTemplates.json in the development instructions. --- README.md | 38 ++++++++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 099bc8d9c..76d525da9 100644 --- a/README.md +++ b/README.md @@ -44,9 +44,10 @@ Thank you for your interest in contributing to our project! 7. _Optional_ Add [loggers.json](#loggers-configuration) file to the root folder and configure multiple loggers. 8. _Optional_ Add [proposalTypes.json](#proposal-types-configuration) file to the root folder and configure the proposal types. 9. _Optional_ Add [datasetTypes.json](#dataset-types-configuration) file to the root folder and configure the dataset types. -10. `npm run start:dev` -11. Go to http://localhost:3000/explorer to get an overview of available endpoints and database schemas. -12. To be able to run the e2e tests with the same setup as in the Github actions you will need to run `npm run prepare:local` and after that run `npm run start:dev`. This will start all needed containers and copy some configuration to the right place. +10. _Optional_ Add [datasetExternalLinkTemplates.json](#dataset-external-link-templates-configuration) file to the root folder and configure the external link types. +11. `npm run start:dev` +12. Go to http://localhost:3000/explorer to get an overview of available endpoints and database schemas. +13. To be able to run the e2e tests with the same setup as in the Github actions you will need to run `npm run prepare:local` and after that run `npm run start:dev`. This will start all needed containers and copy some configuration to the right place. ## Develop in a container using the docker-compose.dev file @@ -57,11 +58,12 @@ Thank you for your interest in contributing to our project! 5. _Optional_ Mount [loggers.json](#loggers-configuration) file to a volume in the container to configure multiple loggers. 6. _Optional_ Mount [proposalTypes.json](#proposal-types-configuration) file to a volume in the container to configure the proposal types. 7. _Optional_ Mount [datasetTypes.json](#dataset-types-configuration) file to a volume in the container to configure the dataset types. -8. _Optional_ Change the container env variables. -9. _Optional_ Create the file test/config/.env.override to override ENV vars that are used when running the tests. -10. Attach to the container. -11. `npm run start:dev` -12. Go to http://localhost:3000/explorer to get an overview of available endpoints and database schemas. +8. _Optional_ Mount [datasetExternalLinkTemplates.json](#dataset-external-link-templates-configuration) file to a volume in the container to configure the external link types. +9. _Optional_ Change the container env variables. +10. _Optional_ Create the file test/config/.env.override to override ENV vars that are used when running the tests. +11. Attach to the container. +12. `npm run start:dev` +13. Go to http://localhost:3000/explorer to get an overview of available endpoints and database schemas. ## Test the app @@ -113,15 +115,27 @@ The `loggers.json.example` file in the root directory showcases the example of c ### Proposal types configuration -Providing a file called _proposalTypes.json_ at the root of the project, locally or in the container, will be automatically loaded into the application configuration service under property called `proposalTypes` and used for validation against proposal creation and update. +If a file called _proposalTypes.json_ is provided at the root of the project, locally or in the container, it will be automatically loaded into the application configuration service under the property `proposalTypes`. + +This content is used for validation against proposal creation and update. -The `proposalTypes.json.example` file in the root directory showcases the example of configuration structure for proposal types. +The file `proposalTypes.example.json` contains an example. ### Dataset types configuration -When providing a file called _datasetTypes.json_ at the root of the project, locally or in the container, it will be automatically loaded into the application configuration service under property called `datasetTypes` and used for validation against dataset creation and update. The types `Raw` and `Derived` are always valid dataset types by default. +If a file called _datasetTypes.json_ is provided at the root of the project, locally or in the container, it will be automatically loaded into the application configuration service under the property `datasetTypes`. + +This content is used for validation against dataset creation and update. The types `Raw` and `Derived` are always valid dataset types by default. + +The file `datasetTypes.example.json` contains an example. + +### Dataset external link templates configuration + +If a file called _datasetExternalLinkTemplates.json_ is provided at at the root of the project, locally or in the container, it will be automatically loaded into the application configuration service under the property `datasetExternalLinkTemplates`. + +The content is used to create links to external websites from individual datasets, based on criteria applied to the dataset metadata. -The `datasetTypes.json.example` file in the root directory showcases an example of configuration structure for dataset types. +The file `datasetExternalLinkTemplates.example.json` contains an example. ## Environment variables From 023bfd7f1cbe69d521cc395beddd769de7e08fc7 Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Thu, 28 Aug 2025 12:40:04 -0700 Subject: [PATCH 11/19] Just a bit of code cleanup. --- src/datasets/datasets.controller.ts | 68 ++++++++++++++--------------- 1 file changed, 33 insertions(+), 35 deletions(-) diff --git a/src/datasets/datasets.controller.ts b/src/datasets/datasets.controller.ts index 2320d3ce8..ff55e47f7 100644 --- a/src/datasets/datasets.controller.ts +++ b/src/datasets/datasets.controller.ts @@ -913,9 +913,9 @@ export class DatasetsController { outputDatasets = datasets.map((dataset) => this.convertCurrentToObsoleteSchema(dataset), ); - await Promise.all( - outputDatasets.map(async (dataset) => { - if (includeFilters) { + if (includeFilters) { + await Promise.all( + outputDatasets.map(async (dataset) => { await Promise.all( includeFilters.map(async ({ relation }) => { switch (relation) { @@ -943,13 +943,9 @@ export class DatasetsController { } }), ); - } else { - /* eslint-disable @typescript-eslint/no-unused-expressions */ - // TODO: check the eslint error "Expected an assignment or function call and instead saw an expression" - dataset; - } - }), - ); + }), + ); + } } return outputDatasets as OutputDatasetObsoleteDto[]; } @@ -1205,7 +1201,7 @@ export class DatasetsController { @ApiOperation({ summary: "It returns the first dataset found.", description: - "It returns the first dataset of the ones that matches the filter provided. The list returned can be modified by providing a filter.", + "Returns the first dataset that matches the provided filters.", }) @ApiQuery({ name: "filter", @@ -1240,33 +1236,35 @@ export class DatasetsController { if (outputDataset) { const includeFilters = mergedFilters.include ?? []; - await Promise.all( - includeFilters.map(async ({ relation }) => { - switch (relation) { - case "attachments": { - outputDataset.attachments = await this.attachmentsService.findAll( - { - datasetId: outputDataset.pid, - }, - ); - break; - } - case "origdatablocks": { - outputDataset.origdatablocks = - await this.origDatablocksService.findAll({ + if (includeFilters) { + await Promise.all( + includeFilters.map(async ({ relation }) => { + switch (relation) { + case "attachments": { + outputDataset.attachments = await this.attachmentsService.findAll( + { + datasetId: outputDataset.pid, + }, + ); + break; + } + case "origdatablocks": { + outputDataset.origdatablocks = + await this.origDatablocksService.findAll({ + where: { datasetId: outputDataset.pid }, + }); + break; + } + case "datablocks": { + outputDataset.datablocks = await this.datablocksService.findAll({ where: { datasetId: outputDataset.pid }, }); - break; + break; + } } - case "datablocks": { - outputDataset.datablocks = await this.datablocksService.findAll({ - where: { datasetId: outputDataset.pid }, - }); - break; - } - } - }), - ); + }), + ); + } } return outputDataset; } From 9183bcf322335b9468be61f0d2985d95d6945c61 Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Thu, 28 Aug 2025 14:28:44 -0700 Subject: [PATCH 12/19] Adding v3 and v4 endpoints for /:pid/externallinks , calling a common function in the Dataset service. --- src/datasets/datasets.controller.ts | 39 ++++++++++++++++++ src/datasets/datasets.service.ts | 55 ++++++++++++++++++++++++++ src/datasets/datasets.v4.controller.ts | 41 +++++++++++++++++++ 3 files changed, 135 insertions(+) diff --git a/src/datasets/datasets.controller.ts b/src/datasets/datasets.controller.ts index ff55e47f7..ba9dace02 100644 --- a/src/datasets/datasets.controller.ts +++ b/src/datasets/datasets.controller.ts @@ -93,6 +93,7 @@ import { import { HistoryClass } from "./schemas/history.schema"; import { TechniqueClass } from "./schemas/technique.schema"; import { RelationshipClass } from "./schemas/relationship.schema"; +import { ExternalLinkClass } from "./schemas/externallink.class"; import { JWTUser } from "src/auth/interfaces/jwt-user.interface"; import { LogbooksService } from "src/logbooks/logbooks.service"; import { OutputDatasetObsoleteDto } from "./dto/output-dataset-obsolete.dto"; @@ -1819,6 +1820,44 @@ export class DatasetsController { return await this.convertCurrentToObsoleteSchema(outputDatasetDto); } + // GET /datasets/:id/externallinks + @UseGuards(PoliciesGuard) + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetRead, DatasetClass) || + ability.can(Action.DatasetReadOnePublic, DatasetClass), + ) + @Get("/:pid/externallinks") + @ApiOperation({ + summary: "Returns dataset external links.", + description: + "Returns the applicable external links for the dataset with the given pid.", + }) + @ApiParam({ + name: "pid", + description: "Id of the dataset to return external links", + type: String, + }) + @ApiResponse({ + status: HttpStatus.OK, + type: ExternalLinkClass, + isArray: true, + description: "A list of exernal link objects.", + }) + async findExternalLinksById( + @Req() request: Request, + @Param("pid") id: string, + ) { + const links = await this.datasetsService.findExternalLinksById(id); + + await this.checkPermissionsForDatasetExtended( + request, + id, + Action.DatasetRead, + ); + + return links; + } + // GET /datasets/:id/thumbnail @UseGuards(PoliciesGuard) @CheckPolicies( diff --git a/src/datasets/datasets.service.ts b/src/datasets/datasets.service.ts index 7227315c9..2897a5a9b 100644 --- a/src/datasets/datasets.service.ts +++ b/src/datasets/datasets.service.ts @@ -37,6 +37,7 @@ import { LogbooksService } from "src/logbooks/logbooks.service"; import { CreateDatasetDto } from "./dto/create-dataset.dto"; import { IDatasetFields } from "./interfaces/dataset-filters.interface"; import { DatasetClass, DatasetDocument } from "./schemas/dataset.schema"; +import { ExternalLinkClass } from "./schemas/externallink.class"; import { PartialUpdateDatasetDto, PartialUpdateDatasetWithHistoryDto, @@ -52,6 +53,7 @@ import { DATASET_LOOKUP_FIELDS, } from "./types/dataset-lookup"; import { DatasetsAccessService } from "./datasets-access.service"; +import {config} from "dotenv"; @Injectable({ scope: Scope.REQUEST }) export class DatasetsService { @@ -406,6 +408,59 @@ export class DatasetsService { throw new NotFoundException(error); } } + + async findExternalLinksById( + id: string, + ): Promise { + + const thisDataSet = await this.findOneComplete({ + where: { pid: id }, + include: [DatasetLookupKeysEnum.all] + }); + + if (!thisDataSet) { + // no luck. we need to create a new dataset + throw new NotFoundException(`Dataset #${id} not found`); + } + + interface ExternalLinkTemplateConfig { + title: string; + url_template: string; + description_template: string; + filter: string; + } + + const templates: ExternalLinkTemplateConfig[] | undefined = + this.configService.get("datasetExternalLinkTemplates"); + if (!templates) { + return []; + } + + return templates + .filter((d) => { + const filterFn = new Function( + "dataset", + `return (${d.filter});`, + ); + return filterFn(thisDataSet); + }) + .map((d) => { + const urlFn = new Function( + "dataset", + `return (\`${d.url_template}\`);`, + ); + const descriptionFn = new Function( + "dataset", + `return (\`${d.description_template}\`);`, + ); + return { + url: urlFn(thisDataSet), + title: d.title, + description: descriptionFn(thisDataSet), + }; + }); + } + // Get metadata keys async metadataKeys( filters: IFilters, diff --git a/src/datasets/datasets.v4.controller.ts b/src/datasets/datasets.v4.controller.ts index 3726c0d98..e074e2cc0 100644 --- a/src/datasets/datasets.v4.controller.ts +++ b/src/datasets/datasets.v4.controller.ts @@ -55,6 +55,7 @@ import { ScientificMetadataValidationPipe } from "./pipes/scientific-metadata-va import { HistoryClass } from "./schemas/history.schema"; import { TechniqueClass } from "./schemas/technique.schema"; import { RelationshipClass } from "./schemas/relationship.schema"; +import { ExternalLinkClass } from "./schemas/externallink.class"; import { JWTUser } from "src/auth/interfaces/jwt-user.interface"; import { LogbooksService } from "src/logbooks/logbooks.service"; import { CreateDatasetDto } from "./dto/create-dataset.dto"; @@ -686,6 +687,46 @@ export class DatasetsV4Controller { return this.datasetsService.count(finalFilters); } + + // GET /datasets/:id/externallinks + @UseGuards(PoliciesGuard) + @CheckPolicies("datasets", (ability: AppAbility) => + ability.can(Action.DatasetRead, DatasetClass), + ) + @Get("/:pid/externallinks") + @ApiOperation({ + summary: "Returns dataset external links.", + description: + "Returns the applicable external links for the dataset with the given pid.", + }) + @ApiParam({ + name: "pid", + description: "Id of the dataset to return external links", + type: String, + }) + @ApiResponse({ + status: HttpStatus.OK, + type: ExternalLinkClass, + isArray: true, + description: "A list of exernal link objects.", + }) + async findExternalLinksById( + @Req() request: Request, + @Param("pid") id: string, + ) { + + const links = await this.datasetsService.findExternalLinksById(id); + + await this.checkPermissionsForDatasetExtended( + request, + id, + Action.DatasetRead, + ); + + return links; + } + + // GET /datasets/:id //@UseGuards(PoliciesGuard) @UseGuards(PoliciesGuard) From ce6e498ddf672b79c8c51fcc22b7e97e908db3b0 Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Thu, 28 Aug 2025 14:29:12 -0700 Subject: [PATCH 13/19] Removing the injected virtual field code for dataset links. --- src/datasets/datasets.module.ts | 45 +-------------------------------- 1 file changed, 1 insertion(+), 44 deletions(-) diff --git a/src/datasets/datasets.module.ts b/src/datasets/datasets.module.ts index c113ef079..13410a118 100644 --- a/src/datasets/datasets.module.ts +++ b/src/datasets/datasets.module.ts @@ -31,55 +31,12 @@ import { CaslModule } from "src/casl/casl.module"; { name: DatasetClass.name, imports: [PoliciesModule], - inject: [ConfigService, PoliciesService], + inject: [PoliciesService], useFactory: ( - configService: ConfigService, policyService: PoliciesService, ) => { const schema = DatasetSchema; - schema.virtual("externalLinks").get(function () { - // eslint-disable-next-line @typescript-eslint/no-this-alias - const thisDataSet = this; - - interface ExternalLinkTemplateConfig { - title: string; - url_template: string; - description_template: string; - filter: string; - } - - const templates: ExternalLinkTemplateConfig[] | undefined = - configService.get("datasetExternalLinkTemplates"); - if (!templates) { - return []; - } - - return templates - .filter((d) => { - const filterFn = new Function( - "dataset", - `return (${d.filter});`, - ); - return filterFn(thisDataSet); - }) - .map((d) => { - const urlFn = new Function( - "dataset", - `return (\`${d.url_template}\`);`, - ); - const descriptionFn = new Function( - "dataset", - `return (\`${d.description_template}\`);`, - ); - return { - url: urlFn(thisDataSet), - title: d.title, - description: descriptionFn(thisDataSet), - }; - }); - }); - schema.pre("save", async function (next) { // if _id is empty or differnet than pid, // set _id to pid From ea96c52a8567779c01be03c0c23fcc8ade25b8c7 Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Thu, 28 Aug 2025 14:29:33 -0700 Subject: [PATCH 14/19] The list of external links is no longer returned as a field in the dataset. --- src/datasets/dto/output-dataset-obsolete.dto.ts | 13 ------------- src/datasets/dto/output-dataset.dto.ts | 16 ---------------- 2 files changed, 29 deletions(-) diff --git a/src/datasets/dto/output-dataset-obsolete.dto.ts b/src/datasets/dto/output-dataset-obsolete.dto.ts index e8703d93f..b0d27bade 100644 --- a/src/datasets/dto/output-dataset-obsolete.dto.ts +++ b/src/datasets/dto/output-dataset-obsolete.dto.ts @@ -14,7 +14,6 @@ import { OrigDatablock } from "src/origdatablocks/schemas/origdatablock.schema"; import { Datablock } from "src/datablocks/schemas/datablock.schema"; import { DatasetType } from "../types/dataset-type.enum"; import { OutputAttachmentV3Dto } from "src/attachments/dto-obsolete/output-attachment.v3.dto"; -import { ExternalLinkClass } from "../schemas/externallink.class"; export class OutputDatasetObsoleteDto extends UpdateDatasetObsoleteDto { @ApiProperty({ @@ -202,18 +201,6 @@ export class OutputDatasetObsoleteDto extends UpdateDatasetObsoleteDto { @Type(() => Datablock) datablocks?: Datablock[]; - @ApiProperty({ - type: [ExternalLinkClass], - required: false, - default: [], - description: "List of external links that involve this data set.", - }) - @IsArray() - @IsOptional() - @ValidateNested({ each: true }) - @Type(() => ExternalLinkClass) - readonly externalLinks?: ExternalLinkClass[]; - @ApiProperty({ type: String, required: true, diff --git a/src/datasets/dto/output-dataset.dto.ts b/src/datasets/dto/output-dataset.dto.ts index 8f0aca903..98bb4ce7d 100644 --- a/src/datasets/dto/output-dataset.dto.ts +++ b/src/datasets/dto/output-dataset.dto.ts @@ -2,13 +2,9 @@ import { ApiProperty, PartialType } from "@nestjs/swagger"; import { CreateDatasetDto } from "./create-dataset.dto"; import { Type } from "class-transformer"; import { - IsArray, IsDateString, - IsOptional, IsString, - ValidateNested, } from "class-validator"; -import { ExternalLinkClass } from "../schemas/externallink.class"; export class OutputDatasetDto extends CreateDatasetDto { @ApiProperty({ @@ -55,18 +51,6 @@ export class OutputDatasetDto extends CreateDatasetDto { @IsDateString() updatedAt: Date; - @ApiProperty({ - type: [ExternalLinkClass], - required: false, - default: [], - description: "List of external links that involve this data set.", - }) - @IsArray() - @IsOptional() - @ValidateNested({ each: true }) - @Type(() => ExternalLinkClass) - readonly externalLinks?: ExternalLinkClass[]; - @ApiProperty({ type: String, required: true, From c5707485158fdf6d9704f8fbcb1f2b9e44fdac24 Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Wed, 3 Sep 2025 13:18:29 -0700 Subject: [PATCH 15/19] This is no longer true. :D --- src/datasets/schemas/externallink.class.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/datasets/schemas/externallink.class.ts b/src/datasets/schemas/externallink.class.ts index 6040146fc..86222d630 100644 --- a/src/datasets/schemas/externallink.class.ts +++ b/src/datasets/schemas/externallink.class.ts @@ -4,7 +4,6 @@ import { IsString } from "class-validator"; // This class defines the externalLinks field in a dataset. // That field is not represented in the Mongoose data store, // so there is no equivalent schema representation for it. -// The content is created in the datasets module as a virtual field. export class ExternalLinkClass { @ApiProperty({ From b76cce269b1dc1d85b92282631dc5e1ce1416da9 Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Wed, 3 Sep 2025 13:20:34 -0700 Subject: [PATCH 16/19] OutputAttachmentV3Dto is not a schema (it has no Mongo representation). Referencing it as one in the APIs causes a validation failure, which prevents the SDK from compiling. I assume this is a new error caused by some package upgrade that made validation more precise. --- src/datasets/dto/output-dataset-obsolete.dto.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/datasets/dto/output-dataset-obsolete.dto.ts b/src/datasets/dto/output-dataset-obsolete.dto.ts index b0d27bade..28e9d4176 100644 --- a/src/datasets/dto/output-dataset-obsolete.dto.ts +++ b/src/datasets/dto/output-dataset-obsolete.dto.ts @@ -166,8 +166,7 @@ export class OutputDatasetObsoleteDto extends UpdateDatasetObsoleteDto { readonly jobLogData?: string; @ApiProperty({ - type: "array", - items: { $ref: getSchemaPath(OutputAttachmentV3Dto) }, + type: [OutputAttachmentV3Dto], required: false, description: "Small, less than 16 MB attachments, envisaged for png/jpeg previews.", From c6503653c9026d80999e65aa9560ae17bb723e6d Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Mon, 8 Sep 2025 13:16:11 -0700 Subject: [PATCH 17/19] Lint --- src/datasets/datasets.controller.ts | 22 +++++++++++----------- src/datasets/datasets.service.ts | 12 +++--------- src/datasets/datasets.v4.controller.ts | 3 --- src/datasets/dto/output-dataset.dto.ts | 5 +---- 4 files changed, 15 insertions(+), 27 deletions(-) diff --git a/src/datasets/datasets.controller.ts b/src/datasets/datasets.controller.ts index 3f700e6bb..0ce61119e 100644 --- a/src/datasets/datasets.controller.ts +++ b/src/datasets/datasets.controller.ts @@ -1204,8 +1204,7 @@ export class DatasetsController { @Get("/findOne") @ApiOperation({ summary: "It returns the first dataset found.", - description: - "Returns the first dataset that matches the provided filters.", + description: "Returns the first dataset that matches the provided filters.", }) @ApiQuery({ name: "filter", @@ -1245,13 +1244,12 @@ export class DatasetsController { includeFilters.map(async ({ relation }) => { switch (relation) { case "attachments": { - outputDataset.attachments = await this.attachmentsService.findAll( - { + outputDataset.attachments = + await this.attachmentsService.findAll({ where: { datasetId: outputDataset.pid, }, - }, - ); + }); break; } case "origdatablocks": { @@ -1259,10 +1257,10 @@ export class DatasetsController { await this.origDatablocksService.findAll({ where: { datasetId: outputDataset.pid }, }); - break; + break; } } - }) + }), ); } } @@ -1818,9 +1816,11 @@ export class DatasetsController { // GET /datasets/:id/externallinks @UseGuards(PoliciesGuard) - @CheckPolicies("datasets", (ability: AppAbility) => - ability.can(Action.DatasetRead, DatasetClass) || - ability.can(Action.DatasetReadOnePublic, DatasetClass), + @CheckPolicies( + "datasets", + (ability: AppAbility) => + ability.can(Action.DatasetRead, DatasetClass) || + ability.can(Action.DatasetReadOnePublic, DatasetClass), ) @Get("/:pid/externallinks") @ApiOperation({ diff --git a/src/datasets/datasets.service.ts b/src/datasets/datasets.service.ts index 4bec7af2f..e5942fd01 100644 --- a/src/datasets/datasets.service.ts +++ b/src/datasets/datasets.service.ts @@ -397,13 +397,10 @@ export class DatasetsService { } } - async findExternalLinksById( - id: string, - ): Promise { - + async findExternalLinksById(id: string): Promise { const thisDataSet = await this.findOneComplete({ where: { pid: id }, - include: [DatasetLookupKeysEnum.all] + include: [DatasetLookupKeysEnum.all], }); if (!thisDataSet) { @@ -426,10 +423,7 @@ export class DatasetsService { return templates .filter((d) => { - const filterFn = new Function( - "dataset", - `return (${d.filter});`, - ); + const filterFn = new Function("dataset", `return (${d.filter});`); return filterFn(thisDataSet); }) .map((d) => { diff --git a/src/datasets/datasets.v4.controller.ts b/src/datasets/datasets.v4.controller.ts index d6b308d3b..c6583e723 100644 --- a/src/datasets/datasets.v4.controller.ts +++ b/src/datasets/datasets.v4.controller.ts @@ -688,7 +688,6 @@ export class DatasetsV4Controller { return this.datasetsService.count(finalFilters); } - // GET /datasets/:id/externallinks @UseGuards(PoliciesGuard) @CheckPolicies("datasets", (ability: AppAbility) => @@ -715,7 +714,6 @@ export class DatasetsV4Controller { @Req() request: Request, @Param("pid") id: string, ) { - const links = await this.datasetsService.findExternalLinksById(id); await this.checkPermissionsForDatasetExtended( @@ -727,7 +725,6 @@ export class DatasetsV4Controller { return links; } - // GET /datasets/:id //@UseGuards(PoliciesGuard) @UseGuards(PoliciesGuard) diff --git a/src/datasets/dto/output-dataset.dto.ts b/src/datasets/dto/output-dataset.dto.ts index 98bb4ce7d..54dc5fe37 100644 --- a/src/datasets/dto/output-dataset.dto.ts +++ b/src/datasets/dto/output-dataset.dto.ts @@ -1,10 +1,7 @@ import { ApiProperty, PartialType } from "@nestjs/swagger"; import { CreateDatasetDto } from "./create-dataset.dto"; import { Type } from "class-transformer"; -import { - IsDateString, - IsString, -} from "class-validator"; +import { IsDateString, IsString } from "class-validator"; export class OutputDatasetDto extends CreateDatasetDto { @ApiProperty({ From 12d5e257051214c85f2be278e268f924c99ffaf8 Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Mon, 8 Sep 2025 13:16:51 -0700 Subject: [PATCH 18/19] Upgrading dependency to latest release to test SDK generation --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 62aac7303..64ebcfd50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "scicat-backend-next", - "version": "4.5.0", + "version": "4.23.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "scicat-backend-next", - "version": "4.5.0", + "version": "4.23.0", "license": "BSD-3-Clause", "dependencies": { "@casl/ability": "^6.3.2", diff --git a/package.json b/package.json index ba8f239b6..7d73ba3fc 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "scicat-backend-next", - "version": "4.5.0", + "version": "4.23.0", "description": "scicat-backend-next", "author": "", "private": true, From 846ff80b0c6cae81d9b70592a58a529cbdbfe6c5 Mon Sep 17 00:00:00 2001 From: Garrett Birkel Date: Mon, 8 Sep 2025 13:19:55 -0700 Subject: [PATCH 19/19] Removing imports that are no longer used. --- src/datasets/dto/output-dataset-obsolete.dto.ts | 4 ++-- src/datasets/dto/output-dataset.dto.ts | 1 - src/datasets/schemas/dataset.schema.ts | 4 ---- 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/datasets/dto/output-dataset-obsolete.dto.ts b/src/datasets/dto/output-dataset-obsolete.dto.ts index 28e9d4176..d367241a6 100644 --- a/src/datasets/dto/output-dataset-obsolete.dto.ts +++ b/src/datasets/dto/output-dataset-obsolete.dto.ts @@ -5,7 +5,6 @@ import { IsObject, IsOptional, IsString, - ValidateNested, } from "class-validator"; import { ApiProperty, getSchemaPath } from "@nestjs/swagger"; import { UpdateDatasetObsoleteDto } from "./update-dataset-obsolete.dto"; @@ -166,7 +165,8 @@ export class OutputDatasetObsoleteDto extends UpdateDatasetObsoleteDto { readonly jobLogData?: string; @ApiProperty({ - type: [OutputAttachmentV3Dto], + type: "array", + items: { $ref: getSchemaPath(OutputAttachmentV3Dto) }, required: false, description: "Small, less than 16 MB attachments, envisaged for png/jpeg previews.", diff --git a/src/datasets/dto/output-dataset.dto.ts b/src/datasets/dto/output-dataset.dto.ts index 54dc5fe37..17c3f42b9 100644 --- a/src/datasets/dto/output-dataset.dto.ts +++ b/src/datasets/dto/output-dataset.dto.ts @@ -1,6 +1,5 @@ import { ApiProperty, PartialType } from "@nestjs/swagger"; import { CreateDatasetDto } from "./create-dataset.dto"; -import { Type } from "class-transformer"; import { IsDateString, IsString } from "class-validator"; export class OutputDatasetDto extends CreateDatasetDto { diff --git a/src/datasets/schemas/dataset.schema.ts b/src/datasets/schemas/dataset.schema.ts index f1a444908..8f4f8a15e 100644 --- a/src/datasets/schemas/dataset.schema.ts +++ b/src/datasets/schemas/dataset.schema.ts @@ -13,12 +13,8 @@ export type DatasetDocument = DatasetClass & Document; collection: "Dataset", minimize: false, timestamps: true, - toObject: { - virtuals: true, - }, toJSON: { getters: true, - virtuals: true, }, }) export class DatasetClass extends OwnableClass {