Skip to content
22 changes: 22 additions & 0 deletions .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/debian
{
"name": "Debian",
// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
"image": "mcr.microsoft.com/devcontainers/base:trixie",
"features": {
"ghcr.io/devcontainers/features/node:1": {}
}

// Features to add to the dev container. More info: https://containers.dev/features.
// "features": {},

// Use 'forwardPorts' to make a list of ports inside the container available locally.
// "forwardPorts": [],

// Configure tool-specific properties.
// "customizations": {},

// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
// "remoteUser": "root"
}
41 changes: 41 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
stages:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess .gitlab-ci is desy-specific and should not be included in the final PR

- build
- publish

build_image:
stage: build
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: [""]
script:
- |-
tag=$CI_COMMIT_SHORT_SHA
if [[ -n "$CI_COMMIT_TAG" ]]; then
tag=$CI_COMMIT_TAG
fi
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- >
/kaniko/executor
--context $CI_PROJECT_DIR
--dockerfile $CI_PROJECT_DIR/Dockerfile
--destination $CI_REGISTRY_IMAGE:${tag}
--cache=true

push_image_harbor:
stage: publish
image:
name: gcr.io/kaniko-project/executor:debug
entrypoint: ['']
script:
- mkdir -p /kaniko/.docker
- echo "{\"auths\":{\"${HARBOR_HOST}\":{\"auth\":\"$(echo -n ${HARBOR_USERNAME}:${HARBOR_PASSWORD} | base64 -w 0)\"}}}" > /kaniko/.docker/config.json
- >-
/kaniko/executor
--context "${CI_PROJECT_DIR}"
--dockerfile ${CI_PROJECT_DIR}/Dockerfile
--destination "${HARBOR_HOST}/${HARBOR_PROJECT}/${CI_PROJECT_NAME}:${CI_COMMIT_TAG}"
--cache=true
rules:
- if: $CI_COMMIT_TAG

3 changes: 3 additions & 0 deletions nest-cli.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"assets": [
{ "include": "common/email-templates/*.hbs", "watchAssets": true }
],
"plugins": [{
"name": "@nestjs/swagger",
"options": {
Expand Down
2 changes: 1 addition & 1 deletion src/common/common.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Module } from "@nestjs/common";
import { MailService } from "./mail.service";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't have 'services' directories elsewhere in the project. Is this file rename necessary?

import { MailService } from "./services/mail.service";

@Module({
providers: [MailService],
Expand Down
8 changes: 8 additions & 0 deletions src/common/email-templates/doi-registered.hbs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<p>Hello,</p>

<p>Your DOI has been successfully registered: <strong><a href="{{exchangeString registerDoiUri "api" "doi"}}{{{urlencode doi}}}">{{doi}}</a></strong></p>

<p>Please proceed to DESY's publication database <a href="https://bib-pubdb1.desy.de/submit/direct?ln=en&sub=SBIdataset&hgf_import={{{urlencode doi}}}">PubDB.</a></p>

<p>Best regards,<br>
Your Service Team</p>
4 changes: 4 additions & 0 deletions src/common/handlebars-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ export const urlencode = (context: string): string => {
return encodeURIComponent(context);
};

export const exchangeString = (registerDoiUri: string, search: string, replacement: string): string => {
return registerDoiUri.replace(search, replacement);
};
/**
* Format a unit using mathjs.
*
Expand Down Expand Up @@ -191,6 +194,7 @@ export const handlebarsHelpers: hb.HelperDeclareSpec = {
jsonify: jsonify,
job_v3: job_v3,
urlencode: urlencode,
exchangeString: exchangeString,
base64enc: base64enc,
base64dec: atob,
formatUnit: formatUnit,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ISendMailOptions, MailerService } from "@nestjs-modules/mailer";
import { Injectable, Logger } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
import { isAxiosError } from "@nestjs/terminus/dist/utils";
import { SentMessageInfo } from "nodemailer";

Expand All @@ -10,7 +11,10 @@ import { SentMessageInfo } from "nodemailer";
*/
@Injectable()
export class MailService {
constructor(private readonly mailerService: MailerService) {}
constructor(
private readonly mailerService: MailerService,
private readonly configService: ConfigService,
) {}

async sendMail(options: ISendMailOptions): Promise<SentMessageInfo> {
try {
Expand Down Expand Up @@ -39,4 +43,32 @@ export class MailService {
}
}
}

async sendTemplateEmail(doi: string, userEmail: string): Promise<void> {
const templateName = "doi-registered"; // hdb
const registerDoiUri = this.configService.get<string>("registerDoiUri");
Logger.debug(`registerDoiUri is ${registerDoiUri}`);

try {
const mailOptions: ISendMailOptions = {
to: userEmail,
subject: `DOI registration confirmed: ${doi}.`,
template: templateName,
context: {
registerDoiUri: registerDoiUri,
doi: doi,
},
};

if (!mailOptions.template) {
throw new Error("INTERNAL ERROR: template property is missing!");
}

Logger.debug(`mailOptions: ${JSON.stringify(mailOptions)}`);
await this.sendMail(mailOptions);
Logger.log(`DOI registration email sent to ${userEmail}.`);
} catch (error) {
Logger.error(`Failed to send email: ${JSON.stringify(error)}`);
}
}
}
4 changes: 4 additions & 0 deletions src/config/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import localconfiguration from "./localconfiguration";
import { boolean } from "mathjs";
import { DEFAULT_PROPOSAL_TYPE } from "src/proposals/schemas/proposal.schema";
import { DatasetType } from "src/datasets/types/dataset-type.enum";
import { Logger } from "@nestjs/common";

const configuration = () => {
const accessGroupsStaticValues =
Expand Down Expand Up @@ -85,12 +86,14 @@ const configuration = () => {
publishedDataConfig:
process.env.PUBLISHED_DATA_CONFIG_FILE || "publishedDataConfig.json",
};

Object.keys(jsonConfigFileList).forEach((key) => {
const filePath = jsonConfigFileList[key];
if (fs.existsSync(filePath)) {
const data = fs.readFileSync(filePath, "utf8");
try {
jsonConfigMap[key] = JSON.parse(data);
Logger.debug(`From ${filePath} parsing data ${data}.`);
} catch (error) {
console.error(
"Error json config file parsing " + filePath + " : " + error,
Expand Down Expand Up @@ -123,6 +126,7 @@ const configuration = () => {
`Example configuration file ${exampleFilePath} does not exist. Using empty configuration for ${key}`,
);
jsonConfigMap[key] = {};
Logger.debug(`jsonConfigMap[key] is ${jsonConfigMap[key]}`);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
} from "../../jobconfig.interface";
import { isEmailJobActionOptions } from "./emailaction.interface";
import { EmailJobAction } from "./emailaction";
import { MailService } from "src/common/mail.service";
import { MailService } from "src/common/services/mail.service";

@Injectable()
export class EmailJobActionCreator implements JobActionCreator<JobDto> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import { EmailJobAction } from "./emailaction";
import { EmailJobActionOptions } from "./emailaction.interface";
import { JobClass } from "../../../../jobs/schemas/job.schema";
import { MailService } from "src/common/mail.service";
import { MailService } from "src/common/services/mail.service";
import { MailerService } from "@nestjs-modules/mailer";
import { DatasetClass } from "src/datasets/schemas/dataset.schema";
import { CreateJobDto } from "src/jobs/dto/create-job.dto";
Expand Down
2 changes: 1 addition & 1 deletion src/config/job-config/actions/emailaction/emailaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
} from "../../jobconfig.interface";
import { ISendMailOptions } from "@nestjs-modules/mailer";
import { actionType, EmailJobActionOptions } from "./emailaction.interface";
import { MailService } from "src/common/mail.service";
import { MailService } from "src/common/services/mail.service";

/**
* Send an email following a job
Expand Down
2 changes: 1 addition & 1 deletion src/jobs/jobs.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { ConfigService } from "@nestjs/config";
import { getModelToken } from "@nestjs/mongoose";
import { Test, TestingModule } from "@nestjs/testing";
import { Model } from "mongoose";
import { MailService } from "src/common/mail.service";
import { MailService } from "src/common/services/mail.service";
import { DatasetsService } from "src/datasets/datasets.service";
import { DatasetsAccessService } from "src/datasets/datasets-access.service";
import { PoliciesService } from "src/policies/policies.service";
Expand Down
3 changes: 2 additions & 1 deletion src/published-data/published-data.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from "./schemas/published-data.schema";
import { applyHistoryPluginOnce } from "src/common/mongoose/plugins/history.plugin.util";
import { PublishedDataV4Controller } from "./published-data.v4.controller";
import { MailService } from "src/common/services/mail.service";

@Module({
imports: [
Expand Down Expand Up @@ -65,6 +66,6 @@ import { PublishedDataV4Controller } from "./published-data.v4.controller";
ProposalsModule,
],
controllers: [PublishedDataController, PublishedDataV4Controller],
providers: [PublishedDataService],
providers: [PublishedDataService, MailService],
})
export class PublishedDataModule {}
12 changes: 12 additions & 0 deletions src/published-data/published-data.v4.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ import {
PublishedData,
PublishedDataDocument,
} from "./schemas/published-data.schema";
import { MailService } from "src/common/services/mail.service";
import { logger } from "handlebars";

@ApiBearerAuth()
@ApiTags("published data v4")
Expand All @@ -76,6 +78,7 @@ export class PublishedDataV4Controller {
private readonly proposalsService: ProposalsService,
private readonly publishedDataService: PublishedDataService,
private caslAbilityFactory: CaslAbilityFactory,
private readonly mailService: MailService,
) {}

@AllowAny()
Expand Down Expand Up @@ -617,6 +620,9 @@ export class PublishedDataV4Controller {
};

try {

console.log("registerDataciteDoiOptions is", JSON.stringify(registerDataciteDoiOptions));

await firstValueFrom(
this.httpService.request(registerDataciteDoiOptions),
);
Expand All @@ -636,6 +642,12 @@ export class PublishedDataV4Controller {
{ status: PublishedDataStatus.REGISTERED, registeredTime: new Date() },
);

const user = request.user as JWTUser;
await this.mailService.sendTemplateEmail(
publishedData.doi, // Replacements for the template
user.email, // Recipient email
);

return res;
}

Expand Down