Skip to content

Commit

Permalink
Merge pull request #27 from alkem-io/notifications-18
Browse files Browse the repository at this point in the history
Notification recipients via YML
  • Loading branch information
valentinyanakiev authored Nov 4, 2021
2 parents e7e0dce + 2cd8492 commit 1dbf354
Show file tree
Hide file tree
Showing 49 changed files with 964 additions and 311 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ lerna-debug.log*

# jetbrains IDEs
.idea
# vscode
.vscode

# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
Expand Down Expand Up @@ -107,4 +109,4 @@ dist
.tern-port

# coverage
coverage-ci/
coverage-ci/
7 changes: 0 additions & 7 deletions .vscode/extensions.json

This file was deleted.

8 changes: 0 additions & 8 deletions .vscode/settings.json

This file was deleted.

39 changes: 0 additions & 39 deletions .vscode/tasks.json

This file was deleted.

42 changes: 21 additions & 21 deletions notifications.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ notification_providers:
# The hosting configuration for the Alkemio Server
hosting:
# The type of environment is used in multiple places to determine whether dev tooling is enabled.
# The tyoe of environment can also be used for monitoring / logging / analysis in an ELK cluster / ElasticCloud instance.
# The type of environment can also be used for monitoring / logging / analysis in an ELK cluster / ElasticCloud instance.
# For production deployments it should be set to Prod.
#
# Options: Dev, Prod
Expand All @@ -77,27 +77,27 @@ alkemio:
endpoint: ${ALKEMIO_SERVER_ENDPOINT}:http://localhost:3000/admin/graphql
service_account:
username: ${SERVICE_ACCOUNT_USERNAME}:[email protected]
password: ${SERVICE_ACCOUNT_PASSWORD}:@lk3m10!
password: ${SERVICE_ACCOUNT_PASSWORD}:obichamazis

kratos:
public_endpoint: ${KRATOS_API_PUBLIC_ENDPOINT}:http://localhost:3000/identity/ory/kratos/public

templates:
user_application_admin_template:
- rule:
type: ChallengeAdmin
resource_id: <challengeID>
- rule:
type: OpportunityAdmin
resource_id: <opportunityID>
- rule:
type: EcoverseAdmin
resource_id: <ecoverseID>
- rule:
type: GlobalAdmin
resource_id: <>

user_application_applicant_template:
- rule:
type: UserSelfManagement
resource_id: <applicantID>
recipients:
application_created:
admin:
- rule:
type: CHALLENGE_ADMIN
resource_id: <challengeID>
- rule:
type: OPPORTUNITY_ADMIN
resource_id: <opportunityID>
- rule:
type: ECOVERSE_ADMIN
resource_id: <ecoverseID>
- rule:
type: GLOBAL_ADMIN
resource_id: <>
applicant:
- rule:
type: USER_SELF_MANAGEMENT
resource_id: <applicantID>
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.2.0",
"version": "0.3.0",
"description": "Alkemio notifications service",
"author": "Cherrytwist Foundation",
"private": false,
Expand Down
44 changes: 27 additions & 17 deletions src/app.controller.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
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 } from './common';
import { NotificationService } from './services/notification/notification.service';
import { IFeatureFlagProvider } from './types';
import { NotificationService } from '@src/services';
import { IFeatureFlagProvider } from '@core/contracts';
import { ApplicationCreatedEventPayload } from '@src/types/application.created.event.payload';

@Controller()
export class AppController {
Expand All @@ -17,26 +19,34 @@ export class AppController {

@EventPattern('communityApplicationCreated')
async sendApplicationNotification(
@Payload() data: any,
// todo is auto validation possible
@Payload() payload: ApplicationCreatedEventPayload,
@Ctx() context: RmqContext
) {
const channel = context.getChannelRef();
const originalMsg = context.getMessage();
const channel: Channel = context.getChannelRef();
const originalMsg = context.getMessage() as Message;

if (await this.featureFlagProvider.areNotificationsEnabled()) {
try {
await this.notificationService.sendApplicationNotifications(data);
channel.ack(originalMsg);
} catch (error) {
this.logger.error(error);

//toDo check how to reject a message
// channel.reject(originalMsg);
return;
}
} else {
if (!(await this.featureFlagProvider.areNotificationsEnabled())) {
//toDo make this nack
channel.ack(originalMsg);
}

this.notificationService
.sendApplicationNotifications(payload)
.then(x => {
const shouldNack = x.some(y => y.status === 'rejected');

if (shouldNack) {
//toDo make this nack
channel.ack(originalMsg);
} else {
channel.ack(originalMsg);
}
})
.catch(err => {
this.logger.error(err);
// toDo check how to reject a message
// channel.reject(originalMsg);
});
}
}
15 changes: 10 additions & 5 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,14 @@ import { AppController } from './app.controller';
import { WinstonConfigService } from './config';
import configuration from './config/configuration';
import { HttpExceptionsFilter } from './core';
import { ApplicationNotificationBuilder } from './services/application-notification-builder/application.notification.builder';
import { AlkemioClientAdapterModule } from './services/alkemio-client-adapter/alkemio.client.adapter.module';
import { AlkemioClientModule } from './wrappers/alkemio-client/alkemio.client.module';
import { NotifmeModule } from './wrappers/notifme/notifme.module';
import { NotificationService } from './services/notification/notification.service';
import {
ApplicationNotificationBuilder,
NotificationService,
TemplateToCredentialMapper,
AlkemioClientAdapterModule,
} from '@src/services';
import { AlkemioClientModule, NotifmeModule } from '@src/wrappers';
import { NotificationRecipientsAdapterModule } from './services/notification-recipients-adapter/notification.recipients.adapter.module';

@Module({
imports: [
Expand All @@ -25,6 +28,7 @@ import { NotificationService } from './services/notification/notification.servic
NotifmeModule,
AlkemioClientModule,
AlkemioClientAdapterModule,
NotificationRecipientsAdapterModule,
],
providers: [
{
Expand All @@ -33,6 +37,7 @@ import { NotificationService } from './services/notification/notification.servic
},
NotificationService,
ApplicationNotificationBuilder,
TemplateToCredentialMapper,
],
controllers: [AppController],
})
Expand Down
1 change: 1 addition & 0 deletions src/common/enums/configuration.type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export enum ConfigurationTypes {
NOTIFICATION_PROVIDERS = 'notification_providers',
ALKEMIO = 'alkemio',
KRATOS = 'kratos',
NOTIFICATION_RECIPIENTS = 'recipients',
}
2 changes: 2 additions & 0 deletions src/common/enums/providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@ export const NOTIFICATIONS_PROVIDER = 'NOTIFICATIONS';
export const TEMPLATE_PROVIDER = 'TEMPLATES';
export const ALKEMIO_CLIENT_PROVIDER = 'ALKEMIO_CLIENT';
export const ALKEMIO_CLIENT_ADAPTER = 'ALKEMIO_CLIENT_ADAPTER';
export const NOTIFICATION_RECIPIENTS_YML_ADAPTER =
'NOTIFICATION_RECIPIENTS_YML_ADAPTER';
3 changes: 3 additions & 0 deletions src/core/contracts/feature.flag.provider.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface IFeatureFlagProvider {
areNotificationsEnabled(): Promise<boolean>;
}
4 changes: 4 additions & 0 deletions src/core/contracts/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export * from './notified.users.provider.interface';
export * from './feature.flag.provider.interface';
export * from './notification.recipient.provider.interface';
export * from './notification.recipient.template.provider.interface';
14 changes: 14 additions & 0 deletions src/core/contracts/notification.recipient.provider.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { AuthorizationCredential } from '@alkemio/client-lib';
import { ApplicationCreatedEventPayload } from '@src/types/application.created.event.payload';

export type RecipientCredential = {
role: AuthorizationCredential;
resourceID?: string;
isAdmin: boolean;
};

export interface INotificationRecipientProvider {
getApplicationCreatedRecipients(
payload: ApplicationCreatedEventPayload
): RecipientCredential[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AuthorizationCredential } from '@alkemio/client-lib';

export type TemplateRule = {
rule: {
type: AuthorizationCredential;
resource_id: string;
};
};

export type TemplateRow = {
admin: TemplateRule[];
applicant: TemplateRule[];
};

export type TemplateConfig = {
application_created?: TemplateRow;
};

export interface INotificationRecipientTemplateProvider {
getTemplate(): TemplateConfig;
}
13 changes: 13 additions & 0 deletions src/core/contracts/notified.users.provider.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { User } from '../models';
import { AuthorizationCredential } from '@alkemio/client-lib';

export interface INotifiedUsersProvider {
getHubAdmins(hubID: string): Promise<User[]> | User[];
getChallengeAdmins(challengeID: string): Promise<User[]> | User[];
getOpportunityAdmins(opportunityID: string): Promise<User[]> | User[];
getApplicant(payload: any): Promise<User> | User;
getUsersWithCredentials(
credential: AuthorizationCredential,
resourceID?: string
): Promise<User[]>;
}
1 change: 1 addition & 0 deletions src/core/models/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './user';
8 changes: 8 additions & 0 deletions src/core/models/user.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export type User = {
id: string;
nameID: string;
firstName: string;
lastName: string;
displayName: string;
email: string;
};
Loading

0 comments on commit 1dbf354

Please sign in to comment.