Skip to content
This repository has been archived by the owner on Mar 14, 2024. It is now read-only.

Notification endpoints #19

Merged
merged 20 commits into from
Feb 19, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
20 changes: 8 additions & 12 deletions .env
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
SENTRY_DSN=
PORT=
DATABASE_URL=http://127.0.0.1:5984
DATABASE_USER=admin
DATABASE_PASSWORD=docker
QUERY_URL=http://127.0.0.1:4984
SCHEMA_CONFIG_ID=_design/sqlite:config
REPORT_COUCH_SQS_CLIENT_CONFIG_BASIC_AUTH_USER=admin
REPORT_COUCH_SQS_CLIENT_CONFIG_BASIC_AUTH_PASSWORD=docker

CHANGES_POLL_INTERVAL=60000
REPORT_COUCH_DB_CLIENT_CONFIG_REPORT_BASIC_AUTH_USER=admin
REPORT_COUCH_DB_CLIENT_CONFIG_REPORT_BASIC_AUTH_PASSWORD=docker

tomwwinter marked this conversation as resolved.
Show resolved Hide resolved
REPORT_DATABASE_URL=http://127.0.0.1:5984
REPORT_DATABASE_NAME=report-calculation
REPORT_COUCH_DB_CLIENT_CONFIG_REPORT_CALCULATION_BASIC_AUTH_USER=admin
REPORT_COUCH_DB_CLIENT_CONFIG_REPORT_CALCULATION_BASIC_AUTH_PASSWORD=docker

COUCH_SQS_CLIENT_CONFIG_BASIC_AUTH_USER=admin
COUCH_SQS_CLIENT_CONFIG_BASIC_AUTH_PASSWORD=docker
REPORT_CHANGES_COUCH_DB_CLIENT_CONFIG_BASIC_AUTH_USER=admin
REPORT_CHANGES_COUCH_DB_CLIENT_CONFIG_BASIC_AUTH_PASSWORD=docker

NOTIFICATION_COUCH_DB_CLIENT_CONFIG_BASIC_AUTH_USER=admin
NOTIFICATION_COUCH_DB_CLIENT_CONFIG_BASIC_AUTH_PASSWORD=docker
Expand Down
8 changes: 0 additions & 8 deletions build/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,13 +32,5 @@ RUN npm ci --no-progress --only=production
COPY --from=builder /app/dist ./dist
COPY --from=builder /app/.env ./

# (optional) The sentry DSN in order to send the error messages to sentry
ENV SENTRY_DSN=""
ENV PORT=""
ENV DATABASE_URL=""
ENV DATABASE_ADMIN="admin"
ENV DATABASE_PASSWORD=""
ENV QUERY_URL=""

CMD ["node", "dist/main"]

21 changes: 18 additions & 3 deletions src/config/app.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,22 @@
COUCH_SQS_CLIENT_CONFIG:
BASE_URL: http://localhost:4984

NOTIFICATION:
COUCH_DB_CLIENT_CONFIG:
BASE_URL: http://localhost:5984
TARGET_DATABASE: notification-webhook

REPORT:
COUCH_DB_CLIENT_CONFIG:
REPORT:
BASE_URL: http://localhost:5984
TARGET_DATABASE: app
REPORT_CALCULATION:
BASE_URL: http://localhost:5984
TARGET_DATABASE: report-calculation
COUCH_SQS_CLIENT_CONFIG:
BASE_URL: http://localhost:4984
SCHEMA_DESIGN_CONFIG: /app/_design/sqlite:config

REPORT_CHANGES:
COUCH_DB_CLIENT_CONFIG:
BASE_URL: http://localhost:5984
TARGET_DATABASE: app
POLL_INTERVAL: 10000
tomwwinter marked this conversation as resolved.
Show resolved Hide resolved
23 changes: 5 additions & 18 deletions src/couchdb/couch-db-client.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable, Logger } from '@nestjs/common';
import { Logger } from '@nestjs/common';
import { catchError, map, Observable, of, switchMap } from 'rxjs';
import { HttpService } from '@nestjs/axios';
import { AxiosHeaders } from 'axios';
Expand All @@ -11,22 +11,14 @@ export class CouchDbClientConfig {
BASIC_AUTH_PASSWORD = '';
}

@Injectable()
export class CouchDbClient {
private readonly logger = new Logger(CouchDbClient.name);

constructor(private httpService: HttpService) {}

changes(
databaseUrl: string,
databaseName: string,
config?: any,
): Observable<CouchDbChangesResponse> {
changes(request: { config?: any }): Observable<CouchDbChangesResponse> {
return this.httpService
.get<CouchDbChangesResponse>(
`${databaseUrl}/${databaseName}/_changes`,
config,
)
.get<CouchDbChangesResponse>(`/_changes`, request.config)
.pipe(
map((response) => {
return response.data;
Expand Down Expand Up @@ -66,14 +58,9 @@ export class CouchDbClient {
);
}

find<T>(
databaseUrl: string,
databaseName: string,
body: any,
config?: any,
): Observable<T> {
find<T>(request: { query: object; config: any }): Observable<T> {
return this.httpService
.post<T>(`${databaseUrl}/${databaseName}/_find`, body, config)
.post<T>(`_find`, request.query, request.config)
.pipe(
map((response) => {
return response.data;
Expand Down
11 changes: 9 additions & 2 deletions src/couchdb/couch-sqs.client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export class CouchSqsClientConfig {
BASE_URL = '';
BASIC_AUTH_USER = '';
BASIC_AUTH_PASSWORD = '';
SCHEMA_DESIGN_CONFIG = '';
}

export interface QueryRequest {
Expand All @@ -16,9 +17,15 @@ export interface QueryRequest {
export class CouchSqsClient {
private readonly logger: Logger = new Logger(CouchSqsClient.name);

constructor(private httpService: HttpService) {}
constructor(
private httpService: HttpService,
private config: CouchSqsClientConfig,
) {}

executeQuery(path: string, query: QueryRequest): Observable<string> {
executeQuery(
query: QueryRequest,
path: string = this.config.SCHEMA_DESIGN_CONFIG,
): Observable<string> {
return this.httpService.post(path, query).pipe(
map((response) => response.data),
catchError((err) => {
Expand Down
53 changes: 53 additions & 0 deletions src/couchdb/default-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { ConfigService } from '@nestjs/config';
import { CouchDbClient, CouchDbClientConfig } from './couch-db-client.service';
import { HttpService } from '@nestjs/axios';
import axios from 'axios';
import { CouchSqsClient, CouchSqsClientConfig } from './couch-sqs.client';

export const DefaultCouchDbClientFactory = (
configPrefix: string,
configService: ConfigService,
): CouchDbClient => {
const config: CouchDbClientConfig = {
BASE_URL: configService.getOrThrow(configPrefix + 'BASE_URL'),
TARGET_DATABASE: configService.getOrThrow(configPrefix + 'TARGET_DATABASE'),
BASIC_AUTH_USER: configService.getOrThrow(configPrefix + 'BASIC_AUTH_USER'),
BASIC_AUTH_PASSWORD: configService.getOrThrow(
configPrefix + 'BASIC_AUTH_PASSWORD',
),
};

const axiosInstance = axios.create();

axiosInstance.defaults.baseURL = `${config.BASE_URL}/${config.TARGET_DATABASE}`;
axiosInstance.defaults.headers['Authorization'] = `Basic ${Buffer.from(
`${config.BASIC_AUTH_USER}:${config.BASIC_AUTH_PASSWORD}`,
).toString('base64')}`;

return new CouchDbClient(new HttpService(axiosInstance));
};

export const DefaultCouchSqsClientFactory = (
tomwwinter marked this conversation as resolved.
Show resolved Hide resolved
configPrefix: string,
configService: ConfigService,
): CouchSqsClient => {
const config: CouchSqsClientConfig = {
BASE_URL: configService.getOrThrow(configPrefix + 'BASE_URL'),
BASIC_AUTH_USER: configService.getOrThrow(configPrefix + 'BASIC_AUTH_USER'),
BASIC_AUTH_PASSWORD: configService.getOrThrow(
configPrefix + 'BASIC_AUTH_PASSWORD',
),
SCHEMA_DESIGN_CONFIG: configService.getOrThrow(
configPrefix + 'SCHEMA_DESIGN_CONFIG',
),
};

const axiosInstance = axios.create();

axiosInstance.defaults.baseURL = config.BASE_URL;
axiosInstance.defaults.headers['Authorization'] = `Basic ${Buffer.from(
`${config.BASIC_AUTH_USER}:${config.BASIC_AUTH_PASSWORD}`,
).toString('base64')}`;

return new CouchSqsClient(new HttpService(axiosInstance), config);
};
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Module } from '@nestjs/common';
import { CryptoService } from '../core/crypto.service';
import { CryptoServiceFactory } from '../di/crypto-configuration';
import { CryptoService } from './core/crypto.service';
import { CryptoServiceFactory } from './di/crypto-configuration';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
Expand Down
45 changes: 22 additions & 23 deletions src/notification/core/notification.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
import { Injectable } from '@nestjs/common';
import {
BehaviorSubject,
catchError,
map,
mergeMap,
Observable,
Expand All @@ -18,7 +16,6 @@ import { UrlParser } from './url-parser.service';
/**
* Manage core subscriptions and delivering events to subscribers.
*/
@Injectable()
export class NotificationService {
private _activeReports: BehaviorSubject<Reference[]> = new BehaviorSubject(
[] as Reference[],
Expand Down Expand Up @@ -71,34 +68,36 @@ export class NotificationService {
reportId: event.report.id,
});

return this.httpService
.request<any>({
method: webhook.target.method,
url: url,
headers: {
'X-API-KEY': webhook.authentication.apiKey,
},
timeout: 5000,
})
.pipe(
map((response) => {
console.log('axios response', response);
}),
catchError((err) => {
console.log('could not send notification to webhook', err);
throw err;
}),
);
return this.httpService.request<any>({
method: webhook.target.method,
url: url,
data: {
calculation_id: event.calculation.id,
},
headers: {
Authorization: `Token ${webhook.authentication.apiKey}`,
},
timeout: 5000,
});
}),
),
zipAll(),
)
.subscribe({
next: () => {
console.log('webhook send');
console.log('webhook called successfully');
},
error: (err) => {
console.log(err);
console.log('could not send notification to webhook', {
error: {
code: err.code,
response: {
status: err.response.status,
statusText: err.response.statusText,
data: err.response.data,
},
},
});
},
complete: () => {
console.log('webhook trigger completed');
Expand Down
3 changes: 0 additions & 3 deletions src/notification/core/url-parser.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
import { Injectable } from '@nestjs/common';

@Injectable()
export class UrlParser {
getPlaceholder(url: string): string[] {
const pattern = /<([^<>]+)>/g;
Expand Down
57 changes: 27 additions & 30 deletions src/notification/di/notification-configuration.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,40 @@
import { HttpService } from '@nestjs/axios';
import { ConfigService } from '@nestjs/config';
import {
CouchDbClient,
CouchDbClientConfig,
} from '../../couchdb/couch-db-client.service';
import { WebhookStorage } from '../storage/webhook-storage.service';
import { WebhookRepository } from '../repository/webhook-repository.service';
import { CryptoService } from '../../crypto/core/crypto.service';
import { DefaultCouchDbClientFactory } from '../../couchdb/default-factory';
import { NotificationService } from '../core/notification.service';
import { HttpService } from '@nestjs/axios';
import axios from 'axios';

export const CouchDbClientFactory = (
configService: ConfigService,
): CouchDbClient => {
const CONFIG_BASE = 'NOTIFICATION_COUCH_DB_CLIENT_CONFIG_';

const config: CouchDbClientConfig = {
BASE_URL: configService.getOrThrow(CONFIG_BASE + 'BASE_URL'),
TARGET_DATABASE: configService.getOrThrow(CONFIG_BASE + 'TARGET_DATABASE'),
BASIC_AUTH_USER: configService.getOrThrow(CONFIG_BASE + 'BASIC_AUTH_USER'),
BASIC_AUTH_PASSWORD: configService.getOrThrow(
CONFIG_BASE + 'BASIC_AUTH_PASSWORD',
),
};

const axiosInstance = axios.create();

axiosInstance.defaults.baseURL = `${config.BASE_URL}/${config.TARGET_DATABASE}`;
axiosInstance.defaults.headers['Authorization'] = `Basic ${Buffer.from(
`${config.BASIC_AUTH_USER}:${config.BASIC_AUTH_PASSWORD}`,
).toString('base64')}`;

return new CouchDbClient(new HttpService(axiosInstance));
};
import { UrlParser } from '../core/url-parser.service';

export const WebhookStorageFactory = (
cryptoService: CryptoService,
configService: ConfigService,
): WebhookStorage => {
const couchDbClient = CouchDbClientFactory(configService);
const couchDbClient = DefaultCouchDbClientFactory(
'NOTIFICATION_COUCH_DB_CLIENT_CONFIG_',
configService,
);
const webhookRepository = new WebhookRepository(couchDbClient);
return new WebhookStorage(webhookRepository, cryptoService);
};

export const NotificationServiceFactory = (
webhookStorage: WebhookStorage,
): NotificationService => {
return new NotificationService(
webhookStorage,
WebhookWebClient(),
new UrlParser(),
);
};

export const WebhookWebClient = (): HttpService => {
const axiosInstance = axios.create();
axiosInstance.interceptors.request.use((config) => {
console.log('Execute Webhook: ', config.url);
return config;
});
return new HttpService(axiosInstance);
};
18 changes: 11 additions & 7 deletions src/notification/notification.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,27 @@ import { NotificationService } from './core/notification.service';
import { WebhookStorage } from './storage/webhook-storage.service';
import { WebhookController } from './controller/webhook.controller';
import { ConfigService } from '@nestjs/config';
import { WebhookStorageFactory } from './di/notification-configuration';
import { CryptoModule } from '../crypto/crypto/crypto.module';
import {
NotificationServiceFactory,
WebhookStorageFactory,
} from './di/notification-configuration';
import { CryptoModule } from '../crypto/crypto.module';
import { CryptoService } from '../crypto/core/crypto.service';
import { HttpModule } from '@nestjs/axios';
import { UrlParser } from './core/url-parser.service';

@Module({
controllers: [WebhookController],
imports: [CryptoModule, HttpModule],
imports: [CryptoModule],
providers: [
NotificationService,
UrlParser,
{
provide: WebhookStorage,
useFactory: WebhookStorageFactory,
inject: [CryptoService, ConfigService],
},
{
provide: NotificationService,
useFactory: NotificationServiceFactory,
inject: [WebhookStorage],
},
],
exports: [NotificationService],
})
Expand Down
1 change: 1 addition & 0 deletions src/notification/storage/webhook-storage.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { v4 as uuidv4 } from 'uuid';
import { CryptoService } from '../../crypto/core/crypto.service';
import { NotFoundException } from '@nestjs/common';

// todo interface
export class WebhookStorage {
constructor(
private webhookRepository: WebhookRepository,
Expand Down
Loading
Loading