From 403ef83030d3684931eedce3fa0541eb909f6518 Mon Sep 17 00:00:00 2001 From: shadrach Date: Tue, 14 Jan 2025 06:08:41 +0100 Subject: [PATCH 1/3] structural changes to api data --- .../src/controllers/attestations/claims.ts | 11 +++ .../controllers/attestations/verification.ts | 9 ++ .../src/controllers/communities/feed.ts | 20 ++++- .../src/controllers/communities/radar.ts | 22 ++++- .../src/controllers/communities/util.ts | 8 +- desci-server/src/controllers/nodes/feed.ts | 2 +- .../src/routes/v1/communities/schema.ts | 2 +- desci-server/src/services/Communities.ts | 90 +++++++++++++++++++ 8 files changed, 153 insertions(+), 11 deletions(-) diff --git a/desci-server/src/controllers/attestations/claims.ts b/desci-server/src/controllers/attestations/claims.ts index 3791343cb..198d610cb 100644 --- a/desci-server/src/controllers/attestations/claims.ts +++ b/desci-server/src/controllers/attestations/claims.ts @@ -7,6 +7,7 @@ import { AuthFailureError, NotFoundError } from '../../core/ApiError.js'; import { SuccessMessageResponse, SuccessResponse } from '../../core/ApiResponse.js'; import { logger } from '../../logger.js'; import { RequestWithUser } from '../../middleware/authorisation.js'; +import { delFromCache } from '../../redisClient.js'; import { removeClaimSchema } from '../../routes/v1/attestations/schema.js'; import { attestationService } from '../../services/Attestation.js'; import { communityService } from '../../services/Communities.js'; @@ -41,6 +42,10 @@ export const claimAttestation = async (req: RequestWithUser, res: Response, _nex await saveInteraction(req, ActionType.CLAIM_ATTESTATION, { ...body, claimId: reclaimed.id }); // trigger update radar entry await communityService.addToRadar(reclaimed.desciCommunityId, reclaimed.nodeUuid); + // invalidate radar and curated feed count cache + await delFromCache(`radar-${reclaimed.desciCommunityId}-count`); + await delFromCache(`curated-${reclaimed.desciCommunityId}-count`); + new SuccessResponse(reclaimed).send(res); return; } @@ -53,6 +58,9 @@ export const claimAttestation = async (req: RequestWithUser, res: Response, _nex }); // trigger update radar entry await communityService.addToRadar(nodeClaim.desciCommunityId, nodeClaim.nodeUuid); + // invalidate radar and curated feed count cache + await delFromCache(`radar-${nodeClaim.desciCommunityId}-count`); + await delFromCache(`curated-${nodeClaim.desciCommunityId}-count`); await saveInteraction(req, ActionType.CLAIM_ATTESTATION, { ...body, claimId: nodeClaim.id }); @@ -193,6 +201,9 @@ export const claimEntryRequirements = async (req: Request, res: Response, _next: }); // trigger update radar entry await communityService.addToRadar(communityId, uuid); + // invalidate radar and curated feed count cache + await delFromCache(`radar-${communityId}-count`); + await delFromCache(`curated-${communityId}-count`); await saveInteraction(req, ActionType.CLAIM_ENTRY_ATTESTATIONS, { communityId, diff --git a/desci-server/src/controllers/attestations/verification.ts b/desci-server/src/controllers/attestations/verification.ts index d4d04f837..298558393 100644 --- a/desci-server/src/controllers/attestations/verification.ts +++ b/desci-server/src/controllers/attestations/verification.ts @@ -7,6 +7,7 @@ import { prisma } from '../../client.js'; import { ForbiddenError } from '../../core/ApiError.js'; import { SuccessMessageResponse, SuccessResponse } from '../../core/ApiResponse.js'; import { logger as parentLogger } from '../../logger.js'; +import { delFromCache } from '../../redisClient.js'; import { attestationService } from '../../services/Attestation.js'; import { getTargetDpidUrl } from '../../services/fixDpid.js'; import { doiService } from '../../services/index.js'; @@ -60,6 +61,10 @@ export const removeVerification = async ( const claim = await attestationService.findClaimById(verification.nodeAttestationId); const attestation = await attestationService.findAttestationById(claim.attestationId); + // invalidate radar and curated feed count cache + await delFromCache(`radar-${claim.desciCommunityId}-count`); + await delFromCache(`curated-${claim.desciCommunityId}-count`); + if (attestation.protected) { /** * Update ORCID Profile @@ -103,6 +108,10 @@ export const addVerification = async ( new SuccessMessageResponse().send(res); + // invalidate radar and curated feed count cache + await delFromCache(`radar-${claim.desciCommunityId}-count`); + await delFromCache(`curated-${claim.desciCommunityId}-count`); + const attestation = await attestationService.findAttestationById(claim.attestationId); if (attestation.protected) { /** diff --git a/desci-server/src/controllers/communities/feed.ts b/desci-server/src/controllers/communities/feed.ts index 5743b6032..988179bad 100644 --- a/desci-server/src/controllers/communities/feed.ts +++ b/desci-server/src/controllers/communities/feed.ts @@ -5,6 +5,7 @@ import z from 'zod'; import { NotFoundError } from '../../core/ApiError.js'; import { SuccessResponse } from '../../core/ApiResponse.js'; import { logger as parentLogger } from '../../logger.js'; +import { getFromCache, setToCache } from '../../redisClient.js'; import { getCommunityFeedSchema } from '../../routes/v1/communities/schema.js'; import { attestationService } from '../../services/Attestation.js'; import { communityService } from '../../services/Communities.js'; @@ -51,15 +52,22 @@ export const getCommunityFeed = async (req: Request, res: Response, next: NextFu 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 page = Math.max(Math.max((query.page ?? 0) - 1, 0), 0); const offset = limit * page; + let totalCount = await getFromCache(`curated-${params.communityId}-count`); + if (!totalCount) { + totalCount = await communityService.countCommunityCuratedFeed(parseInt(params.communityId.toString())); + logger.trace({ totalCount }, 'FeedCount'); + setToCache(`curated-${params.communityId}-count`, totalCount); + } + const curatedNodes = await communityService.listCommunityCuratedFeed({ communityId: parseInt(params.communityId.toString()), offset, limit, }); - logger.trace({ offset, page, cursor: query.cursor }, 'Feed'); + logger.trace({ offset, page }, '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) => { @@ -77,7 +85,13 @@ export const listCommunityFeed = async (req: Request, res: Response, next: NextF 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); + return new SuccessResponse({ + data, + page: page + 1, + count: totalCount, + nextPage: data.length === limit ? page + 2 : undefined, + communityId: params.communityId, + }).send(res); }; export const getCommunityDetails = async (req: Request, res: Response, next: NextFunction) => { diff --git a/desci-server/src/controllers/communities/radar.ts b/desci-server/src/controllers/communities/radar.ts index 91b2f8bcd..fc621844d 100644 --- a/desci-server/src/controllers/communities/radar.ts +++ b/desci-server/src/controllers/communities/radar.ts @@ -4,6 +4,7 @@ import _ from 'lodash'; import { SuccessResponse } from '../../core/ApiResponse.js'; import { logger as parentLogger } from '../../logger.js'; +import { getFromCache, redisClient, setToCache } from '../../redisClient.js'; import { getCommunityFeedSchema } from '../../routes/v1/communities/schema.js'; import { attestationService } from '../../services/Attestation.js'; import { communityService } from '../../services/Communities.js'; @@ -65,15 +66,23 @@ 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 page = Math.max(Math.max((query.page ?? 0) - 1, 0), 0); const offset = limit * page; + let totalCount = await getFromCache(`radar-${params.communityId}-count`); + if (!totalCount) { + totalCount = await communityService.countCommunityRadar(parseInt(params.communityId.toString())); + logger.trace({ totalCount }, 'RadarCount'); + setToCache(`radar-${params.communityId}-count`, totalCount); + } + const communityRadar = await communityService.listCommunityRadar({ communityId: parseInt(params.communityId.toString()), offset, limit, }); - logger.trace({ offset, page, cursor: query.cursor }, 'Radar'); + + logger.trace({ offset, page, cursor: query.page }, 'Radar'); // THIS is necessary because the engagement signal returned from getCommunityRadar // accounts for only engagements on community selected attestations const entries = await asyncMap(communityRadar, async (entry) => { @@ -92,5 +101,12 @@ export const listCommunityRadar = async (req: Request, res: Response, next: Next // rank nodes by sum of sum of verified and non verified signals 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); + + return new SuccessResponse({ + data, + count: totalCount, + page: page + 1, + nextPage: data.length === limit ? page + 2 : undefined, + communityId: params.communityId, + }).send(res); }; diff --git a/desci-server/src/controllers/communities/util.ts b/desci-server/src/controllers/communities/util.ts index 94dc06b91..3ce261de3 100644 --- a/desci-server/src/controllers/communities/util.ts +++ b/desci-server/src/controllers/communities/util.ts @@ -71,7 +71,7 @@ export const resolveLatestNode = async (radar: Partial) => { }; export const getCommunityNodeDetails = async ( - radar: RadarEntry & { node?: Partial; manifest?: ResearchObjectV1 }, + radar: RadarEntry & { node?: Partial; manifest?: ResearchObjectV1 }, ) => { const uuid = ensureUuidEndsWithDot(radar.nodeUuid); @@ -95,8 +95,8 @@ export const getCommunityNodeDetails = async ( logger.warn({ uuid }, 'uuid not found'); } - const selectAttributes: (keyof typeof discovery)[] = ['ownerId', 'NodeCover']; - const node: Partial = _.pick(discovery, selectAttributes); + const selectAttributes: (keyof typeof discovery)[] = ['ownerId', 'NodeCover', 'dpidAlias']; + 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[]; @@ -106,6 +106,8 @@ export const getCommunityNodeDetails = async ( node['publishedDate'] = publishedVersions[0].createdAt; node.manifestUrl = publishedVersions[0].manifestUrl; radar.node = node; + radar.node.dpid = node.dpidAlias; + delete radar.node.dpidAlias; let gatewayUrl = publishedVersions[0].manifestUrl; diff --git a/desci-server/src/controllers/nodes/feed.ts b/desci-server/src/controllers/nodes/feed.ts index 2ccfce5fe..6de57f090 100644 --- a/desci-server/src/controllers/nodes/feed.ts +++ b/desci-server/src/controllers/nodes/feed.ts @@ -1,5 +1,5 @@ import { NodeFeedItem } from '@prisma/client'; -import { Sql } from '@prisma/client/runtime/library.js'; +import { Sql } from '@prisma/client/runtime/data-proxy.js'; import { Request, Response } from 'express'; import { prisma } from '../../client.js'; diff --git a/desci-server/src/routes/v1/communities/schema.ts b/desci-server/src/routes/v1/communities/schema.ts index 1c2ece543..00805de3a 100644 --- a/desci-server/src/routes/v1/communities/schema.ts +++ b/desci-server/src/routes/v1/communities/schema.ts @@ -11,7 +11,7 @@ export const getCommunityFeedSchema = z.object({ communityId: z.coerce.number(), }), query: z.object({ - cursor: z.coerce.number().optional().default(0), + page: z.coerce.number().optional().default(0), }), }); diff --git a/desci-server/src/services/Communities.ts b/desci-server/src/services/Communities.ts index 69a49cd76..0aff0f712 100644 --- a/desci-server/src/services/Communities.ts +++ b/desci-server/src/services/Communities.ts @@ -184,6 +184,50 @@ export class CommunityService { return radar; } + async countCommunityRadar(desciCommunityId: number) { + const entryAttestations = await attestationService.getCommunityEntryAttestations(desciCommunityId); + + const count = (await prisma.$queryRaw` + SELECT + count(*) + 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" + ) NaFiltered ON cre."id" = NaFiltered."communityRadarEntryId" + WHERE + EXISTS ( + SELECT + 1 + FROM + "CommunityEntryAttestation" Cea + WHERE + NaFiltered."attestationId" = Cea."attestationId" + AND NaFiltered."attestationVersionId" = Cea."attestationVersionId" + AND Cea."desciCommunityId" = ${desciCommunityId} + AND Cea."required" = TRUE + ) + GROUP BY + cre.id + HAVING + COUNT(DISTINCT NaFiltered."id") = ${entryAttestations.length} + `) as any[]; + return count.length; + } + async listCommunityRadar({ communityId, offset, limit }: { communityId: number; offset: number; limit: number }) { const entryAttestations = await attestationService.getCommunityEntryAttestations(communityId); const entries = await prisma.$queryRaw` @@ -240,6 +284,52 @@ export class CommunityService { return entries as RadarEntry[]; } + async countCommunityCuratedFeed(desciCommunityId: number) { + const entryAttestations = await attestationService.getCommunityEntryAttestations(desciCommunityId); + + const count = (await prisma.$queryRaw` + SELECT + count(*) + 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" + WHERE + EXISTS ( + SELECT + 1 + FROM + "CommunityEntryAttestation" Cea + WHERE + NaFiltered."attestationId" = Cea."attestationId" + AND NaFiltered."attestationVersionId" = Cea."attestationVersionId" + AND Cea."desciCommunityId" = ${desciCommunityId} + AND Cea."required" = TRUE + ) + GROUP BY + cre.id + HAVING + COUNT(DISTINCT NaFiltered."id") = ${entryAttestations.length} + `) as any[]; + return count.length; + } + async listCommunityCuratedFeed({ communityId, offset, From 65b2ff247adaba292c88ea49c10e7f4bd9d94eb9 Mon Sep 17 00:00:00 2001 From: shadrach Date: Tue, 14 Jan 2025 13:00:18 +0100 Subject: [PATCH 2/3] fix: issues with radar api --- .../src/controllers/attestations/claims.ts | 8 + .../controllers/attestations/verification.ts | 2 + .../src/controllers/communities/feed.ts | 47 ++++- .../src/routes/v1/communities/index.ts | 10 +- .../src/routes/v1/communities/schema.ts | 6 + desci-server/src/services/Communities.ts | 181 ++++++++++++++++++ 6 files changed, 251 insertions(+), 3 deletions(-) diff --git a/desci-server/src/controllers/attestations/claims.ts b/desci-server/src/controllers/attestations/claims.ts index 198d610cb..646fccc98 100644 --- a/desci-server/src/controllers/attestations/claims.ts +++ b/desci-server/src/controllers/attestations/claims.ts @@ -45,6 +45,7 @@ export const claimAttestation = async (req: RequestWithUser, res: Response, _nex // invalidate radar and curated feed count cache await delFromCache(`radar-${reclaimed.desciCommunityId}-count`); await delFromCache(`curated-${reclaimed.desciCommunityId}-count`); + await delFromCache(`all-communities-curated-count`); new SuccessResponse(reclaimed).send(res); return; @@ -61,6 +62,7 @@ export const claimAttestation = async (req: RequestWithUser, res: Response, _nex // invalidate radar and curated feed count cache await delFromCache(`radar-${nodeClaim.desciCommunityId}-count`); await delFromCache(`curated-${nodeClaim.desciCommunityId}-count`); + await delFromCache(`all-communities-curated-count`); await saveInteraction(req, ActionType.CLAIM_ATTESTATION, { ...body, claimId: nodeClaim.id }); @@ -144,6 +146,11 @@ export const removeClaim = async (req: RequestWithUser, res: Response, _next: Ne // trigger update radar entry await communityService.removeFromRadar(claim.desciCommunityId, claim.nodeUuid); + // invalidate radar and curated feed count cache + await delFromCache(`radar-${claim.desciCommunityId}-count`); + await delFromCache(`curated-${claim.desciCommunityId}-count`); + await delFromCache(`all-communities-curated-count`); + await saveInteraction(req, ActionType.REVOKE_CLAIM, body); logger.info({ removeOrRevoke, totalSignal, claimSignal }, 'Claim Removed|Revoked'); @@ -204,6 +211,7 @@ export const claimEntryRequirements = async (req: Request, res: Response, _next: // invalidate radar and curated feed count cache await delFromCache(`radar-${communityId}-count`); await delFromCache(`curated-${communityId}-count`); + await delFromCache(`all-communities-curated-count`); await saveInteraction(req, ActionType.CLAIM_ENTRY_ATTESTATIONS, { communityId, diff --git a/desci-server/src/controllers/attestations/verification.ts b/desci-server/src/controllers/attestations/verification.ts index 298558393..e435595ca 100644 --- a/desci-server/src/controllers/attestations/verification.ts +++ b/desci-server/src/controllers/attestations/verification.ts @@ -64,6 +64,7 @@ export const removeVerification = async ( // invalidate radar and curated feed count cache await delFromCache(`radar-${claim.desciCommunityId}-count`); await delFromCache(`curated-${claim.desciCommunityId}-count`); + await delFromCache(`all-communities-curated-count`); if (attestation.protected) { /** @@ -111,6 +112,7 @@ export const addVerification = async ( // invalidate radar and curated feed count cache await delFromCache(`radar-${claim.desciCommunityId}-count`); await delFromCache(`curated-${claim.desciCommunityId}-count`); + await delFromCache(`all-communities-curated-count`); const attestation = await attestationService.findAttestationById(claim.attestationId); if (attestation.protected) { diff --git a/desci-server/src/controllers/communities/feed.ts b/desci-server/src/controllers/communities/feed.ts index 988179bad..29500c271 100644 --- a/desci-server/src/controllers/communities/feed.ts +++ b/desci-server/src/controllers/communities/feed.ts @@ -6,7 +6,7 @@ import { NotFoundError } from '../../core/ApiError.js'; import { SuccessResponse } from '../../core/ApiResponse.js'; import { logger as parentLogger } from '../../logger.js'; import { getFromCache, setToCache } from '../../redisClient.js'; -import { getCommunityFeedSchema } from '../../routes/v1/communities/schema.js'; +import { getAllCommunitiesFeedSchema, getCommunityFeedSchema } from '../../routes/v1/communities/schema.js'; import { attestationService } from '../../services/Attestation.js'; import { communityService } from '../../services/Communities.js'; import { asyncMap } from '../../utils.js'; @@ -149,3 +149,48 @@ export const getAllFeeds = async (req: Request, res: Response, next: NextFunctio return new SuccessResponse(data).send(res); }; + +export const listAllCommunityCuratedFeeds = async (req: Request, res: Response, next: NextFunction) => { + const { query } = await getAllCommunitiesFeedSchema.parseAsync(req); + const limit = 20; + const page = Math.max(Math.max((query.page ?? 0) - 1, 0), 0); + const offset = limit * page; + + let totalCount = await getFromCache(`all-communities-curated-count`); + if (!totalCount) { + totalCount = await communityService.countAllCommunityCuratedFeeds(); + logger.trace({ totalCount }, 'countAllCommunityCuratedFeeds'); + setToCache(`all-communities-curated-count`, totalCount); + } + + const curatedNodes = await communityService.listAllCommunityCuratedFeeds({ + // communityId: parseInt(params.communityId.toString()), + offset, + limit, + }); + logger.trace({ offset, page }, 'countAllCommunityCuratedFeeds'); + // 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({ + data, + page: page + 1, + count: totalCount, + nextPage: data.length === limit ? page + 2 : undefined, + // communityId: params.communityId, + }).send(res); +}; diff --git a/desci-server/src/routes/v1/communities/index.ts b/desci-server/src/routes/v1/communities/index.ts index 0e6f1b3a2..0ec6ef321 100644 --- a/desci-server/src/routes/v1/communities/index.ts +++ b/desci-server/src/routes/v1/communities/index.ts @@ -8,6 +8,7 @@ import { getAllFeeds, getCommunityDetails, getCommunityFeed, + listAllCommunityCuratedFeeds, listCommunityFeed, } from '../../../controllers/communities/feed.js'; import { checkMemberGuard } from '../../../controllers/communities/guard.js'; @@ -17,13 +18,18 @@ import { ensureUser } from '../../../middleware/permissions.js'; import { validate } from '../../../middleware/validator.js'; import { asyncHandler } from '../../../utils/asyncHandler.js'; -import { getCommunityDetailsSchema, getCommunityFeedSchema, memberGuardSchema } from './schema.js'; +import { + getAllCommunitiesFeedSchema, + getCommunityDetailsSchema, + getCommunityFeedSchema, + memberGuardSchema, +} from './schema.js'; const router = Router(); // list all communities and curated nodes() router.get('/list', [], asyncHandler(listCommunities)); -router.get('/feeds', [], asyncHandler(getAllFeeds)); +router.get('/feeds', [validate(getAllCommunitiesFeedSchema)], asyncHandler(listAllCommunityCuratedFeeds)); router.get('/:communityName', [validate(getCommunityDetailsSchema)], asyncHandler(getCommunityDetails)); router.get( '/:communityName/attestations', diff --git a/desci-server/src/routes/v1/communities/schema.ts b/desci-server/src/routes/v1/communities/schema.ts index 00805de3a..ac12189de 100644 --- a/desci-server/src/routes/v1/communities/schema.ts +++ b/desci-server/src/routes/v1/communities/schema.ts @@ -15,6 +15,12 @@ export const getCommunityFeedSchema = z.object({ }), }); +export const getAllCommunitiesFeedSchema = z.object({ + query: z.object({ + page: z.coerce.number().optional().default(0), + }), +}); + export const memberGuardSchema = z.object({ params: z.object({ communityId: z.coerce.number(), diff --git a/desci-server/src/services/Communities.ts b/desci-server/src/services/Communities.ts index 0aff0f712..e860f47b7 100644 --- a/desci-server/src/services/Communities.ts +++ b/desci-server/src/services/Communities.ts @@ -397,6 +397,187 @@ export class CommunityService { return entries as RadarEntry[]; } + async listAllCommunityCuratedFeeds({ offset, limit }: { offset: number; limit: number }) { + 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, + COUNT(DISTINCT NaEntry."id") :: int AS entry_attestations + FROM + "CommunityRadarEntry" cre + LEFT JOIN ( + SELECT + Na."id", + Na."communityRadarEntryId", + Na."attestationId", + Na."attestationVersionId", + Na."desciCommunityId" + FROM + "NodeAttestation" Na + LEFT JOIN "NodeAttestationVerification" Nav ON Na."id" = Nav."nodeAttestationId" + WHERE + Na."revoked" = false + AND Na."nodeDpid10" IS NOT NULL + AND EXISTS ( + SELECT + 1 + FROM + "CommunityEntryAttestation" Cea + WHERE + Na."attestationId" = Cea."attestationId" + AND Na."attestationVersionId" = Cea."attestationVersionId" + AND Na."desciCommunityId" = Cea."desciCommunityId" + AND Cea."required" = TRUE + ) + 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" + LEFT JOIN ( + SELECT + Na."id", + Na."communityRadarEntryId", + Na."attestationId", + Na."attestationVersionId", + Na."desciCommunityId" + FROM + "NodeAttestation" Na + WHERE + Na."revoked" = false + AND Na."nodeDpid10" IS NOT NULL + AND EXISTS ( + SELECT + 1 + FROM + "CommunityEntryAttestation" Cea + WHERE + Na."attestationId" = Cea."attestationId" + AND Na."attestationVersionId" = Cea."attestationVersionId" + AND Na."desciCommunityId" = Cea."desciCommunityId" + AND Cea."required" = TRUE + ) + GROUP BY + Na."id", + Na."communityRadarEntryId" + ) NaEntry ON cre."id" = NaEntry."communityRadarEntryId" + WHERE + EXISTS ( + SELECT + 1 + FROM + "CommunityEntryAttestation" Cea + WHERE + NaFiltered."attestationId" = Cea."attestationId" + AND NaFiltered."attestationVersionId" = Cea."attestationVersionId" + AND Cea."required" = TRUE + ) + GROUP BY + cre.id, + cre."nodeUuid" + HAVING + COUNT(DISTINCT NaFiltered."id") = COUNT(DISTINCT NaEntry."id") + ORDER BY + verifications DESC, + cre."createdAt" DESC + LIMIT ${limit} + OFFSET ${offset} + `) as RadarEntry[]; + + return entries as RadarEntry[]; + } + + async countAllCommunityCuratedFeeds() { + const count = (await prisma.$queryRaw` + SELECT + count(*), + cre."nodeUuid", + COUNT(DISTINCT NaFiltered."id") :: int AS valid_attestations, + COUNT(DISTINCT NaEntry."id") :: int AS entry_attestations + FROM + "CommunityRadarEntry" cre + LEFT JOIN ( + SELECT + Na."id", + Na."communityRadarEntryId", + Na."attestationId", + Na."attestationVersionId", + Na."desciCommunityId" + FROM + "NodeAttestation" Na + LEFT JOIN "NodeAttestationVerification" Nav ON Na."id" = Nav."nodeAttestationId" + WHERE + Na."revoked" = false + AND Na."nodeDpid10" IS NOT NULL + AND EXISTS ( + SELECT + 1 + FROM + "CommunityEntryAttestation" Cea + WHERE + Na."attestationId" = Cea."attestationId" + AND Na."attestationVersionId" = Cea."attestationVersionId" + AND Na."desciCommunityId" = Cea."desciCommunityId" + AND Cea."required" = TRUE + ) + GROUP BY + Na."id", + Na."communityRadarEntryId" + HAVING + COUNT(Nav."id") > 0 + ) NaFiltered ON cre."id" = NaFiltered."communityRadarEntryId" + LEFT JOIN ( + SELECT + Na."id", + Na."communityRadarEntryId", + Na."attestationId", + Na."attestationVersionId" + FROM + "NodeAttestation" Na + WHERE + Na."revoked" = false + AND Na."nodeDpid10" IS NOT NULL + AND EXISTS ( + SELECT + 1 + FROM + "CommunityEntryAttestation" Cea + WHERE + Na."attestationId" = Cea."attestationId" + AND Na."attestationVersionId" = Cea."attestationVersionId" + AND Cea."required" = TRUE + ) + GROUP BY + Na."id", + Na."communityRadarEntryId" + ) NaEntry ON cre."id" = NaEntry."communityRadarEntryId" + WHERE + EXISTS ( + SELECT + 1 + FROM + "CommunityEntryAttestation" Cea + WHERE + NaFiltered."attestationId" = Cea."attestationId" + AND NaFiltered."attestationVersionId" = Cea."attestationVersionId" + AND Cea."required" = TRUE + ) + GROUP BY + cre.id, + cre."nodeUuid" + HAVING + COUNT(DISTINCT NaFiltered."id") = COUNT(DISTINCT NaEntry."id") +`) as any[]; + return count.length; + } + /** * This methods takes the result of getCommunityRadar and * filter out entries(nodes) whose NodeAttestations don't have atleast on verification From badf63d8dfc3b0c7891eb70879dffc6a11887a8b Mon Sep 17 00:00:00 2001 From: shadrach Date: Tue, 14 Jan 2025 13:16:17 +0100 Subject: [PATCH 3/3] enable all tests --- desci-server/test/integration/Attestation.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/desci-server/test/integration/Attestation.test.ts b/desci-server/test/integration/Attestation.test.ts index 85fa67aec..5111a009c 100644 --- a/desci-server/test/integration/Attestation.test.ts +++ b/desci-server/test/integration/Attestation.test.ts @@ -129,7 +129,7 @@ const clearDatabase = async () => { await prisma.$queryRaw`TRUNCATE TABLE "Node" CASCADE;`; }; -describe.only('Attestations Service', async () => { +describe('Attestations Service', async () => { let baseManifest: ResearchObjectV1; let baseManifestCid: string; let users: User[];