Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Dated comments #753

Merged
merged 3 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions desci-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,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/",
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
-- AlterTable
ALTER TABLE "Annotation" ADD COLUMN "createdAt" TIMESTAMP(3),
ADD COLUMN "updatedAt" TIMESTAMP(3);
19 changes: 19 additions & 0 deletions desci-server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ model Node {
Annotation Annotation[]
PublishStatus PublishStatus[]
ExternalPublications ExternalPublications[]
CommunityRadarEntry CommunityRadarEntry[]

@@index([ownerId])
@@index([uuid])
Expand Down Expand Up @@ -699,6 +700,7 @@ model DesciCommunity {
NodeAttestation NodeAttestation[]
AttestationTemplate AttestationTemplate[]
CommunityEntryAttestation CommunityEntryAttestation[]
CommunityRadarEntry CommunityRadarEntry[]
}

model CommunityMember {
Expand Down Expand Up @@ -801,10 +803,25 @@ model NodeAttestation {
NodeAttestationVerification NodeAttestationVerification[]
NodeAttestationReaction NodeAttestationReaction[]
OrcidPutCodes OrcidPutCodes[]
CommunityRadarEntry CommunityRadarEntry? @relation(fields: [communityRadarEntryId], references: [id])
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())
Expand Down Expand Up @@ -838,6 +855,8 @@ model Annotation {
uuid String?
node Node? @relation(fields: [uuid], references: [uuid])
visible Boolean @default(true)
createdAt DateTime?
updatedAt DateTime? @updatedAt
}

//An emoji reaction to a node
Expand Down
14 changes: 13 additions & 1 deletion desci-server/src/controllers/attestations/claims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -21,9 +22,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);

Expand All @@ -36,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;
}
Expand All @@ -46,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 });

Expand Down Expand Up @@ -126,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.removeFromRadar(claim.desciCommunityId, claim.nodeUuid);

await saveInteraction(req, ActionType.REVOKE_CLAIM, body);

logger.info({ removeOrRevoke, totalSignal, claimSignal }, 'Claim Removed|Revoked');
Expand Down Expand Up @@ -181,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,
Expand Down
36 changes: 35 additions & 1 deletion desci-server/src/controllers/communities/feed.ts
Original file line number Diff line number Diff line change
@@ -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' });

Expand Down Expand Up @@ -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);

Expand Down
40 changes: 35 additions & 5 deletions desci-server/src/controllers/communities/radar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand All @@ -17,10 +18,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(
Expand Down Expand Up @@ -64,3 +61,36 @@ 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 { 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(params.communityId.toString()),
offset,
limit,
});
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 entries = 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
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);
};
4 changes: 3 additions & 1 deletion desci-server/src/controllers/communities/types.ts
Original file line number Diff line number Diff line change
@@ -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[];
Expand Down Expand Up @@ -46,3 +46,5 @@ export type NodeRadar = NodeRadarItem & {
verifications: number;
};
};

export type NodeRadarEntry = RadarEntry & { node?: Partial<Node & { versions: number }>; manifest?: ResearchObjectV1 };
61 changes: 61 additions & 0 deletions desci-server/src/controllers/communities/util.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -68,6 +70,65 @@ export const resolveLatestNode = async (radar: Partial<NodeRadar>) => {
return radar;
};

export const getCommunityNodeDetails = async (
radar: RadarEntry & { node?: Partial<Node & { versions: number }>; 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,
dpidAlias: true,
},
});

if (!discovery) {
logger.warn({ uuid }, 'uuid not found');
}

const selectAttributes: (keyof typeof discovery)[] = ['ownerId', 'NodeCover'];
const node: Partial<Node & { versions: number }> = _.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 {
Expand Down
16 changes: 12 additions & 4 deletions desci-server/src/routes/v1/communities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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));

Expand Down
Loading