Skip to content

Commit

Permalink
Merge pull request #561 from desci-labs/notifications-system
Browse files Browse the repository at this point in the history
Notifications System
  • Loading branch information
hubsmoke authored Oct 21, 2024
2 parents faa671b + 99c6e31 commit 69eb1dc
Show file tree
Hide file tree
Showing 77 changed files with 1,369 additions and 362 deletions.
2 changes: 1 addition & 1 deletion desci-server/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ module.exports = {
'import/resolver': {
node: {
extensions: ['.js', '.jsx', '.ts', '.tsx'],
moduleDirectory: ['node_modules', 'src/'],
moduleDirectory: ['node_modules', 'src/', '/test'],
},
},
},
Expand Down
9 changes: 6 additions & 3 deletions desci-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@
"podman:dev": "podman-compose --file docker-compose.yml --file docker-compose.dev.yml up --build",
"email-dev": "email dev --dir ./src/templates/emails --port 3777",
"check-deps": "npx npm-check",
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org desci-labs --project nodes-backend ./dist && sentry-cli sourcemaps upload --org desci-labs --project nodes-backend ./dist && echo 'Sentry sourcemaps uploaded'"
"sentry:sourcemaps": "sentry-cli sourcemaps inject --org desci-labs --project nodes-backend ./dist && sentry-cli sourcemaps upload --org desci-labs --project nodes-backend ./dist && echo 'Sentry sourcemaps uploaded'",
"madge": "tsc && npx --yes madge --circular --image circularDepGraph.svg dist/index.js && open circularDepGraph.svg 2>/dev/null || xdg-open circularDepGraph.svg"
},
"dependencies": {
"@automerge/automerge-repo": "^1.0.19",
Expand All @@ -73,6 +74,7 @@
"@sentry/node": "8.29.0",
"@sentry/profiling-node": "8.32.0",
"@sentry/tracing": "^7.12.0",
"@socket.io/redis-adapter": "^8.3.0",
"@types/lodash-es": "^4.17.12",
"@types/mkdirp": "^1.0.2",
"@types/multer": "^1.4.7",
Expand Down Expand Up @@ -120,10 +122,11 @@
"rimraf": "^5.0.1",
"short-unique-id": "^4.4.4",
"siwe": "^1.1.6",
"socket.io": "^4.8.0",
"tar": "^6.2.0",
"url-safe-base64": "1.2.0",
"uuid": "^8.3.2",
"ws": "^8.15.0",
"ws": "^8.18.0",
"xml-js": "^1.6.11",
"yauzl": "^2.10.0",
"zod": "^3.22.4"
Expand All @@ -142,7 +145,7 @@
"@types/multer-s3": "^3.0.1",
"@types/node": "^20.10.4",
"@types/supertest": "^2.0.12",
"@types/ws": "^8.5.10",
"@types/ws": "^8.5.12",
"@types/xmldom": "^0.1.34",
"@typescript-eslint/eslint-plugin": "^6.14.0",
"@typescript-eslint/parser": "^6.14.0",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
-- CreateEnum
CREATE TYPE "NotificationType" AS ENUM ('PUBLISH');

-- AlterTable
ALTER TABLE "User" ADD COLUMN "notificationSettings" JSONB;

-- CreateTable
CREATE TABLE "UserNotifications" (
"id" SERIAL NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"userId" INTEGER NOT NULL,
"nodeUuid" TEXT,
"type" "NotificationType" NOT NULL,
"title" TEXT NOT NULL,
"message" TEXT NOT NULL,
"payload" JSONB,
"dismissed" BOOLEAN NOT NULL DEFAULT false,

CONSTRAINT "UserNotifications_pkey" PRIMARY KEY ("id")
);

-- AddForeignKey
ALTER TABLE "UserNotifications" ADD CONSTRAINT "UserNotifications_nodeUuid_fkey" FOREIGN KEY ("nodeUuid") REFERENCES "Node"("uuid") ON DELETE SET NULL ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "UserNotifications" ADD CONSTRAINT "UserNotifications_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterEnum
ALTER TYPE "NotificationType" ADD VALUE 'COMMENTS';
23 changes: 23 additions & 0 deletions desci-server/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ model Node {
DoiSubmissionQueue DoiSubmissionQueue[]
BookmarkedNode BookmarkedNode[]
DeferredEmails DeferredEmails[]
UserNotifications UserNotifications[]
Annotation Annotation[]
@@index([ownerId])
Expand Down Expand Up @@ -154,6 +155,7 @@ model User {
isKeeper Boolean @default(false)
pseudonym String? @unique
orcid String? @unique
notificationSettings Json?
// rorPid String[] @default([])
// organization String[] @default([])
isAdmin Boolean @default(false)
Expand Down Expand Up @@ -199,6 +201,7 @@ model User {
OrcidPutCodes OrcidPutCodes[]
BookmarkedNode BookmarkedNode[]
DeferredEmails DeferredEmails[]
UserNotifications UserNotifications[]
@@index([orcid])
@@index([walletAddress])
Expand Down Expand Up @@ -911,6 +914,21 @@ model DoiSubmissionQueue {
updatedAt DateTime @updatedAt
}

model UserNotifications {
id Int @id @default(autoincrement())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
userId Int
nodeUuid String?
type NotificationType
title String
message String
payload Json? // E.g. hyperlinks, DPID, DOI, frontend handles.
dismissed Boolean @default(false)
node Node? @relation(fields: [nodeUuid], references: [uuid])
user User @relation(fields: [userId], references: [id])
}

enum ORCIDRecord {
WORK
QUALIFICATION
Expand Down Expand Up @@ -1035,3 +1053,8 @@ enum DoiStatus {
FAILED
SUCCESS
}

enum NotificationType {
PUBLISH
COMMENTS
}
20 changes: 8 additions & 12 deletions desci-server/src/controllers/admin/communities/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,11 @@ import { NextFunction, Request, Response } from 'express';
import _ from 'lodash';
import { z } from 'zod';

import {
asyncMap,
attestationService,
BadRequestError,
communityService,
DuplicateDataError,
NotFoundError,
logger as parentLogger,
prisma,
SuccessMessageResponse,
SuccessResponse,
} from '../../../internal.js';
import { prisma } from '../../../client.js';
import { BadRequestError, NotFoundError } from '../../../core/ApiError.js';
import { SuccessMessageResponse, SuccessResponse } from '../../../core/ApiResponse.js';
import { DuplicateDataError } from '../../../core/communities/error.js';
import { logger as parentLogger } from '../../../logger.js';
import {
addAttestationSchema,
addCommunitySchema,
Expand All @@ -24,7 +17,10 @@ import {
updateAttestationSchema,
updateCommunitySchema,
} from '../../../routes/v1/admin/communities/schema.js';
import { attestationService } from '../../../services/Attestation.js';
import { communityService } from '../../../services/Communities.js';
import { processUploadToIpfs } from '../../../services/data/processing.js';
import { asyncMap } from '../../../utils.js';

const logger = parentLogger.child({ module: 'Admin/Communities/controller' });

Expand Down
17 changes: 6 additions & 11 deletions desci-server/src/controllers/attestations/claims.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,16 @@ import { ActionType, CommunityEntryAttestation, EmailType } from '@prisma/client
import { NextFunction, Request, Response } from 'express';
import _ from 'lodash';

import {
AuthFailureError,
NotFoundError,
SuccessMessageResponse,
SuccessResponse,
asyncMap,
attestationService,
ensureUuidEndsWithDot,
logger,
prisma,
} from '../../internal.js';
import { prisma } from '../../client.js';
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 { removeClaimSchema } from '../../routes/v1/attestations/schema.js';
import { attestationService } from '../../services/Attestation.js';
import { saveInteraction } from '../../services/interactionLog.js';
import { getIndexedResearchObjects } from '../../theGraph.js';
import { asyncMap, ensureUuidEndsWithDot } from '../../utils.js';

export const claimAttestation = async (req: RequestWithUser, res: Response, _next: NextFunction) => {
const body = req.body as {
Expand Down
21 changes: 9 additions & 12 deletions desci-server/src/controllers/attestations/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,18 @@ import { NextFunction, Request, Response } from 'express';
import _ from 'lodash';
import zod from 'zod';

import { prisma } from '../../client.js';
import { PUBLIC_IPFS_PATH } from '../../config/index.js';
import {
ForbiddenError,
NotFoundError,
SuccessMessageResponse,
SuccessResponse,
asyncMap,
attestationService,
createCommentSchema,
ensureUuidEndsWithDot,
logger as parentLogger,
prisma,
} from '../../internal.js';
import { ForbiddenError, NotFoundError } from '../../core/ApiError.js';
import { SuccessMessageResponse, SuccessResponse } from '../../core/ApiResponse.js';
import { logger as parentLogger } from '../../logger.js';
import { createCommentSchema } from '../../routes/v1/attestations/schema.js';
import { attestationService } from '../../services/Attestation.js';
import { saveInteraction } from '../../services/interactionLog.js';
import { client } from '../../services/ipfs.js';
import { emitNotificationForAnnotation } from '../../services/NotificationService.js';
import { base64ToBlob } from '../../utils/upload.js';
import { asyncMap, ensureUuidEndsWithDot } from '../../utils.js';

export const getAttestationComments = async (req: Request, res: Response, next: NextFunction) => {
const { claimId } = req.params;
Expand Down Expand Up @@ -129,6 +125,7 @@ export const addComment = async (req: Request<any, any, AddCommentBody['body']>,
});
}
await saveInteraction(req, ActionType.ADD_COMMENT, { annotationId: annotation.id, claimId, authorId });
await emitNotificationForAnnotation(annotation.id);
new SuccessResponse({
...annotation,
highlights: annotation.highlights.map((h) => JSON.parse(h as string)),
Expand Down
14 changes: 4 additions & 10 deletions desci-server/src/controllers/attestations/reactions.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import { NextFunction, Request, Response } from 'express';
import _ from 'lodash';

import {
BadRequestError,
EMOJI_OPTIONS,
ForbiddenError,
NotFoundError,
SuccessMessageResponse,
SuccessResponse,
attestationService,
logger as parentLogger,
} from '../../internal.js';
import { ForbiddenError } from '../../core/ApiError.js';
import { SuccessMessageResponse, SuccessResponse } from '../../core/ApiResponse.js';
import { logger as parentLogger } from '../../logger.js';
import { attestationService } from '../../services/Attestation.js';

export const getAttestationReactions = async (req: Request, res: Response, next: NextFunction) => {
const logger = parentLogger.child({
Expand Down
12 changes: 5 additions & 7 deletions desci-server/src/controllers/attestations/recommendations.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
import { NextFunction, Request, Response } from 'express';
import _ from 'lodash';

import {
NotFoundError,
SuccessResponse,
attestationService,
communityService,
logger as parentLogger,
} from '../../internal.js';
import { NotFoundError } from '../../core/ApiError.js';
import { SuccessResponse } from '../../core/ApiResponse.js';
import { logger as parentLogger } from '../../logger.js';
import { attestationService } from '../../services/Attestation.js';
import { communityService } from '../../services/Communities.js';

const logger = parentLogger.child({ module: 'Recommendations' });

Expand Down
4 changes: 3 additions & 1 deletion desci-server/src/controllers/attestations/show.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { AttestationVersion, DesciCommunity, NodeAttestation } from '@prisma/client';
import { Request, Response } from 'express';

import { BadRequestError, SuccessResponse, attestationService } from '../../internal.js';
import { BadRequestError } from '../../core/ApiError.js';
import { SuccessResponse } from '../../core/ApiResponse.js';
import { logger as parentLogger } from '../../logger.js';
import { attestationService } from '../../services/Attestation.js';

export type NodeAttestationFragment = NodeAttestation & {
community: Pick<DesciCommunity, 'name' | 'description' | 'keywords'>;
Expand Down
13 changes: 5 additions & 8 deletions desci-server/src/controllers/attestations/verification.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,14 @@ import { NextFunction, Request, Response } from 'express';
// import { Attestation, NodeAttestation } from '@prisma/client';
import _ from 'lodash';

import {
ForbiddenError,
SuccessMessageResponse,
SuccessResponse,
attestationService,
ensureUuidEndsWithDot,
prisma,
} from '../../internal.js';
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 { attestationService } from '../../services/Attestation.js';
import { saveInteraction, saveInteractionWithoutReq } from '../../services/interactionLog.js';
import orcidApiService from '../../services/orcid.js';
import { ensureUuidEndsWithDot } from '../../utils.js';

type RemoveVerificationBody = {
verificationId: string;
Expand Down
1 change: 1 addition & 0 deletions desci-server/src/controllers/auth/profile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const profile = async (req: Request, res: Response, next: NextFunction) =
orcid: user.orcid,
userOrganization: organization.map((org) => org.organization),
consent: !!(await getUserConsent(user.id)),
notificationSettings: user.notificationSettings || {},
},
};
try {
Expand Down
18 changes: 9 additions & 9 deletions desci-server/src/controllers/communities/feed.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { NextFunction, Request, Response } from 'express';
import _ from 'lodash';

import {
NotFoundError,
SuccessResponse,
asyncMap,
attestationService,
communityService,
resolveLatestNode,
} from '../../internal.js';
import { logger as parentLogger } from '../../internal.js';
import { NotFoundError } from '../../core/ApiError.js';
import { SuccessResponse } from '../../core/ApiResponse.js';
import { logger as parentLogger } from '../../logger.js';
import { attestationService } from '../../services/Attestation.js';
import { communityService } from '../../services/Communities.js';
import { asyncMap } from '../../utils.js';

import { resolveLatestNode } from './util.js';

const logger = parentLogger.child({ module: 'communities/feed.ts' });

export const getCommunityFeed = async (req: Request, res: Response, next: NextFunction) => {
Expand Down
4 changes: 3 additions & 1 deletion desci-server/src/controllers/communities/guard.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { Response } from 'express';

import { SuccessResponse, communityService, logger } from '../../internal.js';
import { SuccessResponse } from '../../core/ApiResponse.js';
import { logger } from '../../logger.js';
import { RequestWithUser } from '../../middleware/authorisation.js';
import { communityService } from '../../services/Communities.js';

export const checkMemberGuard = async (req: RequestWithUser, res: Response) => {
const log = logger.child({
Expand Down
6 changes: 4 additions & 2 deletions desci-server/src/controllers/communities/list.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { NextFunction, Request, Response } from 'express';
import _ from 'lodash';

import { SuccessResponse, communityService } from '../../internal.js';
import { logger as parentLogger, asyncMap } from '../../internal.js';
import { SuccessResponse } from '../../core/ApiResponse.js';
import { logger as parentLogger } from '../../logger.js';
import { communityService } from '../../services/Communities.js';
import { asyncMap } from '../../utils.js';

const logger = parentLogger.child({ module: 'LIST COMMUNITIES' });

Expand Down
15 changes: 7 additions & 8 deletions desci-server/src/controllers/communities/radar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,13 @@
import { NextFunction, Request, Response } from 'express';
import _ from 'lodash';

import {
SuccessResponse,
asyncMap,
attestationService,
communityService,
logger as parentLogger,
resolveLatestNode,
} from '../../internal.js';
import { SuccessResponse } from '../../core/ApiResponse.js';
import { logger as parentLogger } from '../../logger.js';
import { attestationService } from '../../services/Attestation.js';
import { communityService } from '../../services/Communities.js';
import { asyncMap } from '../../utils.js';

import { resolveLatestNode } from './util.js';

const logger = parentLogger.child({ module: 'GET COMMUNITY RADAR' });
export const getCommunityRadar = async (req: Request, res: Response, next: NextFunction) => {
Expand Down
2 changes: 1 addition & 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 '../../internal.js';
import { CommunityRadarNode } from '../../services/Communities.js';

export type NodeRadarItem = {
NodeAttestation: CommunityRadarNode[];
Expand Down
Loading

0 comments on commit 69eb1dc

Please sign in to comment.