From 26cd763437e6d7dab7b60d87c82e578654d6fde6 Mon Sep 17 00:00:00 2001 From: shadrach Date: Wed, 8 Jan 2025 13:21:00 +0100 Subject: [PATCH 1/5] add schema and implement backfill script --- desci-server/prisma/schema.prisma | 17 +++++ .../src/controllers/attestations/claims.ts | 4 +- .../src/controllers/communities/radar.ts | 4 -- desci-server/src/scripts/backfill-radar.ts | 66 +++++++++++++++++++ desci-server/src/services/Attestation.ts | 2 +- desci-server/src/services/Communities.ts | 27 ++++++++ 6 files changed, 114 insertions(+), 6 deletions(-) create mode 100644 desci-server/src/scripts/backfill-radar.ts diff --git a/desci-server/prisma/schema.prisma b/desci-server/prisma/schema.prisma index 820176b88..7ef6f3e88 100755 --- a/desci-server/prisma/schema.prisma +++ b/desci-server/prisma/schema.prisma @@ -54,6 +54,7 @@ model Node { UserNotifications UserNotifications[] Annotation Annotation[] ExternalPublications ExternalPublications[] + CommunityRadarEntry CommunityRadarEntry[] @@index([ownerId]) @@index([uuid]) @@ -697,6 +698,7 @@ model DesciCommunity { NodeAttestation NodeAttestation[] AttestationTemplate AttestationTemplate[] CommunityEntryAttestation CommunityEntryAttestation[] + CommunityRadarEntry CommunityRadarEntry[] } model CommunityMember { @@ -799,10 +801,25 @@ model NodeAttestation { NodeAttestationVerification NodeAttestationVerification[] NodeAttestationReaction NodeAttestationReaction[] OrcidPutCodes OrcidPutCodes[] + CommunityRadarEntry CommunityRadarEntry? @relation(fields: [nodeUuid, desciCommunityId], references: [nodeUuid, desciCommunityId]) + // communityRadarEntryId Int? @@unique([nodeUuid, nodeVersion, attestationId, attestationVersionId]) } +model CommunityRadarEntry { + id Int @id @default(autoincrement()) + desciCommunityId Int + community DesciCommunity @relation(fields: [desciCommunityId], references: [id]) + nodeUuid String + node Node @relation(fields: [nodeUuid], references: [uuid]) + nodeAttestations NodeAttestation[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + + @@unique([nodeUuid, desciCommunityId]) +} + // Deferred emails are usually used when a published node is required, e.g. unpublished attestation claims, emails are deferred until node is published. model DeferredEmails { id Int @id @default(autoincrement()) diff --git a/desci-server/src/controllers/attestations/claims.ts b/desci-server/src/controllers/attestations/claims.ts index 06f95b281..b248d85d5 100644 --- a/desci-server/src/controllers/attestations/claims.ts +++ b/desci-server/src/controllers/attestations/claims.ts @@ -21,9 +21,11 @@ export const claimAttestation = async (req: RequestWithUser, res: Response, _nex nodeDpid: string; claimerId: number; }; + + // TODO: verify attestationVersions[0] === latest const attestationVersions = await attestationService.getAttestationVersions(body.attestationId); const latest = attestationVersions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()); - const attestationVersion = latest[0]; + const attestationVersion = attestationVersions[0]; logger.info({ body, latest, attestationVersion }, 'CLAIM'); const uuid = ensureUuidEndsWithDot(body.nodeUuid); diff --git a/desci-server/src/controllers/communities/radar.ts b/desci-server/src/controllers/communities/radar.ts index 4d66a6ec0..e744e8058 100644 --- a/desci-server/src/controllers/communities/radar.ts +++ b/desci-server/src/controllers/communities/radar.ts @@ -17,10 +17,6 @@ export const getCommunityRadar = async (req: Request, res: Response, next: NextF // THIS is necessary because the engagement signal returned from getCommunityRadar // accounts for only engagements on community selected attestations const nodes = await asyncMap(communityRadar, async (node) => { - // const engagements = await communityService.getNodeCommunityEngagementSignals( - // parseInt(req.params.communityId), - // node.nodeDpid10, - // ); const engagements = await attestationService.getNodeEngagementSignals(node.nodeDpid10); const verifiedEngagements = node.NodeAttestation.reduce( diff --git a/desci-server/src/scripts/backfill-radar.ts b/desci-server/src/scripts/backfill-radar.ts new file mode 100644 index 000000000..2ab43e724 --- /dev/null +++ b/desci-server/src/scripts/backfill-radar.ts @@ -0,0 +1,66 @@ +import { prisma } from '../client.js'; +import { logger } from '../logger.js'; +import { communityService } from '../services/Communities.js'; + +const main = async () => { + const nodeAttestations = await prisma.nodeAttestation.findMany({ where: { revoked: false } }); + let radarCount = 0; + for (const nodeAttestation of nodeAttestations) { + const exists = await prisma.communityRadarEntry.findFirst({ + where: { nodeUuid: nodeAttestation.nodeUuid, desciCommunityId: nodeAttestation.desciCommunityId }, + }); + if (exists) { + logger.trace( + { + node: nodeAttestation.nodeDpid10, + communityId: nodeAttestation.desciCommunityId, + }, + 'Radar exists', + ); + continue; + } + + // check if node has claimed all community entry attestations + const entryAttestations = await communityService.getEntryAttestations({ + desciCommunityId: nodeAttestation.desciCommunityId, + }); + + const claimedAttestations = await prisma.nodeAttestation.findMany({ + where: { desciCommunityId: nodeAttestation.desciCommunityId, nodeUuid: nodeAttestation.nodeUuid }, + }); + + const isEntriesClaimed = entryAttestations.every((entry) => + claimedAttestations.find( + (claimed) => + claimed.attestationId === entry.attestationId && claimed.attestationVersionId === entry.attestationVersionId, + ), + ); + if (!isEntriesClaimed) { + logger.info( + { + node: nodeAttestation.nodeDpid10, + claims: claimedAttestations.length, + entryAttestations: entryAttestations.length, + communityId: nodeAttestation.desciCommunityId, + }, + 'Not Qualified for Radar', + ); + continue; + } + // End check if node has claimed all community entry attestations + + await prisma.communityRadarEntry.create({ + data: { + desciCommunityId: nodeAttestation.desciCommunityId, + nodeUuid: nodeAttestation.nodeUuid, + }, + }); + radarCount++; + } + logger.info({ radarCount }, 'Community radar fields: '); + return radarCount; +}; + +main() + .then((result) => console.log('Community backfilled', result)) + .catch((err) => console.log('Error running script ', err)); diff --git a/desci-server/src/services/Attestation.ts b/desci-server/src/services/Attestation.ts index 8045f16bb..a448771f1 100644 --- a/desci-server/src/services/Attestation.ts +++ b/desci-server/src/services/Attestation.ts @@ -233,7 +233,7 @@ export class AttestationService { } async getAttestationVersions(attestationId: number) { - return prisma.attestationVersion.findMany({ where: { attestationId } }); + return prisma.attestationVersion.findMany({ where: { attestationId }, orderBy: { createdAt: 'desc' } }); } async getAttestationVersion(id: number, attestationId: number) { diff --git a/desci-server/src/services/Communities.ts b/desci-server/src/services/Communities.ts index 6b1fda9af..ce73faa71 100644 --- a/desci-server/src/services/Communities.ts +++ b/desci-server/src/services/Communities.ts @@ -366,6 +366,33 @@ export class CommunityService { async removeMemberById(id: number) { return prisma.communityMember.delete({ where: { id } }); } + + async addToRadar(desciCommunityId: number, nodeUuid: string) { + // check if node has claimed all community entry attestations + const entryAttestations = await communityService.getEntryAttestations({ + desciCommunityId, + }); + + const claimedAttestations = await prisma.nodeAttestation.findMany({ + where: { desciCommunityId, nodeUuid }, + }); + + const isEntriesClaimed = entryAttestations.every((entry) => + claimedAttestations.find( + (claimed) => + claimed.attestationId === entry.attestationId && claimed.attestationVersionId === entry.attestationVersionId, + ), + ); + + if (!isEntriesClaimed) return undefined; + + return await prisma.communityRadarEntry.create({ + data: { + desciCommunityId, + nodeUuid, + }, + }); + } } export const communityService = new CommunityService(); From 540bf9010cfa86c9f8f6fb74393964678df71748 Mon Sep 17 00:00:00 2001 From: shadrach Date: Thu, 9 Jan 2025 10:15:57 +0100 Subject: [PATCH 2/5] feat: add db schema and logic for community entry aggregation --- desci-server/package.json | 1 + .../migration.sql | 25 +++ desci-server/prisma/schema.prisma | 4 +- .../src/controllers/attestations/claims.ts | 8 + .../src/controllers/communities/radar.ts | 29 +++ desci-server/src/scripts/backfill-radar.ts | 25 ++- desci-server/src/services/Attestation.ts | 32 +++ desci-server/src/services/Communities.ts | 185 +++++++++++++++++- 8 files changed, 299 insertions(+), 10 deletions(-) create mode 100644 desci-server/prisma/migrations/20250108131055_community_radar_entry/migration.sql diff --git a/desci-server/package.json b/desci-server/package.json index d8c0e8aa6..45fa6d914 100755 --- a/desci-server/package.json +++ b/desci-server/package.json @@ -27,6 +27,7 @@ "script:seed-community-member": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/seed-community-members.ts", "script:backfill-annotations": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/backfill-annotations.ts", "script:prune-auth-tokens": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/prune-auth-tokens.ts", + "script:backfill-radar": "debug=* node --no-warnings --enable-source-maps --loader ts-node/esm ./src/scripts/backfill-radar.ts", "build": "rimraf dist && tsc && yarn copy-files; if [ \"$SENTRY_AUTH_TOKEN\" ]; then yarn sentry:sourcemaps; else echo 'SENTRY_AUTH_TOKEN not set, sourcemaps will not upload'; fi", "build:worker": "cd ../sync-server && ./scripts/build.sh test", "copy-files": "copyfiles -u 1 src/**/*.cjs dist/", diff --git a/desci-server/prisma/migrations/20250108131055_community_radar_entry/migration.sql b/desci-server/prisma/migrations/20250108131055_community_radar_entry/migration.sql new file mode 100644 index 000000000..c67377d3d --- /dev/null +++ b/desci-server/prisma/migrations/20250108131055_community_radar_entry/migration.sql @@ -0,0 +1,25 @@ +-- AlterTable +ALTER TABLE "NodeAttestation" ADD COLUMN "communityRadarEntryId" INTEGER; + +-- CreateTable +CREATE TABLE "CommunityRadarEntry" ( + "id" SERIAL NOT NULL, + "desciCommunityId" INTEGER NOT NULL, + "nodeUuid" TEXT NOT NULL, + "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, + "updatedAt" TIMESTAMP(3) NOT NULL, + + CONSTRAINT "CommunityRadarEntry_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "CommunityRadarEntry_nodeUuid_desciCommunityId_key" ON "CommunityRadarEntry"("nodeUuid", "desciCommunityId"); + +-- AddForeignKey +ALTER TABLE "NodeAttestation" ADD CONSTRAINT "NodeAttestation_communityRadarEntryId_fkey" FOREIGN KEY ("communityRadarEntryId") REFERENCES "CommunityRadarEntry"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CommunityRadarEntry" ADD CONSTRAINT "CommunityRadarEntry_desciCommunityId_fkey" FOREIGN KEY ("desciCommunityId") REFERENCES "DesciCommunity"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CommunityRadarEntry" ADD CONSTRAINT "CommunityRadarEntry_nodeUuid_fkey" FOREIGN KEY ("nodeUuid") REFERENCES "Node"("uuid") ON DELETE RESTRICT ON UPDATE CASCADE; diff --git a/desci-server/prisma/schema.prisma b/desci-server/prisma/schema.prisma index 7ef6f3e88..e9145dc5b 100755 --- a/desci-server/prisma/schema.prisma +++ b/desci-server/prisma/schema.prisma @@ -801,8 +801,8 @@ model NodeAttestation { NodeAttestationVerification NodeAttestationVerification[] NodeAttestationReaction NodeAttestationReaction[] OrcidPutCodes OrcidPutCodes[] - CommunityRadarEntry CommunityRadarEntry? @relation(fields: [nodeUuid, desciCommunityId], references: [nodeUuid, desciCommunityId]) - // communityRadarEntryId Int? + CommunityRadarEntry CommunityRadarEntry? @relation(fields: [communityRadarEntryId], references: [id]) + communityRadarEntryId Int? @@unique([nodeUuid, nodeVersion, attestationId, attestationVersionId]) } diff --git a/desci-server/src/controllers/attestations/claims.ts b/desci-server/src/controllers/attestations/claims.ts index b248d85d5..961164bd3 100644 --- a/desci-server/src/controllers/attestations/claims.ts +++ b/desci-server/src/controllers/attestations/claims.ts @@ -9,6 +9,7 @@ import { logger } from '../../logger.js'; import { RequestWithUser } from '../../middleware/authorisation.js'; import { removeClaimSchema } from '../../routes/v1/attestations/schema.js'; import { attestationService } from '../../services/Attestation.js'; +import { communityService } from '../../services/Communities.js'; import { saveInteraction } from '../../services/interactionLog.js'; import { getIndexedResearchObjects } from '../../theGraph.js'; import { asyncMap, ensureUuidEndsWithDot } from '../../utils.js'; @@ -38,6 +39,8 @@ export const claimAttestation = async (req: RequestWithUser, res: Response, _nex if (claim && claim.revoked) { const reclaimed = await attestationService.reClaimAttestation(claim.id); await saveInteraction(req, ActionType.CLAIM_ATTESTATION, { ...body, claimId: reclaimed.id }); + // trigger update radar entry + await communityService.addToRadar(reclaimed.desciCommunityId, reclaimed.nodeUuid); new SuccessResponse(reclaimed).send(res); return; } @@ -48,6 +51,8 @@ export const claimAttestation = async (req: RequestWithUser, res: Response, _nex nodeUuid: uuid, attestationVersion: attestationVersion.id, }); + // trigger update radar entry + await communityService.addToRadar(nodeClaim.desciCommunityId, nodeClaim.nodeUuid); await saveInteraction(req, ActionType.CLAIM_ATTESTATION, { ...body, claimId: nodeClaim.id }); @@ -128,6 +133,9 @@ export const removeClaim = async (req: RequestWithUser, res: Response, _next: Ne ? await attestationService.revokeAttestation(claim.id) : await attestationService.unClaimAttestation(claim.id); + // trigger update radar entry + await communityService.addToRadar(claim.desciCommunityId, claim.nodeUuid); + await saveInteraction(req, ActionType.REVOKE_CLAIM, body); logger.info({ removeOrRevoke, totalSignal, claimSignal }, 'Claim Removed|Revoked'); diff --git a/desci-server/src/controllers/communities/radar.ts b/desci-server/src/controllers/communities/radar.ts index e744e8058..743bb1173 100644 --- a/desci-server/src/controllers/communities/radar.ts +++ b/desci-server/src/controllers/communities/radar.ts @@ -60,3 +60,32 @@ export const getCommunityRadar = async (req: Request, res: Response, next: NextF return new SuccessResponse(data).send(res); }; + +export const listCommunityRadar = async (req: Request, res: Response, next: NextFunction) => { + const communityRadar = await communityService.listCommunityRadar({ + communityId: parseInt(req.params.communityId as string), + offset: 0, + limit: 20, + }); + logger.info({ communityRadar }, 'Radar'); + // THIS is necessary because the engagement signal returned from getCommunityRadar + // accounts for only engagements on community selected attestations + const nodes = await asyncMap(communityRadar, async (entry) => { + const engagements = await attestationService.getNodeEngagementSignalsByUuid(entry.nodeUuid); + return { + ...entry, + engagements, + verifiedEngagements: { + reactions: entry.reactions, + annotations: entry.annotations, + verifications: entry.verifications, + }, + }; + }); + + // rank nodes by sum of sum of verified and non verified signals + + logger.info({ nodes }, 'CHECK Verification SignalS'); + + return new SuccessResponse(nodes).send(res); +}; diff --git a/desci-server/src/scripts/backfill-radar.ts b/desci-server/src/scripts/backfill-radar.ts index 2ab43e724..180b1728c 100644 --- a/desci-server/src/scripts/backfill-radar.ts +++ b/desci-server/src/scripts/backfill-radar.ts @@ -1,6 +1,6 @@ import { prisma } from '../client.js'; import { logger } from '../logger.js'; -import { communityService } from '../services/Communities.js'; +// import { communityService } from '../services/Communities.js'; const main = async () => { const nodeAttestations = await prisma.nodeAttestation.findMany({ where: { revoked: false } }); @@ -21,12 +21,20 @@ const main = async () => { } // check if node has claimed all community entry attestations - const entryAttestations = await communityService.getEntryAttestations({ - desciCommunityId: nodeAttestation.desciCommunityId, + const entryAttestations = await prisma.communityEntryAttestation.findMany({ + orderBy: { createdAt: 'asc' }, + where: { desciCommunityId: nodeAttestation.desciCommunityId }, + include: { + attestation: { select: { protected: true, community: { select: { name: true } } } }, + // desciCommunity: { select: { name: true } }, + attestationVersion: { + select: { id: true, attestationId: true, name: true, image_url: true, description: true }, + }, + }, }); const claimedAttestations = await prisma.nodeAttestation.findMany({ - where: { desciCommunityId: nodeAttestation.desciCommunityId, nodeUuid: nodeAttestation.nodeUuid }, + where: { desciCommunityId: nodeAttestation.desciCommunityId, nodeUuid: nodeAttestation.nodeUuid, revoked: false }, }); const isEntriesClaimed = entryAttestations.every((entry) => @@ -49,13 +57,20 @@ const main = async () => { } // End check if node has claimed all community entry attestations - await prisma.communityRadarEntry.create({ + const radarEntry = await prisma.communityRadarEntry.create({ data: { desciCommunityId: nodeAttestation.desciCommunityId, nodeUuid: nodeAttestation.nodeUuid, }, }); radarCount++; + + const claims = await prisma.$transaction( + claimedAttestations.map((claim) => + prisma.nodeAttestation.update({ where: { id: claim.id }, data: { communityRadarEntryId: radarEntry.id } }), + ), + ); + logger.info({ rows: claims.length }, 'Claims Updated'); } logger.info({ radarCount }, 'Community radar fields: '); return radarCount; diff --git a/desci-server/src/services/Attestation.ts b/desci-server/src/services/Attestation.ts index a448771f1..359519dda 100644 --- a/desci-server/src/services/Attestation.ts +++ b/desci-server/src/services/Attestation.ts @@ -1005,6 +1005,38 @@ export class AttestationService { ); return groupedEngagements; } + + /** + * Returns all engagement signals for a node across all claimed attestations + * This verification signal is the number returned for the verification field + * @param dpid + * @returns + */ + async getNodeEngagementSignalsByUuid(uuid: string) { + const claims = (await prisma.$queryRaw` + SELECT t1.*, + count(DISTINCT "Annotation".id)::int AS annotations, + count(DISTINCT "NodeAttestationReaction".id)::int AS reactions, + count(DISTINCT "NodeAttestationVerification".id)::int AS verifications + FROM "NodeAttestation" t1 + left outer JOIN "Annotation" ON t1."id" = "Annotation"."nodeAttestationId" + left outer JOIN "NodeAttestationReaction" ON t1."id" = "NodeAttestationReaction"."nodeAttestationId" + left outer JOIN "NodeAttestationVerification" ON t1."id" = "NodeAttestationVerification"."nodeAttestationId" + WHERE t1."nodeUuid" = ${uuid} AND t1."revoked" = false + GROUP BY + t1.id + `) as CommunityRadarNode[]; + + const groupedEngagements = claims.reduce( + (total, claim) => ({ + reactions: total.reactions + claim.reactions, + annotations: total.annotations + claim.annotations, + verifications: total.verifications + claim.verifications, + }), + { reactions: 0, annotations: 0, verifications: 0 }, + ); + return groupedEngagements; + } /** * Returns all engagement signals for a claimed attestation * @param claimId diff --git a/desci-server/src/services/Communities.ts b/desci-server/src/services/Communities.ts index ce73faa71..f95d15991 100644 --- a/desci-server/src/services/Communities.ts +++ b/desci-server/src/services/Communities.ts @@ -1,4 +1,11 @@ -import { Attestation, CommunityMembershipRole, NodeAttestation, NodeFeedItem, Prisma } from '@prisma/client'; +import { + Attestation, + CommunityMembershipRole, + CommunityRadarEntry, + NodeAttestation, + NodeFeedItem, + Prisma, +} from '@prisma/client'; import _, { includes } from 'lodash'; import { prisma } from '../client.js'; @@ -8,6 +15,7 @@ import { logger } from '../logger.js'; import { attestationService } from './Attestation.js'; export type CommunityRadarNode = NodeAttestation & { annotations: number; reactions: number; verifications: number }; +export type RadarEntry = CommunityRadarEntry & { annotations: number; reactions: number; verifications: number }; export class CommunityService { async createCommunity(data: Prisma.DesciCommunityCreateManyInput) { const exists = await prisma.desciCommunity.findFirst({ where: { name: data.name } }); @@ -176,6 +184,129 @@ export class CommunityService { return radar; } + async listCommunityRadar({ communityId, offset, limit }: { communityId: number; offset: number; limit: number }) { + const entryAttestations = await attestationService.getCommunityEntryAttestations(communityId); + const entries = await prisma.$queryRaw` + SELECT + cre.*, + count(DISTINCT "Annotation".id) :: int AS annotations, + count(DISTINCT "NodeAttestationReaction".id) :: int AS reactions, + count(DISTINCT "NodeAttestationVerification".id) :: int AS verifications, + COUNT(DISTINCT NaFiltered."id") :: int AS valid_attestations + FROM + "CommunityRadarEntry" cre + LEFT JOIN ( + SELECT + Na."id", + Na."communityRadarEntryId", + Na."attestationId", + Na."attestationVersionId" + FROM + "NodeAttestation" Na + WHERE + Na."revoked" = false + AND Na."nodeDpid10" IS NOT NULL + GROUP BY + Na."id", + Na."communityRadarEntryId" + ) NaFiltered ON cre."id" = NaFiltered."communityRadarEntryId" + LEFT OUTER JOIN "Annotation" ON NaFiltered."id" = "Annotation"."nodeAttestationId" + LEFT OUTER JOIN "NodeAttestationReaction" ON NaFiltered."id" = "NodeAttestationReaction"."nodeAttestationId" + LEFT OUTER JOIN "NodeAttestationVerification" ON NaFiltered."id" = "NodeAttestationVerification"."nodeAttestationId" + WHERE + EXISTS ( + SELECT + * + FROM + "CommunityEntryAttestation" Cea + WHERE + NaFiltered."attestationId" = Cea."attestationId" + AND NaFiltered."attestationVersionId" = cea."attestationVersionId" + AND Cea."desciCommunityId" = ${communityId} + AND Cea."required" = TRUE + ) + GROUP BY + cre.id + HAVING + COUNT(DISTINCT NaFiltered."id") = ${entryAttestations.length} + ORDER BY + verifications ASC, + cre."createdAt" DESC + LIMIT + ${limit}; + OFFSET ${offset}; + `; + + return entries as RadarEntry[]; + } + + async listCommunityCuratedFeed({ + communityId, + offset, + limit, + }: { + communityId: number; + offset: number; + limit: number; + }) { + const entryAttestations = await attestationService.getCommunityEntryAttestations(communityId); + + const entries = await prisma.$queryRaw` + SELECT + cre.*, + COUNT(DISTINCT "Annotation".id) :: int AS annotations, + COUNT(DISTINCT "NodeAttestationReaction".id) :: int AS reactions, + COUNT(DISTINCT "NodeAttestationVerification".id) :: int AS verifications, + COUNT(DISTINCT NaFiltered."id") :: int AS valid_attestations + FROM + "CommunityRadarEntry" cre + LEFT JOIN ( + SELECT + Na."id", + Na."communityRadarEntryId", + Na."attestationId", + Na."attestationVersionId" + FROM + "NodeAttestation" Na + LEFT JOIN "NodeAttestationVerification" Nav ON Na."id" = Nav."nodeAttestationId" + WHERE + Na."revoked" = false + AND Na."nodeDpid10" IS NOT NULL + GROUP BY + Na."id", + Na."communityRadarEntryId" + HAVING + COUNT(Nav."id") > 0 + ) NaFiltered ON cre."id" = NaFiltered."communityRadarEntryId" + LEFT JOIN "Annotation" ON NaFiltered."id" = "Annotation"."nodeAttestationId" + LEFT JOIN "NodeAttestationReaction" ON NaFiltered."id" = "NodeAttestationReaction"."nodeAttestationId" + LEFT JOIN "NodeAttestationVerification" ON NaFiltered."id" = "NodeAttestationVerification"."nodeAttestationId" + WHERE + EXISTS ( + SELECT + 1 + FROM + "CommunityEntryAttestation" Cea + WHERE + NaFiltered."attestationId" = Cea."attestationId" + AND NaFiltered."attestationVersionId" = Cea."attestationVersionId" + AND Cea."desciCommunityId" = ${communityId} + AND Cea."required" = TRUE + ) + GROUP BY + cre.id + HAVING + COUNT(DISTINCT NaFiltered."id") = ${entryAttestations.length} + ORDER BY + verifications DESC + OFFSET ${offset} + LIMIT + ${limit}; + `; + + return entries as RadarEntry[]; + } + /** * This methods takes the result of getCommunityRadar and * filter out entries(nodes) whose NodeAttestations don't have atleast on verification @@ -371,10 +502,11 @@ export class CommunityService { // check if node has claimed all community entry attestations const entryAttestations = await communityService.getEntryAttestations({ desciCommunityId, + required: true, }); const claimedAttestations = await prisma.nodeAttestation.findMany({ - where: { desciCommunityId, nodeUuid }, + where: { desciCommunityId, nodeUuid, revoked: false }, }); const isEntriesClaimed = entryAttestations.every((entry) => @@ -386,12 +518,59 @@ export class CommunityService { if (!isEntriesClaimed) return undefined; - return await prisma.communityRadarEntry.create({ + const radarEntry = await prisma.communityRadarEntry.create({ data: { desciCommunityId, nodeUuid, }, }); + + await prisma.$transaction( + claimedAttestations.map((claim) => + prisma.nodeAttestation.update({ where: { id: claim.id }, data: { communityRadarEntryId: radarEntry.id } }), + ), + ); + + return radarEntry; + } + + async removeFromRadar(desciCommunityId: number, nodeUuid: string) { + // check if node has claimed all community entry attestations + const entryAttestations = await communityService.getEntryAttestations({ + desciCommunityId, + required: true, + }); + + const claimedAttestations = await prisma.nodeAttestation.findMany({ + where: { desciCommunityId, nodeUuid, revoked: false }, + }); + + const isEntriesClaimed = entryAttestations.every((entry) => + claimedAttestations.find( + (claimed) => + claimed.attestationId === entry.attestationId && claimed.attestationVersionId === entry.attestationVersionId, + ), + ); + + if (isEntriesClaimed) return undefined; + const entry = await prisma.communityRadarEntry.findFirst({ + where: { + desciCommunityId, + nodeUuid, + }, + }); + + await prisma.$transaction( + claimedAttestations.map((claim) => + prisma.nodeAttestation.update({ where: { id: claim.id }, data: { communityRadarEntryId: null } }), + ), + ); + + return await prisma.communityRadarEntry.delete({ + where: { + id: entry.id, + }, + }); } } From 6b14995ef59cb9d1e8bab59d8a6b870e82ccf5be Mon Sep 17 00:00:00 2001 From: shadrach Date: Thu, 9 Jan 2025 11:48:24 +0100 Subject: [PATCH 3/5] update radar and feed apis with new impl --- .../src/controllers/communities/feed.ts | 36 ++++++++++- .../src/controllers/communities/radar.ts | 25 ++++---- .../src/controllers/communities/types.ts | 4 +- .../src/controllers/communities/util.ts | 60 +++++++++++++++++++ .../src/routes/v1/communities/index.ts | 16 +++-- .../src/routes/v1/communities/schema.ts | 3 + desci-server/src/services/Communities.ts | 2 +- 7 files changed, 129 insertions(+), 17 deletions(-) diff --git a/desci-server/src/controllers/communities/feed.ts b/desci-server/src/controllers/communities/feed.ts index eb73327e2..5743b6032 100644 --- a/desci-server/src/controllers/communities/feed.ts +++ b/desci-server/src/controllers/communities/feed.ts @@ -1,14 +1,16 @@ import { NextFunction, Request, Response } from 'express'; import _ from 'lodash'; +import z from 'zod'; import { NotFoundError } from '../../core/ApiError.js'; import { SuccessResponse } from '../../core/ApiResponse.js'; import { logger as parentLogger } from '../../logger.js'; +import { getCommunityFeedSchema } from '../../routes/v1/communities/schema.js'; import { attestationService } from '../../services/Attestation.js'; import { communityService } from '../../services/Communities.js'; import { asyncMap } from '../../utils.js'; -import { resolveLatestNode } from './util.js'; +import { getCommunityNodeDetails, resolveLatestNode } from './util.js'; const logger = parentLogger.child({ module: 'communities/feed.ts' }); @@ -46,6 +48,38 @@ export const getCommunityFeed = async (req: Request, res: Response, next: NextFu return new SuccessResponse(data).send(res); }; +export const listCommunityFeed = async (req: Request, res: Response, next: NextFunction) => { + const { query, params } = await getCommunityFeedSchema.parseAsync(req); + const limit = 20; + const page = Math.max(Math.max((query.cursor ?? 0) - 1, 0), 0); + const offset = limit * page; + + const curatedNodes = await communityService.listCommunityCuratedFeed({ + communityId: parseInt(params.communityId.toString()), + offset, + limit, + }); + logger.trace({ offset, page, cursor: query.cursor }, 'Feed'); + // THIS is necessary because the engagement signal returned from getcuratedNodes + // accounts for only engagements on community selected attestations + const entries = await asyncMap(curatedNodes, async (entry) => { + const engagements = await attestationService.getNodeEngagementSignalsByUuid(entry.nodeUuid); + return { + ...entry, + engagements, + verifiedEngagements: { + reactions: entry.reactions, + annotations: entry.annotations, + verifications: entry.verifications, + }, + }; + }); + + const data = await Promise.all(entries.map(getCommunityNodeDetails)); + // logger.info({ count: data.length, page: offset }, 'listCommunityFeed'); + return new SuccessResponse({ count: data.length, cursor: page + 1, data }).send(res); +}; + export const getCommunityDetails = async (req: Request, res: Response, next: NextFunction) => { const community = await communityService.findCommunityByNameOrSlug(req.params.communityName as string); diff --git a/desci-server/src/controllers/communities/radar.ts b/desci-server/src/controllers/communities/radar.ts index 743bb1173..91b2f8bcd 100644 --- a/desci-server/src/controllers/communities/radar.ts +++ b/desci-server/src/controllers/communities/radar.ts @@ -4,11 +4,12 @@ import _ from 'lodash'; import { SuccessResponse } from '../../core/ApiResponse.js'; import { logger as parentLogger } from '../../logger.js'; +import { getCommunityFeedSchema } from '../../routes/v1/communities/schema.js'; import { attestationService } from '../../services/Attestation.js'; import { communityService } from '../../services/Communities.js'; import { asyncMap } from '../../utils.js'; -import { resolveLatestNode } from './util.js'; +import { getCommunityNodeDetails, resolveLatestNode } from './util.js'; const logger = parentLogger.child({ module: 'GET COMMUNITY RADAR' }); export const getCommunityRadar = async (req: Request, res: Response, next: NextFunction) => { @@ -62,15 +63,20 @@ export const getCommunityRadar = async (req: Request, res: Response, next: NextF }; export const listCommunityRadar = async (req: Request, res: Response, next: NextFunction) => { + const { query, params } = await getCommunityFeedSchema.parseAsync(req); + const limit = 20; + const page = Math.max(Math.max((query.cursor ?? 0) - 1, 0), 0); + const offset = limit * page; + const communityRadar = await communityService.listCommunityRadar({ - communityId: parseInt(req.params.communityId as string), - offset: 0, - limit: 20, + communityId: parseInt(params.communityId.toString()), + offset, + limit, }); - logger.info({ communityRadar }, 'Radar'); + logger.trace({ offset, page, cursor: query.cursor }, 'Radar'); // THIS is necessary because the engagement signal returned from getCommunityRadar // accounts for only engagements on community selected attestations - const nodes = await asyncMap(communityRadar, async (entry) => { + const entries = await asyncMap(communityRadar, async (entry) => { const engagements = await attestationService.getNodeEngagementSignalsByUuid(entry.nodeUuid); return { ...entry, @@ -84,8 +90,7 @@ export const listCommunityRadar = async (req: Request, res: Response, next: Next }); // rank nodes by sum of sum of verified and non verified signals - - logger.info({ nodes }, 'CHECK Verification SignalS'); - - return new SuccessResponse(nodes).send(res); + const data = await Promise.all(entries.map(getCommunityNodeDetails)); + // logger.trace({ count: data.length }, 'listCommunityRadar'); + return new SuccessResponse({ count: data.length, cursor: page + 1, data }).send(res); }; diff --git a/desci-server/src/controllers/communities/types.ts b/desci-server/src/controllers/communities/types.ts index b66657b9b..b635f058b 100644 --- a/desci-server/src/controllers/communities/types.ts +++ b/desci-server/src/controllers/communities/types.ts @@ -1,7 +1,7 @@ import { ResearchObjectV1 } from '@desci-labs/desci-models'; import { Node } from '@prisma/client'; -import { CommunityRadarNode } from '../../services/Communities.js'; +import { CommunityRadarNode, RadarEntry } from '../../services/Communities.js'; export type NodeRadarItem = { NodeAttestation: CommunityRadarNode[]; @@ -46,3 +46,5 @@ export type NodeRadar = NodeRadarItem & { verifications: number; }; }; + +export type NodeRadarEntry = RadarEntry & { node?: Partial; manifest?: ResearchObjectV1 }; diff --git a/desci-server/src/controllers/communities/util.ts b/desci-server/src/controllers/communities/util.ts index c5269399f..958c795d6 100644 --- a/desci-server/src/controllers/communities/util.ts +++ b/desci-server/src/controllers/communities/util.ts @@ -1,9 +1,11 @@ +import { ResearchObjectV1 } from '@desci-labs/desci-models'; import { Node, NodeVersion } from '@prisma/client'; import axios from 'axios'; import _ from 'lodash'; import { prisma } from '../../client.js'; import { logger } from '../../logger.js'; +import { RadarEntry } from '../../services/Communities.js'; import { NodeUuid } from '../../services/manifestRepo.js'; import repoService from '../../services/repoService.js'; import { IndexedResearchObject, getIndexedResearchObjects } from '../../theGraph.js'; @@ -68,6 +70,64 @@ export const resolveLatestNode = async (radar: Partial) => { return radar; }; +export const getCommunityNodeDetails = async ( + radar: RadarEntry & { node?: Partial; manifest?: ResearchObjectV1 }, +) => { + const uuid = ensureUuidEndsWithDot(radar.nodeUuid); + + const discovery = await prisma.node.findFirst({ + where: { + uuid, + isDeleted: false, + }, + select: { + id: true, + manifestUrl: true, + ownerId: true, + uuid: true, + title: true, + NodeCover: true, + }, + }); + + if (!discovery) { + logger.warn({ uuid }, 'uuid not found'); + } + + const selectAttributes: (keyof typeof discovery)[] = ['ownerId', 'NodeCover']; + const node: Partial = _.pick(discovery, selectAttributes); + const publishedVersions = + (await prisma.$queryRaw`SELECT * from "NodeVersion" where "nodeId" = ${discovery.id} AND ("transactionId" IS NOT NULL or "commitId" IS NOT NULL) ORDER BY "createdAt" DESC`) as NodeVersion[]; + + // const nodeVersions = (await getNodeVersion + logger.info({ uuid: discovery.uuid, publishedVersions }, 'Resolve node'); + node['versions'] = publishedVersions.length; + node['publishedDate'] = publishedVersions[0].createdAt; + node.manifestUrl = publishedVersions[0].manifestUrl; + radar.node = node; + + let gatewayUrl = publishedVersions[0].manifestUrl; + + try { + gatewayUrl = cleanupManifestUrl(gatewayUrl); + // logger.trace({ gatewayUrl, uuid }, 'transforming manifest'); + const manifest = (await axios.get(gatewayUrl)).data; + radar.manifest = manifest; + + // logger.info({ manifest }, '[SHOW API GET LAST PUBLISHED MANIFEST]'); + } catch (err) { + const manifest = await repoService.getDraftManifest({ + uuid: node.uuid as NodeUuid, + documentId: node.manifestDocumentId, + }); + radar.manifest = manifest; + logger.error({ err, manifestUrl: discovery.manifestUrl, gatewayUrl }, 'nodes/show.ts: failed to preload manifest'); + } + + radar.node = { ...radar.node, ...node }; + return radar; +}; + export const getNodeVersion = async (uuid: string) => { let indexingResults: { researchObjects: IndexedResearchObject[] }; try { diff --git a/desci-server/src/routes/v1/communities/index.ts b/desci-server/src/routes/v1/communities/index.ts index ba157424c..0e6f1b3a2 100644 --- a/desci-server/src/routes/v1/communities/index.ts +++ b/desci-server/src/routes/v1/communities/index.ts @@ -4,10 +4,15 @@ import { getCommunityRecommendations, getValidatedAttestations, } from '../../../controllers/attestations/recommendations.js'; -import { getAllFeeds, getCommunityDetails, getCommunityFeed } from '../../../controllers/communities/feed.js'; +import { + getAllFeeds, + getCommunityDetails, + getCommunityFeed, + listCommunityFeed, +} from '../../../controllers/communities/feed.js'; import { checkMemberGuard } from '../../../controllers/communities/guard.js'; import { listCommunities } from '../../../controllers/communities/list.js'; -import { getCommunityRadar } from '../../../controllers/communities/radar.js'; +import { getCommunityRadar, listCommunityRadar } from '../../../controllers/communities/radar.js'; import { ensureUser } from '../../../middleware/permissions.js'; import { validate } from '../../../middleware/validator.js'; import { asyncHandler } from '../../../utils/asyncHandler.js'; @@ -32,8 +37,11 @@ router.get( asyncHandler(getValidatedAttestations), ); -router.get('/:communityId/feed', [validate(getCommunityFeedSchema)], asyncHandler(getCommunityFeed)); -router.get('/:communityId/radar', [validate(getCommunityFeedSchema)], asyncHandler(getCommunityRadar)); +// router.get('/:communityId/feed', [validate(getCommunityFeedSchema)], asyncHandler(getCommunityFeed)); +// router.get('/:communityId/radar', [validate(getCommunityFeedSchema)], asyncHandler(getCommunityRadar)); + +router.get('/:communityId/feed', [validate(getCommunityFeedSchema)], asyncHandler(listCommunityFeed)); +router.get('/:communityId/radar', [validate(getCommunityFeedSchema)], asyncHandler(listCommunityRadar)); router.post('/:communityId/memberGuard', [ensureUser, validate(memberGuardSchema)], asyncHandler(checkMemberGuard)); diff --git a/desci-server/src/routes/v1/communities/schema.ts b/desci-server/src/routes/v1/communities/schema.ts index 39ec76e7a..1c2ece543 100644 --- a/desci-server/src/routes/v1/communities/schema.ts +++ b/desci-server/src/routes/v1/communities/schema.ts @@ -10,6 +10,9 @@ export const getCommunityFeedSchema = z.object({ params: z.object({ communityId: z.coerce.number(), }), + query: z.object({ + cursor: z.coerce.number().optional().default(0), + }), }); export const memberGuardSchema = z.object({ diff --git a/desci-server/src/services/Communities.ts b/desci-server/src/services/Communities.ts index f95d15991..3aab0601f 100644 --- a/desci-server/src/services/Communities.ts +++ b/desci-server/src/services/Communities.ts @@ -233,7 +233,7 @@ export class CommunityService { verifications ASC, cre."createdAt" DESC LIMIT - ${limit}; + ${limit} OFFSET ${offset}; `; From 776b5e59097580ce83174e744e3ef56f608401e3 Mon Sep 17 00:00:00 2001 From: shadrach Date: Thu, 9 Jan 2025 15:11:36 +0100 Subject: [PATCH 4/5] fix tests --- desci-server/test/integration/Attestation.test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desci-server/test/integration/Attestation.test.ts b/desci-server/test/integration/Attestation.test.ts index ea03d90ad..45baad6f3 100644 --- a/desci-server/test/integration/Attestation.test.ts +++ b/desci-server/test/integration/Attestation.test.ts @@ -1679,7 +1679,7 @@ describe('Attestations Service', async () => { .set('authorization', authHeaderVal) .field('communityId', desciCommunity.id); - apiResponse = res.body.data; + apiResponse = res.body.data.data; console.log(apiResponse[0]); console.log(apiResponse[1]); console.log(apiResponse[2]); @@ -2079,7 +2079,7 @@ describe('Attestations Service', async () => { .get(`/v1/communities/${desciCommunity.id}/radar`) .set('authorization', authHeaderVal) .field('communityId', desciCommunity.id); - const radar = res1.body.data as NodeRadar[]; + const radar = res1.body.data.data as NodeRadar[]; expect(res1.status).to.equal(200); expect(radar.length).to.equal(1); const radarNode = radar[0]; From e05efa19286160cdd4baf3cbf6109befa181fe1f Mon Sep 17 00:00:00 2001 From: shadrach Date: Fri, 10 Jan 2025 11:35:44 +0100 Subject: [PATCH 5/5] fix tests --- .../src/controllers/attestations/claims.ts | 4 ++- .../src/controllers/communities/util.ts | 1 + desci-server/src/services/Communities.ts | 11 ++++-- .../test/integration/Attestation.test.ts | 36 +++++++++++++------ 4 files changed, 38 insertions(+), 14 deletions(-) diff --git a/desci-server/src/controllers/attestations/claims.ts b/desci-server/src/controllers/attestations/claims.ts index 961164bd3..3791343cb 100644 --- a/desci-server/src/controllers/attestations/claims.ts +++ b/desci-server/src/controllers/attestations/claims.ts @@ -134,7 +134,7 @@ export const removeClaim = async (req: RequestWithUser, res: Response, _next: Ne : await attestationService.unClaimAttestation(claim.id); // trigger update radar entry - await communityService.addToRadar(claim.desciCommunityId, claim.nodeUuid); + await communityService.removeFromRadar(claim.desciCommunityId, claim.nodeUuid); await saveInteraction(req, ActionType.REVOKE_CLAIM, body); @@ -191,6 +191,8 @@ export const claimEntryRequirements = async (req: Request, res: Response, _next: nodeUuid: uuid, attestations: claims, }); + // trigger update radar entry + await communityService.addToRadar(communityId, uuid); await saveInteraction(req, ActionType.CLAIM_ENTRY_ATTESTATIONS, { communityId, diff --git a/desci-server/src/controllers/communities/util.ts b/desci-server/src/controllers/communities/util.ts index 958c795d6..94dc06b91 100644 --- a/desci-server/src/controllers/communities/util.ts +++ b/desci-server/src/controllers/communities/util.ts @@ -87,6 +87,7 @@ export const getCommunityNodeDetails = async ( uuid: true, title: true, NodeCover: true, + dpidAlias: true, }, }); diff --git a/desci-server/src/services/Communities.ts b/desci-server/src/services/Communities.ts index 3aab0601f..69a49cd76 100644 --- a/desci-server/src/services/Communities.ts +++ b/desci-server/src/services/Communities.ts @@ -518,8 +518,15 @@ export class CommunityService { if (!isEntriesClaimed) return undefined; - const radarEntry = await prisma.communityRadarEntry.create({ - data: { + const radarEntry = await prisma.communityRadarEntry.upsert({ + where: { + nodeUuid_desciCommunityId: { desciCommunityId, nodeUuid }, + }, + create: { + nodeUuid, + desciCommunityId, + }, + update: { desciCommunityId, nodeUuid, }, diff --git a/desci-server/test/integration/Attestation.test.ts b/desci-server/test/integration/Attestation.test.ts index 45baad6f3..85fa67aec 100644 --- a/desci-server/test/integration/Attestation.test.ts +++ b/desci-server/test/integration/Attestation.test.ts @@ -22,7 +22,7 @@ import request from 'supertest'; import { prisma } from '../../src/client.js'; import { NodeAttestationFragment } from '../../src/controllers/attestations/show.js'; -import { Engagement, NodeRadar, NodeRadarItem } from '../../src/controllers/communities/types.js'; +import { Engagement, NodeRadar, NodeRadarEntry, NodeRadarItem } from '../../src/controllers/communities/types.js'; import { DuplicateReactionError, DuplicateVerificationError, @@ -129,7 +129,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[]; @@ -264,6 +264,8 @@ describe('Attestations Service', async () => { nodeVersion, claimerId: author.id, }); + // trigger update radar entry + await communityService.addToRadar(desciCommunity.id, node.uuid); // publish new node version nodeVersion2 = await prisma.nodeVersion.create({ @@ -1567,7 +1569,7 @@ describe('Attestations Service', async () => { let fairMetadataAttestationVersion: AttestationVersion; let res: request.Response; - let apiResponse: NodeRadar[]; + let apiResponse: NodeRadarEntry[]; before(async () => { node = nodes[0]; @@ -1621,6 +1623,8 @@ describe('Attestations Service', async () => { nodeVersion, claimerId: author.id, }); + // trigger update radar entry + await communityService.addToRadar(desciCommunity.id, node.uuid); // claim all entry requirements for node 2 [claim2, openDataAttestationClaim2] = await attestationService.claimAttestations({ @@ -1639,6 +1643,8 @@ describe('Attestations Service', async () => { nodeVersion, claimerId: author2.id, }); + // trigger update radar entry + await communityService.addToRadar(desciCommunity.id, node2.uuid); // claim all entry requirements for node 3 [claim3, openDataAttestationClaim3] = await attestationService.claimAttestations({ @@ -1657,6 +1663,8 @@ describe('Attestations Service', async () => { nodeVersion, claimerId: author3.id, }); + // trigger update radar entry + await communityService.addToRadar(desciCommunity.id, node3.uuid); // verify both claims for node 1 await attestationService.verifyClaim(claim.id, users[3].id); @@ -1696,9 +1704,9 @@ describe('Attestations Service', async () => { it('should return nodes in radar in ASC order of verified engagements sorted by last submission/claim date', async () => { expect(res.status).to.equal(200); expect(apiResponse.length).to.equal(3); - expect(apiResponse[0].nodeDpid10).to.be.equal('2'); - expect(apiResponse[1].nodeDpid10).to.be.equal('3'); - expect(apiResponse[2].nodeDpid10).to.be.equal('1'); + expect(apiResponse[0].nodeUuid).to.be.equal(node2.uuid); + expect(apiResponse[1].nodeUuid).to.be.equal(node3.uuid); + expect(apiResponse[2].nodeUuid).to.be.equal(node.uuid); }); }); @@ -2003,6 +2011,8 @@ describe('Attestations Service', async () => { nodeVersion, claimerId: author.id, }); + // trigger update radar entry + await communityService.addToRadar(desciCommunity.id, node.uuid); [claim2, openDataAttestationClaim2] = await attestationService.claimAttestations({ attestations: [ @@ -2020,6 +2030,8 @@ describe('Attestations Service', async () => { nodeVersion, claimerId: author2.id, }); + // trigger update radar entry + await communityService.addToRadar(desciCommunity.id, node2.uuid); // verify both claims for node 1 await attestationService.verifyClaim(claim.id, users[1].id); @@ -2074,24 +2086,26 @@ describe('Attestations Service', async () => { expect(communityEngagementSignal.reactions).to.equal(0); }); - it('should remove node from radar and curated if an entry attestation claim is revoked', async () => { + it('should remove node from radar and curated if claim is revoked', async () => { const res1 = await request(app) .get(`/v1/communities/${desciCommunity.id}/radar`) .set('authorization', authHeaderVal) .field('communityId', desciCommunity.id); - const radar = res1.body.data.data as NodeRadar[]; + console.log('radar', res1.body); + const radar = res1.body.data.data as NodeRadarEntry[]; expect(res1.status).to.equal(200); expect(radar.length).to.equal(1); const radarNode = radar[0]; - expect(radarNode.nodeDpid10).to.be.equal('2'); - expect(radarNode.nodeuuid).to.be.equal(node2.uuid); + // expect(radarNode.nodeDpid10).to.be.equal('2'); + expect(radarNode.nodeUuid).to.be.equal(node2.uuid); const res = await request(app) .get(`/v1/communities/${desciCommunity.id}/feed`) .set('authorization', authHeaderVal) .field('communityId', desciCommunity.id); - const curatedNodes = res.body.data as NodeRadar[]; + console.log('feed', res.body); + const curatedNodes = res.body.data.data as NodeRadarEntry[]; expect(res.status).to.equal(200); expect(curatedNodes.length).to.equal(0); });