From b9db70f590611583ee8a703fc157026337dc1c29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20Rialland?= Date: Wed, 5 Oct 2022 12:56:06 +0200 Subject: [PATCH] Add Mattermost bot for stand-up --- Procfile | 1 + package.json | 7 +- standup-mattermost-bot/.env.template | 1 + standup-mattermost-bot/README.md | 12 + standup-mattermost-bot/docker-compose.yml | 21 + standup-mattermost-bot/package.json | 39 ++ standup-mattermost-bot/source/config.ts | 41 ++ standup-mattermost-bot/source/index.ts | 2 + standup-mattermost-bot/source/jobs.ts | 41 ++ .../source/jobs/daily-stand-up.ts | 50 ++ .../source/jobs/refresh-token.ts | 26 + .../source/jobs/weekly-randomizer.ts | 76 +++ standup-mattermost-bot/source/mattermost.ts | 179 ++++++ standup-mattermost-bot/source/mongodb.ts | 53 ++ standup-mattermost-bot/source/oauth.ts | 67 +++ standup-mattermost-bot/source/server.ts | 81 +++ standup-mattermost-bot/source/utils.ts | 35 ++ standup-mattermost-bot/tsconfig.json | 44 ++ standup-mattermost-bot/types/index.d.ts | 1 + yarn.lock | 531 +++++++++++++++++- 20 files changed, 1297 insertions(+), 11 deletions(-) create mode 100644 standup-mattermost-bot/.env.template create mode 100644 standup-mattermost-bot/README.md create mode 100644 standup-mattermost-bot/docker-compose.yml create mode 100644 standup-mattermost-bot/package.json create mode 100644 standup-mattermost-bot/source/config.ts create mode 100644 standup-mattermost-bot/source/index.ts create mode 100644 standup-mattermost-bot/source/jobs.ts create mode 100644 standup-mattermost-bot/source/jobs/daily-stand-up.ts create mode 100644 standup-mattermost-bot/source/jobs/refresh-token.ts create mode 100644 standup-mattermost-bot/source/jobs/weekly-randomizer.ts create mode 100644 standup-mattermost-bot/source/mattermost.ts create mode 100644 standup-mattermost-bot/source/mongodb.ts create mode 100644 standup-mattermost-bot/source/oauth.ts create mode 100644 standup-mattermost-bot/source/server.ts create mode 100644 standup-mattermost-bot/source/utils.ts create mode 100644 standup-mattermost-bot/tsconfig.json create mode 100644 standup-mattermost-bot/types/index.d.ts diff --git a/Procfile b/Procfile index 91ea364ba..5fcb29685 100644 --- a/Procfile +++ b/Procfile @@ -1 +1,2 @@ web: node -v ; yarn -v ; echo "$PORT" ; yarn workspace api run start:prod +bot: node -v ; yarn -v ; echo "$PORT" ; yarn workspace standup-mattermost-bot run start:prod diff --git a/package.json b/package.json index 52426f19a..aeddf9de9 100644 --- a/package.json +++ b/package.json @@ -10,10 +10,13 @@ "modele-social", "exoneration-covid", "api", - "site" + "site", + "standup-mattermost-bot" ], "scripts": { - "scalingo-postbuild": "CI=true ; yarn test:type && yarn workspaces focus api && yarn workspace api run build && yarn workspaces focus --all && yarn test && yarn workspaces focus api --production", + "scalingo-postbuild": "CI=true ; yarn test:type && yarn build:api && yarn build:standup-mattermost-bot && yarn workspaces focus --all && yarn test && yarn workspaces focus standup-mattermost-bot api --production", + "build:api": "yarn workspaces focus api && yarn workspace api run build", + "build:standup-mattermost-bot": "yarn workspaces focus standup-mattermost-bot && yarn workspace standup-mattermost-bot run build", "lint:eslintrc": "npx eslint-config-prettier .eslintrc.cjs", "lint:eslint": "NODE_OPTIONS='--max-old-space-size=4096' eslint .", "lint:eslint:fix": "yarn lint:eslint --fix", diff --git a/standup-mattermost-bot/.env.template b/standup-mattermost-bot/.env.template new file mode 100644 index 000000000..c7c0f415e --- /dev/null +++ b/standup-mattermost-bot/.env.template @@ -0,0 +1 @@ +MONGO_URL=mongodb://root:example@localhost:27017/ diff --git a/standup-mattermost-bot/README.md b/standup-mattermost-bot/README.md new file mode 100644 index 000000000..72f5efc44 --- /dev/null +++ b/standup-mattermost-bot/README.md @@ -0,0 +1,12 @@ +# Bot Mattermost pour stand-up asynchrone + +Ce bot envoie une notification périodique sur un canal Mattermost dédié afin que chaque membre de l'équipe puisse écrire en réponse où il en est dans ses tâches. + +Il désigne également qui sera l'animateur pour chaque jour de la semaine à venir. + +Et peut-être plus à venir... + +# Détail technique + +Le bot peut être installé facilement sur Scalingo, vous pouvez aller dans le `Procfile` à la racine du projet. +Il nécessite une base de données Mongodb pour stocker les token d'authentification et la liste des animateurs de la semaine. diff --git a/standup-mattermost-bot/docker-compose.yml b/standup-mattermost-bot/docker-compose.yml new file mode 100644 index 000000000..aaeb546b9 --- /dev/null +++ b/standup-mattermost-bot/docker-compose.yml @@ -0,0 +1,21 @@ +version: '3.7' + +services: + mongo: + image: mongo + restart: always + ports: + - 27017:27017 + environment: + MONGO_INITDB_ROOT_USERNAME: root + MONGO_INITDB_ROOT_PASSWORD: example + + mongo-express: + image: mongo-express + restart: always + ports: + - 8081:8081 + environment: + ME_CONFIG_MONGODB_ADMINUSERNAME: root + ME_CONFIG_MONGODB_ADMINPASSWORD: example + ME_CONFIG_MONGODB_URL: mongodb://root:example@mongo:27017/ diff --git a/standup-mattermost-bot/package.json b/standup-mattermost-bot/package.json new file mode 100644 index 000000000..83937c2d5 --- /dev/null +++ b/standup-mattermost-bot/package.json @@ -0,0 +1,39 @@ +{ + "name": "standup-mattermost-bot", + "license": "MIT", + "version": "2.0.0", + "description": "Code source du standup-mattermost-bot", + "repository": { + "type": "git", + "url": "https://github.com/betagouv/mon-entreprise.git", + "directory": "standup-mattermost-bot" + }, + "private": true, + "engines": { + "node": "^16" + }, + "type": "module", + "scripts": { + "build": "yarn tsc", + "start": "nodemon -e \"js,ts\" -x 'NODE_OPTIONS=\"--loader ts-node/esm\" node ./source/index.ts'", + "start:prod": "NODE_ENV=production nodemon -d 500ms -w ./dist/index.js -x 'node ./dist/index.js || touch ./dist/index.js'" + }, + "dependencies": { + "@breejs/later": "^4.1.0", + "@koa/cors": "^3.4.1", + "@koa/router": "^12.0.0", + "bree": "^9.1.2", + "dotenv": "^16.0.3", + "got": "^12.5.1", + "koa": "^2.13.4", + "mongodb": "^4.10.0", + "nodemon": "^2.0.20" + }, + "devDependencies": { + "@types/koa": "^2.13.5", + "@types/koa__cors": "^3.3.0", + "@types/koa__router": "^12.0.0", + "ts-node": "^10.9.1", + "typescript": "^4.8.4" + } +} diff --git a/standup-mattermost-bot/source/config.ts b/standup-mattermost-bot/source/config.ts new file mode 100644 index 000000000..8763e1c81 --- /dev/null +++ b/standup-mattermost-bot/source/config.ts @@ -0,0 +1,41 @@ +import type { MattermostSendMessage } from './mattermost.js' + +export const PORT = process.env.PORT || 4000 +export const ORIGIN = process.env.ORIGIN || 'http://localhost:4000' +export const MONGO_URL = process.env.MONGO_URL +export const NODE_ENV = process.env.NODE_ENV + +export const serverUrl = 'https://mattermost.incubateur.net' +export const clientSecret = 'pgnch7w643yfdkdjnnjcxhpsoe' +export const clientId = 'wbkot91tjbd6byn4fbmrtu8h6o' +export const redirectUri = `${ORIGIN}/oauth` + +const days = [ + 'Lundi', + 'Mardi', + 'Mercredi', + 'Jeudi', + 'Vendredi', + 'Samedi', + 'Dimanche', +] + +interface BotConfig { + channelName: string + standupDays: string[] + messageProps: MattermostSendMessage['props'] +} + +export const botConfig: BotConfig = { + channelName: + NODE_ENV !== 'production' + ? 'startup-monentreprise-dev-bot-stand-up' + : 'startup-monentreprise-stand-up', + standupDays: days.slice(0, 4), + messageProps: { + from_webhook: 'true', + override_username: 'L’URSSAF est votre amie', + override_icon_url: + 'https://mon-entreprise.urssaf.fr/favicon/favicon-32x32.png?v=2.0', + }, +} diff --git a/standup-mattermost-bot/source/index.ts b/standup-mattermost-bot/source/index.ts new file mode 100644 index 000000000..deb8a5617 --- /dev/null +++ b/standup-mattermost-bot/source/index.ts @@ -0,0 +1,2 @@ +import './server.js' +import './jobs.js' diff --git a/standup-mattermost-bot/source/jobs.ts b/standup-mattermost-bot/source/jobs.ts new file mode 100644 index 000000000..7874232e9 --- /dev/null +++ b/standup-mattermost-bot/source/jobs.ts @@ -0,0 +1,41 @@ +import later from '@breejs/later' +import Bree, { BreeOptions } from 'bree' +import { dirname, join } from 'node:path' +import { fileURLToPath } from 'node:url' +import { NODE_ENV } from './config.js' + +const __dirname = dirname(fileURLToPath(import.meta.url)) + +const jobs: BreeOptions['jobs'] = [ + { + name: 'refresh-token', + interval: 'at 2:00', + }, + { + name: 'daily-stand-up', + interval: 'every weekday at 16:42', + }, + { + name: 'weekly-randomizer', + interval: 'on Thursday at 17:42', + }, +] + +const badJob = jobs.findIndex( + (job) => + typeof job === 'object' && + 'interval' in job && + // eslint-disable-next-line + later.parse.text(job.interval).error >= 0 +) +if (badJob >= 0) { + throw new Error(`Bad interval in job n°${badJob}`) +} + +const bree = new Bree({ + root: join(__dirname, 'jobs'), + defaultExtension: NODE_ENV === 'production' ? 'js' : 'ts', + jobs, +}) + +await bree.start() diff --git a/standup-mattermost-bot/source/jobs/daily-stand-up.ts b/standup-mattermost-bot/source/jobs/daily-stand-up.ts new file mode 100644 index 000000000..0caf51bcb --- /dev/null +++ b/standup-mattermost-bot/source/jobs/daily-stand-up.ts @@ -0,0 +1,50 @@ +import { botConfig, serverUrl } from '../config.js' +import { getUserChannels, sendMessage } from '../mattermost.js' +import { initMongodb } from '../mongodb.js' + +const mongo = await initMongodb() + +const oauth = await mongo.getOAuth() + +if (!oauth) { + throw new Error('No OAuth in database') +} + +const { accessToken } = oauth + +const standupChannel = ( + await getUserChannels({ serverUrl, accessToken, userId: 'me' }) +).body.find(({ name }) => name === botConfig.channelName) + +if (!standupChannel) { + throw new Error('Standup channel not found') +} + +const index = new Date().getDay() - 1 +const nextDayMember = (await mongo.getWeeklyTeamOrder())?.memberIds[index] +const nextDayStandup = nextDayMember + ? `:arrow_forward: Demain, c'est ${nextDayMember} qui anime le stand-up.` + : '' + +const now = new Date() + .toLocaleTimeString('fr', { + hour: '2-digit', + minute: '2-digit', + }) + .replace(':', 'h') + +await sendMessage({ + serverUrl, + accessToken, + channelId: standupChannel.id, + message: ` +Coucou tout le monde :wave: +Il est ${now}, l'heure des champion·ne·s, ou plutôt celle d'indiquer nos sujets pour le stand-up de demain. +_(Rappel : Je note ici ce dont je suis fier·e, ce sur quoi je bloque, ce que je veux montrer, ce dont j'ai besoin – aide, outillage… )_ + +${nextDayStandup} +`, + props: botConfig.messageProps, +}) + +await mongo.close() diff --git a/standup-mattermost-bot/source/jobs/refresh-token.ts b/standup-mattermost-bot/source/jobs/refresh-token.ts new file mode 100644 index 000000000..6aa524884 --- /dev/null +++ b/standup-mattermost-bot/source/jobs/refresh-token.ts @@ -0,0 +1,26 @@ +import { clientId, clientSecret, redirectUri, serverUrl } from '../config.js' +import { initMongodb } from '../mongodb.js' +import { refreshAccessToken } from '../oauth.js' +import { snakeToCamelCaseKeys } from '../utils.js' + +const mongo = await initMongodb() + +const oauth = await mongo.getOAuth() + +if (!oauth) { + throw new Error('No OAuth in database') +} + +const { refreshToken } = oauth + +const newToken = await refreshAccessToken({ + serverUrl, + refreshToken, + redirectUri, + clientId, + clientSecret, +}) + +await mongo.saveOAuth(snakeToCamelCaseKeys(newToken.body)) + +await mongo.close() diff --git a/standup-mattermost-bot/source/jobs/weekly-randomizer.ts b/standup-mattermost-bot/source/jobs/weekly-randomizer.ts new file mode 100644 index 000000000..075821b25 --- /dev/null +++ b/standup-mattermost-bot/source/jobs/weekly-randomizer.ts @@ -0,0 +1,76 @@ +import { botConfig, serverUrl } from '../config.js' +import { + getChannelMembers, + getUser, + getUserChannels, + sendMessage, +} from '../mattermost.js' +import { initMongodb } from '../mongodb.js' +import { shuffleArray, snakeToCamelCaseKeys } from '../utils.js' + +const mongo = await initMongodb() + +const oauth = await mongo.getOAuth() + +if (!oauth) { + throw new Error('No OAuth in database') +} + +const { accessToken } = oauth + +const standupChannel = ( + await getUserChannels({ serverUrl, accessToken, userId: 'me' }) +).body.find(({ name }) => name === botConfig.channelName) + +if (!standupChannel) { + throw new Error('Standup channel not found') +} + +const standupChannelMembers = ( + await getChannelMembers({ + serverUrl, + accessToken, + channelId: standupChannel.id, + }) +).body.map((x) => snakeToCamelCaseKeys(x)) + +const standupMembers = await Promise.all( + standupChannelMembers.map(({ userId }) => + getUser({ serverUrl, accessToken, userId }).then(({ body }) => + snakeToCamelCaseKeys(body) + ) + ) +) + +const shuffleMembers = shuffleArray(standupMembers) + +await mongo.setWeeklyTeamOrder(shuffleMembers.map(({ id }) => id)) + +const nextStandupOrder = botConfig.standupDays + .map((day, i) => + shuffleMembers[i] ? `- ${day} : @${shuffleMembers[i].username}` : '' + ) + .filter((x: T | null): x is T => x !== null) + .join('\n') + +const now = new Date() + .toLocaleTimeString('fr', { + hour: '2-digit', + minute: '2-digit', + }) + .replace(':', 'h') + +await sendMessage({ + serverUrl, + accessToken, + channelId: standupChannel.id, + message: ` +Il est ${now}, c'est déja la fin de semaine (ou presque pour certain :smile:) ! + +Voici l'ordre des animateurs proposé pour le stand-up de la semaine prochaine : +${nextStandupOrder} +`, + props: botConfig.messageProps, +}) + +await mongo.close() diff --git a/standup-mattermost-bot/source/mattermost.ts b/standup-mattermost-bot/source/mattermost.ts new file mode 100644 index 000000000..0dbd64b4c --- /dev/null +++ b/standup-mattermost-bot/source/mattermost.ts @@ -0,0 +1,179 @@ +import got from 'got' + +interface MattermostInput { + serverUrl: string + accessToken: string +} + +interface MattermostGetUser extends MattermostInput { + userId: string +} + +interface MattermostUser { + id: string + create_at: number + update_at: number + delete_at: number + username: string + email: string + email_verified: boolean + nickname: string + first_name: string + last_name: string + roles: string +} + +export const getUser = ({ + serverUrl, + accessToken, + userId, +}: MattermostGetUser) => + got.get(`${serverUrl}/api/v4/users/${userId}`, { + responseType: 'json', + headers: { Authorization: `Bearer ${accessToken}` }, + }) + +interface MattermostGetUserChannels extends MattermostInput { + userId: string +} + +interface MattermostChannel { + id: string + create_at: number + update_at: number + delete_at: number + team_id: string + type: string + display_name: string + name: string + header: string + purpose: string + last_post_at: number + total_msg_count: number + extra_update_at: number + creator_id: string + total_msg_count_root: number + last_root_post_at: number +} + +export const getUserChannels = ({ + serverUrl, + accessToken, + userId, +}: MattermostGetUserChannels) => + got.get(`${serverUrl}/api/v4/users/${userId}/channels`, { + responseType: 'json', + headers: { Authorization: `Bearer ${accessToken}` }, + }) + +interface MattermostGetChannelMembers extends MattermostInput { + channelId: string +} + +interface MattermostChannelMember { + channel_id: string + user_id: string + roles: string + last_viewed_at: number + msg_count: number + mention_count: number + mention_count_root: number + msg_count_root: number + notify_props: unknown + last_update_at: number + scheme_guest: false + scheme_user: boolean + scheme_admin: boolean + explicit_roles: string +} + +export const getChannelMembers = ({ + serverUrl, + accessToken, + channelId, +}: MattermostGetChannelMembers) => + got.get( + `${serverUrl}/api/v4/channels/${channelId}/members`, + { + responseType: 'json', + headers: { Authorization: `Bearer ${accessToken}` }, + } + ) + +interface MattermostPost { + id: string + create_at: number + update_at: number + delete_at: number + is_pinned: false + user_id: string + channel_id: string + root_id: string + original_id: string + message: string + type: string + props: unknown + hashtags: string + pending_post_id: string + reply_count: number + last_reply_at: number + metadata: unknown +} + +/* get posts from a channel + +interface MattermostGetChannelPosts extends MattermostInput { + channelId: string +} + +interface MattermostChannelPost { + order: string[] + posts: { [id: string]: MattermostPost } + next_post_id: string + prev_post_id: string +} + +export const getChannelPosts = ({ + serverUrl, + accessToken, + channelId, +}: MattermostGetChannelPosts) => + got.get( + `${serverUrl}/api/v4/channels/${channelId}/posts`, + { + responseType: 'json', + headers: { Authorization: `Bearer ${accessToken}` }, + } + ) + +*/ + +export interface MattermostSendMessage extends MattermostInput { + channelId: string + message: string + props?: { + from_webhook?: 'true' + override_icon_url?: string + override_username?: string + } + rootId?: string +} + +export const sendMessage = ({ + serverUrl, + accessToken, + channelId, + message, + props, + rootId, +}: MattermostSendMessage) => + got.post(`${serverUrl}/api/v4/posts`, { + json: { + channel_id: channelId, + message, + props, + root_id: rootId, + }, + responseType: 'json', + headers: { Authorization: `Bearer ${accessToken}` }, + }) diff --git a/standup-mattermost-bot/source/mongodb.ts b/standup-mattermost-bot/source/mongodb.ts new file mode 100644 index 000000000..8e33559d2 --- /dev/null +++ b/standup-mattermost-bot/source/mongodb.ts @@ -0,0 +1,53 @@ +import { MongoClient } from 'mongodb' +import { MONGO_URL } from './config.js' + +interface OAuthCollection { + accessToken: string + refreshToken: string +} +interface MemberIds { + memberIds: string[] +} + +export const initMongodb = async () => { + if (!MONGO_URL) { + throw new Error('MONGO_URL env var is empty') + } + + const client = new MongoClient(MONGO_URL) + await client.connect() + + const db = client.db('bot') + + return { + saveOAuth: ({ accessToken, refreshToken }: OAuthCollection) => { + const collection = db.collection('oauth') + + return collection.findOneAndReplace( + {}, + { accessToken, refreshToken }, + { upsert: true } + ) + }, + + getOAuth: () => { + const collection = db.collection('oauth') + + return collection.findOne() + }, + + setWeeklyTeamOrder: (memberIds: string[]) => { + const collection = db.collection('weeklyTeamOrder') + + return collection.findOneAndReplace({}, { memberIds }, { upsert: true }) + }, + + getWeeklyTeamOrder: () => { + const collection = db.collection('weeklyTeamOrder') + + return collection.findOne() + }, + + close: () => client.close(), + } +} diff --git a/standup-mattermost-bot/source/oauth.ts b/standup-mattermost-bot/source/oauth.ts new file mode 100644 index 000000000..734409e29 --- /dev/null +++ b/standup-mattermost-bot/source/oauth.ts @@ -0,0 +1,67 @@ +import got from 'got' + +export interface OAuthResponse { + access_token: string + token_type: string + expires_in: number + scope: string + refresh_token: string + id_token: string +} + +interface OAuthParams { + serverUrl: string + clientSecret: string + clientId: string + redirectUri: string +} + +interface GetOAuthParams extends OAuthParams { + code: string +} + +export const getAccessToken = ({ + serverUrl, + clientSecret, + clientId, + redirectUri, + code, +}: GetOAuthParams) => + got.post(`${serverUrl}/oauth/access_token`, { + form: { + client_secret: clientSecret, + client_id: clientId, + redirect_uri: redirectUri, + grant_type: 'authorization_code', + code, + }, + responseType: 'json', + throwHttpErrors: true, + }) + +interface RefreshOAuthParams extends OAuthParams { + serverUrl: string + clientSecret: string + clientId: string + redirectUri: string + refreshToken: string +} + +export const refreshAccessToken = ({ + serverUrl, + clientSecret, + clientId, + redirectUri, + refreshToken, +}: RefreshOAuthParams) => + got.post(`${serverUrl}/oauth/access_token`, { + form: { + client_secret: clientSecret, + client_id: clientId, + redirect_uri: redirectUri, + grant_type: 'refresh_token', + refresh_token: refreshToken, + }, + responseType: 'json', + throwHttpErrors: true, + }) diff --git a/standup-mattermost-bot/source/server.ts b/standup-mattermost-bot/source/server.ts new file mode 100644 index 000000000..e60e671ac --- /dev/null +++ b/standup-mattermost-bot/source/server.ts @@ -0,0 +1,81 @@ +import cors from '@koa/cors' +import Router from '@koa/router' +import 'dotenv/config' +import Koa from 'koa' +import { + clientId, + clientSecret, + PORT, + redirectUri, + serverUrl, +} from './config.js' +import { initMongodb } from './mongodb.js' +import { getAccessToken } from './oauth.js' +import { snakeToCamelCaseKeys } from './utils.js' + +const mongo = await initMongodb() + +type KoaState = Koa.DefaultState +type KoaContext = Koa.DefaultContext + +const app = new Koa() +const router = new Router() + +app.use(cors()) + +router.get('/connect', (ctx) => { + const url = + `${serverUrl}/oauth/authorize?` + + [ + `client_id=${clientId}`, + `redirect_uri=${redirectUri}`, + `response_type=code`, + `state=`, + ].join('&') + + ctx.redirect(url) +}) + +router.get('/oauth', async (ctx) => { + const { code, error } = ctx.query + + if (error) { + ctx.status = 400 + ctx.body = error + + return + } + if (typeof code !== 'string') { + ctx.status = 400 + ctx.body = 'Bad code' + + return + } + + try { + const { body } = await getAccessToken({ + serverUrl, + clientSecret, + clientId, + redirectUri, + code, + }) + + await mongo.saveOAuth(snakeToCamelCaseKeys(body)) + + ctx.status = 200 + } catch (err) { + // eslint-disable-next-line no-console + console.error(err) + + ctx.status = 400 + } +}) + +app.use(router.routes()) +app.use(router.allowedMethods()) + +app.listen(PORT, () => { + // eslint-disable-next-line no-console + console.log(`app listening on port ${PORT}`) +}) diff --git a/standup-mattermost-bot/source/utils.ts b/standup-mattermost-bot/source/utils.ts new file mode 100644 index 000000000..39d0a1383 --- /dev/null +++ b/standup-mattermost-bot/source/utils.ts @@ -0,0 +1,35 @@ +type CamelCase = + S extends `${infer P1}_${infer P2}${infer P3}` + ? `${Lowercase}${Uppercase}${CamelCase}` + : Lowercase + +const snakeToCamelCase = (str: T) => + str + .replace(/_/g, ' ') + .replace(/(? (+m === 0 ? '' : m.toUpperCase())) + .replace(/^./, (m) => m?.toLowerCase()) as CamelCase + +export type KeysToCamelCase = { + [K in keyof T as CamelCase]: T[K] +} + +export const snakeToCamelCaseKeys = (object: T) => + Object.fromEntries( + Object.entries(object).map( + ([key, value]) => [snakeToCamelCase(key), value], + {} + ) + ) as KeysToCamelCase + +export const shuffleArray = (array: T[]) => { + const shuffle = [...array] + + for (let i = shuffle.length - 1; i > 0; i--) { + const j = Math.floor(Math.random() * (i + 1)) + const [shuffleI, shuffleJ] = [shuffle[i], shuffle[j]] + shuffle[i] = shuffleJ + shuffle[j] = shuffleI + } + + return shuffle +} diff --git a/standup-mattermost-bot/tsconfig.json b/standup-mattermost-bot/tsconfig.json new file mode 100644 index 000000000..2213cd29e --- /dev/null +++ b/standup-mattermost-bot/tsconfig.json @@ -0,0 +1,44 @@ +{ + "compilerOptions": { + /* Basic Options */ + "incremental": true, + "target": "ES2020", + "module": "NodeNext", + "outDir": "dist", + "declaration": true, + + /* Strict Type-Checking Options */ + "strict": true, + "noImplicitAny": true, + "strictNullChecks": true, + "strictFunctionTypes": true, + "strictBindCallApply": true, + "strictPropertyInitialization": true, + "noImplicitThis": true, + "alwaysStrict": true, + + /* Additional Checks */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noFallthroughCasesInSwitch": true, + + /* Module Resolution Options */ + "moduleResolution": "NodeNext", + "isolatedModules": true, + "esModuleInterop": true, + "resolveJsonModule": true, + + /* Advanced Options */ + "forceConsistentCasingInFileNames": true, + + // temporary disable lib check waiting a fix for https://github.com/sindresorhus/got/issues/2051 + "skipLibCheck": true + }, + "ts-node": { + "esm": true, + "files": true + }, + "include": ["source", "types/**/*.d.ts"], + "exclude": ["**/node_modules", "**/dist", "vitest.config.ts"] +} diff --git a/standup-mattermost-bot/types/index.d.ts b/standup-mattermost-bot/types/index.d.ts new file mode 100644 index 000000000..43bcc64e1 --- /dev/null +++ b/standup-mattermost-bot/types/index.d.ts @@ -0,0 +1 @@ +declare module '@breejs/later' diff --git a/yarn.lock b/yarn.lock index c31fa0292..400421dae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2909,7 +2909,7 @@ __metadata: languageName: node linkType: hard -"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.18.9": +"@babel/runtime@npm:^7.10.2, @babel/runtime@npm:^7.10.5, @babel/runtime@npm:^7.18.9": version: 7.19.0 resolution: "@babel/runtime@npm:7.19.0" dependencies: @@ -3068,6 +3068,13 @@ __metadata: languageName: node linkType: hard +"@breejs/later@npm:^4.1.0": + version: 4.1.0 + resolution: "@breejs/later@npm:4.1.0" + checksum: 0d1c2e06b8b20266ec1aaa09ce96f1b24504d6248a62ac3455a0f46fb9189bae0a5960f962c02cd21dbf9794dd11566e970a8204f02af12f6d6815b32b2d2f1e + languageName: node + linkType: hard + "@bugsnag/browser@npm:^7.17.0": version: 7.17.0 resolution: "@bugsnag/browser@npm:7.17.0" @@ -3752,6 +3759,15 @@ __metadata: languageName: node linkType: hard +"@koa/cors@npm:^3.4.1": + version: 3.4.1 + resolution: "@koa/cors@npm:3.4.1" + dependencies: + vary: ^1.1.2 + checksum: 8359f19e156f36016ae3f174ef374bd377eee271b29b9106d595f53fb8c80b63d6b8db7471a466fdc68f62f06286722c5351b5463ed2d46fe88256a568045af6 + languageName: node + linkType: hard + "@koa/router@npm:^10.1.1": version: 10.1.1 resolution: "@koa/router@npm:10.1.1" @@ -3765,6 +3781,18 @@ __metadata: languageName: node linkType: hard +"@koa/router@npm:^12.0.0": + version: 12.0.0 + resolution: "@koa/router@npm:12.0.0" + dependencies: + http-errors: ^2.0.0 + koa-compose: ^4.1.0 + methods: ^1.1.2 + path-to-regexp: ^6.2.1 + checksum: 5529629f7517dba20319cf70b66c5a6111673019f6d196cd500a7b90e6663c99848c188f1bce17415d46c7b20b06566d432299a53affe0a15d05b9213917b9f6 + languageName: node + linkType: hard + "@mapbox/node-pre-gyp@npm:^1.0.5": version: 1.0.9 resolution: "@mapbox/node-pre-gyp@npm:1.0.9" @@ -8309,7 +8337,7 @@ __metadata: languageName: node linkType: hard -"@types/http-cache-semantics@npm:*": +"@types/http-cache-semantics@npm:*, @types/http-cache-semantics@npm:^4.0.1": version: 4.0.1 resolution: "@types/http-cache-semantics@npm:4.0.1" checksum: 1048aacf627829f0d5f00184e16548205cd9f964bf0841c29b36bc504509230c40bc57c39778703a1c965a6f5b416ae2cbf4c1d4589c889d2838dd9dbfccf6e9 @@ -8455,6 +8483,22 @@ __metadata: languageName: node linkType: hard +"@types/koa@npm:^2.13.5": + version: 2.13.5 + resolution: "@types/koa@npm:2.13.5" + dependencies: + "@types/accepts": "*" + "@types/content-disposition": "*" + "@types/cookies": "*" + "@types/http-assert": "*" + "@types/http-errors": "*" + "@types/keygrip": "*" + "@types/koa-compose": "*" + "@types/node": "*" + checksum: e3b634d934b79ce8f394bf4130511596081f9c073dbfb4309aa32e4c421c47049a002b65111f8d9687eabec55d5a27b1b9ae0699afa83894cb7032c3536bfa17 + languageName: node + linkType: hard + "@types/koa__cors@npm:^3.3.0": version: 3.3.0 resolution: "@types/koa__cors@npm:3.3.0" @@ -8464,6 +8508,15 @@ __metadata: languageName: node linkType: hard +"@types/koa__router@npm:^12.0.0": + version: 12.0.0 + resolution: "@types/koa__router@npm:12.0.0" + dependencies: + "@types/koa": "*" + checksum: ebfee772f7a9c5ff5e296a24443eb2c288fa2e5b8c60afbb460b41f0bd0d68c8a722fff5984eef7b7d90d057fe6f556171695ea83e2cdbeef3e2462f73c362b7 + languageName: node + linkType: hard + "@types/koa__router@npm:^8.0.11": version: 8.0.11 resolution: "@types/koa__router@npm:8.0.11" @@ -8473,6 +8526,13 @@ __metadata: languageName: node linkType: hard +"@types/lodash@npm:^4.14.165": + version: 4.14.186 + resolution: "@types/lodash@npm:4.14.186" + checksum: ee0c1368a8100bb6efb88335107473a41928fc307ff1ef4ff1278868ccddba9c04c68c36d1ffe3a0392ef4a956e1955f7de3203ec09df4f1655dd1b88485c549 + languageName: node + linkType: hard + "@types/lodash@npm:^4.14.167": version: 4.14.181 resolution: "@types/lodash@npm:4.14.181" @@ -8838,6 +8898,13 @@ __metadata: languageName: node linkType: hard +"@types/webidl-conversions@npm:*": + version: 7.0.0 + resolution: "@types/webidl-conversions@npm:7.0.0" + checksum: 60142c7ddd9eb6f907d232d6b3a81ecf990f73b5a62a004eba8bd0f54809a42ece68ce512e7e3e1d98af8b6393d66cddb96f3622d2fb223c4e9c8937c61bfed7 + languageName: node + linkType: hard + "@types/webpack-env@npm:^1.16.0": version: 1.16.3 resolution: "@types/webpack-env@npm:1.16.3" @@ -8870,6 +8937,16 @@ __metadata: languageName: node linkType: hard +"@types/whatwg-url@npm:^8.2.1": + version: 8.2.2 + resolution: "@types/whatwg-url@npm:8.2.2" + dependencies: + "@types/node": "*" + "@types/webidl-conversions": "*" + checksum: 5dc5afe078dfa1a8a266745586fa3db9baa8ce7cc904789211d1dca1d34d7f3dd17d0b7423c36bc9beab9d98aa99338f1fc60798c0af6cbb8356f20e20d9f243 + languageName: node + linkType: hard + "@types/yargs-parser@npm:*": version: 21.0.0 resolution: "@types/yargs-parser@npm:21.0.0" @@ -10977,6 +11054,13 @@ __metadata: languageName: node linkType: hard +"boolean@npm:^3.2.0": + version: 3.2.0 + resolution: "boolean@npm:3.2.0" + checksum: fb29535b8bf710ef45279677a86d14f5185d604557204abd2ca5fa3fb2a5c80e04d695c8dbf13ab269991977a79bb6c04b048220a6b2a3849853faa94f4a7d77 + languageName: node + linkType: hard + "boxen@npm:^5.0.0, boxen@npm:^5.1.2": version: 5.1.2 resolution: "boxen@npm:5.1.2" @@ -11048,6 +11132,24 @@ __metadata: languageName: node linkType: hard +"bree@npm:^9.1.2": + version: 9.1.2 + resolution: "bree@npm:9.1.2" + dependencies: + "@breejs/later": ^4.1.0 + boolean: ^3.2.0 + combine-errors: ^3.0.3 + cron-validate: ^1.4.3 + human-interval: ^2.0.1 + is-string-and-not-blank: ^0.0.2 + is-valid-path: ^0.1.1 + ms: ^2.1.3 + p-wait-for: 3 + safe-timers: ^1.1.0 + checksum: c23c6f1fc3aa27b33c0649668534806140f8fa0d80991d322304d669993ab1188b097c431b633735cd11ca0a49eff2a8b464d8eb781fcaa8e976b2c1098fc4ed + languageName: node + linkType: hard + "broccoli-node-api@npm:^1.7.0": version: 1.7.0 resolution: "broccoli-node-api@npm:1.7.0" @@ -11220,6 +11322,15 @@ __metadata: languageName: node linkType: hard +"bson@npm:^4.7.0": + version: 4.7.0 + resolution: "bson@npm:4.7.0" + dependencies: + buffer: ^5.6.0 + checksum: 83e7b64afdad5a505073a7e6206e7b345f59e7888fbcb1948fba72b6101a1baf58b7499314f8e24b650567665f7973eda048aabbb1ddcfbadfba7d6c6b0f5e83 + languageName: node + linkType: hard + "buffer-alloc-unsafe@npm:^1.1.0": version: 1.1.0 resolution: "buffer-alloc-unsafe@npm:1.1.0" @@ -11495,6 +11606,28 @@ __metadata: languageName: node linkType: hard +"cacheable-lookup@npm:^7.0.0": + version: 7.0.0 + resolution: "cacheable-lookup@npm:7.0.0" + checksum: 9e2856763fc0a7347ab34d704c010440b819d4bb5e3593b664381b7433e942dd22e67ee5581f12256f908e79b82d30b86ebbacf40a081bfe10ee93fbfbc2d6a9 + languageName: node + linkType: hard + +"cacheable-request@npm:^10.2.1": + version: 10.2.1 + resolution: "cacheable-request@npm:10.2.1" + dependencies: + "@types/http-cache-semantics": ^4.0.1 + get-stream: ^6.0.1 + http-cache-semantics: ^4.1.0 + keyv: ^4.5.0 + mimic-response: ^4.0.0 + normalize-url: ^7.1.0 + responselike: ^3.0.0 + checksum: 11bcee52d676623fbfe4b02b9f30930457796a7320a5db25392238a92a98185ffa7e87619d82a472c411c7f48eada5ec182bab41e6d849a038e84774dcdbba7d + languageName: node + linkType: hard + "cacheable-request@npm:^2.1.1": version: 2.1.4 resolution: "cacheable-request@npm:2.1.4" @@ -12330,6 +12463,16 @@ __metadata: languageName: node linkType: hard +"combine-errors@npm:^3.0.3": + version: 3.0.3 + resolution: "combine-errors@npm:3.0.3" + dependencies: + custom-error-instance: 2.1.1 + lodash.uniqby: 4.5.0 + checksum: bd0b0d2a4020f9976b8fe8eb7d5aa855b43ecacdcb61ee1fc5664d73ff8c1d7d0bbe4dd948bea7ba1870518bfc5688b89941de7a4967659418b4664cdb02884f + languageName: node + linkType: hard + "combined-stream@npm:^1.0.6, combined-stream@npm:^1.0.8, combined-stream@npm:~1.0.6": version: 1.0.8 resolution: "combined-stream@npm:1.0.8" @@ -12829,6 +12972,15 @@ __metadata: languageName: node linkType: hard +"cron-validate@npm:^1.4.3": + version: 1.4.3 + resolution: "cron-validate@npm:1.4.3" + dependencies: + yup: 0.32.9 + checksum: beb4ef6ee7a0f07ee815866a621fbb01dd27e090da0ec5ac41c2ce845469af4e0e130adde967dd310839ad9e93db6de4c3c00617baa2fb80fdf78c773977f554 + languageName: node + linkType: hard + "cross-fetch@npm:^3.1.5": version: 3.1.5 resolution: "cross-fetch@npm:3.1.5" @@ -13022,6 +13174,13 @@ __metadata: languageName: node linkType: hard +"custom-error-instance@npm:2.1.1": + version: 2.1.1 + resolution: "custom-error-instance@npm:2.1.1" + checksum: db01483864c9f4356b720b443a1f9b374758745a75199187a0ccc12505cf822bc801a0d8e3f96d727559880024f40e09667d5c08e5de0bff243c6b5ae0bd303c + languageName: node + linkType: hard + "cyclist@npm:^1.0.1": version: 1.0.1 resolution: "cyclist@npm:1.0.1" @@ -13576,7 +13735,7 @@ __metadata: languageName: node linkType: hard -"denque@npm:^2.0.1": +"denque@npm:^2.0.1, denque@npm:^2.1.0": version: 2.1.0 resolution: "denque@npm:2.1.0" checksum: 1d4ae1d05e59ac3a3481e7b478293f4b4c813819342273f3d5b826c7ffa9753c520919ba264f377e09108d24ec6cf0ec0ac729a5686cbb8f32d797126c5dae74 @@ -13988,6 +14147,13 @@ __metadata: languageName: node linkType: hard +"dotenv@npm:^16.0.3": + version: 16.0.3 + resolution: "dotenv@npm:16.0.3" + checksum: afcf03f373d7a6d62c7e9afea6328e62851d627a4e73f2e12d0a8deae1cd375892004f3021883f8aec85932cd2834b091f568ced92b4774625b321db83b827f8 + languageName: node + linkType: hard + "dotenv@npm:^8.0.0": version: 8.6.0 resolution: "dotenv@npm:8.6.0" @@ -16408,7 +16574,7 @@ __metadata: languageName: node linkType: hard -"form-data-encoder@npm:^2.1.0": +"form-data-encoder@npm:^2.1.0, form-data-encoder@npm:^2.1.2": version: 2.1.2 resolution: "form-data-encoder@npm:2.1.2" checksum: 5c6401e3ebd2ba2adfa151c9fbd1df5adae8b1dd7bca5b18ab25ad1f9b83e20ddffb6b9ff77f5006b93d7ec2032e22828c9543c0bd4b2bc5b05335c31a3ba5b9 @@ -17212,6 +17378,25 @@ __metadata: languageName: node linkType: hard +"got@npm:^12.5.1": + version: 12.5.1 + resolution: "got@npm:12.5.1" + dependencies: + "@sindresorhus/is": ^5.2.0 + "@szmarczak/http-timer": ^5.0.1 + cacheable-lookup: ^7.0.0 + cacheable-request: ^10.2.1 + decompress-response: ^6.0.0 + form-data-encoder: ^2.1.2 + get-stream: ^6.0.1 + http2-wrapper: ^2.1.10 + lowercase-keys: ^3.0.0 + p-cancelable: ^3.0.0 + responselike: ^3.0.0 + checksum: 479a279e8f0ba39270d500e8998dda897c850a9f43eef716c58fcaa7b5049e74ca3fd8ab70be1e2a59a5c8b59a83b9deab16d4e523423bb1e2dfe17401750efa + languageName: node + linkType: hard + "got@npm:^8.3.1": version: 8.3.2 resolution: "got@npm:8.3.2" @@ -17796,7 +17981,7 @@ __metadata: languageName: node linkType: hard -"http-errors@npm:2.0.0": +"http-errors@npm:2.0.0, http-errors@npm:^2.0.0": version: 2.0.0 resolution: "http-errors@npm:2.0.0" dependencies: @@ -17912,6 +18097,15 @@ __metadata: languageName: node linkType: hard +"human-interval@npm:^2.0.1": + version: 2.0.1 + resolution: "human-interval@npm:2.0.1" + dependencies: + numbered: ^1.1.0 + checksum: 8f1b37485f554d1f7a3fa2cefdbd9cee2df3ff2cfb86ae1db9f58f2db9d524d44863c2d291f04c810317f2fa7ceeda2188281985c7d65b3774d53c9e835ed55c + languageName: node + linkType: hard + "human-signals@npm:^1.1.1": version: 1.1.1 resolution: "human-signals@npm:1.1.1" @@ -18319,6 +18513,13 @@ __metadata: languageName: node linkType: hard +"ip@npm:^2.0.0": + version: 2.0.0 + resolution: "ip@npm:2.0.0" + checksum: cfcfac6b873b701996d71ec82a7dd27ba92450afdb421e356f44044ed688df04567344c36cbacea7d01b1c39a4c732dc012570ebe9bebfb06f27314bca625349 + languageName: node + linkType: hard + "ipaddr.js@npm:1.9.1": version: 1.9.1 resolution: "ipaddr.js@npm:1.9.1" @@ -18976,6 +19177,22 @@ __metadata: languageName: node linkType: hard +"is-string-and-not-blank@npm:^0.0.2": + version: 0.0.2 + resolution: "is-string-and-not-blank@npm:0.0.2" + dependencies: + is-string-blank: ^1.0.1 + checksum: 9b0fff4f0974ffaceb382dcf012587d1ad5d5013d7260a4e85621b67fbcb29ca906bcd607fe71bdd72c642f7b56b5b9e779f9d54224b826da9aa48b2003b6113 + languageName: node + linkType: hard + +"is-string-blank@npm:^1.0.1": + version: 1.0.1 + resolution: "is-string-blank@npm:1.0.1" + checksum: 00a0955c2bac08cc84f9f878d2a3fdba86997ac23c0b661e50f39efba635444d9cec84237337200be9a4e07234069318498592817614525cd959ae0d43df2151 + languageName: node + linkType: hard + "is-string@npm:^1.0.5, is-string@npm:^1.0.7": version: 1.0.7 resolution: "is-string@npm:1.0.7" @@ -19772,6 +19989,15 @@ __metadata: languageName: node linkType: hard +"keyv@npm:^4.5.0": + version: 4.5.0 + resolution: "keyv@npm:4.5.0" + dependencies: + json-buffer: 3.0.1 + checksum: d294873cf88ec8f691e5edeb7b4b884f886c5f021a01902a0e243c362449db2b55419d7fb7187d059add747b7398321e39e44d391b65f94935174ce13452714d + languageName: node + linkType: hard + "kind-of@npm:^3.0.2, kind-of@npm:^3.0.3, kind-of@npm:^3.2.0": version: 3.2.2 resolution: "kind-of@npm:3.2.2" @@ -20218,6 +20444,62 @@ __metadata: languageName: node linkType: hard +"lodash-es@npm:^4.17.15": + version: 4.17.21 + resolution: "lodash-es@npm:4.17.21" + checksum: 05cbffad6e2adbb331a4e16fbd826e7faee403a1a04873b82b42c0f22090f280839f85b95393f487c1303c8a3d2a010048bf06151a6cbe03eee4d388fb0a12d2 + languageName: node + linkType: hard + +"lodash._baseiteratee@npm:~4.7.0": + version: 4.7.0 + resolution: "lodash._baseiteratee@npm:4.7.0" + dependencies: + lodash._stringtopath: ~4.8.0 + checksum: 814a7125b9e2fa7e436c4402eae842a200189e2839b56bd6cde7cd0a3628b60842f5d39a9f5dceaf8766669b2e4a17a36ce2a213d1d6a891c1bef8a6bda36ea9 + languageName: node + linkType: hard + +"lodash._basetostring@npm:~4.12.0": + version: 4.12.0 + resolution: "lodash._basetostring@npm:4.12.0" + checksum: ccaf83827f86be5c9daeb7b939f761d6a43f0de0781bc3b6772fcb8568fbcbfa1e1082c66e5e12dd23e00ac40a18349c5a793a6a552e3574cbbcb3e1545fcb4c + languageName: node + linkType: hard + +"lodash._baseuniq@npm:~4.6.0": + version: 4.6.0 + resolution: "lodash._baseuniq@npm:4.6.0" + dependencies: + lodash._createset: ~4.0.0 + lodash._root: ~3.0.0 + checksum: 8c16fe2e80716b18c2f28bbcc902768141d432b0b98e03b30a2fba6a097377fabdc8753da232568375d2aa9502dc6b3a390200aa1467d2f685a582a46a271936 + languageName: node + linkType: hard + +"lodash._createset@npm:~4.0.0": + version: 4.0.3 + resolution: "lodash._createset@npm:4.0.3" + checksum: fb4450fbf4846aa7b420837ee44400b88664e28499388b7e04b4db38adca1305915f68a245fb2a87e031e7f440b997de4f360de6dea2712952520e97c7898de1 + languageName: node + linkType: hard + +"lodash._root@npm:~3.0.0": + version: 3.0.1 + resolution: "lodash._root@npm:3.0.1" + checksum: 3e12c6f409ae13164a8db358f44a691f1e038dad4e25463802980d0ed641ed118c147b65657501c51778c885422b913264dfbe33ec0c5d676443dd630a7e685a + languageName: node + linkType: hard + +"lodash._stringtopath@npm:~4.8.0": + version: 4.8.0 + resolution: "lodash._stringtopath@npm:4.8.0" + dependencies: + lodash._basetostring: ~4.12.0 + checksum: 00663b317796333e6315ebb4e8b590e68845de10d5d25c7585751fd9d28adf3e60e1ce85a6fbb6a0d440447c841465b91877e761239e358231eed2f52f0a5472 + languageName: node + linkType: hard + "lodash.camelcase@npm:^4.3.0": version: 4.3.0 resolution: "lodash.camelcase@npm:4.3.0" @@ -20372,6 +20654,16 @@ __metadata: languageName: node linkType: hard +"lodash.uniqby@npm:4.5.0": + version: 4.5.0 + resolution: "lodash.uniqby@npm:4.5.0" + dependencies: + lodash._baseiteratee: ~4.7.0 + lodash._baseuniq: ~4.6.0 + checksum: 40a4fdd4c31323fcb6db91ec3124020333212ca1f13e75cc9939decdd33e8b176d204fb277be36a51a855c2c90e14d67932b3b130b2f0eedc729e4cb9cdcaed1 + languageName: node + linkType: hard + "lodash@npm:^4.17.11, lodash@npm:^4.17.12, lodash@npm:^4.17.13, lodash@npm:^4.17.15, lodash@npm:^4.17.19, lodash@npm:^4.17.20, lodash@npm:^4.17.21": version: 4.17.21 resolution: "lodash@npm:4.17.21" @@ -20897,6 +21189,13 @@ __metadata: languageName: node linkType: hard +"memory-pager@npm:^1.0.2": + version: 1.5.0 + resolution: "memory-pager@npm:1.5.0" + checksum: d1a2e684583ef55c61cd3a49101da645b11ad57014dfc565e0b43baa9004b743f7e4ab81493d8fff2ab24e9950987cc3209c94bcc4fc8d7e30a475489a1f15e9 + languageName: node + linkType: hard + "meow@npm:^3.1.0": version: 3.7.0 resolution: "meow@npm:3.7.0" @@ -21092,6 +21391,13 @@ __metadata: languageName: node linkType: hard +"mimic-response@npm:^4.0.0": + version: 4.0.0 + resolution: "mimic-response@npm:4.0.0" + checksum: 33b804cc961efe206efdb1fca6a22540decdcfce6c14eb5c0c50e5ae9022267ab22ce8f5568b1f7247ba67500fe20d523d81e0e9f009b321ccd9d472e78d1850 + languageName: node + linkType: hard + "min-document@npm:^2.19.0": version: 2.19.0 resolution: "min-document@npm:2.19.0" @@ -21306,6 +21612,32 @@ __metadata: languageName: node linkType: hard +"mongodb-connection-string-url@npm:^2.5.3": + version: 2.5.4 + resolution: "mongodb-connection-string-url@npm:2.5.4" + dependencies: + "@types/whatwg-url": ^8.2.1 + whatwg-url: ^11.0.0 + checksum: 9f431826b229488808e4a8a9e6bdde0162be3e6d5cad40867b69b2199ce009f568b67dc1bf587a43367904d8184f1c68689f7ea6574ed40b396726abde9485e1 + languageName: node + linkType: hard + +"mongodb@npm:^4.10.0": + version: 4.10.0 + resolution: "mongodb@npm:4.10.0" + dependencies: + bson: ^4.7.0 + denque: ^2.1.0 + mongodb-connection-string-url: ^2.5.3 + saslprep: ^1.0.3 + socks: ^2.7.0 + dependenciesMeta: + saslprep: + optional: true + checksum: 4847fe69b6d3baddc440936d306b4d00fa40a1dafabd387f9fb6f3ecd63b27c41f11b2cc46774ac2bf17e9b508d35908ebe21f47badf3449fb7afcbde2733951 + languageName: node + linkType: hard + "moo@npm:^0.5.0, moo@npm:^0.5.1": version: 0.5.1 resolution: "moo@npm:0.5.1" @@ -21357,7 +21689,7 @@ __metadata: languageName: node linkType: hard -"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1": +"ms@npm:2.1.3, ms@npm:^2.0.0, ms@npm:^2.1.1, ms@npm:^2.1.3": version: 2.1.3 resolution: "ms@npm:2.1.3" checksum: aa92de608021b242401676e35cfa5aa42dd70cbdc082b916da7fb925c542173e36bce97ea3e804923fe92c0ad991434e4a38327e15a1b5b5f945d66df615ae6d @@ -21398,6 +21730,13 @@ __metadata: languageName: node linkType: hard +"nanoclone@npm:^0.2.1": + version: 0.2.1 + resolution: "nanoclone@npm:0.2.1" + checksum: 96b2954e22f70561f41e20d69856266c65583c2a441dae108f1dc71b716785d2c8038dac5f1d5e92b117aed3825f526b53139e2e5d6e6db8a77cfa35b3b8bf40 + languageName: node + linkType: hard + "nanoid@npm:^3.3.4": version: 3.3.4 resolution: "nanoid@npm:3.3.4" @@ -21852,6 +22191,26 @@ __metadata: languageName: node linkType: hard +"nodemon@npm:^2.0.20": + version: 2.0.20 + resolution: "nodemon@npm:2.0.20" + dependencies: + chokidar: ^3.5.2 + debug: ^3.2.7 + ignore-by-default: ^1.0.1 + minimatch: ^3.1.2 + pstree.remy: ^1.1.8 + semver: ^5.7.1 + simple-update-notifier: ^1.0.7 + supports-color: ^5.5.0 + touch: ^3.1.0 + undefsafe: ^2.0.5 + bin: + nodemon: bin/nodemon.js + checksum: 9fe858682414fe703179f4fe36c86e71f40d2693b5345c09803d7b191816a6589c5df8f1f9873bffee92893880183b95a031c86340e46b364ef1b0b7f619edbf + languageName: node + linkType: hard + "noop2@npm:^2.0.0": version: 2.0.0 resolution: "noop2@npm:2.0.0" @@ -21974,6 +22333,13 @@ __metadata: languageName: node linkType: hard +"normalize-url@npm:^7.1.0": + version: 7.2.0 + resolution: "normalize-url@npm:7.2.0" + checksum: 7753f081ee997520c9cd855f06975d7ac24b1ef58002e310d5058c831b9a6165ec2ec9fc0c5bc9e886e1257affaffa7c36731ae39073fcf74af07197997d4fb6 + languageName: node + linkType: hard + "now-and-later@npm:^2.0.0": version: 2.0.1 resolution: "now-and-later@npm:2.0.1" @@ -22064,6 +22430,13 @@ __metadata: languageName: node linkType: hard +"numbered@npm:^1.1.0": + version: 1.1.0 + resolution: "numbered@npm:1.1.0" + checksum: cdc238990fe29818cb603ac8d4050ff82a05b63e10d8848d1ff1e315231e94813c48b405e651b22d32edc8ccf1f323920d1182ea02c081f11d94ea22b1f8c17a + languageName: node + linkType: hard + "object-assign@npm:^4.0.1, object-assign@npm:^4.1.0, object-assign@npm:^4.1.1": version: 4.1.1 resolution: "object-assign@npm:4.1.1" @@ -22725,7 +23098,7 @@ __metadata: languageName: node linkType: hard -"p-wait-for@npm:^3.0.0": +"p-wait-for@npm:3, p-wait-for@npm:^3.0.0": version: 3.2.0 resolution: "p-wait-for@npm:3.2.0" dependencies: @@ -23027,7 +23400,7 @@ __metadata: languageName: node linkType: hard -"path-to-regexp@npm:^6.1.0": +"path-to-regexp@npm:^6.1.0, path-to-regexp@npm:^6.2.1": version: 6.2.1 resolution: "path-to-regexp@npm:6.2.1" checksum: f0227af8284ea13300f4293ba111e3635142f976d4197f14d5ad1f124aebd9118783dd2e5f1fe16f7273743cc3dbeddfb7493f237bb27c10fdae07020cc9b698 @@ -23597,6 +23970,13 @@ __metadata: languageName: node linkType: hard +"property-expr@npm:^2.0.4": + version: 2.0.5 + resolution: "property-expr@npm:2.0.5" + checksum: 4ebe82ce45aaf1527e96e2ab84d75d25217167ec3ff6378cf83a84fb4abc746e7c65768a79d275881602ae82f168f9a6dfaa7f5e331d0fcc83d692770bcce5f1 + languageName: node + linkType: hard + "property-information@npm:^5.0.0, property-information@npm:^5.3.0": version: 5.6.0 resolution: "property-information@npm:5.6.0" @@ -25346,6 +25726,13 @@ __metadata: languageName: node linkType: hard +"safe-timers@npm:^1.1.0": + version: 1.1.0 + resolution: "safe-timers@npm:1.1.0" + checksum: 6387c1e09fae12c4b96fb62e2575ace9ec6b1f73ad73d937e0ecc79de92cb64e56dbbfa60a8d5d7c54c035960457fa59cffd5d6b396fe0d82ed8042c889a7f1a + languageName: node + linkType: hard + "safer-buffer@npm:>= 2.1.2 < 3, safer-buffer@npm:>= 2.1.2 < 3.0.0, safer-buffer@npm:^2.0.2, safer-buffer@npm:^2.1.0, safer-buffer@npm:~2.1.0": version: 2.1.2 resolution: "safer-buffer@npm:2.1.2" @@ -25372,6 +25759,15 @@ __metadata: languageName: node linkType: hard +"saslprep@npm:^1.0.3": + version: 1.0.3 + resolution: "saslprep@npm:1.0.3" + dependencies: + sparse-bitfield: ^3.0.3 + checksum: 4fdc0b70fb5e523f977de405e12cca111f1f10dd68a0cfae0ca52c1a7919a94d1556598ba2d35f447655c3b32879846c77f9274c90806f6673248ae3cea6ee43 + languageName: node + linkType: hard + "sax@npm:>=0.6.0": version: 1.2.4 resolution: "sax@npm:1.2.4" @@ -25473,7 +25869,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:7.0.0": +"semver@npm:7.0.0, semver@npm:~7.0.0": version: 7.0.0 resolution: "semver@npm:7.0.0" bin: @@ -25709,6 +26105,15 @@ __metadata: languageName: node linkType: hard +"simple-update-notifier@npm:^1.0.7": + version: 1.0.7 + resolution: "simple-update-notifier@npm:1.0.7" + dependencies: + semver: ~7.0.0 + checksum: aaadc1f158ad5101b363d1c7aed1f30fc1cac59a760aa31702633e0e6fe423348f07d0e78185aef0aad29130a7b7f0f188c21c7bc7353f897a0ea3682e051a70 + languageName: node + linkType: hard + "sisteransi@npm:^1.0.5": version: 1.0.5 resolution: "sisteransi@npm:1.0.5" @@ -25948,6 +26353,16 @@ __metadata: languageName: node linkType: hard +"socks@npm:^2.7.0": + version: 2.7.1 + resolution: "socks@npm:2.7.1" + dependencies: + ip: ^2.0.0 + smart-buffer: ^4.2.0 + checksum: 259d9e3e8e1c9809a7f5c32238c3d4d2a36b39b83851d0f573bfde5f21c4b1288417ce1af06af1452569cd1eb0841169afd4998f0e04ba04656f6b7f0e46d748 + languageName: node + linkType: hard + "sort-keys-length@npm:^1.0.0": version: 1.0.1 resolution: "sort-keys-length@npm:1.0.1" @@ -26081,6 +26496,15 @@ __metadata: languageName: node linkType: hard +"sparse-bitfield@npm:^3.0.3": + version: 3.0.3 + resolution: "sparse-bitfield@npm:3.0.3" + dependencies: + memory-pager: ^1.0.2 + checksum: 174da88dbbcc783d5dbd26921931cc83830280b8055fb05333786ebe6fc015b9601b24972b3d55920dd2d9f5fb120576fbfa2469b08e5222c9cadf3f05210aab + languageName: node + linkType: hard + "spdx-correct@npm:^3.0.0": version: 3.1.1 resolution: "spdx-correct@npm:3.1.1" @@ -26232,6 +26656,27 @@ __metadata: languageName: node linkType: hard +"standup-mattermost-bot@workspace:standup-mattermost-bot": + version: 0.0.0-use.local + resolution: "standup-mattermost-bot@workspace:standup-mattermost-bot" + dependencies: + "@breejs/later": ^4.1.0 + "@koa/cors": ^3.4.1 + "@koa/router": ^12.0.0 + "@types/koa": ^2.13.5 + "@types/koa__cors": ^3.3.0 + "@types/koa__router": ^12.0.0 + bree: ^9.1.2 + dotenv: ^16.0.3 + got: ^12.5.1 + koa: ^2.13.4 + mongodb: ^4.10.0 + nodemon: ^2.0.20 + ts-node: ^10.9.1 + typescript: ^4.8.4 + languageName: unknown + linkType: soft + "state-toggle@npm:^1.0.0": version: 1.0.3 resolution: "state-toggle@npm:1.0.3" @@ -27379,6 +27824,13 @@ __metadata: languageName: node linkType: hard +"toposort@npm:^2.0.2": + version: 2.0.2 + resolution: "toposort@npm:2.0.2" + checksum: d64c74b570391c9432873f48e231b439ee56bc49f7cb9780b505cfdf5cb832f808d0bae072515d93834dd6bceca5bb34448b5b4b408335e4d4716eaf68195dcb + languageName: node + linkType: hard + "tosource@npm:^1.0.0": version: 1.0.0 resolution: "tosource@npm:1.0.0" @@ -27416,6 +27868,15 @@ __metadata: languageName: node linkType: hard +"tr46@npm:^3.0.0": + version: 3.0.0 + resolution: "tr46@npm:3.0.0" + dependencies: + punycode: ^2.1.1 + checksum: 44c3cc6767fb800490e6e9fd64fd49041aa4e49e1f6a012b34a75de739cc9ed3a6405296072c1df8b6389ae139c5e7c6496f659cfe13a04a4bff3a1422981270 + languageName: node + linkType: hard + "tr46@npm:~0.0.3": version: 0.0.3 resolution: "tr46@npm:0.0.3" @@ -27787,6 +28248,16 @@ __metadata: languageName: node linkType: hard +"typescript@npm:^4.8.4": + version: 4.8.4 + resolution: "typescript@npm:4.8.4" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 3e4f061658e0c8f36c820802fa809e0fd812b85687a9a2f5430bc3d0368e37d1c9605c3ce9b39df9a05af2ece67b1d844f9f6ea8ff42819f13bcb80f85629af0 + languageName: node + linkType: hard + "typescript@patch:typescript@^4.2.4#~builtin, typescript@patch:typescript@^4.5.4#~builtin, typescript@patch:typescript@^4.5.5#~builtin": version: 4.7.4 resolution: "typescript@patch:typescript@npm%3A4.7.4#~builtin::version=4.7.4&hash=a1c5e5" @@ -27807,6 +28278,16 @@ __metadata: languageName: node linkType: hard +"typescript@patch:typescript@^4.8.4#~builtin": + version: 4.8.4 + resolution: "typescript@patch:typescript@npm%3A4.8.4#~builtin::version=4.8.4&hash=a1c5e5" + bin: + tsc: bin/tsc + tsserver: bin/tsserver + checksum: 563a0ef47abae6df27a9a3ab38f75fc681f633ccf1a3502b1108e252e187787893de689220f4544aaf95a371a4eb3141e4a337deb9895de5ac3c1ca76430e5f0 + languageName: node + linkType: hard + "uglify-js@npm:^3.1.4": version: 3.16.2 resolution: "uglify-js@npm:3.16.2" @@ -28854,6 +29335,13 @@ __metadata: languageName: node linkType: hard +"webidl-conversions@npm:^7.0.0": + version: 7.0.0 + resolution: "webidl-conversions@npm:7.0.0" + checksum: f05588567a2a76428515333eff87200fae6c83c3948a7482ebb109562971e77ef6dc49749afa58abb993391227c5697b3ecca52018793e0cb4620a48f10bd21b + languageName: node + linkType: hard + "webpack-dev-middleware@npm:^3.7.3": version: 3.7.3 resolution: "webpack-dev-middleware@npm:3.7.3" @@ -29015,6 +29503,16 @@ __metadata: languageName: node linkType: hard +"whatwg-url@npm:^11.0.0": + version: 11.0.0 + resolution: "whatwg-url@npm:11.0.0" + dependencies: + tr46: ^3.0.0 + webidl-conversions: ^7.0.0 + checksum: ed4826aaa57e66bb3488a4b25c9cd476c46ba96052747388b5801f137dd740b73fde91ad207d96baf9f17fbcc80fc1a477ad65181b5eb5fa718d27c69501d7af + languageName: node + linkType: hard + "whatwg-url@npm:^5.0.0": version: 5.0.0 resolution: "whatwg-url@npm:5.0.0" @@ -29655,6 +30153,21 @@ __metadata: languageName: node linkType: hard +"yup@npm:0.32.9": + version: 0.32.9 + resolution: "yup@npm:0.32.9" + dependencies: + "@babel/runtime": ^7.10.5 + "@types/lodash": ^4.14.165 + lodash: ^4.17.20 + lodash-es: ^4.17.15 + nanoclone: ^0.2.1 + property-expr: ^2.0.4 + toposort: ^2.0.2 + checksum: 3f33850913a237fb7e59b0b5c71d2cc6bdb1d6b2b01fc24c53ec68b35ad2b95cd49f735532117136495cb57f0ec58f0a7e1f1c55fad685555c7114155170f1ea + languageName: node + linkType: hard + "z-schema@npm:^5.0.1, z-schema@npm:~5.0.2": version: 5.0.3 resolution: "z-schema@npm:5.0.3"