From 45b3dfb6700ba4df74b897482e598b10a1d19f73 Mon Sep 17 00:00:00 2001 From: shadrach-tayo Date: Tue, 9 Jul 2024 13:49:25 +0200 Subject: [PATCH 01/13] Update RecommendedAttestations return data model --- .../attestations/recommendations.ts | 34 +++++++++---------- desci-server/src/services/Attestation.ts | 2 +- .../test/integration/Attestation.test.ts | 4 +-- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/desci-server/src/controllers/attestations/recommendations.ts b/desci-server/src/controllers/attestations/recommendations.ts index 81b4ffe98..7004729b6 100644 --- a/desci-server/src/controllers/attestations/recommendations.ts +++ b/desci-server/src/controllers/attestations/recommendations.ts @@ -13,27 +13,27 @@ const logger = parentLogger.child({ module: 'Recommendations' }); export const getAllRecommendations = async (_req: Request, res: Response, _next: NextFunction) => { const attestations = await attestationService.getRecommendedAttestations(); - const communityEntries = _(attestations) - .groupBy((x) => x.desciCommunityId) + const attestationEntries = _(attestations) + .groupBy((x) => x.attestationId) .map((value, key) => ({ - community: value[0].desciCommunity.name, - communityId: key, - attestations: value.map((attestation) => ({ - id: attestation.id, - communityId: value[0].attestation.community.id, - communityName: value[0].attestation.community.name, - attestationId: attestation.attestationId, - attestationVersionId: attestation.attestationVersionId, - required: attestation.required, - createdAt: attestation.createdAt, - name: attestation.attestationVersion.name, - description: attestation.attestationVersion.description, - image_url: attestation.attestationVersion.image_url, + attestationId: value[0].attestationId, + attestationVersionId: value[0].attestationVersionId, + required: value[0].required, + createdAt: value[0].createdAt, + name: value[0].attestationVersion.name, + description: value[0].attestationVersion.description, + image_url: value[0].attestationVersion.image_url, + communities: value.map((entry) => ({ + communityId: key, + communityName: entry.desciCommunity.name, + image_url: entry.desciCommunity.image_url, })), })) - .value(); + .value() + .sort((entryA, entryB) => entryB.communities.length - entryA.communities.length); - return new SuccessResponse(communityEntries).send(res); + logger.info({ attestationEntries }, 'getAllRecommendations'); + return new SuccessResponse(attestationEntries).send(res); }; export const getCommunityRecommendations = async (req: Request, res: Response, _next: NextFunction) => { diff --git a/desci-server/src/services/Attestation.ts b/desci-server/src/services/Attestation.ts index ca8f76813..686aafd14 100644 --- a/desci-server/src/services/Attestation.ts +++ b/desci-server/src/services/Attestation.ts @@ -744,7 +744,7 @@ export class AttestationService { image_url: true, }, }, - desciCommunity: { select: { name: true, hidden: true } }, + desciCommunity: { select: { name: true, hidden: true, image_url: true } }, }, where: { desciCommunity: { diff --git a/desci-server/test/integration/Attestation.test.ts b/desci-server/test/integration/Attestation.test.ts index 45a484d80..1d7bd25e0 100644 --- a/desci-server/test/integration/Attestation.test.ts +++ b/desci-server/test/integration/Attestation.test.ts @@ -2029,7 +2029,7 @@ describe('Attestations Service', async () => { }); expect(res.status).to.equal(200); - const claims = await attestationService.getAllNodeAttestations(node.uuid); + const claims = await attestationService.getAllNodeAttestations(node.uuid!); expect(claims.length).to.equal(1); }); @@ -2077,7 +2077,7 @@ describe('Attestations Service', async () => { }); expect(res.status).to.equal(200); - const attestations = await attestationService.getAllNodeAttestations(node.uuid); + const attestations = await attestationService.getAllNodeAttestations(node.uuid!); expect(attestations.length).to.equal(2); res = await request(app).get(`/v1/attestations/${node.uuid}`).set('authorization', authHeaderVal); From ca571e4b0e4ca1d52f83c8c51739a1e08a86605f Mon Sep 17 00:00:00 2001 From: shadrach-tayo Date: Wed, 10 Jul 2024 13:06:54 +0200 Subject: [PATCH 02/13] fix: recommended attestation community groups incorrect id --- desci-server/src/controllers/attestations/recommendations.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desci-server/src/controllers/attestations/recommendations.ts b/desci-server/src/controllers/attestations/recommendations.ts index 7004729b6..f20eb18b0 100644 --- a/desci-server/src/controllers/attestations/recommendations.ts +++ b/desci-server/src/controllers/attestations/recommendations.ts @@ -15,7 +15,7 @@ export const getAllRecommendations = async (_req: Request, res: Response, _next: const attestations = await attestationService.getRecommendedAttestations(); const attestationEntries = _(attestations) .groupBy((x) => x.attestationId) - .map((value, key) => ({ + .map((value, _) => ({ attestationId: value[0].attestationId, attestationVersionId: value[0].attestationVersionId, required: value[0].required, @@ -24,7 +24,7 @@ export const getAllRecommendations = async (_req: Request, res: Response, _next: description: value[0].attestationVersion.description, image_url: value[0].attestationVersion.image_url, communities: value.map((entry) => ({ - communityId: key, + communityId: entry.desciCommunityId, communityName: entry.desciCommunity.name, image_url: entry.desciCommunity.image_url, })), From 4169c9b681b11514cd24f9fb63a5af076ac02ed5 Mon Sep 17 00:00:00 2001 From: shadrach-tayo Date: Thu, 11 Jul 2024 15:35:28 +0200 Subject: [PATCH 03/13] handle crossref callback retrieve-url response edge case --- desci-server/package.json | 1 + desci-server/src/controllers/doi/mint.ts | 63 +++++++++-------- desci-server/src/services/crossRef/client.ts | 73 ++++++++++++++++---- desci-server/yarn.lock | 12 ++++ 4 files changed, 109 insertions(+), 40 deletions(-) diff --git a/desci-server/package.json b/desci-server/package.json index caa207b5c..34374a8e1 100755 --- a/desci-server/package.json +++ b/desci-server/package.json @@ -117,6 +117,7 @@ "url-safe-base64": "1.2.0", "uuid": "^8.3.2", "ws": "^8.15.0", + "xml-js": "^1.6.11", "yauzl": "^2.10.0", "zod": "^3.22.4" }, diff --git a/desci-server/src/controllers/doi/mint.ts b/desci-server/src/controllers/doi/mint.ts index e64b7e555..a8a85aa4c 100644 --- a/desci-server/src/controllers/doi/mint.ts +++ b/desci-server/src/controllers/doi/mint.ts @@ -52,7 +52,9 @@ export const handleCrossrefNotificationCallback = async ( if (!submission) { logger.error({ payload: req.payload }, 'Crossref Notifiication: pending submission not found'); - throw new NotFoundError('submission not found'); + // throw new NotFoundError('submission not found'); + new SuccessMessageResponse().send(res); + return; } await doiService.updateSubmission({ id: submission.id }, { notification: req.payload }); @@ -60,33 +62,38 @@ export const handleCrossrefNotificationCallback = async ( new SuccessMessageResponse().send(res); - // check retrieve url to get submission result - const response = await crossRefClient.retrieveSubmission(req.payload.retrieveUrl); + try { + // check retrieve url to get submission result + const response = await crossRefClient.retrieveSubmission(req.payload.retrieveUrl); - logger.info({ response, submission }, 'CREATE DOI CALLBACK RESPONSE'); - if (response.success) { - logger.info('CREATE DOI '); - const doiRecord = await prisma.doiRecord.create({ - data: { - uuid: submission.uuid, - dpid: submission.dpid, - doi: submission.uniqueDoi, - }, - }); - await doiService.updateSubmission( - { id: submission.id }, - { - status: DoiStatus.SUCCESS, - doiRecordId: doiRecord.id, - }, - ); - } else { - logger.info('ERROR CREATING DOI'); - await doiService.updateSubmission( - { id: submission.id }, - { status: response.failure ? DoiStatus.FAILED : DoiStatus.PENDING }, - ); - } + logger.info({ response, submission }, 'CREATE DOI CALLBACK RESPONSE'); + if (response.success) { + logger.info({ response, submission }, 'CREATE DOI '); - // TODO: email authors about the submission status + const doiRecord = await prisma.doiRecord.create({ + data: { + uuid: submission.uuid, + dpid: submission.dpid, + doi: submission.uniqueDoi, + }, + }); + await doiService.updateSubmission( + { id: submission.id }, + { + status: DoiStatus.SUCCESS, + doiRecordId: doiRecord.id, + }, + ); + } else { + logger.info('ERROR CREATING DOI'); + await doiService.updateSubmission( + { id: submission.id }, + { status: response.failure ? DoiStatus.FAILED : DoiStatus.PENDING }, + ); + } + + // TODO: email authors about the submission status + } catch (error) { + logger.error({ error }, 'Error updating DOI submission'); + } }; diff --git a/desci-server/src/services/crossRef/client.ts b/desci-server/src/services/crossRef/client.ts index b8026f2f4..b2942b9a0 100644 --- a/desci-server/src/services/crossRef/client.ts +++ b/desci-server/src/services/crossRef/client.ts @@ -3,6 +3,7 @@ import FormData from 'form-data'; import fetch from 'node-fetch'; import { default as Remixml } from 'remixml'; import { v4 } from 'uuid'; +import { xml2json } from 'xml-js'; import { prisma } from '../../client.js'; import { logger as parentLogger } from '../../logger.js'; @@ -341,22 +342,45 @@ class CrossRefClient { } async retrieveSubmission(retrieveUrl: string) { - // retrieve submission log whose batchId == param.['CROSSREF-EXTERNAL-ID'] - // update with notifiication payload - // query submssion payload from param.CROSSREF-RETRIEVE-URL - // only create doi if submission status is success - try { logger.info({ retrieveUrl }, 'ATTEMPT TO RETRIEVE SUBMISSION'); - const response = (await fetch(retrieveUrl).then((res) => res.json())) as NotificationResult; - logger.info(response, 'RETRIEVE SUBMISSION'); - // return interprete the response from the api to determine if the - // submission status has either `success | pending | failed` - const isSuccess = response?.completed !== null && !!response.recordCreated; - return { success: isSuccess, failure: !isSuccess }; + const response = await fetch(retrieveUrl); + const contentType = response.headers.get('content-type'); + logger.info({ contentType }, 'RETRIEVE SUBMISSION'); + // handle when response is gone + + if (contentType === 'text/xml;charset=UTF-8') { + // handle xml response + const data = await response.text(); + const xmlPayload = xml2json(data); + const result = JSON.parse(xmlPayload) as NotificationResultXmlJson; + logger.info({ result }, 'PAYLOAD'); + const doi_batch_diagnostic = result?.elements?.[0]; + const batch_data = doi_batch_diagnostic.elements?.find((el) => el.name === 'batch_data'); + const success = batch_data?.elements?.find((element) => element.name === 'success_count'); + const isSuccess = success?.elements?.[0]?.text === '1'; + return { success: isSuccess, failure: !isSuccess }; + } else { + // handle json response + const data = await response.text(); + logger.info({ data }, 'RESPONSE'); + let payload: NotificationResult; + try { + payload = JSON.parse(data) as NotificationResult; + logger.info({ payload }, 'PAYLOAD'); + } catch (err) { + logger.info({ err }, 'Cannot parse json body'); + payload = JSON.parse(data.substring(1, data.length - 1)); + logger.info({ payload }, 'PAYLOAD'); + } + // return interprete the response from the api to determine if the + // submission status has either `success | pending | failed` + const isSuccess = payload?.completed !== null && !!payload?.recordCreated; + return { success: isSuccess, failure: !isSuccess }; + } } catch (err) { logger.error({ err }, 'ERROR RETRIEVING SUBMISSION'); - return { success: false, failure: true }; + return { success: false, failure: false }; } } } @@ -377,4 +401,29 @@ type NotificationResult = { recordUpdated: string | null; }; +interface NotificationResultXmlJson { + elements: Array<{ + type: string; + name: 'doi_batch_diagnostic'; + attributes: { + status: 'completed'; + sp: 'a-cs1'; + }; + elements: Array<{ + type: 'element'; + name: 'batch_data'; + elements: Array<{ + type: 'element'; + name: 'success_count'; + elements: [ + { + type: 'text'; + text: string; + }, + ]; + }>; + }>; + }>; +} + // TODO: run yarn generate diff --git a/desci-server/yarn.lock b/desci-server/yarn.lock index c64176e10..7f9f2b0fc 100644 --- a/desci-server/yarn.lock +++ b/desci-server/yarn.lock @@ -15148,6 +15148,11 @@ sax@>=0.6.0: resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== +sax@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + scheduler@^0.23.0: version "0.23.0" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe" @@ -16890,6 +16895,13 @@ ws@~8.11.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.11.0.tgz#6a0d36b8edfd9f96d8b25683db2f8d7de6e8e143" integrity sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg== +xml-js@^1.6.11: + version "1.6.11" + resolved "https://registry.yarnpkg.com/xml-js/-/xml-js-1.6.11.tgz#927d2f6947f7f1c19a316dd8eea3614e8b18f8e9" + integrity sha512-7rVi2KMfwfWFl+GpPg6m80IVMWXLRjO+PxTq7V2CDhoGak0wzYzFgUY2m4XJ47OGdXd8eLE8EmwfAmdjw7lC1g== + dependencies: + sax "^1.2.4" + xml2js@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.6.2.tgz#dd0b630083aa09c161e25a4d0901e2b2a929b499" From 12aa1a0e73896a7c852f11334b1d19f07c4d7ca8 Mon Sep 17 00:00:00 2001 From: shadrach-tayo Date: Thu, 11 Jul 2024 16:38:02 +0200 Subject: [PATCH 04/13] feat: send successful DOI minting notification mail to node owner --- desci-server/src/controllers/doi/mint.ts | 37 ++++++++- .../src/templates/emails/DoiMinted.tsx | 81 +++++++++++++++++++ .../templates/emails/utils/emailRenderer.tsx | 3 + 3 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 desci-server/src/templates/emails/DoiMinted.tsx diff --git a/desci-server/src/controllers/doi/mint.ts b/desci-server/src/controllers/doi/mint.ts index a8a85aa4c..02aa9e344 100644 --- a/desci-server/src/controllers/doi/mint.ts +++ b/desci-server/src/controllers/doi/mint.ts @@ -1,4 +1,5 @@ import { DoiStatus } from '@prisma/client'; +import sgMail from '@sendgrid/mail'; import { Request, Response, NextFunction } from 'express'; import _ from 'lodash'; @@ -14,6 +15,7 @@ import { logger as parentLogger, prisma, } from '../../internal.js'; +import { DoiMintedEmailHtml } from '../../templates/emails/utils/emailRenderer.js'; export const mintDoi = async (req: Request, res: Response, _next: NextFunction) => { const { uuid } = req.params; @@ -84,6 +86,39 @@ export const handleCrossrefNotificationCallback = async ( doiRecordId: doiRecord.id, }, ); + + // Send Notification Email Node author about the submission status + const node = await prisma.node.findFirst({ + where: { uuid: submission.uuid }, + include: { owner: { select: { email: true, name: true } } }, + }); + + if (!node.owner.email) return; + const message = { + to: node.owner.email, + from: 'no-reply@desci.com', + subject: 'DOI Registration successful 🎉', + text: `Hello ${node.owner.name}, You DOI registration for the research object ${node.title} has been completed. Here is your DOI: ${process.env.CROSSREF_DOI_URL}/${submission.uniqueDoi}`, + html: DoiMintedEmailHtml({ + dpid: submission.dpid, + userName: node.owner.name.split(' ')?.[0] ?? '', + dpidPath: `${process.env.DAPP_URL}/dpid/${submission.dpid}`, + doi: `${process.env.CROSSREF_DOI_URL}/${submission.uniqueDoi}`, + nodeTitle: node.title, + }), + }; + + try { + logger.info({ members: message, NODE_ENV: process.env.NODE_ENV }, 'DOI MINTED EMAIL'); + if (process.env.NODE_ENV === 'production') { + const response = await sgMail.send(message); + logger.info(response, '[EMAIL]:: Response'); + } else { + logger.info({ nodeEnv: process.env.NODE_ENV }, message.subject); + } + } catch (err) { + logger.info({ err }, '[ERROR]:: DOI MINTED EMAIL'); + } } else { logger.info('ERROR CREATING DOI'); await doiService.updateSubmission( @@ -91,8 +126,6 @@ export const handleCrossrefNotificationCallback = async ( { status: response.failure ? DoiStatus.FAILED : DoiStatus.PENDING }, ); } - - // TODO: email authors about the submission status } catch (error) { logger.error({ error }, 'Error updating DOI submission'); } diff --git a/desci-server/src/templates/emails/DoiMinted.tsx b/desci-server/src/templates/emails/DoiMinted.tsx new file mode 100644 index 000000000..f6e2908ab --- /dev/null +++ b/desci-server/src/templates/emails/DoiMinted.tsx @@ -0,0 +1,81 @@ +import { + Body, + Container, + Column, + Head, + Heading, + Html, + Preview, + Row, + Text, + Button, + Section, + render, +} from '@react-email/components'; +import * as React from 'react'; + +import MainLayout from './MainLayout.js'; + +export interface DoiMintedEmailProps { + dpid: string; + dpidPath: string; + userName: string; + nodeTitle: string; + doi: string; +} + +const DoiMintedEmail = ({ dpidPath, userName, nodeTitle, doi }: DoiMintedEmailProps) => ( + + + + {nodeTitle} just received a DOI 🎉 + + + + Hello ${userName}, You DOI registration for the research object ${nodeTitle} has been completed. Here is + your DOI: {doi} + +
+ +
+
+ + +
+); + +export default DoiMintedEmail; + +const main = { + backgroundColor: '#ffffff', + margin: '0 auto', + fontFamily: + "-apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif", +}; + +const container = { + margin: '0 auto', + padding: '0px 20px', +}; + +const h1 = { + // color: '#000000', + fontSize: '30px', + fontWeight: '700', + margin: '30px 0', + padding: '0', + lineHeight: '42px', +}; diff --git a/desci-server/src/templates/emails/utils/emailRenderer.tsx b/desci-server/src/templates/emails/utils/emailRenderer.tsx index 5dec6d314..261b79ce3 100644 --- a/desci-server/src/templates/emails/utils/emailRenderer.tsx +++ b/desci-server/src/templates/emails/utils/emailRenderer.tsx @@ -2,6 +2,7 @@ import { render } from '@react-email/components'; import AttestationClaimedEmail, { AttestationClaimedEmailProps } from '../AttestationClaimed.js'; import ContributorInvite, { ContributorInviteEmailProps } from '../ContributorInvite.js'; +import DoiMintedEmail, { DoiMintedEmailProps } from '../DoiMinted.js'; import MagicCodeEmail, { MagicCodeEmailProps } from '../MagicCode.js'; import NodeUpdated, { NodeUpdatedEmailProps } from '../NodeUpdated.js'; import SubmissionPackage, { SubmissionPackageEmailProps } from '../SubmissionPackage.js'; @@ -23,3 +24,5 @@ export const AttestationClaimedEmailHtml = (props: AttestationClaimedEmailProps) export const NodeUpdatedEmailHtml = (props: NodeUpdatedEmailProps) => render(NodeUpdated(props)); export const SubmissionPackageEmailHtml = (props: SubmissionPackageEmailProps) => render(SubmissionPackage(props)); + +export const DoiMintedEmailHtml = (props: DoiMintedEmailProps) => render(DoiMintedEmail(props)); From 0d8bdd48cce96aa6a869c3e9b3e08f9d6176baaa Mon Sep 17 00:00:00 2001 From: shadrach-tayo Date: Fri, 12 Jul 2024 12:21:09 +0200 Subject: [PATCH 05/13] return early if submission has been processed successfully before --- desci-server/src/controllers/doi/mint.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/desci-server/src/controllers/doi/mint.ts b/desci-server/src/controllers/doi/mint.ts index 02aa9e344..615e99a04 100644 --- a/desci-server/src/controllers/doi/mint.ts +++ b/desci-server/src/controllers/doi/mint.ts @@ -59,6 +59,11 @@ export const handleCrossrefNotificationCallback = async ( return; } + if (submission.status === DoiStatus.SUCCESS) { + new SuccessMessageResponse().send(res); + return; + } + await doiService.updateSubmission({ id: submission.id }, { notification: req.payload }); logger.info('SUBMISSION UPDATED'); From 11398eb82daacb0ee41eb4e116ed0c78ce0be05b Mon Sep 17 00:00:00 2001 From: kadami <86646883+kadamidev@users.noreply.github.com> Date: Mon, 15 Jul 2024 10:12:09 +0000 Subject: [PATCH 06/13] priv share functionality backend fix --- .../src/controllers/nodes/checkNodeAccess.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/desci-server/src/controllers/nodes/checkNodeAccess.ts b/desci-server/src/controllers/nodes/checkNodeAccess.ts index 4a2ecf1fd..6872b2af4 100644 --- a/desci-server/src/controllers/nodes/checkNodeAccess.ts +++ b/desci-server/src/controllers/nodes/checkNodeAccess.ts @@ -1,13 +1,12 @@ +import { ResearchObjectV1, ResearchObjectV1Dpid } from '@desci-labs/desci-models'; +import { NodeCover } from '@prisma/client'; import type { Request, Response } from 'express'; import { prisma } from '../../client.js'; import { resolveNodeManifest } from '../../internal.js'; import { logger as parentLogger } from '../../logger.js'; -import { decodeBase64UrlSafeToHex, ensureUuidEndsWithDot, randomUUID64 } from '../../utils.js'; import { IndexedResearchObject, getIndexedResearchObjects } from '../../theGraph.js'; -import { ResearchObjectV1, ResearchObjectV1Dpid } from '@desci-labs/desci-models'; -import { NodeCover } from '@prisma/client'; -import { PRIV_SHARE_CONTRIBUTION_PREFIX } from '../../services/Contributors.js'; +import { decodeBase64UrlSafeToHex, ensureUuidEndsWithDot, randomUUID64 } from '../../utils.js'; const logger = parentLogger.child({ module: 'NODE::checkNodeAccess', @@ -44,19 +43,21 @@ type GetCheckNodeAccessErrorResponse = { }; export const checkNodeAccess = async ( - req: Request, + req: Request, res: Response, ) => { const owner = (req as any).user; const ipfsQuery = req.query.g; + const { shareId } = req.query; logger.info({ body: req.body, user: (req as any).user, ipfsQuery, + shareId, }); - let node = await prisma.node.findFirst({ + const node = await prisma.node.findFirst({ select: { uuid: true, id: true, @@ -78,12 +79,12 @@ export const checkNodeAccess = async ( res.status(404).send({ ok: false, message: 'Node not found' }); return; } - - const privSharedNode = owner + // debugger; + const privSharedNode = !!shareId ? await prisma.privateShare.findFirst({ where: { - memo: `${PRIV_SHARE_CONTRIBUTION_PREFIX}${owner?.email}`, nodeUUID: node.uuid, + shareId: shareId, }, }) : undefined; From 51ffece2d4816e28ca892fd41d62147fae45ec77 Mon Sep 17 00:00:00 2001 From: Sina Iman Date: Mon, 15 Jul 2024 19:02:48 +0300 Subject: [PATCH 07/13] config fixes for prod --- desci-contracts/subgraph/subgraph.sepoliaProd.yaml | 2 +- desci-media-isolated/kubernetes/deployment_prod.yaml | 12 +++++++----- desci-server/kubernetes/deployment_prod.yaml | 4 ++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/desci-contracts/subgraph/subgraph.sepoliaProd.yaml b/desci-contracts/subgraph/subgraph.sepoliaProd.yaml index db0010bb0..163d768a1 100644 --- a/desci-contracts/subgraph/subgraph.sepoliaProd.yaml +++ b/desci-contracts/subgraph/subgraph.sepoliaProd.yaml @@ -8,7 +8,7 @@ dataSources: name: DeSciNodes network: sepolia source: - address: "0xB954B4F1c01DcA2508278C4426EaF895f4133aDF" + address: "0x1fA4c72680af35FE1eb7345509E39498be6Ce03b" abi: ResearchObjectMigrated startBlock: 5530308 mapping: diff --git a/desci-media-isolated/kubernetes/deployment_prod.yaml b/desci-media-isolated/kubernetes/deployment_prod.yaml index 3909e505a..3a3d249e9 100644 --- a/desci-media-isolated/kubernetes/deployment_prod.yaml +++ b/desci-media-isolated/kubernetes/deployment_prod.yaml @@ -42,15 +42,17 @@ spec: - containerPort: 7771 name: api env: + - name: NODE_ENV + value: 'production' - name: PORT value: '7771' - name: IPFS_GATEWAY - value: 'http://host.docker.internal:5420/v1/ipfs' + value: 'https://ipfs.desci.com/ipfs' resources: limits: - cpu: '0.5' - memory: 2Gi + cpu: '4' + memory: 4Gi requests: - cpu: 250m - memory: 1Gi + cpu: '1' + memory: 2Gi serviceAccountName: 'default' diff --git a/desci-server/kubernetes/deployment_prod.yaml b/desci-server/kubernetes/deployment_prod.yaml index 85cf47764..794d45908 100755 --- a/desci-server/kubernetes/deployment_prod.yaml +++ b/desci-server/kubernetes/deployment_prod.yaml @@ -88,8 +88,8 @@ spec: export CROSSREF_METADATA_API={{ .Data.CROSSREF_METADATA_API }} export CROSSREF_ADMIN_API={{ .Data.CROSSREF_ADMIN_API }} export CROSSREF_NOTIFY_ENDPOINT={{ .Data.CROSSREF_NOTIFY_ENDPOINT }} - export AUTOMATED_METADATA_API={{ .Data.AUTOMATED_METADATA_API }} - export AUTOMATED_METADATA_API_KEY={{ .Data.AUTOMATED_METADATA_API_KEY }} + export AUTOMATED_METADATA_API="{{ .Data.AUTOMATED_METADATA_API }}" + export AUTOMATED_METADATA_API_KEY="{{ .Data.AUTOMATED_METADATA_API_KEY }}" export IGNORE_LINE=0; export DEBUG_TEST=0; echo "appfinish"; From 5da52a301010f027faa82b6db542772786fce2a6 Mon Sep 17 00:00:00 2001 From: kadami <86646883+kadamidev@users.noreply.github.com> Date: Mon, 15 Jul 2024 16:03:06 +0000 Subject: [PATCH 08/13] covered edgecase of a new user inheriting all previous contributions under their email --- desci-server/src/controllers/auth/magic.ts | 10 +++++++++ desci-server/src/services/Contributors.ts | 26 +++++++++++++++++++++- desci-server/src/services/user.ts | 8 +++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/desci-server/src/controllers/auth/magic.ts b/desci-server/src/controllers/auth/magic.ts index e36a18135..422a34914 100644 --- a/desci-server/src/controllers/auth/magic.ts +++ b/desci-server/src/controllers/auth/magic.ts @@ -5,6 +5,7 @@ import jwt from 'jsonwebtoken'; import { prisma as prismaClient } from '../../client.js'; import { logger } from '../../logger.js'; import { magicLinkRedeem, sendMagicLink } from '../../services/auth.js'; +import { contributorService } from '../../services/Contributors.js'; import { saveInteraction } from '../../services/interactionLog.js'; import { checkIfUserAcceptedTerms, connectOrcidToUserIfPossible } from '../../services/user.js'; import { sendCookie } from '../../utils/sendCookie.js'; @@ -52,6 +53,15 @@ export const magic = async (req: Request, res: Response, next: NextFunction) => email, }, }); + + if (user.email) { + // Inherits existing user contribution entries that were made with the same email + const inheritedContributions = await contributorService.updateContributorEntriesForNewUser({ + email: user.email, + userId: user.id, + }); + logger.trace({ inheritedContributions: inheritedContributions?.count, user, email }); + } } try { diff --git a/desci-server/src/services/Contributors.ts b/desci-server/src/services/Contributors.ts index 839694b3e..10b44d4ee 100644 --- a/desci-server/src/services/Contributors.ts +++ b/desci-server/src/services/Contributors.ts @@ -1,7 +1,7 @@ import { format } from 'path'; import { IpldUrl, ResearchObjectV1Dpid } from '@desci-labs/desci-models'; -import { Node, NodeContribution, User } from '@prisma/client'; +import { Node, NodeContribution, Prisma, User } from '@prisma/client'; import ShortUniqueId from 'short-unique-id'; import { prisma } from '../client.js'; @@ -342,6 +342,30 @@ class ContributorService { return null; } + /* + Associates the user's nodes profile (userId) with all entries under that email/orcid, used on new signups to link previously added contributions + */ + async updateContributorEntriesForNewUser({ + email, + orcid, + userId, + }: { + email?: string; + orcid?: string; + userId: number; + }): Promise { + if (!orcid && !email) return { count: 0 }; + return await prisma.nodeContribution.updateMany({ + where: { + email, + orcid, + }, + data: { + userId, + }, + }); + } + async getContributionById(contributorId: string): Promise { return prisma.nodeContribution.findUnique({ where: { contributorId } }); } diff --git a/desci-server/src/services/user.ts b/desci-server/src/services/user.ts index 130d6a39a..28a039370 100644 --- a/desci-server/src/services/user.ts +++ b/desci-server/src/services/user.ts @@ -6,6 +6,7 @@ import { OrcIdRecordData, generateAccessToken, getOrcidRecord } from '../control import { logger as parentLogger } from '../logger.js'; import { hideEmail } from '../utils.js'; +import { contributorService } from './Contributors.js'; import { getUserConsent } from './interactionLog.js'; const logger = parentLogger.child({ module: 'Services::User', @@ -247,6 +248,13 @@ export async function setOrcidForUser( }, }); logger.trace({ fn: 'setOrcidForUser' }, 'added auth token'); + + // Inherits existing user contribution entries that were made with the same ORCID + const inheritedContributions = await contributorService.updateContributorEntriesForNewUser({ + orcid, + userId: user.id, + }); + logger.trace({ inheritedContributions: inheritedContributions?.count, user, orcid }); } else { logger.trace({ fn: 'setOrcidForUser' }, 'no user found'); return false; From 5ee339874291b360018fc5d554be45129020ff79 Mon Sep 17 00:00:00 2001 From: shadrach-tayo Date: Tue, 16 Jul 2024 14:11:39 +0200 Subject: [PATCH 09/13] fix: community radar query --- .gitignore | 1 + desci-server/src/services/Communities.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 9998940fb..8230f87d6 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ node-modules/ node_modules/* .idea .composedbRuntimeDefinition.json +openalex-importer \ No newline at end of file diff --git a/desci-server/src/services/Communities.ts b/desci-server/src/services/Communities.ts index bb23fd245..ecd3a3308 100644 --- a/desci-server/src/services/Communities.ts +++ b/desci-server/src/services/Communities.ts @@ -99,7 +99,7 @@ export class CommunityService { EXISTS (SELECT * from "CommunityEntryAttestation" c1 - where t1."attestationId" = c1."attestationId" and t1."attestationVersionId" = c1."attestationVersionId" and c1."desciCommunityId" = ${communityId}) + where t1."attestationId" = c1."attestationId" and t1."attestationVersionId" = c1."attestationVersionId" and c1."desciCommunityId" = ${communityId}) and c1."required" = true GROUP BY t1.id `) as CommunityRadarNode[]; From 18dd1870ad0d4c74f24ca88cbd7ba0cd77848cd7 Mon Sep 17 00:00:00 2001 From: kadami <86646883+kadamidev@users.noreply.github.com> Date: Tue, 16 Jul 2024 12:45:04 +0000 Subject: [PATCH 10/13] add dpid env var for pdf cover generations --- desci-media-isolated/.env.example | 4 +++- desci-media-isolated/kubernetes/deployment_dev.yaml | 2 ++ desci-media-isolated/kubernetes/deployment_prod.yaml | 2 ++ desci-media-isolated/kubernetes/deployment_staging.yaml | 2 ++ desci-media-isolated/src/services/pdf.ts | 4 +++- desci-server/src/services/PublishPackage.ts | 5 +++-- 6 files changed, 15 insertions(+), 4 deletions(-) diff --git a/desci-media-isolated/.env.example b/desci-media-isolated/.env.example index d61e65e5f..c9641f6d6 100644 --- a/desci-media-isolated/.env.example +++ b/desci-media-isolated/.env.example @@ -2,4 +2,6 @@ NODE_ENV=development PORT=7771 -IPFS_GATEWAY=http://host.docker.internal:5420/v1/ipfs \ No newline at end of file +IPFS_GATEWAY=http://host.docker.internal:5420/v1/ipfs + +DPID_RESOLVER_URL=https://dev-beta.dpid.org \ No newline at end of file diff --git a/desci-media-isolated/kubernetes/deployment_dev.yaml b/desci-media-isolated/kubernetes/deployment_dev.yaml index cf2b6ae08..497faf8bc 100644 --- a/desci-media-isolated/kubernetes/deployment_dev.yaml +++ b/desci-media-isolated/kubernetes/deployment_dev.yaml @@ -46,6 +46,8 @@ spec: value: '7771' - name: IPFS_GATEWAY value: 'https://ipfs.desci.com/ipfs' + - name: DPID_RESOLVER_URL + value: 'https://dev-beta.dpid.org' resources: limits: cpu: '4' diff --git a/desci-media-isolated/kubernetes/deployment_prod.yaml b/desci-media-isolated/kubernetes/deployment_prod.yaml index 3909e505a..e1b3180d9 100644 --- a/desci-media-isolated/kubernetes/deployment_prod.yaml +++ b/desci-media-isolated/kubernetes/deployment_prod.yaml @@ -46,6 +46,8 @@ spec: value: '7771' - name: IPFS_GATEWAY value: 'http://host.docker.internal:5420/v1/ipfs' + - name: DPID_RESOLVER_URL + value: 'https://beta.dpid.org' resources: limits: cpu: '0.5' diff --git a/desci-media-isolated/kubernetes/deployment_staging.yaml b/desci-media-isolated/kubernetes/deployment_staging.yaml index 1da4834b8..e92bf4dc9 100644 --- a/desci-media-isolated/kubernetes/deployment_staging.yaml +++ b/desci-media-isolated/kubernetes/deployment_staging.yaml @@ -46,6 +46,8 @@ spec: value: '7771' - name: IPFS_GATEWAY value: 'http://host.docker.internal:5420/v1/ipfs' + - name: DPID_RESOLVER_URL + value: 'https://dev-beta.dpid.org' resources: limits: cpu: '0.5' diff --git a/desci-media-isolated/src/services/pdf.ts b/desci-media-isolated/src/services/pdf.ts index 0dffc5f9a..48c374af2 100644 --- a/desci-media-isolated/src/services/pdf.ts +++ b/desci-media-isolated/src/services/pdf.ts @@ -85,8 +85,10 @@ export class PdfManipulationService { /* * Header */ + const dpidResolverUrl = process.env.DPID_RESOLVER_URL ?? 'https://beta.dpid.org'; + const licenseStartsWithVowel = startsWithVowel(license); - const nodeUrl = usingDoi ? `https://doi.org/${doi}` : `https://beta.dpid.org/${dpid}`; + const nodeUrl = usingDoi ? `https://doi.org/${doi}` : `${dpidResolverUrl}/${dpid}`; const topHeader = `Research object ${nodeUrl}, this version posted ${publishDate}. The copyright holder for this research object (which was not certified by peer review) is the author/funder, who has granted DeSci Labs a non-exclsuive license to display the research object in perpetuity. It is made available under a${ licenseStartsWithVowel ? 'n' : '' } ${license} license.`; diff --git a/desci-server/src/services/PublishPackage.ts b/desci-server/src/services/PublishPackage.ts index d4987efb6..9e9019f5a 100644 --- a/desci-server/src/services/PublishPackage.ts +++ b/desci-server/src/services/PublishPackage.ts @@ -73,12 +73,13 @@ class PublishPackageService { const openCodeAttestation = attestations.find((a) => a.attestationVersion.name === 'Open Code'); const openDataAttestation = attestations.find((a) => a.attestationVersion.name === 'Open Data'); + const dpidUrl = process.env.DPID_URL_OVERRIDE ?? 'https://beta.dpid.org'; const attestationLinks = { ...(openCodeAttestation && { - codeAvailableDpid: `https://beta.dpid.org/${dpid}/attestations/${toKebabCase(openCodeAttestation.attestationVersion.name)}`, + codeAvailableDpid: `${dpidUrl}/${dpid}/attestations/${toKebabCase(openCodeAttestation.attestationVersion.name)}`, }), ...(openDataAttestation && { - dataAvailableDpid: `https://beta.dpid.org/${dpid}/attestations/${toKebabCase(openDataAttestation.attestationVersion.name)}`, + dataAvailableDpid: `${dpidUrl}/${dpid}/attestations/${toKebabCase(openDataAttestation.attestationVersion.name)}`, }), }; From 1e0704bdddd05e61af51bc664912bfc96a49e9eb Mon Sep 17 00:00:00 2001 From: shadrach-tayo Date: Tue, 16 Jul 2024 15:46:42 +0200 Subject: [PATCH 11/13] fix: community radar listing query --- desci-server/src/services/Attestation.ts | 4 ++-- desci-server/src/services/Communities.ts | 4 ++-- desci-server/test/integration/Attestation.test.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/desci-server/src/services/Attestation.ts b/desci-server/src/services/Attestation.ts index 686aafd14..570a4da81 100644 --- a/desci-server/src/services/Attestation.ts +++ b/desci-server/src/services/Attestation.ts @@ -779,7 +779,7 @@ export class AttestationService { LEFT JOIN "Annotation" AN ON AN."nodeAttestationId" = NA.id LEFT JOIN "NodeAttestationReaction" NAR ON NAR."nodeAttestationId" = NA.id LEFT JOIN "NodeAttestationVerification" NAV ON NAV."nodeAttestationId" = NA.id - LEFT JOIN "CommunityEntryAttestation" CSA ON CSA."attestationId" = A."id" + LEFT JOIN "CommunityEntryAttestation" CSA ON CSA."attestationId" = A."id" AND CSA."required" = true where CSA."desciCommunityId" = ${communityId} GROUP BY @@ -874,7 +874,7 @@ export class AttestationService { EXISTS (SELECT * from "CommunityEntryAttestation" c1 - where t1."attestationId" = c1."attestationId" and t1."attestationVersionId" = c1."attestationVersionId" and c1."desciCommunityId" = ${communityId}) + where t1."attestationId" = c1."attestationId" and t1."attestationVersionId" = c1."attestationVersionId" and c1."desciCommunityId" = ${communityId} ) GROUP BY t1.id `) as CommunityRadarNode[]; diff --git a/desci-server/src/services/Communities.ts b/desci-server/src/services/Communities.ts index ecd3a3308..7896df763 100644 --- a/desci-server/src/services/Communities.ts +++ b/desci-server/src/services/Communities.ts @@ -99,7 +99,7 @@ export class CommunityService { EXISTS (SELECT * from "CommunityEntryAttestation" c1 - where t1."attestationId" = c1."attestationId" and t1."attestationVersionId" = c1."attestationVersionId" and c1."desciCommunityId" = ${communityId}) and c1."required" = true + where t1."attestationId" = c1."attestationId" and t1."attestationVersionId" = c1."attestationVersionId" and c1."desciCommunityId" = ${communityId} and c1."required" = true) GROUP BY t1.id `) as CommunityRadarNode[]; @@ -180,7 +180,7 @@ export class CommunityService { EXISTS (SELECT * from "CommunityEntryAttestation" c1 - where t1."attestationId" = c1."attestationId" and t1."attestationVersionId" = c1."attestationVersionId" and c1."desciCommunityId" = ${communityId}) + where t1."attestationId" = c1."attestationId" and t1."attestationVersionId" = c1."attestationVersionId" and c1."desciCommunityId" = ${communityId}) and c1."required" = true GROUP BY t1.id `) as CommunityRadarNode[]; diff --git a/desci-server/test/integration/Attestation.test.ts b/desci-server/test/integration/Attestation.test.ts index 1d7bd25e0..a6a111899 100644 --- a/desci-server/test/integration/Attestation.test.ts +++ b/desci-server/test/integration/Attestation.test.ts @@ -133,7 +133,7 @@ const clearDatabase = async () => { await prisma.$queryRaw`TRUNCATE TABLE "Node" CASCADE;`; }; -describe('Attestations Service', async () => { +describe.only('Attestations Service', async () => { let baseManifest: ResearchObjectV1; let baseManifestCid: string; let users: User[]; From 4949d9b909142b4a028f8fd42e852baf3975e634 Mon Sep 17 00:00:00 2001 From: shadrach-tayo Date: Tue, 16 Jul 2024 15:52:00 +0200 Subject: [PATCH 12/13] fix: radar engagement signals --- desci-server/src/services/Communities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desci-server/src/services/Communities.ts b/desci-server/src/services/Communities.ts index 7896df763..8b3114251 100644 --- a/desci-server/src/services/Communities.ts +++ b/desci-server/src/services/Communities.ts @@ -180,7 +180,7 @@ export class CommunityService { EXISTS (SELECT * from "CommunityEntryAttestation" c1 - where t1."attestationId" = c1."attestationId" and t1."attestationVersionId" = c1."attestationVersionId" and c1."desciCommunityId" = ${communityId}) and c1."required" = true + where t1."attestationId" = c1."attestationId" and t1."attestationVersionId" = c1."attestationVersionId" and c1."desciCommunityId" = ${communityId} and c1."required" = true) GROUP BY t1.id `) as CommunityRadarNode[]; From 78fcf12b5a805bafab06565cbbec69e1ca8c0228 Mon Sep 17 00:00:00 2001 From: shadrach-tayo Date: Wed, 17 Jul 2024 10:04:55 +0200 Subject: [PATCH 13/13] show all entry attestations --- desci-server/src/services/Attestation.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desci-server/src/services/Attestation.ts b/desci-server/src/services/Attestation.ts index 570a4da81..cf3d178ee 100644 --- a/desci-server/src/services/Attestation.ts +++ b/desci-server/src/services/Attestation.ts @@ -779,7 +779,7 @@ export class AttestationService { LEFT JOIN "Annotation" AN ON AN."nodeAttestationId" = NA.id LEFT JOIN "NodeAttestationReaction" NAR ON NAR."nodeAttestationId" = NA.id LEFT JOIN "NodeAttestationVerification" NAV ON NAV."nodeAttestationId" = NA.id - LEFT JOIN "CommunityEntryAttestation" CSA ON CSA."attestationId" = A."id" AND CSA."required" = true + LEFT JOIN "CommunityEntryAttestation" CSA ON CSA."attestationId" = A."id" where CSA."desciCommunityId" = ${communityId} GROUP BY