From b6025734c8c061245cc46a04cceb20dad38be612 Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Tue, 4 May 2021 17:11:32 +0000 Subject: [PATCH 01/22] :fire: Deleted old discordbot implementation --- packages/backend/src/configuration/discord.ts | 11 --- .../backend/src/controllers/discord/index.ts | 44 ---------- .../discordChannel/discordChannel.schema.ts | 12 --- .../discordChannel/discordChannel.ts | 83 ------------------- 4 files changed, 150 deletions(-) delete mode 100644 packages/backend/src/configuration/discord.ts delete mode 100644 packages/backend/src/controllers/discord/index.ts delete mode 100644 packages/backend/src/controllers/discordChannel/discordChannel.schema.ts delete mode 100644 packages/backend/src/controllers/discordChannel/discordChannel.ts diff --git a/packages/backend/src/configuration/discord.ts b/packages/backend/src/configuration/discord.ts deleted file mode 100644 index 67290796..00000000 --- a/packages/backend/src/configuration/discord.ts +++ /dev/null @@ -1,11 +0,0 @@ -import Discord from 'discord.js'; -import { config } from './environment'; -import { loggerFile } from './logger'; - -export const client = new Discord.Client(); - -client.once('ready', () => { - loggerFile.info(`Logged in as ${client.user.tag}!`); - }); - -client.login(config.discordToken); diff --git a/packages/backend/src/controllers/discord/index.ts b/packages/backend/src/controllers/discord/index.ts deleted file mode 100644 index 77176fb4..00000000 --- a/packages/backend/src/controllers/discord/index.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * discord/index.ts provides an interface to interact with the discord.js libary on an easy way. - */ -import { client } from '../../configuration/discord'; -import { FMDBMessageTemplate } from './message.interface'; -import { TextChannel } from 'discord.js'; -import { getDiscordChannel } from '../discordChannel/discordChannel'; -import { ApiError } from '../../utils/api'; - -/** - * Send a templated message to a discord user - * - * @export - * @param {string} userId Discord ID of the recipient - * @param {FMDBMessageTemplate} messageTemplate Message template - * @param {object} values - */ -export function sendTo(userId: string, messageTemplate: FMDBMessageTemplate, values: object) { - const discordUser = client.users.cache.get(userId); - if (!discordUser) throw new ApiError(409, `User not in discord cache. Send the bot a small 'test' message (via DM) and try again.`); - - const message = messageTemplate.apply(values); - if (!message) throw new Error('Internal server error'); - - discordUser.send(message); -} - -/** - * Publish a message to the set discord channel - * - * @export - * @param {FMDBMessageTemplate} messageTemplate Message template - * @param {object} value - */ -export async function publish(messageTemplate: FMDBMessageTemplate, values: object) { - const channelId = await getDiscordChannel(); - const discordChannel = await client.channels.cache.get(channelId); - if (!discordChannel) throw new Error(`Channel not in discord cache. Send a small 'test' message to the channel and try again.`); - - const message = messageTemplate.apply(values); - if (!message) throw new Error('Internal server error'); - - (discordChannel as TextChannel).send(message); -} diff --git a/packages/backend/src/controllers/discordChannel/discordChannel.schema.ts b/packages/backend/src/controllers/discordChannel/discordChannel.schema.ts deleted file mode 100644 index 19b6b2d6..00000000 --- a/packages/backend/src/controllers/discordChannel/discordChannel.schema.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { Schema, model, Model, Document } from 'mongoose'; - -interface IDiscordChannelSchema extends Document { - [_id: string]: any; - channel: string; -} - -const discordChannelSchema = new Schema({ - channel: {type: String}, -}); - -export const DiscordChannel: Model = model('DiscordChannel', discordChannelSchema); diff --git a/packages/backend/src/controllers/discordChannel/discordChannel.ts b/packages/backend/src/controllers/discordChannel/discordChannel.ts deleted file mode 100644 index cb08e297..00000000 --- a/packages/backend/src/controllers/discordChannel/discordChannel.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { DiscordChannel } from './discordChannel.schema'; -import { config } from '../../configuration/environment'; -import { loggerFile } from '../../configuration/logger'; -import { Request, Response, NextFunction } from 'express'; -import { object, string } from '@hapi/joi'; -import { ApiSuccess, ApiError } from '../../utils/api'; - -/** - * Writes the discord channel into the database. - * Uses the default config value if no value is given. - * Creates a new document if it doesn't exist before. - * @param channel {number} Discord channel. - * @export - */ -export async function setDiscordChannel(channelNr:string = config.discordChannel):Promise{ - await DiscordChannel.findOneAndUpdate({},{$set: {channel: channelNr}},{upsert: true}); -} - -/** - * Returns the discord channel value from the database. - * Creates a new document with the config value if it doesn't exist before. - * @export - * @returns {Promise} A promise to the discord channel loaded from the database as string. - */ -export async function getDiscordChannel():Promise{ - const result = await DiscordChannel.findOne(); - // Set default channel from config if theres nothing in the database - if (!result) await setDiscordChannel(); - return result ? result.channel : config.discordChannel; -} - -/** - * Handles GET /api/settings/discordChannel requests and responds - * with the current discord channel (as JSON Object). - * @param req Request - * @param res Response - * @param next NextFunction - */ -export async function getDiscordChannelRequest(req: Request, res: Response, next: NextFunction) { - - try { - const channelId = await getDiscordChannel(); - if (!channelId) throw new ApiError(503, 'Internal error while retrieving discord channel id'); - - const response = new ApiSuccess(200, {channelId}); - next(response); - } - catch (err) { - loggerFile.error(err.message); - next(err); - } - } - -// Schema for validating api input -const discordChannelRequestSchema = object({ - channelId: string().length(18).required() -}); - -/** - * Handles PUT /api/settings/discordChannel/ requests. - * Writes given string to the database. - * @param req Request: Contains discord channel id as string - * @param res Response - * @param next NextFunction - */ -export async function setDiscordChannelRequest(req: Request, res: Response, next: NextFunction) { - - try { - // Input checking - const request = discordChannelRequestSchema.validate(req.body); - if (request.error) throw new ApiError(400, request.error.message); - - // Method call and exit - await setDiscordChannel(request.value.channelId); - - const response = new ApiSuccess(); - next(response); - } - catch (err) { - loggerFile.error(err.message); - next(err); - } - } From 2ccc7049f6b38b51f0975f4f96c0aa61f198b39a Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Tue, 4 May 2021 17:13:08 +0000 Subject: [PATCH 02/22] :truck: Moved old templates to new folder --- .../src/controllers/{discord => messages}/message.class.ts | 0 .../src/controllers/{discord => messages}/message.interface.ts | 0 .../{discord => messages}/templates/assignmentMessage.class.ts | 0 .../templates/assignmentReminderMessage.class.ts | 0 .../src/controllers/{discord => messages}/templates/index.ts | 0 .../{discord => messages}/templates/resourceMessage.class.ts | 0 .../{discord => messages}/templates/tokenRequestMessage.class.ts | 0 7 files changed, 0 insertions(+), 0 deletions(-) rename packages/backend/src/controllers/{discord => messages}/message.class.ts (100%) rename packages/backend/src/controllers/{discord => messages}/message.interface.ts (100%) rename packages/backend/src/controllers/{discord => messages}/templates/assignmentMessage.class.ts (100%) rename packages/backend/src/controllers/{discord => messages}/templates/assignmentReminderMessage.class.ts (100%) rename packages/backend/src/controllers/{discord => messages}/templates/index.ts (100%) rename packages/backend/src/controllers/{discord => messages}/templates/resourceMessage.class.ts (100%) rename packages/backend/src/controllers/{discord => messages}/templates/tokenRequestMessage.class.ts (100%) diff --git a/packages/backend/src/controllers/discord/message.class.ts b/packages/backend/src/controllers/messages/message.class.ts similarity index 100% rename from packages/backend/src/controllers/discord/message.class.ts rename to packages/backend/src/controllers/messages/message.class.ts diff --git a/packages/backend/src/controllers/discord/message.interface.ts b/packages/backend/src/controllers/messages/message.interface.ts similarity index 100% rename from packages/backend/src/controllers/discord/message.interface.ts rename to packages/backend/src/controllers/messages/message.interface.ts diff --git a/packages/backend/src/controllers/discord/templates/assignmentMessage.class.ts b/packages/backend/src/controllers/messages/templates/assignmentMessage.class.ts similarity index 100% rename from packages/backend/src/controllers/discord/templates/assignmentMessage.class.ts rename to packages/backend/src/controllers/messages/templates/assignmentMessage.class.ts diff --git a/packages/backend/src/controllers/discord/templates/assignmentReminderMessage.class.ts b/packages/backend/src/controllers/messages/templates/assignmentReminderMessage.class.ts similarity index 100% rename from packages/backend/src/controllers/discord/templates/assignmentReminderMessage.class.ts rename to packages/backend/src/controllers/messages/templates/assignmentReminderMessage.class.ts diff --git a/packages/backend/src/controllers/discord/templates/index.ts b/packages/backend/src/controllers/messages/templates/index.ts similarity index 100% rename from packages/backend/src/controllers/discord/templates/index.ts rename to packages/backend/src/controllers/messages/templates/index.ts diff --git a/packages/backend/src/controllers/discord/templates/resourceMessage.class.ts b/packages/backend/src/controllers/messages/templates/resourceMessage.class.ts similarity index 100% rename from packages/backend/src/controllers/discord/templates/resourceMessage.class.ts rename to packages/backend/src/controllers/messages/templates/resourceMessage.class.ts diff --git a/packages/backend/src/controllers/discord/templates/tokenRequestMessage.class.ts b/packages/backend/src/controllers/messages/templates/tokenRequestMessage.class.ts similarity index 100% rename from packages/backend/src/controllers/discord/templates/tokenRequestMessage.class.ts rename to packages/backend/src/controllers/messages/templates/tokenRequestMessage.class.ts From 83d5c464207978c3917e8e4a8575a6832f31648b Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Wed, 5 May 2021 17:13:36 +0000 Subject: [PATCH 03/22] :fire: Removed unneccessary comment --- .../src/controllers/authentication/index.ts | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/backend/src/controllers/authentication/index.ts b/packages/backend/src/controllers/authentication/index.ts index 0e4afb28..ec55707f 100644 --- a/packages/backend/src/controllers/authentication/index.ts +++ b/packages/backend/src/controllers/authentication/index.ts @@ -137,22 +137,6 @@ export async function authAttestationGetRequest(req: Request, res: Response, nex userName: userDoc.username.toString(), timeout: 60000, attestationType: 'indirect', - /** - * Passing in a user's list of already-registered authenticator IDs here prevents users from - * registering the same device multiple times. The authenticator will simply throw an error in - * the browser if it's asked to perform an attestation when one of these ID's already resides - * on it. - * - * excludeCredentials: [{ - * id: userDoc.device.credentialID, - * type: 'public-key', - * transports: userDoc.device.transports, - * }], - */ - /** - * The optional authenticatorSelection property allows for specifying more constraints around - * the types of authenticators that users can use for attestation - */ authenticatorSelection: { userVerification: 'preferred', requireResidentKey: false, From 61b91a5dcae2bd5941b8d11465e0fe4501fb7562 Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Wed, 5 May 2021 17:14:24 +0000 Subject: [PATCH 04/22] :card_file_box: Added new database schemas --- .../src/controllers/connectors/index.ts | 7 +++++ .../connectors/schemas/connector.schema.ts | 26 +++++++++++++++++++ .../schemas/connectorLogItem.schema.ts | 22 ++++++++++++++++ 3 files changed, 55 insertions(+) create mode 100644 packages/backend/src/controllers/connectors/index.ts create mode 100644 packages/backend/src/controllers/connectors/schemas/connector.schema.ts create mode 100644 packages/backend/src/controllers/connectors/schemas/connectorLogItem.schema.ts diff --git a/packages/backend/src/controllers/connectors/index.ts b/packages/backend/src/controllers/connectors/index.ts new file mode 100644 index 00000000..e7fdfd55 --- /dev/null +++ b/packages/backend/src/controllers/connectors/index.ts @@ -0,0 +1,7 @@ +export { connectorService } from './service'; + +export enum ConnectorLogType { + Info = 'info', + Warning = 'warning', + Error = 'error' +} diff --git a/packages/backend/src/controllers/connectors/schemas/connector.schema.ts b/packages/backend/src/controllers/connectors/schemas/connector.schema.ts new file mode 100644 index 00000000..4f879028 --- /dev/null +++ b/packages/backend/src/controllers/connectors/schemas/connector.schema.ts @@ -0,0 +1,26 @@ +/* tslint:disable:ban-types */ +import { Schema, model, Model, Document } from 'mongoose'; +import { ConnectorType } from '../plugins'; + +export interface IConnectorDocument extends Document { + [_id: string]: any; + active: boolean; + courses: { [key: string]: string }[]; + createdAt: Date; + default: boolean; + name: string; + socket: any; + type: ConnectorType; +} + +const connectorSchema = new Schema({ + active: { type: Boolean, default: true }, + courses: { type: Array, default: [] }, + createdAt: { type: Date, default: Date.now }, + default: { type: Boolean, default: false }, + name: { type: String, required: true }, + socket: { type: Object }, + type: { type: ConnectorType }, +}); + +export const Connector: Model = model('Connector', connectorSchema); diff --git a/packages/backend/src/controllers/connectors/schemas/connectorLogItem.schema.ts b/packages/backend/src/controllers/connectors/schemas/connectorLogItem.schema.ts new file mode 100644 index 00000000..23494e7a --- /dev/null +++ b/packages/backend/src/controllers/connectors/schemas/connectorLogItem.schema.ts @@ -0,0 +1,22 @@ +/* tslint:disable:ban-types */ +import { Schema, model, Model, Document } from 'mongoose'; +import { config } from '../../../configuration/environment'; +import { ConnectorLogType } from '..'; +import { ConnectorType } from '../plugins'; + +export interface IConnectorLogItemDocument extends Document { + [_id: string]: any; + connector: string; + createdAt: Date; + message: string; + type: ConnectorType; +} + +const connectorLogSchema = new Schema({ + connector: { type: Schema.Types.ObjectId, ref: 'Connector', required: true }, + createdAt: { type: Date, default: Date.now, expires: config.registrationTokenLifetime, }, + message: { type: String, required: true }, + type: { type: ConnectorLogType, required: true }, +}); + +export const ConnectorLogItem: Model = model('connectorlog', connectorLogSchema); From d1aa2451b2acee491ad9ffc17cd3d169f54db718 Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Thu, 6 May 2021 17:15:01 +0000 Subject: [PATCH 05/22] :sparkles: Added connector logger --- .../src/controllers/connectors/logger.ts | 75 +++++++++++++++++++ 1 file changed, 75 insertions(+) create mode 100644 packages/backend/src/controllers/connectors/logger.ts diff --git a/packages/backend/src/controllers/connectors/logger.ts b/packages/backend/src/controllers/connectors/logger.ts new file mode 100644 index 00000000..01982f94 --- /dev/null +++ b/packages/backend/src/controllers/connectors/logger.ts @@ -0,0 +1,75 @@ +import { ConnectorLogType } from '.'; +import { loggerFile } from '../../configuration/logger'; +import { ConnectorLogItem } from './schemas/connectorLogItem.schema'; + +class ConnectorLogger { + + /** + * Creates object with message and connector attribute for ConnectorLogItem + * + * @param message message that needs to be stored + * @param objectId objectId of the connector + * @returns object + */ + private createContent(message: string, objectId: string): { [key: string]: string } { + return { + connector: objectId, + message + }; + } + + /** + * Print and stores a info message + * + * @param message message that needs to be stored + * @param objectId objectId of the connector + * @param skipSave default: true - skips adding the message to ConnectorLogs + * @returns void + */ + public info(message: string, objectId: string, skipSave: boolean = false): void { + loggerFile.info(message); + + if (skipSave) return; + + const content = this.createContent(message, objectId); + new ConnectorLogItem({ ...content, type: ConnectorLogType.Info }).save(); + } + + /** + * Print and stores a warning message + * + * @param message message that needs to be stored + * @param objectId objectId of the connector + * @param skipSave default: true - skips adding the message to ConnectorLogs + * @returns void + */ + public warn(message: string, objectId: string, skipSave: boolean = false): void { + loggerFile.warn(message); + + if (skipSave) return; + + const content = this.createContent(message, objectId); + new ConnectorLogItem({ ...content, type: ConnectorLogType.Warning }).save(); + } + + /** + * Print and stores a error message + * + * @param message message that needs to be stored + * @param objectId objectId of the connector + * @param skipSave default: true - skips adding the message to ConnectorLogs + * @returns void + */ + public error(message: string, objectId: string, skipSave: boolean = false): void { + loggerFile.error(message); + + if (skipSave) return; + + const content = this.createContent(message, objectId); + new ConnectorLogItem({ ...content, type: ConnectorLogType.Error }).save(); + } +} + +// This step is not required, because all functions are static +// But the usage should be similar to the loggerFile object +export const connectorLogger = new ConnectorLogger(); From 95e7a1b9d2bab8f076647ebcb4445e4011091d12 Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Thu, 6 May 2021 17:15:30 +0000 Subject: [PATCH 06/22] :sparkles: Added connector service and discord bot plugin --- .../connectors/plugins/discordBot.ts | 137 ++++++++++++++++++ .../controllers/connectors/plugins/index.ts | 17 +++ .../src/controllers/connectors/service.ts | 104 +++++++++++++ 3 files changed, 258 insertions(+) create mode 100644 packages/backend/src/controllers/connectors/plugins/discordBot.ts create mode 100644 packages/backend/src/controllers/connectors/plugins/index.ts create mode 100644 packages/backend/src/controllers/connectors/service.ts diff --git a/packages/backend/src/controllers/connectors/plugins/discordBot.ts b/packages/backend/src/controllers/connectors/plugins/discordBot.ts new file mode 100644 index 00000000..c114a5fc --- /dev/null +++ b/packages/backend/src/controllers/connectors/plugins/discordBot.ts @@ -0,0 +1,137 @@ +import Discord, { TextChannel } from 'discord.js'; +import { config } from '../../../configuration/environment'; +import { IConnectorDocument } from '../schemas/connector.schema'; +import { ConnectorLogItem, IConnectorLogItemDocument } from '../schemas/connectorLogItem.schema'; +import { connectorLogger } from '../logger'; +import { ConnectorPlugin } from '.'; +import { object, ObjectSchema, string } from '@hapi/joi'; +import { ApiError } from '../../../utils/api'; + +export class DiscordBotConnectorPlugin extends ConnectorPlugin { + private document: IConnectorDocument; + private readonly client: Discord.Client = new Discord.Client(); + private readonly updateRequestSchema: ObjectSchema = object({ + channel: string().alphanum().length(18), + }).required(); + + /** + * Creates an instance of DiscordBotConnectorPlugin. + * + * @param {IConnectorDocument} doc mongoose document + * @memberof DiscordBotConnectorPlugin + */ + constructor(doc: IConnectorDocument) { + super(); + this.document = doc; + + this.setUpListeners(); + this.client.login(config.discordToken); + } + + /** + * Creates all listeners to provide better overview about the health of the bot. + * + * @private + * @memberof DiscordBotConnectorPlugin + */ + private setUpListeners(): void { + this.client.once('ready', () => { + connectorLogger.info(`Logged in as ${this.client.user.tag}!`, this.objectId); + }); + + this.client.on('warn', (info) => { + connectorLogger.warn(`discord.js: ${info}`, this.objectId); + }); + + this.client.on('disconnect', (info) => { + connectorLogger.error(`discord.js: ${info}`, this.objectId); + }); + } + + /** + * Returns an array of the newest log items of this bot. + * The amount of items can be limited by parameter. Default is 50. + * + * @param {number} [limit=50] Set a limit to the amount of items + * @return {Promise} Array of ConnectorLogItemDocuments + * @memberof DiscordBotConnectorPlugin + */ + public async getLogs(limit: number = 50): Promise { + const query = { + connector: this.objectId + }; + return await ConnectorLogItem.find(query).sort({ createdAt: -1 }).limit(limit); + } + + /** + * Sends the given message to the discord channel + * + * @param {string} message to send + */ + public send(message: string): void { + const discordChannel = this.client.channels.cache.get(this.document.socket.channel); + if (!discordChannel) return connectorLogger.error(`Channel not in discord cache. Send a small 'test' message to the channel and try again.`, this.objectId); + + (discordChannel as TextChannel).send(message) + .then(() => { + connectorLogger.info('Successfully send message via discord bot!', this.objectId); + }) + .catch((error) => { + connectorLogger.info(`Failed to send message via discord bot! ${error.message}`, this.objectId); + }); + } + + /** + * Applies the given patch to the discord bot document. + * + * @param {{ [key: string]: any }} body + * @return {Promise} The updated document + * @memberof DiscordBotConnectorPlugin + */ + public async update(body: { [key: string]: any }): Promise { + // Validate user input + const updateRequest = this.updateRequestSchema.validate(body); + if (updateRequest.error) throw new ApiError(400, updateRequest.error.message); + + // Apply changes + this.document.socket.channel = updateRequest.value.channel; + const result = await this.document.save(); + + // Log update process + connectorLogger.info('New values have been applied', this.objectId); + + return result; + } + + /** + * Returns the mongoDb objectId, this plugin is build on. + * + * @readonly + * @type {string} + * @memberof DiscordBotConnectorPlugin + */ + public get objectId(): string { return this.document.id; } + + /** + * Returns all courses, that are assigned to this bot. + * + * @readonly + * @type {{ [key: string]: string; }[]} + * @memberof DiscordBotConnectorPlugin + */ + public get courses(): { [key: string]: string; }[] { + return this.document.courses; + } + + /** + * Returns true, if the bot is an default handler for not + * assigned courses. + * + * @readonly + * @type {boolean} + * @memberof DiscordBotConnectorPlugin + */ + public get isDefault(): boolean { + return this.document.default; + } +} diff --git a/packages/backend/src/controllers/connectors/plugins/index.ts b/packages/backend/src/controllers/connectors/plugins/index.ts new file mode 100644 index 00000000..9c963d0c --- /dev/null +++ b/packages/backend/src/controllers/connectors/plugins/index.ts @@ -0,0 +1,17 @@ +import { IConnectorDocument } from '../schemas/connector.schema'; +import { IConnectorLogItemDocument } from '../schemas/connectorLogItem.schema'; +export { DiscordBotConnectorPlugin } from './discordBot'; + + +export enum ConnectorType { + Discord = 'discord', +} + +export abstract class ConnectorPlugin { + public abstract send(message: string): void; + public abstract getLogs(limit: number): Promise; + public abstract update(body: { [key: string]: any }): Promise; + public abstract get objectId(): string; + public abstract get courses(): { [key: string]: string; }[]; + public abstract get isDefault(): boolean; +} diff --git a/packages/backend/src/controllers/connectors/service.ts b/packages/backend/src/controllers/connectors/service.ts new file mode 100644 index 00000000..e8d3b950 --- /dev/null +++ b/packages/backend/src/controllers/connectors/service.ts @@ -0,0 +1,104 @@ +import { ApiError } from '../../utils/api'; +import { loggerFile } from '../../configuration/logger'; +import { MessageTemplate } from '../messages/message.class'; +import { ConnectorPlugin, ConnectorType, DiscordBotConnectorPlugin } from './plugins'; +import { Connector, IConnectorDocument } from './schemas/connector.schema'; + +class ConnectorService { + + private connectors: ConnectorPlugin[]; + + /** + * Creates an instance of ConnectorService. + * Fetches all connectors from database and creates ConnectorPlugin instances + * @memberof ConnectorService + */ + constructor() { + loggerFile.info('ConnectorService has been started'); + // Get all connectors from Database + Connector.find().then((connectorList: IConnectorDocument[]) => { + connectorList.forEach(connector => { + switch (connector.type) { + case ConnectorType.Discord: + loggerFile.debug(`Found connector of type Discord (${connector._id})`); + this.connectors.push(new DiscordBotConnectorPlugin(connector)); + break; + + default: + loggerFile.error(`Found connector with unknown type ${connector.type} at ${connector._id}`); + break; + } + }); + + // If no connector has been found, start to create connectors based on .env + if (connectorList.length === 0) { + loggerFile.warn('Not connector found at database'); + this.createConnectorsFromEnv(); + } + }); + } + + /** + * Creates new connectors based on environment + * + * @private + * @memberof ConnectorService + */ + private createConnectorsFromEnv(): void { + loggerFile.debug('Start creating connectors from environment variables'); + // TODO: To be continue + + } + + /** + * Publish a message to connectors that have this course assigned + * If the course is assigned to no course, it will send the message to all + * connectors with the default flag. + * + * @param {string} moodleId objectId of MoodleInstance + * @param {number} courseId id of the moodle course + * @param {MessageTemplate} template message template that will be used + * @param {*} options content that will be applied on template + * @memberof ConnectorService + */ + public publish(moodleId: string, courseId: string, template: MessageTemplate, options: any): void { + const message = template.apply(options); + let messageWasSent: boolean = false; + + this.connectors.forEach(connector => { + const course = connector.courses.find(element => element.moodleId === moodleId && element.courseId === courseId); + if (course === undefined) return; + + messageWasSent = true; + + // Course is assigned to this connector, send message + connector.send(message); + }); + + // If the message was sent to a plugin, stop here + if (messageWasSent) return; + + // If not, send the message to all default connectors + this.connectors.forEach(connector => { + if (connector.isDefault) connector.send(message); + }); + } + + /** + * Updates a selected connector + * Throws ApiError + * + * @param {string} connectorId objectId of the connector + * @param {[key: string]: any} body body of http request + * @returns Updated document + * @memberof ConnectorService + */ + public async update(connectorId: string, body: { [key: string]: any }) : Promise { + const connector = this.connectors.find(element => element.objectId === connectorId); + if (!connector) throw new ApiError(404, `Connector with id ${connectorId} not found!`); + + return await connector.update(body); + } +} + +export const connectorService = new ConnectorService(); From 7d7c4148dd0c7068b206ae8197b147e765012c08 Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Thu, 6 May 2021 20:18:19 +0000 Subject: [PATCH 07/22] :fire: Delete old discord files --- .../templates/tokenRequestMessage.class.ts | 11 ---- packages/backend/tests/discord.spec.ts | 66 ------------------- 2 files changed, 77 deletions(-) delete mode 100644 packages/backend/src/controllers/messages/templates/tokenRequestMessage.class.ts delete mode 100644 packages/backend/tests/discord.spec.ts diff --git a/packages/backend/src/controllers/messages/templates/tokenRequestMessage.class.ts b/packages/backend/src/controllers/messages/templates/tokenRequestMessage.class.ts deleted file mode 100644 index cbbad46d..00000000 --- a/packages/backend/src/controllers/messages/templates/tokenRequestMessage.class.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { MessageTemplate } from '../message.class'; - -export interface TokenRequestMessageOptions { - key: string; -} - -export class TokenRequestMessage extends MessageTemplate { - readonly template = `:key: **Es wurde ein Zugangstoken angefordert** - Zugangstoken lautet: {key} - Solltest du den Token nicht angefordert haben - Kein Problem, lösche diese Nachricht einfach!`; -} diff --git a/packages/backend/tests/discord.spec.ts b/packages/backend/tests/discord.spec.ts deleted file mode 100644 index 8f63b8f6..00000000 --- a/packages/backend/tests/discord.spec.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { client } from '../src/configuration/discord'; -import { TokenRequestMessage } from '../src/controllers/discord/templates'; -import { publish, sendTo } from '../src/controllers/discord'; -import { ApiError } from "../src/utils/api"; -import * as discordChannel from '../src/controllers/discordChannel/discordChannel'; - -jest.mock('../src/configuration/environment.ts'); -jest.mock('../src/configuration/discord.ts'); - -describe('discord.ts discordSendTo', () => { - let spyDiscordClientUsers: jest.SpyInstance; - - beforeEach(() => { - spyDiscordClientUsers = jest.spyOn(client.users.cache, 'get'); - }); - - it('should throw error if user is not in cache', async () => { - spyDiscordClientUsers.mockImplementation(() => null); - const compareError = new ApiError(409, `User not in discord cache. Send the bot a small 'test' message (via DM) and try again.`); - try { - await sendTo('1234657890', new TokenRequestMessage(), { key: 123123 }); - } catch (error) { - expect(error).toEqual(compareError); - } - }); - - it('should send message if everything is fine', async () => { - const mockDiscordUser = { send: jest.fn() }; - spyDiscordClientUsers.mockImplementation(() => mockDiscordUser); - - await sendTo('1234657890', new TokenRequestMessage(), { key: 123123 }); - expect(mockDiscordUser.send).toHaveBeenCalled(); - }); - -}); - -describe('discord.ts discordPublish', () => { - let spyDiscordClientChannels: jest.SpyInstance; - let spyDiscordChannel: jest.SpyInstance; - - beforeEach(() => { - spyDiscordClientChannels = jest.spyOn(client.channels.cache, 'get'); - spyDiscordChannel = jest.spyOn(discordChannel, 'getDiscordChannel'); - }); - - it('should throw error if channel is not in cache', async () => { - spyDiscordClientChannels.mockImplementation(() => null); - spyDiscordChannel.mockImplementation(() => null); - const compareError = new Error(`Channel not in discord cache. Send a small 'test' message to the channel and try again.`); - try { - await publish(new TokenRequestMessage(), { key: 123123 }); - } catch (error) { - expect(error).toEqual(compareError); - } - }); - - it('should send message if everything is fine', async () => { - const mockDiscordChannel = { send: jest.fn() }; - spyDiscordClientChannels.mockImplementation(() => mockDiscordChannel); - spyDiscordChannel.mockResolvedValue(() => 123123123); - - await publish(new TokenRequestMessage(), { key: 123123 }); - expect(mockDiscordChannel.send).toHaveBeenCalled(); - }); - -}); From 48cca826d68234a047b18f965f96b0bdd49e7c0d Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Thu, 6 May 2021 20:19:33 +0000 Subject: [PATCH 08/22] =?UTF-8?q?=E2=9C=85=20Added=20unit=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/tests/auth.spec.ts | 2 -- packages/backend/tests/moodle.fetch.spec.ts | 3 --- packages/backend/tests/moodle.handle.spec.ts | 26 +++++++++----------- packages/backend/tests/moodle.spec.ts | 3 +-- 4 files changed, 13 insertions(+), 21 deletions(-) diff --git a/packages/backend/tests/auth.spec.ts b/packages/backend/tests/auth.spec.ts index e0d819c6..f6ae28f4 100644 --- a/packages/backend/tests/auth.spec.ts +++ b/packages/backend/tests/auth.spec.ts @@ -10,8 +10,6 @@ import { v4 as uuidv4 } from "uuid"; import { IAuthenticatorDocument } from '../src/controllers/administrator/authenticator.schema'; jest.mock('../src/configuration/environment.ts'); -jest.mock('../src/configuration/discord.ts'); -jest.mock('../src/controllers/discord/index.ts'); describe('auth/index.ts getTokenFromHeader', () => { it('should return the correct jwt', () => { diff --git a/packages/backend/tests/moodle.fetch.spec.ts b/packages/backend/tests/moodle.fetch.spec.ts index b51fb48a..5bd4ce17 100644 --- a/packages/backend/tests/moodle.fetch.spec.ts +++ b/packages/backend/tests/moodle.fetch.spec.ts @@ -5,9 +5,6 @@ import { loggerFile } from '../src/configuration/logger'; jest.mock('node-fetch', () => jest.fn()); jest.mock('../src/configuration/environment.ts'); -jest.mock('../src/configuration/discord.ts'); -jest.mock('../src/controllers/discord/index.ts'); - const mockFetch = (res: any) => mocked(fetch).mockImplementationOnce((): Promise => Promise.resolve({ diff --git a/packages/backend/tests/moodle.handle.spec.ts b/packages/backend/tests/moodle.handle.spec.ts index b1a0945d..dcf8244b 100644 --- a/packages/backend/tests/moodle.handle.spec.ts +++ b/packages/backend/tests/moodle.handle.spec.ts @@ -1,25 +1,23 @@ -import * as discord from '../src/controllers/discord'; +import { connectorService } from '../src/controllers/connectors/service'; import mockingoose from 'mockingoose'; import { handleResources, handleAssignments } from '../src/controllers/moodle/handle'; import { IResource } from '../src/controllers/moodle/interfaces/resource.interface'; -import { ResourceMessage, AssignmentMessage } from '../src/controllers/discord/templates'; +import { ResourceMessage, AssignmentMessage } from '../src/controllers/messages/templates'; import { ICourse } from '../src/controllers/moodle/interfaces/course.interface'; import { Reminder } from '../src/controllers/moodle/schemas/reminder.schema'; -jest.mock('../src/configuration/discord.ts'); jest.mock('../src/configuration/environment.ts'); -jest.mock('../src/controllers/discord/index.ts'); jest.mock('../src/controllers/moodle/fetch.ts'); describe('moodle/handle.ts handleAssignments', () => { - let spyDiscordPublish: jest.SpyInstance; + let spyConnectorServicePublish: jest.SpyInstance; let spyReminderSave: jest.SpyInstance; let mockCourses: ICourse[]; const dateOptions = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric', hour: 'numeric', minute: 'numeric' }; beforeEach(() => { - spyDiscordPublish = jest.spyOn(discord, 'publish'); + spyConnectorServicePublish = jest.spyOn(connectorService, 'publish'); spyReminderSave = jest.spyOn(new Reminder(), 'save'); mockCourses = [ { fullname: "Course01", shortname: "C1", assignments: [{ id: 0, name: "As1", duedate: 0, timemodified: 999 }] }, @@ -43,8 +41,8 @@ describe('moodle/handle.ts handleAssignments', () => { ] await handleAssignments(mockCourses, 1000); - expect(spyDiscordPublish).toBeCalledTimes(1); - expect(spyDiscordPublish).toBeCalledWith(...expectedParameters); + expect(spyConnectorServicePublish).toBeCalledTimes(1); + expect(spyConnectorServicePublish).toBeCalledWith(...expectedParameters); }); it('should write new reminders to the database', async () => { @@ -52,7 +50,7 @@ describe('moodle/handle.ts handleAssignments', () => { mockingoose(Reminder).toReturn(null, 'findOne'); await handleAssignments(mockCourses, 2000); - expect(spyDiscordPublish).toHaveBeenCalledTimes(1); + expect(spyConnectorServicePublish).toHaveBeenCalledTimes(1); expect(spyReminderSave).toHaveBeenCalledTimes(1); }); @@ -60,18 +58,18 @@ describe('moodle/handle.ts handleAssignments', () => { mockCourses[0].assignments[0].duedate = Math.floor(Date.now() / 1000) + 3000; mockingoose(Reminder).toReturn({ assignment_id: 0 }, 'findOne'); - expect(spyDiscordPublish).toHaveBeenCalledTimes(0); + expect(spyConnectorServicePublish).toHaveBeenCalledTimes(0); }); }); describe('moodle/handle.ts handleResources', () => { - let spyDiscordPublish: jest.SpyInstance; + let spyConnectorServicePublish: jest.SpyInstance; let mockResources: IResource[]; const courseMap = new Map().set(1, 'Course01').set(2, 'Course02'); beforeEach(() => { - spyDiscordPublish = jest.spyOn(discord, 'publish'); + spyConnectorServicePublish = jest.spyOn(connectorService, 'publish'); mockResources = [ { course: 1, contentfiles: [{ timemodified: 999 }] }, { course: 2, contentfiles: [{ timemodified: 1001, fileurl: 'test/webservice', filename: 'testname' }] } @@ -93,7 +91,7 @@ describe('moodle/handle.ts handleResources', () => { ] await handleResources(mockResources, courseMap, 1000); - expect(spyDiscordPublish).toBeCalledTimes(1); - expect(spyDiscordPublish).toBeCalledWith(...expectedParameters) + expect(spyConnectorServicePublish).toBeCalledTimes(1); + expect(spyConnectorServicePublish).toBeCalledWith(...expectedParameters) }); }); diff --git a/packages/backend/tests/moodle.spec.ts b/packages/backend/tests/moodle.spec.ts index af142d1f..1b8319be 100644 --- a/packages/backend/tests/moodle.spec.ts +++ b/packages/backend/tests/moodle.spec.ts @@ -11,8 +11,7 @@ import { ICourseDetails } from '../src/controllers/moodle/interfaces/coursedetai jest.mock('../src/configuration/environment.ts'); jest.mock('../src/controllers/moodle/fetch.ts'); -jest.mock('../src/configuration/discord.ts'); -jest.mock('../src/controllers/discord/index.ts'); +jest.mock('../src/controllers/connectors/plugins/discordBot.ts'); jest.useFakeTimers(); From 23a2b0c8bd324dd85dc8fc0ab0e0d8af989d5705 Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Fri, 7 May 2021 07:51:15 +0000 Subject: [PATCH 09/22] =?UTF-8?q?=F0=9F=90=9B=20Fixed=20some=20bugs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/connectorPlugin.class.ts | 11 ++++++ .../{discordBot.ts => discordBot.class.ts} | 12 +++---- .../controllers/connectors/plugins/index.ts | 15 ++------ .../src/controllers/connectors/service.ts | 35 +++++++++++++++---- .../controllers/messages/templates/index.ts | 7 +++- .../backend/src/controllers/moodle/handle.ts | 14 ++++---- 6 files changed, 59 insertions(+), 35 deletions(-) create mode 100644 packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts rename packages/backend/src/controllers/connectors/plugins/{discordBot.ts => discordBot.class.ts} (92%) diff --git a/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts b/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts new file mode 100644 index 00000000..14b1fdc2 --- /dev/null +++ b/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts @@ -0,0 +1,11 @@ +import { IConnectorDocument } from '../schemas/connector.schema'; +import { IConnectorLogItemDocument } from '../schemas/connectorLogItem.schema'; + +export abstract class ConnectorPlugin { + public abstract send(message: string): void; + public abstract getLogs(limit: number): Promise; + public abstract update(body: { [key: string]: any }): Promise; + public abstract get objectId(): string; + public abstract get courses(): { [key: string]: string; }[]; + public abstract get isDefault(): boolean; +} diff --git a/packages/backend/src/controllers/connectors/plugins/discordBot.ts b/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts similarity index 92% rename from packages/backend/src/controllers/connectors/plugins/discordBot.ts rename to packages/backend/src/controllers/connectors/plugins/discordBot.class.ts index c114a5fc..957de96f 100644 --- a/packages/backend/src/controllers/connectors/plugins/discordBot.ts +++ b/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts @@ -3,12 +3,11 @@ import { config } from '../../../configuration/environment'; import { IConnectorDocument } from '../schemas/connector.schema'; import { ConnectorLogItem, IConnectorLogItemDocument } from '../schemas/connectorLogItem.schema'; import { connectorLogger } from '../logger'; -import { ConnectorPlugin } from '.'; +import { ConnectorPlugin } from './connectorPlugin.class'; import { object, ObjectSchema, string } from '@hapi/joi'; import { ApiError } from '../../../utils/api'; export class DiscordBotConnectorPlugin extends ConnectorPlugin { - private document: IConnectorDocument; private readonly client: Discord.Client = new Discord.Client(); private readonly updateRequestSchema: ObjectSchema = object({ channel: string().alphanum().length(18), @@ -17,12 +16,11 @@ export class DiscordBotConnectorPlugin extends ConnectorPlugin { /** * Creates an instance of DiscordBotConnectorPlugin. * - * @param {IConnectorDocument} doc mongoose document + * @param {IConnectorDocument} document mongoose document * @memberof DiscordBotConnectorPlugin */ - constructor(doc: IConnectorDocument) { + constructor(private document: IConnectorDocument) { super(); - this.document = doc; this.setUpListeners(); this.client.login(config.discordToken); @@ -74,10 +72,10 @@ export class DiscordBotConnectorPlugin extends ConnectorPlugin { (discordChannel as TextChannel).send(message) .then(() => { - connectorLogger.info('Successfully send message via discord bot!', this.objectId); + connectorLogger.info('Successfully send message via Discord bot!', this.objectId); }) .catch((error) => { - connectorLogger.info(`Failed to send message via discord bot! ${error.message}`, this.objectId); + connectorLogger.info(`Failed to send message via Discord bot! ${error.message}`, this.objectId); }); } diff --git a/packages/backend/src/controllers/connectors/plugins/index.ts b/packages/backend/src/controllers/connectors/plugins/index.ts index 9c963d0c..fe04449b 100644 --- a/packages/backend/src/controllers/connectors/plugins/index.ts +++ b/packages/backend/src/controllers/connectors/plugins/index.ts @@ -1,17 +1,6 @@ -import { IConnectorDocument } from '../schemas/connector.schema'; -import { IConnectorLogItemDocument } from '../schemas/connectorLogItem.schema'; -export { DiscordBotConnectorPlugin } from './discordBot'; - +export { DiscordBotConnectorPlugin } from './discordBot.class'; +export { ConnectorPlugin } from './connectorPlugin.class'; export enum ConnectorType { Discord = 'discord', } - -export abstract class ConnectorPlugin { - public abstract send(message: string): void; - public abstract getLogs(limit: number): Promise; - public abstract update(body: { [key: string]: any }): Promise; - public abstract get objectId(): string; - public abstract get courses(): { [key: string]: string; }[]; - public abstract get isDefault(): boolean; -} diff --git a/packages/backend/src/controllers/connectors/service.ts b/packages/backend/src/controllers/connectors/service.ts index e8d3b950..64128a8e 100644 --- a/packages/backend/src/controllers/connectors/service.ts +++ b/packages/backend/src/controllers/connectors/service.ts @@ -1,12 +1,13 @@ import { ApiError } from '../../utils/api'; import { loggerFile } from '../../configuration/logger'; -import { MessageTemplate } from '../messages/message.class'; import { ConnectorPlugin, ConnectorType, DiscordBotConnectorPlugin } from './plugins'; import { Connector, IConnectorDocument } from './schemas/connector.schema'; +import { Message } from '../messages/templates'; +import { config } from '../../configuration/environment'; class ConnectorService { - private connectors: ConnectorPlugin[]; + private connectors: ConnectorPlugin[] = []; /** * Creates an instance of ConnectorService. @@ -39,15 +40,31 @@ class ConnectorService { } /** - * Creates new connectors based on environment + * Creates new connectors based on environment variables * * @private + * @async * @memberof ConnectorService */ - private createConnectorsFromEnv(): void { + private async createConnectorsFromEnv(): Promise { loggerFile.debug('Start creating connectors from environment variables'); - // TODO: To be continue + // Create optional discord Bot + if (!!config.discordChannel && !!config.discordToken) { + const options: IConnectorDocument = { + name: 'Discord Bot (Environment)', + type: ConnectorType.Discord, + default: true, + socket: { + channel: config.discordChannel, + token: config.discordToken, + }, + } as IConnectorDocument; + + const connector = await new Connector(options).save(); + this.connectors.push(new DiscordBotConnectorPlugin(connector)); + loggerFile.debug(`Connector of type Discord (${connector._id}) has been created`); + } } /** @@ -61,10 +78,12 @@ class ConnectorService { * @param {*} options content that will be applied on template * @memberof ConnectorService */ - public publish(moodleId: string, courseId: string, template: MessageTemplate, options: any): void { + public publish(moodleId: string, courseId: string, template: Message, options: any): void { const message = template.apply(options); let messageWasSent: boolean = false; + loggerFile.info('Got new message publish order'); + this.connectors.forEach(connector => { const course = connector.courses.find(element => element.moodleId === moodleId && element.courseId === courseId); if (course === undefined) return; @@ -78,6 +97,8 @@ class ConnectorService { // If the message was sent to a plugin, stop here if (messageWasSent) return; + loggerFile.info(`No connector for course ${courseId} of moodle ${moodleId} found. Use default connectors!`); + // If not, send the message to all default connectors this.connectors.forEach(connector => { if (connector.isDefault) connector.send(message); @@ -90,7 +111,7 @@ class ConnectorService { * * @param {string} connectorId objectId of the connector * @param {[key: string]: any} body body of http request - * @returns Updated document + * @returns {Promise} Updated document * @memberof ConnectorService */ public async update(connectorId: string, body: { [key: string]: any }) : Promise { diff --git a/packages/backend/src/controllers/messages/templates/index.ts b/packages/backend/src/controllers/messages/templates/index.ts index d632ae07..ee9a3920 100644 --- a/packages/backend/src/controllers/messages/templates/index.ts +++ b/packages/backend/src/controllers/messages/templates/index.ts @@ -1,4 +1,9 @@ +import { AssignmentMessage } from './assignmentMessage.class'; +import { AssignmentReminderMessage } from './assignmentReminderMessage.class'; +import { ResourceMessage } from './resourceMessage.class'; + export { AssignmentMessage, AssignmentMessageOptions } from './assignmentMessage.class'; export { AssignmentReminderMessage, AssignmentReminderMessageOptions } from './assignmentReminderMessage.class'; export { ResourceMessage, ResourceMessageOptions } from './resourceMessage.class'; -export { TokenRequestMessage, TokenRequestMessageOptions } from './tokenRequestMessage.class'; + +export type Message = AssignmentMessage | AssignmentReminderMessage | ResourceMessage; diff --git a/packages/backend/src/controllers/moodle/handle.ts b/packages/backend/src/controllers/moodle/handle.ts index b3cfb763..49cdc21c 100644 --- a/packages/backend/src/controllers/moodle/handle.ts +++ b/packages/backend/src/controllers/moodle/handle.ts @@ -1,8 +1,8 @@ import { config } from '../../configuration/environment'; import { ICourse } from './interfaces/course.interface'; import { IResource } from './interfaces/resource.interface'; -import { publish } from '../discord'; -import { AssignmentMessage, AssignmentMessageOptions, AssignmentReminderMessage, AssignmentReminderMessageOptions, ResourceMessage, ResourceMessageOptions } from '../discord/templates'; +import { connectorService } from '../connectors/service'; +import { AssignmentMessage, AssignmentMessageOptions, AssignmentReminderMessage, AssignmentReminderMessageOptions, ResourceMessage, ResourceMessageOptions } from '../messages/templates'; import { Reminder } from './schemas/reminder.schema'; import { IContentfile } from './interfaces/contentfile.interface'; @@ -22,7 +22,7 @@ export async function handleAssignments(courses: ICourse[], lastFetch: number): day: 'numeric', hour: 'numeric', minute: 'numeric' - }; + } as Intl.DateTimeFormatOptions; for (const course of courses) { for (const assignment of course.assignments) { @@ -36,7 +36,7 @@ export async function handleAssignments(courses: ICourse[], lastFetch: number): dueDate: new Date(assignment.duedate * 1000).toLocaleString('de-DE', dateOptions) }; - await publish(new AssignmentMessage(), options); + connectorService.publish(null, null, new AssignmentMessage(), options); } // check if new deadline is incoming that hasn`t been notified about @@ -50,7 +50,7 @@ export async function handleAssignments(courses: ICourse[], lastFetch: number): title: assignment.name }; await new Reminder({assignment_id: assignment.id}).save(); - await publish(new AssignmentReminderMessage(), options); + connectorService.publish(null, null, new AssignmentReminderMessage(), options); } } } @@ -104,7 +104,7 @@ export async function handleContents(contents: any, courseName: string, lastFetc title: file.filename, link: file.fileurl.replace('/webservice', '') }; - await publish(new ResourceMessage(), options); + connectorService.publish(null, null, new ResourceMessage(), options); } } @@ -128,7 +128,7 @@ export async function handleResources(resources: IResource[], courseMap: Map Date: Fri, 7 May 2021 07:51:43 +0000 Subject: [PATCH 10/22] =?UTF-8?q?=F0=9F=94=A5=20Removed=20current=20discor?= =?UTF-8?q?d=20enpoints?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/controllers/status/status.ts | 14 -------------- packages/backend/src/routes/settings.routes.ts | 3 --- 2 files changed, 17 deletions(-) diff --git a/packages/backend/src/controllers/status/status.ts b/packages/backend/src/controllers/status/status.ts index 3331ea10..a3858508 100644 --- a/packages/backend/src/controllers/status/status.ts +++ b/packages/backend/src/controllers/status/status.ts @@ -3,8 +3,6 @@ import { Request, Response, NextFunction } from 'express'; import { ApiSuccess } from '../../utils/api'; import { fetchEnrolledCourses } from '../moodle/fetch'; import { getBaseUrl } from '../moodle/index'; -import { client } from '../../configuration/discord'; -import { getDiscordChannel } from '../discordChannel/discordChannel'; import { MoodleSettings } from '../moodle/schemas/moodle.schema'; /** @@ -33,23 +31,11 @@ export async function getStatusRequest(req: Request, res: Response, next: NextFu const moodleLastFetchTimestamp = await MoodleSettings.getLastFetch(); const moodleNextFetchTimestamp = await MoodleSettings.getNextFetch(); - const discordLastReadyTimestamp = client.readyTimestamp; - const discordCurrentChannelId = await getDiscordChannel(); - - let discordCurrentChannelName = 'Unknown'; - try { - discordCurrentChannelName = (client.channels.cache.get(discordCurrentChannelId) as any).name; - } - catch { /* continue */ } - const responseObject = { moodleConnectionStatus, moodleLastFetchTimestamp, moodleNextFetchTimestamp, moodleCurrentFetchInterval, - discordLastReadyTimestamp, - discordCurrentChannelId, - discordCurrentChannelName, }; const response = new ApiSuccess(200, responseObject); diff --git a/packages/backend/src/routes/settings.routes.ts b/packages/backend/src/routes/settings.routes.ts index 8e020738..6b0a025d 100644 --- a/packages/backend/src/routes/settings.routes.ts +++ b/packages/backend/src/routes/settings.routes.ts @@ -1,7 +1,6 @@ import { Router } from 'express'; import { setRefreshRateRequest, getRefreshRateRequest } from '../controllers/moodle/refreshRate'; import { getCourseListRequest, setCourseRequest } from '../controllers/courseList/courseList'; -import { setDiscordChannelRequest, getDiscordChannelRequest } from '../controllers/discordChannel/discordChannel'; import { adminAdministratorPostRequest, adminAdministratorGetRequest,adminAdministratorDeleteRequest } from '../controllers/administrator'; import { getStatusRequest } from '../controllers/status/status'; export const settingsRoutes = Router(); @@ -11,8 +10,6 @@ settingsRoutes.get('/refreshRate', getRefreshRateRequest); settingsRoutes.put('/refreshRate', setRefreshRateRequest); settingsRoutes.get('/courses', getCourseListRequest); settingsRoutes.put('/courses/:id', setCourseRequest); -settingsRoutes.get('/discordChannel', getDiscordChannelRequest); -settingsRoutes.put('/discordChannel', setDiscordChannelRequest); settingsRoutes.post('/administrators', adminAdministratorPostRequest); settingsRoutes.get('/administrators', adminAdministratorGetRequest); settingsRoutes.delete('/administrators/:username', adminAdministratorDeleteRequest); From fb6ef4f63b1910729b78fbbbc060c62bc983a87f Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Fri, 7 May 2021 07:59:41 +0000 Subject: [PATCH 11/22] =?UTF-8?q?=F0=9F=94=A7=20Added=20new=20environment?= =?UTF-8?q?=20variable?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/src/configuration/environment.ts | 42 ++++++++++--------- .../schemas/connectorLogItem.schema.ts | 3 +- 2 files changed, 25 insertions(+), 20 deletions(-) diff --git a/packages/backend/src/configuration/environment.ts b/packages/backend/src/configuration/environment.ts index 4333ed0c..80e94b61 100644 --- a/packages/backend/src/configuration/environment.ts +++ b/packages/backend/src/configuration/environment.ts @@ -6,23 +6,9 @@ dotenvConfig(); // define validation for all the env vars const envVarsSchema = object({ - NODE_ENV: string() - .allow('development') - .allow('production') - .allow('test') - .allow('provision') - .default('production'), - PORT: number() - .default(4040), - MONGOOSE_DEBUG: boolean() - .when('NODE_ENV', { - is: string().equal('development'), - then: boolean().default(true), - otherwise: boolean().default(false) - }), - MONGO_HOST: string() - .required() - .description('Path to your mongodb instance.'), + CONNECTOR_LOG_LIFETIME: string() + .default('31d') + .description('Defines how long log entries/items will be stored'), DISCORD_TOKEN: string() .required() .description('Discord Token for bot'), @@ -35,6 +21,15 @@ const envVarsSchema = object({ JWT_EXPIRESIN: string() .default('10m') .description('Defines how long a user will be logged in'), + MONGOOSE_DEBUG: boolean() + .when('NODE_ENV', { + is: string().equal('development'), + then: boolean().default(true), + otherwise: boolean().default(false) + }), + MONGO_HOST: string() + .required() + .description('Path to your mongodb instance.'), MOODLE_BASE_URL: string() .required() .uri() @@ -54,7 +49,15 @@ const envVarsSchema = object({ MOODLE_USERID: number() .required() .description('Moodle user Id required to fetch course details'), - REGISTRATIONTOKEN_LIFETIME: string() + NODE_ENV: string() + .allow('development') + .allow('production') + .allow('test') + .allow('provision') + .default('production'), + PORT: number() + .default(4040), + REGISTRATION_TOKEN_LIFETIME: string() .default('15m') .description('Defines how long a registration token can be used until it expires'), RP_NAME: string() @@ -79,6 +82,7 @@ const envDescriptionLink = 'https://github.com/tjarbo/discord-moodle-bot/wiki/Wh if (error) throw new Error(`Config validation error: ${error.message} \nSee ${envDescriptionLink} for more information`); export const config = { + connectorLogLifetime: envVars.CONNECTOR_LOG_LIFETIME, discordToken: envVars.DISCORD_TOKEN, discordChannel: envVars.DISCORD_CHANNEL, env: envVars.NODE_ENV, @@ -99,7 +103,7 @@ export const config = { userId: envVars.MOODLE_USERID, }, port: envVars.PORT, - registrationTokenLifetime: envVars.REGISTRATIONTOKEN_LIFETIME, + registrationTokenLifetime: envVars.REGISTRATION_TOKEN_LIFETIME, rp: { name: envVars.RP_NAME, id: envVars.RP_ID, diff --git a/packages/backend/src/controllers/connectors/schemas/connectorLogItem.schema.ts b/packages/backend/src/controllers/connectors/schemas/connectorLogItem.schema.ts index 23494e7a..64655473 100644 --- a/packages/backend/src/controllers/connectors/schemas/connectorLogItem.schema.ts +++ b/packages/backend/src/controllers/connectors/schemas/connectorLogItem.schema.ts @@ -12,9 +12,10 @@ export interface IConnectorLogItemDocument extends Document { type: ConnectorType; } +// TODO: Add expire date to envvar! const connectorLogSchema = new Schema({ connector: { type: Schema.Types.ObjectId, ref: 'Connector', required: true }, - createdAt: { type: Date, default: Date.now, expires: config.registrationTokenLifetime, }, + createdAt: { type: Date, default: Date.now, expires: config.connectorLogLifetime, }, message: { type: String, required: true }, type: { type: ConnectorLogType, required: true }, }); From 048bc5d7100f816ebc109f21e68c53efe3272d9a Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Fri, 7 May 2021 20:10:17 +0000 Subject: [PATCH 12/22] =?UTF-8?q?=F0=9F=8F=B7=20Changed=20type=20of=20cour?= =?UTF-8?q?ses?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connectors/plugins/connectorPlugin.class.ts | 2 +- .../connectors/plugins/discordBot.class.ts | 2 +- .../connectors/schemas/connector.schema.ts | 2 +- .../backend/src/controllers/connectors/service.ts | 11 +++++------ packages/backend/src/controllers/moodle/handle.ts | 8 ++++---- 5 files changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts b/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts index 14b1fdc2..6cad6e05 100644 --- a/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts +++ b/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts @@ -6,6 +6,6 @@ export abstract class ConnectorPlugin { public abstract getLogs(limit: number): Promise; public abstract update(body: { [key: string]: any }): Promise; public abstract get objectId(): string; - public abstract get courses(): { [key: string]: string; }[]; + public abstract get courses(): number[]; public abstract get isDefault(): boolean; } diff --git a/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts b/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts index 957de96f..9dfcaead 100644 --- a/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts +++ b/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts @@ -117,7 +117,7 @@ export class DiscordBotConnectorPlugin extends ConnectorPlugin { * @type {{ [key: string]: string; }[]} * @memberof DiscordBotConnectorPlugin */ - public get courses(): { [key: string]: string; }[] { + public get courses(): number[] { return this.document.courses; } diff --git a/packages/backend/src/controllers/connectors/schemas/connector.schema.ts b/packages/backend/src/controllers/connectors/schemas/connector.schema.ts index 4f879028..67733266 100644 --- a/packages/backend/src/controllers/connectors/schemas/connector.schema.ts +++ b/packages/backend/src/controllers/connectors/schemas/connector.schema.ts @@ -5,7 +5,7 @@ import { ConnectorType } from '../plugins'; export interface IConnectorDocument extends Document { [_id: string]: any; active: boolean; - courses: { [key: string]: string }[]; + courses: number[]; createdAt: Date; default: boolean; name: string; diff --git a/packages/backend/src/controllers/connectors/service.ts b/packages/backend/src/controllers/connectors/service.ts index 64128a8e..b6287a40 100644 --- a/packages/backend/src/controllers/connectors/service.ts +++ b/packages/backend/src/controllers/connectors/service.ts @@ -21,7 +21,7 @@ class ConnectorService { connectorList.forEach(connector => { switch (connector.type) { case ConnectorType.Discord: - loggerFile.debug(`Found connector of type Discord (${connector._id})`); + loggerFile.debug(`Found connector of type Discord Bot (${connector._id})`); this.connectors.push(new DiscordBotConnectorPlugin(connector)); break; @@ -72,21 +72,20 @@ class ConnectorService { * If the course is assigned to no course, it will send the message to all * connectors with the default flag. * - * @param {string} moodleId objectId of MoodleInstance * @param {number} courseId id of the moodle course * @param {MessageTemplate} template message template that will be used * @param {*} options content that will be applied on template * @memberof ConnectorService */ - public publish(moodleId: string, courseId: string, template: Message, options: any): void { + public publish(courseId: number, template: Message, options: any): void { const message = template.apply(options); let messageWasSent: boolean = false; loggerFile.info('Got new message publish order'); this.connectors.forEach(connector => { - const course = connector.courses.find(element => element.moodleId === moodleId && element.courseId === courseId); - if (course === undefined) return; + // See if course is assigned to this connector - skip if not + if (connector.courses.indexOf(courseId) === -1) return; messageWasSent = true; @@ -97,7 +96,7 @@ class ConnectorService { // If the message was sent to a plugin, stop here if (messageWasSent) return; - loggerFile.info(`No connector for course ${courseId} of moodle ${moodleId} found. Use default connectors!`); + loggerFile.info(`No connector for course ${courseId} found. Use default connectors!`); // If not, send the message to all default connectors this.connectors.forEach(connector => { diff --git a/packages/backend/src/controllers/moodle/handle.ts b/packages/backend/src/controllers/moodle/handle.ts index 49cdc21c..f671a199 100644 --- a/packages/backend/src/controllers/moodle/handle.ts +++ b/packages/backend/src/controllers/moodle/handle.ts @@ -36,7 +36,7 @@ export async function handleAssignments(courses: ICourse[], lastFetch: number): dueDate: new Date(assignment.duedate * 1000).toLocaleString('de-DE', dateOptions) }; - connectorService.publish(null, null, new AssignmentMessage(), options); + connectorService.publish(course.id, new AssignmentMessage(), options); } // check if new deadline is incoming that hasn`t been notified about @@ -50,7 +50,7 @@ export async function handleAssignments(courses: ICourse[], lastFetch: number): title: assignment.name }; await new Reminder({assignment_id: assignment.id}).save(); - connectorService.publish(null, null, new AssignmentReminderMessage(), options); + connectorService.publish(course.id, new AssignmentReminderMessage(), options); } } } @@ -104,7 +104,7 @@ export async function handleContents(contents: any, courseName: string, lastFetc title: file.filename, link: file.fileurl.replace('/webservice', '') }; - connectorService.publish(null, null, new ResourceMessage(), options); + connectorService.publish(undefined, new ResourceMessage(), options); } } @@ -128,7 +128,7 @@ export async function handleResources(resources: IResource[], courseMap: Map Date: Tue, 11 May 2021 22:22:59 +0200 Subject: [PATCH 13/22] =?UTF-8?q?=F0=9F=91=8C=20Applied=20review=20changes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/controllers/connectors/logger.ts | 6 +++--- .../controllers/connectors/plugins/discordBot.class.ts | 5 ++--- packages/backend/src/controllers/connectors/service.ts | 10 +++++----- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/packages/backend/src/controllers/connectors/logger.ts b/packages/backend/src/controllers/connectors/logger.ts index 01982f94..458816cf 100644 --- a/packages/backend/src/controllers/connectors/logger.ts +++ b/packages/backend/src/controllers/connectors/logger.ts @@ -19,7 +19,7 @@ class ConnectorLogger { } /** - * Print and stores a info message + * Prints and stores a info message * * @param message message that needs to be stored * @param objectId objectId of the connector @@ -36,7 +36,7 @@ class ConnectorLogger { } /** - * Print and stores a warning message + * Prints and stores a warning message * * @param message message that needs to be stored * @param objectId objectId of the connector @@ -53,7 +53,7 @@ class ConnectorLogger { } /** - * Print and stores a error message + * Prints and stores a error message * * @param message message that needs to be stored * @param objectId objectId of the connector diff --git a/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts b/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts index 9dfcaead..e811e36d 100644 --- a/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts +++ b/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts @@ -72,7 +72,7 @@ export class DiscordBotConnectorPlugin extends ConnectorPlugin { (discordChannel as TextChannel).send(message) .then(() => { - connectorLogger.info('Successfully send message via Discord bot!', this.objectId); + connectorLogger.info('Successfully sent message via Discord bot!', this.objectId); }) .catch((error) => { connectorLogger.info(`Failed to send message via Discord bot! ${error.message}`, this.objectId); @@ -122,8 +122,7 @@ export class DiscordBotConnectorPlugin extends ConnectorPlugin { } /** - * Returns true, if the bot is an default handler for not - * assigned courses. + * Returns true, if the bot is a default handler for unassigned courses. * * @readonly * @type {boolean} diff --git a/packages/backend/src/controllers/connectors/service.ts b/packages/backend/src/controllers/connectors/service.ts index b6287a40..8769b418 100644 --- a/packages/backend/src/controllers/connectors/service.ts +++ b/packages/backend/src/controllers/connectors/service.ts @@ -16,7 +16,7 @@ class ConnectorService { */ constructor() { loggerFile.info('ConnectorService has been started'); - // Get all connectors from Database + // Get all connectors from database Connector.find().then((connectorList: IConnectorDocument[]) => { connectorList.forEach(connector => { switch (connector.type) { @@ -33,7 +33,7 @@ class ConnectorService { // If no connector has been found, start to create connectors based on .env if (connectorList.length === 0) { - loggerFile.warn('Not connector found at database'); + loggerFile.warn('No connector found within database'); this.createConnectorsFromEnv(); } }); @@ -69,7 +69,7 @@ class ConnectorService { /** * Publish a message to connectors that have this course assigned - * If the course is assigned to no course, it will send the message to all + * If the course is not assigned to a connector, it will send the message to all * connectors with the default flag. * * @param {number} courseId id of the moodle course @@ -106,8 +106,8 @@ class ConnectorService { /** * Updates a selected connector - * Throws ApiError - * + * + * @throws ApiError * @param {string} connectorId objectId of the connector * @param {[key: string]: any} body body of http request * @returns {Promise} Updated document From ea0b4f1a28074540f425c50d20292d0aa243949f Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Wed, 12 May 2021 18:38:29 +0000 Subject: [PATCH 14/22] =?UTF-8?q?=E2=9C=85=20Added=20frist=20unit=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/tests/connectors/logger.spec.ts | 83 +++++++++++++++++++ packages/backend/tests/moodle.handle.spec.ts | 2 + packages/backend/tests/moodle.spec.ts | 1 - 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 packages/backend/tests/connectors/logger.spec.ts diff --git a/packages/backend/tests/connectors/logger.spec.ts b/packages/backend/tests/connectors/logger.spec.ts new file mode 100644 index 00000000..d8010bd1 --- /dev/null +++ b/packages/backend/tests/connectors/logger.spec.ts @@ -0,0 +1,83 @@ +import { loggerFile } from '../../src/configuration/logger'; +import { connectorLogger } from '../../src/controllers/connectors/logger'; +import { ConnectorLogItem } from '../../src/controllers/connectors/schemas/connectorLogItem.schema'; + +jest.mock('../../src/controllers/connectors/schemas/connectorLogItem.schema'); +//jest.mock('../../src/configuration/environment.ts'); +jest.mock('../../src/configuration/logger.ts'); + + +describe('connectors/logger.ts info()', () => { + let spyLogger: jest.SpyInstance; + const message = "Test"; + + beforeEach(() => { + spyLogger = jest.spyOn(loggerFile,'info'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should save message to logs', () => { + connectorLogger.info(message, "123"); + expect(spyLogger).toHaveBeenCalledWith(message); + expect(ConnectorLogItem).toHaveBeenCalled(); + }); + + it('should skip to save the message to logs', () => { + connectorLogger.info(message, "123", true); + expect(spyLogger).toHaveBeenCalledWith(message); + expect(ConnectorLogItem).not.toHaveBeenCalled(); + }); +}); + +describe('connectors/logger.ts warn()', () => { + let spyLogger: jest.SpyInstance; + const message = "Test"; + + beforeEach(() => { + spyLogger = jest.spyOn(loggerFile,'warn'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should save message to logs', () => { + connectorLogger.warn(message, "123"); + expect(spyLogger).toHaveBeenCalledWith(message); + expect(ConnectorLogItem).toHaveBeenCalled(); + }); + + it('should skip to save the message to logs', () => { + connectorLogger.warn(message, "123", true); + expect(spyLogger).toHaveBeenCalledWith(message); + expect(ConnectorLogItem).not.toHaveBeenCalled(); + }); +}); + +describe('connectors/logger.ts error()', () => { + let spyLogger: jest.SpyInstance; + const message = "Test"; + + beforeEach(() => { + spyLogger = jest.spyOn(loggerFile,'error'); + }); + + afterEach(() => { + jest.clearAllMocks(); + }); + + it('should save message to logs', () => { + connectorLogger.error(message, "123"); + expect(spyLogger).toHaveBeenCalledWith(message); + expect(ConnectorLogItem).toHaveBeenCalled(); + }); + + it('should skip to save the message to logs', () => { + connectorLogger.error(message, "123", true); + expect(spyLogger).toHaveBeenCalledWith(message); + expect(ConnectorLogItem).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/backend/tests/moodle.handle.spec.ts b/packages/backend/tests/moodle.handle.spec.ts index dcf8244b..47093ade 100644 --- a/packages/backend/tests/moodle.handle.spec.ts +++ b/packages/backend/tests/moodle.handle.spec.ts @@ -32,6 +32,7 @@ describe('moodle/handle.ts handleAssignments', () => { it('should only print assignments newer than the last fetch timestamp', async () => { // expected are the Courses with course.assignments[0].timemodified > 1000 const expectedParameters = [ + undefined, new AssignmentMessage(), { "course": "C2", @@ -82,6 +83,7 @@ describe('moodle/handle.ts handleResources', () => { it('should only print resources newer than the last fetch timestamp', async () => { const expectedParameters = [ + undefined, new ResourceMessage(), { course: 'Course02', diff --git a/packages/backend/tests/moodle.spec.ts b/packages/backend/tests/moodle.spec.ts index 1b8319be..0b203489 100644 --- a/packages/backend/tests/moodle.spec.ts +++ b/packages/backend/tests/moodle.spec.ts @@ -11,7 +11,6 @@ import { ICourseDetails } from '../src/controllers/moodle/interfaces/coursedetai jest.mock('../src/configuration/environment.ts'); jest.mock('../src/controllers/moodle/fetch.ts'); -jest.mock('../src/controllers/connectors/plugins/discordBot.ts'); jest.useFakeTimers(); From fe03fdc7b6b624949265d407bb42c1389572bdb4 Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Fri, 14 May 2021 13:59:56 +0000 Subject: [PATCH 15/22] =?UTF-8?q?=F0=9F=91=8C=20Removed=20todo=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../connectors/schemas/connectorLogItem.schema.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/controllers/connectors/schemas/connectorLogItem.schema.ts b/packages/backend/src/controllers/connectors/schemas/connectorLogItem.schema.ts index 64655473..3bef91a0 100644 --- a/packages/backend/src/controllers/connectors/schemas/connectorLogItem.schema.ts +++ b/packages/backend/src/controllers/connectors/schemas/connectorLogItem.schema.ts @@ -12,12 +12,11 @@ export interface IConnectorLogItemDocument extends Document { type: ConnectorType; } -// TODO: Add expire date to envvar! -const connectorLogSchema = new Schema({ +const connectorLogItemSchema = new Schema({ connector: { type: Schema.Types.ObjectId, ref: 'Connector', required: true }, createdAt: { type: Date, default: Date.now, expires: config.connectorLogLifetime, }, message: { type: String, required: true }, type: { type: ConnectorLogType, required: true }, }); -export const ConnectorLogItem: Model = model('connectorlog', connectorLogSchema); +export const ConnectorLogItem: Model = model('connectorlog', connectorLogItemSchema); From 7dd67a9f98fae9dceb435feb8a25b1a3074325ac Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Fri, 14 May 2021 14:00:32 +0000 Subject: [PATCH 16/22] =?UTF-8?q?=E2=99=BB=20Refactored=20connectorPlugin?= =?UTF-8?q?=20class?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../plugins/connectorPlugin.class.ts | 56 +++++++++++++++++-- .../connectors/plugins/discordBot.class.ts | 52 +---------------- 2 files changed, 53 insertions(+), 55 deletions(-) diff --git a/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts b/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts index 6cad6e05..21113df5 100644 --- a/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts +++ b/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts @@ -1,11 +1,57 @@ import { IConnectorDocument } from '../schemas/connector.schema'; -import { IConnectorLogItemDocument } from '../schemas/connectorLogItem.schema'; +import { ConnectorLogItem, IConnectorLogItemDocument } from '../schemas/connectorLogItem.schema'; export abstract class ConnectorPlugin { + + protected abstract document: IConnectorDocument; + public abstract send(message: string): void; - public abstract getLogs(limit: number): Promise; public abstract update(body: { [key: string]: any }): Promise; - public abstract get objectId(): string; - public abstract get courses(): number[]; - public abstract get isDefault(): boolean; + + /** + * Returns an array of the newest log items of this connector. + * The amount of items can be limited by parameter. Default is 50. + * + * @param {number} [limit=50] Set a limit to the amount of items + * @return {Promise} Array of ConnectorLogItemDocuments + * @memberof ConnectorPlugin + */ + public async getLogs(limit: number = 50): Promise { + const query = { + connector: this.objectId + }; + return await ConnectorLogItem.find(query).sort({ createdAt: -1 }).limit(limit); + } + + /** + * Returns the mongoDb objectId, this connector is build on. + * + * @readonly + * @type {string} + * @memberof ConnectorPlugin + */ + public get objectId(): string { return this.document.id; } + + /** + * Returns all courses, that are assigned to this bot. + * + * @readonly + * @type {{ [key: string]: string; }[]} + * @memberof ConnectorPlugin + */ + public get courses(): number[] { + return this.document.courses; + } + + /** + * Returns true, if this plugin is an default handler for not + * assigned courses. + * + * @readonly + * @type {boolean} + * @memberof ConnectorPlugin + */ + public get isDefault(): boolean { + return this.document.default; + } } diff --git a/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts b/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts index 9dfcaead..ecb8b7da 100644 --- a/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts +++ b/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts @@ -1,7 +1,6 @@ import Discord, { TextChannel } from 'discord.js'; import { config } from '../../../configuration/environment'; import { IConnectorDocument } from '../schemas/connector.schema'; -import { ConnectorLogItem, IConnectorLogItemDocument } from '../schemas/connectorLogItem.schema'; import { connectorLogger } from '../logger'; import { ConnectorPlugin } from './connectorPlugin.class'; import { object, ObjectSchema, string } from '@hapi/joi'; @@ -19,7 +18,7 @@ export class DiscordBotConnectorPlugin extends ConnectorPlugin { * @param {IConnectorDocument} document mongoose document * @memberof DiscordBotConnectorPlugin */ - constructor(private document: IConnectorDocument) { + constructor(protected document: IConnectorDocument) { super(); this.setUpListeners(); @@ -46,21 +45,6 @@ export class DiscordBotConnectorPlugin extends ConnectorPlugin { }); } - /** - * Returns an array of the newest log items of this bot. - * The amount of items can be limited by parameter. Default is 50. - * - * @param {number} [limit=50] Set a limit to the amount of items - * @return {Promise} Array of ConnectorLogItemDocuments - * @memberof DiscordBotConnectorPlugin - */ - public async getLogs(limit: number = 50): Promise { - const query = { - connector: this.objectId - }; - return await ConnectorLogItem.find(query).sort({ createdAt: -1 }).limit(limit); - } - /** * Sends the given message to the discord channel * @@ -99,37 +83,5 @@ export class DiscordBotConnectorPlugin extends ConnectorPlugin { connectorLogger.info('New values have been applied', this.objectId); return result; - } - - /** - * Returns the mongoDb objectId, this plugin is build on. - * - * @readonly - * @type {string} - * @memberof DiscordBotConnectorPlugin - */ - public get objectId(): string { return this.document.id; } - - /** - * Returns all courses, that are assigned to this bot. - * - * @readonly - * @type {{ [key: string]: string; }[]} - * @memberof DiscordBotConnectorPlugin - */ - public get courses(): number[] { - return this.document.courses; - } - - /** - * Returns true, if the bot is an default handler for not - * assigned courses. - * - * @readonly - * @type {boolean} - * @memberof DiscordBotConnectorPlugin - */ - public get isDefault(): boolean { - return this.document.default; - } + } } From 2a9413ea62f1389c0727bc38b062b5c881abb8e2 Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Fri, 14 May 2021 14:00:42 +0000 Subject: [PATCH 17/22] =?UTF-8?q?=F0=9F=9A=A7=20wip?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/tests/connectors/service.spec.ts | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 packages/backend/tests/connectors/service.spec.ts diff --git a/packages/backend/tests/connectors/service.spec.ts b/packages/backend/tests/connectors/service.spec.ts new file mode 100644 index 00000000..e69de29b From 107bc69ee3b13bb183952f7f91484d2fcfccc2c2 Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Tue, 18 May 2021 16:56:57 +0000 Subject: [PATCH 18/22] =?UTF-8?q?=F0=9F=91=8C=20Applied=20changes=20from?= =?UTF-8?q?=20@p-fruck?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/backend/src/controllers/connectors/logger.ts | 4 ++-- packages/backend/tests/connectors/service.spec.ts | 0 2 files changed, 2 insertions(+), 2 deletions(-) delete mode 100644 packages/backend/tests/connectors/service.spec.ts diff --git a/packages/backend/src/controllers/connectors/logger.ts b/packages/backend/src/controllers/connectors/logger.ts index 458816cf..e31aa990 100644 --- a/packages/backend/src/controllers/connectors/logger.ts +++ b/packages/backend/src/controllers/connectors/logger.ts @@ -19,7 +19,7 @@ class ConnectorLogger { } /** - * Prints and stores a info message + * Prints and stores an info message * * @param message message that needs to be stored * @param objectId objectId of the connector @@ -53,7 +53,7 @@ class ConnectorLogger { } /** - * Prints and stores a error message + * Prints and stores an error message * * @param message message that needs to be stored * @param objectId objectId of the connector diff --git a/packages/backend/tests/connectors/service.spec.ts b/packages/backend/tests/connectors/service.spec.ts deleted file mode 100644 index e69de29b..00000000 From 3e5ad5f36703f5e7c535f6f51acc79e42082d2d0 Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Tue, 18 May 2021 16:57:33 +0000 Subject: [PATCH 19/22] =?UTF-8?q?=F0=9F=94=A7=20Added=20further=20extensio?= =?UTF-8?q?ns=20to=20devcontainer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .devcontainer/devcontainer.json | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 6842dffc..3e87344c 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -18,6 +18,11 @@ "aaron-bond.better-comments", "streetsidesoftware.code-spell-checker", "coenraads.bracket-pair-colorizer-2", + "docsmsft.docs-markdown", + "oouo-diogo-perdigao.docthis", + "octref.vetur", + "benjaminadk.emojis4git", + "fabiospampinato.vscode-todo-plus" ] // Uncomment the next line if you want start specific services in your Docker Compose config. From 4347c54e605fd4cbe04f56c32b652c734c176b68 Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Tue, 18 May 2021 17:04:46 +0000 Subject: [PATCH 20/22] =?UTF-8?q?=F0=9F=9A=A8=20Fixed=20linter=20warnings?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/controllers/connectors/plugins/connectorPlugin.class.ts | 2 +- .../src/controllers/connectors/plugins/discordBot.class.ts | 2 +- packages/backend/src/controllers/connectors/service.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts b/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts index 21113df5..d58fd3d0 100644 --- a/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts +++ b/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts @@ -22,7 +22,7 @@ export abstract class ConnectorPlugin { }; return await ConnectorLogItem.find(query).sort({ createdAt: -1 }).limit(limit); } - + /** * Returns the mongoDb objectId, this connector is build on. * diff --git a/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts b/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts index a394c159..40c48df7 100644 --- a/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts +++ b/packages/backend/src/controllers/connectors/plugins/discordBot.class.ts @@ -83,5 +83,5 @@ export class DiscordBotConnectorPlugin extends ConnectorPlugin { connectorLogger.info('New values have been applied', this.objectId); return result; - } + } } diff --git a/packages/backend/src/controllers/connectors/service.ts b/packages/backend/src/controllers/connectors/service.ts index 8769b418..9e6aa5e3 100644 --- a/packages/backend/src/controllers/connectors/service.ts +++ b/packages/backend/src/controllers/connectors/service.ts @@ -106,7 +106,7 @@ class ConnectorService { /** * Updates a selected connector - * + * * @throws ApiError * @param {string} connectorId objectId of the connector * @param {[key: string]: any} body body of http request From e3f8b58fe19d68e589f25acc416f74f04cf2ea8e Mon Sep 17 00:00:00 2001 From: tjarbo <16938041+tjarbo@users.noreply.github.com> Date: Tue, 18 May 2021 19:02:38 +0000 Subject: [PATCH 21/22] =?UTF-8?q?=E2=9C=85=20Fixed=20unit=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/configuration/__mocks__/environment.ts | 11 +++++------ packages/backend/src/configuration/logger.ts | 2 +- packages/backend/src/controllers/connectors/index.ts | 2 -- packages/backend/tests/connectors/logger.spec.ts | 4 +--- 4 files changed, 7 insertions(+), 12 deletions(-) diff --git a/packages/backend/src/configuration/__mocks__/environment.ts b/packages/backend/src/configuration/__mocks__/environment.ts index f760cbd3..39d7c2ec 100644 --- a/packages/backend/src/configuration/__mocks__/environment.ts +++ b/packages/backend/src/configuration/__mocks__/environment.ts @@ -1,9 +1,7 @@ export const config = { - admin: { - id: '1234567890123456789', - name: 'testuser#00000', - }, + connectorLogLifetime: 1, discordToken: 'xxxxxxxxxxxxxx', + discordChannel: 'xxxxxxxxxxxxxx', env: 'test', jwt: { secret: 'secret', @@ -11,17 +9,18 @@ export const config = { }, mongo: { host: 'mongodb://localhost:27017/fmdb', - port: 27017 }, mongooseDebug: true, - port: 4040, moodle: { baseURL: 'https://moodle.example.com', + fetchInterval: '12', reminderTimeLeft: 86400, token: 'MOODLETOKEN123', useCourseShortname: true, userId: 123456 }, + port: 8080, + registrationTokenLifetime: 123, rp: { name: 'Unit Test', id: 'localhost', diff --git a/packages/backend/src/configuration/logger.ts b/packages/backend/src/configuration/logger.ts index de89408b..b659f9fe 100644 --- a/packages/backend/src/configuration/logger.ts +++ b/packages/backend/src/configuration/logger.ts @@ -1,5 +1,5 @@ import log4js from 'log4js'; -import {config} from './environment'; +import { config } from './environment'; const configLogger = { appenders: { diff --git a/packages/backend/src/controllers/connectors/index.ts b/packages/backend/src/controllers/connectors/index.ts index e7fdfd55..42b8291c 100644 --- a/packages/backend/src/controllers/connectors/index.ts +++ b/packages/backend/src/controllers/connectors/index.ts @@ -1,5 +1,3 @@ -export { connectorService } from './service'; - export enum ConnectorLogType { Info = 'info', Warning = 'warning', diff --git a/packages/backend/tests/connectors/logger.spec.ts b/packages/backend/tests/connectors/logger.spec.ts index d8010bd1..f097a7c8 100644 --- a/packages/backend/tests/connectors/logger.spec.ts +++ b/packages/backend/tests/connectors/logger.spec.ts @@ -2,10 +2,8 @@ import { loggerFile } from '../../src/configuration/logger'; import { connectorLogger } from '../../src/controllers/connectors/logger'; import { ConnectorLogItem } from '../../src/controllers/connectors/schemas/connectorLogItem.schema'; +jest.mock('../../src/configuration/environment.ts'); jest.mock('../../src/controllers/connectors/schemas/connectorLogItem.schema'); -//jest.mock('../../src/configuration/environment.ts'); -jest.mock('../../src/configuration/logger.ts'); - describe('connectors/logger.ts info()', () => { let spyLogger: jest.SpyInstance; From a7bf483624b72f7297605dce5726a62066a7cf0a Mon Sep 17 00:00:00 2001 From: Anton Plagemann <54081139+antonplagemann@users.noreply.github.com> Date: Tue, 18 May 2021 22:08:43 +0200 Subject: [PATCH 22/22] :ok_hand: Apply suggestions from code review --- .../connectors/plugins/connectorPlugin.class.ts | 2 +- packages/backend/tests/connectors/logger.spec.ts | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts b/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts index d58fd3d0..1b767776 100644 --- a/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts +++ b/packages/backend/src/controllers/connectors/plugins/connectorPlugin.class.ts @@ -24,7 +24,7 @@ export abstract class ConnectorPlugin { } /** - * Returns the mongoDb objectId, this connector is build on. + * Returns the mongoDB objectId, this connector is build on. * * @readonly * @type {string} diff --git a/packages/backend/tests/connectors/logger.spec.ts b/packages/backend/tests/connectors/logger.spec.ts index f097a7c8..ee9918b1 100644 --- a/packages/backend/tests/connectors/logger.spec.ts +++ b/packages/backend/tests/connectors/logger.spec.ts @@ -10,20 +10,20 @@ describe('connectors/logger.ts info()', () => { const message = "Test"; beforeEach(() => { - spyLogger = jest.spyOn(loggerFile,'info'); + spyLogger = jest.spyOn(loggerFile, 'info'); }); afterEach(() => { jest.clearAllMocks(); }); - it('should save message to logs', () => { + it('should save the message to logs', () => { connectorLogger.info(message, "123"); expect(spyLogger).toHaveBeenCalledWith(message); expect(ConnectorLogItem).toHaveBeenCalled(); }); - it('should skip to save the message to logs', () => { + it('should skip saving the message to logs', () => { connectorLogger.info(message, "123", true); expect(spyLogger).toHaveBeenCalledWith(message); expect(ConnectorLogItem).not.toHaveBeenCalled(); @@ -42,13 +42,13 @@ describe('connectors/logger.ts warn()', () => { jest.clearAllMocks(); }); - it('should save message to logs', () => { + it('should save the message to logs', () => { connectorLogger.warn(message, "123"); expect(spyLogger).toHaveBeenCalledWith(message); expect(ConnectorLogItem).toHaveBeenCalled(); }); - it('should skip to save the message to logs', () => { + it('should skip saving the message to logs', () => { connectorLogger.warn(message, "123", true); expect(spyLogger).toHaveBeenCalledWith(message); expect(ConnectorLogItem).not.toHaveBeenCalled(); @@ -67,13 +67,13 @@ describe('connectors/logger.ts error()', () => { jest.clearAllMocks(); }); - it('should save message to logs', () => { + it('should save the message to logs', () => { connectorLogger.error(message, "123"); expect(spyLogger).toHaveBeenCalledWith(message); expect(ConnectorLogItem).toHaveBeenCalled(); }); - it('should skip to save the message to logs', () => { + it('should skip saving the message to logs', () => { connectorLogger.error(message, "123", true); expect(spyLogger).toHaveBeenCalledWith(message); expect(ConnectorLogItem).not.toHaveBeenCalled();