Skip to content

Commit

Permalink
Merge pull request #55 from alkem-io/develop
Browse files Browse the repository at this point in the history
Release - multiple notification handlers
  • Loading branch information
techsmyth authored Dec 2, 2021
2 parents 82cdd49 + 9cb7f9f commit d9cc9ef
Show file tree
Hide file tree
Showing 55 changed files with 1,665 additions and 775 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ lerna-debug.log*
# jetbrains IDEs
.idea
# vscode
.vscode
#.vscode

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
Expand Down
7 changes: 7 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"recommendations": [
"EditorConfig.EditorConfig",
"dbaeumer.vscode-eslint",
"esbenp.prettier-vscode"
]
}
40 changes: 40 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Debug Alkemio Notifications",
"args": ["${workspaceFolder}/src/main.ts"],
"runtimeArgs": [
"--nolazy",
"-r",
"ts-node/register",
"-r",
"tsconfig-paths/register"
],
"sourceMaps": true,
"envFile": "${workspaceFolder}/.env",
"cwd": "${workspaceRoot}",
"console": "integratedTerminal",
"protocol": "inspector"
},
{
"name": "Debug Jest CI Tests",
"type": "node",
"request": "launch",
"runtimeArgs": [
"--inspect-brk",
"${workspaceRoot}/node_modules/.bin/jest",
"--runInBand",
"--config",
"${workspaceRoot}/test/config/jest.config.ci.js"
],
"console": "integratedTerminal",
"internalConsoleOptions": "neverOpen"
}
]
}
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"files.eol": "\n"
}
35 changes: 35 additions & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"version": "2.0.0",
"tasks": [
{
"type": "docker-build",
"label": "docker-build",
"platform": "node",
"dockerBuild": {
"dockerfile": "${workspaceFolder}/Dockerfile",
"context": "${workspaceFolder}",
"pull": true
}
},
{
"type": "docker-run",
"label": "docker-run: release",
"dependsOn": ["docker-build"],
"platform": "node"
},
{
"type": "docker-run",
"label": "docker-run: debug",
"dependsOn": ["docker-build"],
"dockerRun": {
"env": {
"DEBUG": "*",
"NODE_ENV": "development"
}
},
"node": {
"enableDebugging": true
}
}
]
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ npm run start:services
```

3. Go to http://localhost:15672/#/queues/%2F/alkemio-notifications.
4. Under publish message, select content_type under properties with value application/json.
4. Under publish message, go to `properties` and add a new property with name `content_type` and value `application/json`.
5. Select payload:

```json
Expand Down
49 changes: 35 additions & 14 deletions notifications.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ hosting:

alkemio:
endpoint: ${ALKEMIO_SERVER_ENDPOINT}:http://localhost:3000/admin/graphql
webclient_endpoint: ${ALKEMIO_WEBCLIENT_ENDPOINT}:http://localhost:3000
service_account:
username: ${SERVICE_ACCOUNT_USERNAME}:[email protected]
password: ${SERVICE_ACCOUNT_PASSWORD}:change-me-now
Expand All @@ -94,42 +95,62 @@ recipients:
resource_id: <opportunityID>
- rule:
type: ECOVERSE_ADMIN
resource_id: <ecoverseID>
resource_id: <hubID>
- rule:
type: GLOBAL_COMMUNITY_ADMIN
resource_id: <>
type: GLOBAL_ADMIN_COMMUNITY
resource_id:
- rule:
type: GLOBAL_ADMIN
resource_id: <>
resource_id:
- name: applicant
rules:
- rule:
type: USER_SELF_MANAGEMENT
resource_id: <applicantID>
user_registration:
user_registered:
- name: admin
rules:
- rule:
type: GLOBAL_COMMUNITY_ADMIN
resource_id: <>
type: GLOBAL_ADMIN_COMMUNITY
resource_id:
- rule:
type: GLOBAL_ADMIN
resource_id: <>
resource_id:
- name: registrant
rules:
- rule:
type: USER_SELF_MANAGEMENT
resource_id: <applicantID>
communication_update:
resource_id: <registrantID>
communication_update_sent:
- name: admin
rules:
- rule:
type: GLOBAL_ADMIN_COMMUNITY
resource_id:
- rule:
type: GLOBAL_ADMIN
resource_id:
- name: member
rules:
- rule:
type: CHALLENGE_MEMBER
resource_id: <challengeID>
- rule:
type: OPPORTUNITY_MEMBER
resource_id: <opportunityID>
- rule:
type: ECOVERSE_MEMBER
resource_id: <hubID>
communication_discussion_created:
- name: admin
rules:
- rule:
type: GLOBAL_COMMUNITY_ADMIN
resource_id: <>
resource_id:
- rule:
type: GLOBAL_ADMIN
resource_id: <>
- name: community_member
resource_id:
- name: member
rules:
- rule:
type: CHALLENGE_MEMBER
Expand All @@ -139,4 +160,4 @@ recipients:
resource_id: <opportunityID>
- rule:
type: ECOVERSE_MEMBER
resource_id: <ecoverseID>
resource_id: <hubID>
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "alkemio-notifications",
"version": "0.3.3",
"version": "0.4.0",
"description": "Alkemio notifications service",
"author": "Cherrytwist Foundation",
"private": false,
Expand Down
121 changes: 106 additions & 15 deletions src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,20 @@ import { Controller, Inject, LoggerService } from '@nestjs/common';
import { Ctx, EventPattern, Payload, RmqContext } from '@nestjs/microservices';
import { WINSTON_MODULE_NEST_PROVIDER } from 'nest-winston';
import { Channel, Message } from 'amqplib';
import { ALKEMIO_CLIENT_ADAPTER, LogContext } from './common';
import { NotificationService } from '@src/services';
import {
ALKEMIO_CLIENT_ADAPTER,
COMMUNICATION_DISCUSSION_CREATED,
COMMUNICATION_UPDATE_SENT,
COMMUNITY_APPLICATION_CREATED,
LogContext,
USER_REGISTERED,
} from './common';
import { IFeatureFlagProvider } from '@core/contracts';
import { ApplicationCreatedEventPayload } from '@src/types/application.created.event.payload';
import { UserRegistrationEventPayload } from './types';
import { CommunicationUpdateEventPayload } from './types/communication.update.event.payload';
import { CommunicationDiscussionCreatedEventPayload } from './types/communication.discussion.created.event.payload';
import { NotificationService } from './services/domain/notification/notification.service';

@Controller()
export class AppController {
Expand All @@ -17,13 +27,78 @@ export class AppController {
private readonly featureFlagProvider: IFeatureFlagProvider
) {}

@EventPattern('communityApplicationCreated')
@EventPattern(COMMUNITY_APPLICATION_CREATED)
async sendApplicationNotification(
// todo is auto validation possible
@Payload() payload: ApplicationCreatedEventPayload,
@Payload() eventPayload: ApplicationCreatedEventPayload,
@Ctx() context: RmqContext
) {
this.logger.verbose?.(JSON.stringify(payload), LogContext.NOTIFICATIONS);
this.sendNotifications(
eventPayload,
context,
this.notificationService.sendApplicationCreatedNotifications(
eventPayload
),
COMMUNITY_APPLICATION_CREATED
);
}
@EventPattern(USER_REGISTERED)
async sendUserRegisteredNotification(
// todo is auto validation possible
@Payload() eventPayload: UserRegistrationEventPayload,
@Ctx() context: RmqContext
) {
this.sendNotifications(
eventPayload,
context,
this.notificationService.sendUserRegisteredNotification(eventPayload),
USER_REGISTERED
);
}

@EventPattern(COMMUNICATION_UPDATE_SENT)
async sendCommunicationUpdatedNotifications(
// todo is auto validation possible
@Payload() eventPayload: CommunicationUpdateEventPayload,
@Ctx() context: RmqContext
) {
this.sendNotifications(
eventPayload,
context,
this.notificationService.sendCommunicationUpdateddNotification(
eventPayload
),
COMMUNICATION_UPDATE_SENT
);
}

@EventPattern(COMMUNICATION_DISCUSSION_CREATED)
async sendCommunicationDiscussionCreatedNotifications(
// todo is auto validation possible
@Payload() eventPayload: CommunicationDiscussionCreatedEventPayload,
@Ctx() context: RmqContext
) {
this.sendNotifications(
eventPayload,
context,
this.notificationService.sendCommunicationDiscussionCreatedNotification(
eventPayload
),
COMMUNICATION_DISCUSSION_CREATED
);
}

private async sendNotifications(
@Payload() eventPayload: any,
@Ctx() context: RmqContext,
// notificationBuilder: any,
sendNotifications: any,
eventName: string
) {
this.logger.verbose?.(
`[Event received: ${eventName}]: ${JSON.stringify(eventPayload)}`,
LogContext.NOTIFICATIONS
);

const channel: Channel = context.getChannelRef();
const originalMsg = context.getMessage() as Message;
Expand All @@ -33,23 +108,39 @@ export class AppController {
return;
}

this.notificationService
.sendApplicationNotifications(payload)
.then(x => {
const nacked = x.filter(y => y.status === 'rejected');
// https://www.squaremobius.net/amqp.node/channel_api.html#channel_nack
sendNotifications
.then((x: any[]) => {
const nacked = x.filter(
(y: { status: string }) => y.status === 'rejected'
);

if (nacked.length === 0) {
this.logger.verbose?.(`All ${x.length} messages successfully sent!`);
// if all is fine, acknowledge the given message. allUpTo (second, optional parameter) defaults to false,
// so only the message supplied is acknowledged.
channel.ack(originalMsg);
} else {
this.logger.verbose?.(`${nacked.length} messages failed to be sent!`);
//channel.nack(originalMsg);
channel.ack(originalMsg);
if (nacked.length === x.length) {
this.logger.verbose?.('All messages failed to be sent!');
// if all messages failed to be sent, we reject the message but we make sure the message is
// not discarded so we provide 'true' to requeue parameter
channel.reject(originalMsg, true);
} else {
this.logger.verbose?.(
`${nacked.length} messages out of total ${x.length} messages failed to be sent!`
);
// if at least one message is sent successfully, we acknowledge just this message but we make sure the message is
// dead-lettered / discarded, providing 'false' to the 3rd parameter, requeue
channel.nack(originalMsg, false, false);
}
}
})
.catch(err => {
//channel.reject(originalMsg);
channel.ack(originalMsg);
.catch((err: any) => {
// if there is an unhandled bug in the flow, we reject the message but we make sure the message is
// not discarded so we provide 'true' to requeue parameter
// channel.reject(originalMsg, true);
channel.nack(originalMsg, false, false);
this.logger.error(err);
});
}
Expand Down
Loading

0 comments on commit d9cc9ef

Please sign in to comment.