feat: add api support and auth

This commit is contained in:
Matthieu Bessat 2020-07-10 22:44:01 +02:00
parent 31e4b854db
commit a244d98806
21 changed files with 585 additions and 63 deletions

18
.env.example Normal file
View file

@ -0,0 +1,18 @@
S3_ORGANIZATION_ID=
S3_ACCESS_KEY=
S3_SECRET_KEY=
MONGO_URI=mongodb://root:root@127.0.0.1:27017/forumvirt?authSource=admin
SMTP_HOST=
SMTP_PORT=
SMTP_USERNAME=
SMTP_PASSWORD=
SMTP_SECURE=false
ADMIN_TOKEN=pass
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=root

View file

@ -1,6 +1,12 @@
{ {
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "^1.2.29",
"@fortawesome/free-brands-svg-icons": "^5.13.1",
"@fortawesome/free-regular-svg-icons": "^5.13.1",
"@fortawesome/free-solid-svg-icons": "^5.13.1",
"@types/cors": "^2.8.6",
"@types/express": "^4.17.6", "@types/express": "^4.17.6",
"@types/ioredis": "^4.17.0",
"@types/mongoose": "^5.7.28", "@types/mongoose": "^5.7.28",
"@types/multer": "^1.4.3", "@types/multer": "^1.4.3",
"@types/multer-s3": "^2.7.7", "@types/multer-s3": "^2.7.7",
@ -8,13 +14,17 @@
"@types/nodemailer": "^6.4.0", "@types/nodemailer": "^6.4.0",
"aws-sdk": "^2.706.0", "aws-sdk": "^2.706.0",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"cors": "^2.8.5",
"crypto-random-string": "^3.2.0",
"dotenv": "^8.2.0", "dotenv": "^8.2.0",
"express": "^4.17.1", "express": "^4.17.1",
"ioredis": "^4.17.3",
"mongoose": "^5.9.20", "mongoose": "^5.9.20",
"multer": "^1.4.2", "multer": "^1.4.2",
"multer-s3": "^2.9.0", "multer-s3": "^2.9.0",
"multer-s3-v2": "^2.2.1", "multer-s3-v2": "^2.2.1",
"mustache": "^4.0.1", "mustache": "^4.0.1",
"nanoid": "^3.1.10",
"nodemailer": "^6.4.10", "nodemailer": "^6.4.10",
"typescript": "^3.9.5" "typescript": "^3.9.5"
}, },

18
src/EmailService.ts Normal file
View file

@ -0,0 +1,18 @@
import nodemailer from 'nodemailer'
export default class EmailService {
static getTransporter() {
if (process.env.SMTP_HOST == undefined || process.env.SMTP_PORT == undefined) {
throw new Error("Invalid SMTP config")
}
return nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT),
secure: process.env.SMTP_SECURE == 'true',
auth: {
user: process.env.SMTP_USERNAME,
pass: process.env.SMTP_PASSWORD,
}
})
}
}

51
src/IconService.ts Normal file
View file

@ -0,0 +1,51 @@
import * as faSolid from '@fortawesome/free-solid-svg-icons'
import * as faBrands from '@fortawesome/free-brands-svg-icons'
import * as faRegular from '@fortawesome/free-regular-svg-icons'
import { IconDefinition } from '@fortawesome/fontawesome-common-types'
export interface IconInterface {
id: string,
width: number,
height: number,
path: string
}
export class IconService {
static snakeToCamel(str: string): string {
return str.replace(
/([-_][a-z])/g,
group => group.toUpperCase().replace('-', '').replace('_', '')
)
}
static getIcon(id: string): IconInterface|null {
let components: string[] = id.split(id.indexOf('_') !== -1 ? '_' : ' ')
if (components.length < 2) {
return null
}
let iconSet: any = {}
switch (components[0]) {
case 'fas':
iconSet = faSolid
break
case 'fab':
iconSet = faBrands
break
case 'far':
iconSet = faRegular
break
}
let iconName: string = this.snakeToCamel(components[1])
let iconVal: IconDefinition|undefined = iconSet[iconName]
if (iconVal === undefined) {
return null
}
return {
id: components[0] + ' ' + components[1],
width: iconVal.icon[0],
height: iconVal.icon[1],
path: iconVal.icon[4] as string
}
}
}

13
src/RedisService.ts Normal file
View file

@ -0,0 +1,13 @@
import Redis from 'ioredis'
import IORedis from 'ioredis'
export default class RedisService {
static getClient(): IORedis.Redis {
return new IORedis(
parseInt(process.env.REDIS_PORT === undefined ? '6379' : process.env.REDIS_PORT),
process.env.REDIS_HOST === undefined ? "127.0.0.1" : process.env.REDIS_HOST,
{ password: process.env.REDIS_PASSWORD }
)
}
}

View file

@ -3,9 +3,15 @@ import bodyParser from 'body-parser'
import express from 'express' import express from 'express'
import mongoose from 'mongoose' import mongoose from 'mongoose'
import AdminTagController from './controllers/AdminTagController' import AdminTagController from './controllers/AdminTagController'
import AdminOrganizationController from './controllers/AdminOrganizationController'
import DefaultController from './controllers/DefaultController' import DefaultController from './controllers/DefaultController'
import MediaController from './controllers/MediaController' import MediaController from './controllers/MediaController'
import dotenv from 'dotenv' import dotenv from 'dotenv'
import DelegateController from './controllers/DelegateController'
import AdminAuthMiddleware from './middlewares/AdminAuthMiddleware'
import DelegateAuthMiddleware from './middlewares/DelegateAuthMiddleware'
import PublicController from './controllers/PublicController'
import cors from 'cors'
console.log('WOOWOWO') console.log('WOOWOWO')
@ -32,20 +38,62 @@ let main = async () => {
console.log('> Connected to mongodb') console.log('> Connected to mongodb')
}) })
app.use(cors())
app.use(bodyParser.json()) app.use(bodyParser.json())
app.get('/', DefaultController.home) app.use((err: any, req: express.Request, res: express.Response, next: express.RequestHandler) => {
app.get('/association/:slug', DefaultController.publicOrganization) console.error(err.stack)
res.status(res.statusCode).json({ success: false, error: err.stack })
})
app.get('/', PublicController.home)
app.get('/association/:slug', PublicController.organization)
app.get('/icon/:id', DefaultController.viewIcon)
app.get('/email', DefaultController.sendEmail) app.get('/email', DefaultController.sendEmail)
app.get('/api/tags', AdminTagController.getTags) app.use('/admin', express.Router()
app.put('/api/tags/:id', AdminTagController.updateTag) .use(AdminAuthMiddleware.handle)
app.post('/api/tags', AdminTagController.storeTag) .get('/', DefaultController.success)
app.delete('/api/tags/:id', AdminTagController.destroyTag) .use('/tags', express.Router()
.get('/', AdminTagController.getMany)
.post('/', AdminTagController.store)
.put('/:id', AdminTagController.update)
.delete('/:id', AdminTagController.destroy)
)
.use('/organizations', express.Router()
.get('/', AdminOrganizationController.getMany)
.get('/:id', AdminOrganizationController.getOne)
.post('', AdminOrganizationController.store)
.put('/:id', AdminOrganizationController.update)
.delete('/:id', AdminOrganizationController.destroy)
.post('/:id/reset-token', AdminOrganizationController.resetToken)
.post('/:id/approve', AdminOrganizationController.approve)
.post('/:id/reject', AdminOrganizationController.reject)
)
)
app.use('/delegate', express.Router()
.use(DelegateAuthMiddleware.handle)
.get('/', DelegateController.get)
.put('/', DelegateController.update)
.put('/submit', DelegateController.submit)
.delete('/', DelegateController.destroy)
)
/*
.put('/tags/:id', AdminTagController.update)
.put('/tags/:id', AdminTagController.destroy)
.use('/organizations', () => express.Router()
.get('/', AdminOrganizationController.getMany)
)
*/
app.post('/api/upload', MediaController.uploadRoute()) app.post('/api/upload', MediaController.uploadRoute())
app.listen(port, host, () => { app.listen(port, host, () => {
console.log(`API listening on ${host}:${port}`) console.log(`API listening on ${host}:${port}`)
}) })
} }

View file

@ -1,10 +1,79 @@
import * as express from 'express' import * as express from 'express'
import Organization from '../models/Organization' import Organization from '../models/Organization'
import cryptoRandomString from 'crypto-random-string'
export default class AdminOrganizationController { export default class AdminOrganizationController {
static getOrganizations(req: express.Request, res: express.Response) { static getMany(req: express.Request, res: express.Response) {
Organization.find().then(data => { Organization.find().then(data => {
res.json(data) res.json(data)
}) })
} }
static getOne(req: express.Request, res: express.Response) {
Organization.findById(req.params.id).then(data => {
res.json(data)
})
}
static store(req: express.Request, res: express.Response) {
Organization.create({
token: cryptoRandomString({ length: 10, type: 'distinguishable' }),
...req.body
}).then(data => {
res.json({
success: true,
data
})
}).catch(err => {
res.status(400).json({
success: false,
errors: err.errors
})
})
}
static update(req: express.Request, res: express.Response) {
Organization.updateOne({ _id: req.params.id }, req.body).then(data => {
res.json({
success: true,
data
})
}).catch(err => {
res.status(400).json({
success: false,
errors: err.errors !== undefined ? err.errors : err
})
})
}
static destroy(req: express.Request, res: express.Response) {
Organization.deleteOne({ _id: req.params.id }).then(data => {
let isSuccess = data.deletedCount !== undefined && data.deletedCount > 0
res.status(isSuccess ? 200 : 400).json({
success: isSuccess,
data
})
}).catch(err => {
res.status(400).json({
success: false,
errors: err.errors
})
})
}
static sendEmailToken() {
}
static resetToken(req: express.Request, res: express.Response) {
}
static approve(req: express.Request, res: express.Response) {
}
static reject(req: express.Request, res: express.Response) {
}
} }

View file

@ -1,14 +1,26 @@
import Tag from '../models/Tag' import Tag from '../models/Tag'
import express from 'express' import express from 'express'
import { IconService, IconInterface } from '../IconService'
export default class AdminTagController { export default class AdminTagController {
static getTags(_: any, res: express.Response) { static getMany(_: any, res: express.Response) {
Tag.find().then(data => { Tag.find().then(data => {
res.json(data) res.json({ success: true, data })
}) })
} }
static storeTag(req: express.Request, res: express.Response) { static store(req: express.Request, res: express.Response) {
// verify the icon
let icon: IconInterface|null = IconService.getIcon(req.body.icon)
if (icon === null) {
return res.status(400).json({
success: false,
errors: [
{code: 'invalid-icon', message: 'Invalid font awesome icon code'}
]
})
}
req.body.icon = icon
Tag.create(req.body).then(data => { Tag.create(req.body).then(data => {
res.json({ res.json({
success: true, success: true,
@ -22,30 +34,35 @@ export default class AdminTagController {
}) })
} }
static updateTag(req: express.Request, res: express.Response) { static update(req: express.Request, res: express.Response) {
let icon: IconInterface|null = IconService.getIcon(req.body.icon)
if (icon === null) {
return res.status(400).json({
success: false,
errors: [
{code: 'invalid-icon', message: 'Invalid font awesome icon code'}
]
})
}
req.body.icon = icon
Tag.updateOne({ _id: req.params.id }, req.body).then(data => { Tag.updateOne({ _id: req.params.id }, req.body).then(data => {
res.json({ res.json({
success: true, success: true,
data data
}) })
}).catch(err => { }).catch(err => {
let errors
if (err.errors !== undefined) {
errors = err.errors
} else {
errors = err
}
res.status(400).json({ res.status(400).json({
success: false, success: false,
errors errors: err.errors !== undefined ? err.errors : err
}) })
}) })
} }
static destroyTag(req: express.Request, res: express.Response) { static destroy(req: express.Request, res: express.Response) {
Tag.deleteOne({ _id: req.params.id }).then(data => { Tag.deleteOne({ _id: req.params.id }).then(data => {
res.json({ let isSuccess = data.deletedCount !== undefined && data.deletedCount > 0
success: true, res.status(isSuccess ? 200 : 400).json({
success: isSuccess,
data data
}) })
}).catch(err => { }).catch(err => {

View file

@ -3,45 +3,65 @@ import * as express from 'express'
import nodemailer from 'nodemailer' import nodemailer from 'nodemailer'
import Mustache from 'mustache' import Mustache from 'mustache'
import fs from 'fs' import fs from 'fs'
import { IconService, IconInterface } from '../IconService'
import EmailService from '../EmailService'
export default class DefaultController { export default class DefaultController {
static home(req: express.Request, res: express.Response) { static success(req: express.Request, res: express.Response) {
// Organization.create({
// admin_name: "hello",
// email: 'spamfree@matthieubessat.fr',
// token: 'dqsdsqdsq',
// validationState: "ndsqqdsd"
// }).then(data => {
// console.log(data)
// res.json({
// success: true
// })
// }).catch(err => {
// res.json({
// success: false,
// errors: err.errors
// })
// })
res.json({ res.json({
success: true success: true
}) })
} }
static viewIcon(req: express.Request, res: express.Response) {
// let components: string[] = req.params.id.split('_')
// if (components.length < 2) {
// return res
// .status(400)
// .json({ success: false, errors: [{ code: 'invalid-input', message: 'Invalid Input icon' }] })
// }
// components = [components[0].replace(' ', ''), components[1].replace(' ', '')]
// let iconSet: any = {}
// console.log(components[0], components[1])
// if (components[0] === "fas") {
// iconSet = faSolid
// }
// if (components[0] === "fab") {
// iconSet = faBrands
// }
// if (components[0] === "far") {
// iconSet = faRegular
// }
// let iconName: string = snakeToCamel(components[1])
// let iconVal: IconDefinition|undefined = iconSet[iconName]
let icon: IconInterface|null = IconService.getIcon(req.params.id)
if (icon === null) {
return res
.status(400)
.json({ success: false, errors: [{ code: 'invalid-icon', message: 'The icon is invalid' }] })
}
res.json({ success: true, data: icon })
// res.setHeader('Content-Type', 'text/html')
// return res.send(Mustache.render(`
// <svg
// style="width: {{width}}px; height: {{height}}px;"
// aria-hidden="true"
// focusable="false"
// data-prefix="fas"
// data-icon="camera"
// class="svg-inline--fa fa-camera fa-w-16"
// role="img"
// xmlns="http://www.w3.org/2000/svg"
// viewBox="0 0 {{ width }} {{ height }}">
// <path fill="currentColor" d="{{ path }}"></path>
// </svg>`, {...icon}))
}
static async sendEmail(req: express.Request, res: express.Response) { static async sendEmail(req: express.Request, res: express.Response) {
// create reusable transporter object using the default SMTP transport // create reusable transporter object using the default SMTP transport
let transporter = nodemailer.createTransport({ EmailService.getTransporter().sendMail({
host: "node02.cluster.stantabcorp.net", from: '"Some Sender" <noreply@somedomain.com>',
port: 587, to: "spamfree@matthieubessat.fr",
secure: false,
auth: {
user: "mail@matthieubessat.fr",
pass: "",
},
});
transporter.sendMail({
from: '"Matthieu Bessat" <mail@matthieubessat.fr>',
to: "matthieu.bessat.27@gmail.com",
subject: "Hello ✔", subject: "Hello ✔",
text: "Hello world? Comment va tu Earum facilis libero excepturi sunt fuga eveniet autem. Illo odit quae aperiam et praesentium. Error dignissimos atque omnis. Ea iste in doloribus praesentium corrupti. Ut consequatur eius eveniet quia aut. Nam a rerum quis. Repudiandae sit nobis esse. Eaque ipsum qui enim. Expedita laudantium officia omnis maxime. Odio exercitationem recusandae quis consequatur voluptatum.", text: "Hello world? Comment va tu Earum facilis libero excepturi sunt fuga eveniet autem. Illo odit quae aperiam et praesentium. Error dignissimos atque omnis. Ea iste in doloribus praesentium corrupti. Ut consequatur eius eveniet quia aut. Nam a rerum quis. Repudiandae sit nobis esse. Eaque ipsum qui enim. Expedita laudantium officia omnis maxime. Odio exercitationem recusandae quis consequatur voluptatum.",
html: "<p><b>Hello world?</b> Comment va tu Earum facilis libero excepturi sunt fuga eveniet autem. Illo odit quae aperiam et praesentium. Error dignissimos atque omnis. Ea iste in doloribus praesentium corrupti. Ut consequatur eius eveniet quia aut. Nam a rerum quis. Repudiandae sit nobis esse. Eaque ipsum qui enim. Expedita laudantium officia omnis maxime. Odio exercitationem recusandae quis consequatur voluptatum.</p>", html: "<p><b>Hello world?</b> Comment va tu Earum facilis libero excepturi sunt fuga eveniet autem. Illo odit quae aperiam et praesentium. Error dignissimos atque omnis. Ea iste in doloribus praesentium corrupti. Ut consequatur eius eveniet quia aut. Nam a rerum quis. Repudiandae sit nobis esse. Eaque ipsum qui enim. Expedita laudantium officia omnis maxime. Odio exercitationem recusandae quis consequatur voluptatum.</p>",
@ -55,9 +75,9 @@ export default class DefaultController {
return res.json({ success: true }) return res.json({ success: true })
} }
static async publicOrganization(req: express.Request, res: express.Response) { // static async publicOrganization(req: express.Request, res: express.Response) {
res.setHeader('Content-Type', 'text/html') // res.setHeader('Content-Type', 'text/html')
let path: string = __dirname + '/../../templates/organization.html' // let path: string = __dirname + '/../../templates/organization.html'
return res.send(Mustache.render(fs.readFileSync(path).toString(), {})) // return res.send(Mustache.render(fs.readFileSync(path).toString(), {}))
} // }
} }

View file

@ -0,0 +1,21 @@
import Organization from '../models/Organization'
import * as express from 'express'
export default class DelegateController {
static get(req: express.Request, res: express.Response) {
res.json({ success: true, data: res.locals.organization })
}
static update(req: express.Request, res: express.Response) {
res.json({ success: true })
}
static submit(req: express.Request, res: express.Response) {
res.json({ success: true })
}
static destroy(req: express.Request, res: express.Response) {
res.json({ success: true })
}
}

View file

@ -0,0 +1,27 @@
import RedisService from '../RedisService'
import Organization from '../models/Organization'
import * as express from 'express'
import nodemailer from 'nodemailer'
import Mustache from 'mustache'
import fs from 'fs'
import { IconService, IconInterface } from '../IconService'
import IORedis from 'ioredis'
export default class PublicController {
static async home(req: express.Request, res: express.Response) {
let client: IORedis.Redis = RedisService.getClient()
await client.set('hello', 'world')
res.json({
data: await client.get('hello')
})
}
static async organization(req: express.Request, res: express.Response) {
res.setHeader('Content-Type', 'text/html')
let path: string = __dirname + '/../../templates/organization.html'
return res.send(Mustache.render(fs.readFileSync(path).toString(), {}))
}
}

View file

@ -0,0 +1,17 @@
import express from 'express'
export default class AdminAuthMiddleware {
static handle(req: express.Request, res: express.Response, next: any) {
let auth: string | undefined = req.get('Authorization')
if (auth === undefined || auth.replace('Bearer ', '') !== process.env.ADMIN_TOKEN) {
res
.status(400)
.json({
success: false,
errors: { code: 'invalid-auth', message: 'Invalid admin Authorization header' }
})
return
}
next()
}
}

View file

@ -0,0 +1,23 @@
import express from 'express'
import Organization from '../models/Organization'
export default class DelegateAuthMiddleware {
static async handle(req: express.Request, res: express.Response, next: express.NextFunction) {
let token: string | undefined = req.get('Authorization')
// fetch the token
if (token !== undefined) {
let data = await Organization.findOne({ token: token.replace('Bearer ', '') })
if (data !== null) {
res.locals.organization = data
next()
return
}
}
res
.status(400)
.json({
success: false,
errors: { code: 'invalid-auth', message: 'Invalid admin Authorization header' }
})
}
}

View file

@ -17,7 +17,6 @@ class AllowedString extends mongoose.SchemaType {
} }
cast(value: any) { cast(value: any) {
console.log(value + ' is being validated')
if (this.allowedValues.indexOf(value) === -1) { if (this.allowedValues.indexOf(value) === -1) {
let validationStr: string = 'AllowedString: ' + value + ' must be one of which: [' + this.allowedValues.join(', ') + ']' let validationStr: string = 'AllowedString: ' + value + ' must be one of which: [' + this.allowedValues.join(', ') + ']'
console.log(validationStr) console.log(validationStr)

View file

@ -1,8 +1,15 @@
import mongoose, { Schema } from 'mongoose' import mongoose, { Schema } from 'mongoose'
const Icon = new Schema({
id: { type: String, required: true },
width: { type: Number, required: true },
height: { type: Number, required: true },
path: { type: String, required: true }
})
const _Tag = new Schema({ const _Tag = new Schema({
name: { type: String, required: true }, name: { type: String, required: true },
icon: { type: String, required: true }, icon: { type: Icon, required: true },
description: { type: String } description: { type: String }
}) })

1
static/fa_icons Symbolic link
View file

@ -0,0 +1 @@
/home/mbess/font_awesome_icons/

0
templates/about.html Normal file
View file

0
templates/home.html Normal file
View file

37
templates/legals.html Normal file
View file

@ -0,0 +1,37 @@
<h1>Informations légalesw</h1>
<h3>Siège social</h3>
<p>
Espace Condorcet Centre Social<br>
12 rue Jean Moulin,<br>
27600 GAILLON
</p>
<h3>Contact</h3>
<p>
Téléphone : 02 32 77 50 80<br>
Fax : 02 32 77 50 99
</p>
<h3>Représentant légal</h3>
<p>Liliane COQUET</p>
<h3>Immatriculation</h3>
<p>
Numéro SIREN : 338 248 206 000 25<br>
Code APE 88 99 B
</p>
<h3>Hébergement</h3>
<p>
Scaleway<br>
ONLINE SAS BP 438 75366 PARIS CEDEX 08 FRANCE
</p>

View file

132
yarn.lock
View file

@ -2,6 +2,39 @@
# yarn lockfile v1 # yarn lockfile v1
"@fortawesome/fontawesome-common-types@^0.2.29":
version "0.2.29"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.29.tgz#e1a456b643237462d390304cab6975ff3fd68397"
integrity sha512-cY+QfDTbZ7XVxzx7jxbC98Oxr/zc7R2QpTLqTxqlfyXDrAJjzi/xUIqAUsygELB62JIrbsWxtSRhayKFkGI7MA==
"@fortawesome/fontawesome-svg-core@^1.2.29":
version "1.2.29"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-1.2.29.tgz#34ef32824664534f9e4ef37982ebf286b899a189"
integrity sha512-xmPmP2t8qrdo8RyKihTkGb09RnZoc+7HFBCnr0/6ZhStdGDSLeEd7ajV181+2W29NWIFfylO13rU+s3fpy3cnA==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.29"
"@fortawesome/free-brands-svg-icons@^5.13.1":
version "5.13.1"
resolved "https://registry.yarnpkg.com/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-5.13.1.tgz#fef07a4b957dc1708f2191a94cdfcd6901c35383"
integrity sha512-dKwF+NpIV2LVCNBA7hibH53k+ChF4Wu59P2z35gu3zwRBZpmpLVhS9k1/RiSqUqkyXUQvA2rSv48GY6wp5axZQ==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.29"
"@fortawesome/free-regular-svg-icons@^5.13.1":
version "5.13.1"
resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-5.13.1.tgz#02ac0c2fd52948d2139eca3ca42b267f17e3d725"
integrity sha512-sSeaqqmv2ovA5LKcrbh3VnEDZHVhaxijWKm4R0AdT0eG21pgxNsJbStD8lW9z6bgSuWXRNHhbhOmARuRCLS8tw==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.29"
"@fortawesome/free-solid-svg-icons@^5.13.1":
version "5.13.1"
resolved "https://registry.yarnpkg.com/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-5.13.1.tgz#010a846b718a0f110b3cd137d072639b4e8bd41a"
integrity sha512-LQH/0L1p4+rqtoSHa9qFYR84hpuRZKqaQ41cfBQx8b68p21zoWSekTAeA54I/2x9VlCHDLFlG74Nmdg4iTPQOg==
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.29"
"@sindresorhus/is@^0.14.0": "@sindresorhus/is@^0.14.0":
version "0.14.0" version "0.14.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea" resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.14.0.tgz#9fb3a3cf3132328151f353de4632e01e52102bea"
@ -41,6 +74,13 @@
dependencies: dependencies:
"@types/node" "*" "@types/node" "*"
"@types/cors@^2.8.6":
version "2.8.6"
resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.6.tgz#cfaab33c49c15b1ded32f235111ce9123009bd02"
integrity sha512-invOmosX0DqbpA+cE2yoHGUlF/blyf7nB0OGYBBiH27crcVm5NmFaZkLP4Ta1hGaesckCi5lVLlydNJCxkTOSg==
dependencies:
"@types/express" "*"
"@types/express-serve-static-core@*": "@types/express-serve-static-core@*":
version "4.17.8" version "4.17.8"
resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz#b8f7b714138536742da222839892e203df569d1c" resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.17.8.tgz#b8f7b714138536742da222839892e203df569d1c"
@ -60,6 +100,13 @@
"@types/qs" "*" "@types/qs" "*"
"@types/serve-static" "*" "@types/serve-static" "*"
"@types/ioredis@^4.17.0":
version "4.17.0"
resolved "https://registry.yarnpkg.com/@types/ioredis/-/ioredis-4.17.0.tgz#25fea62388f546958d53e31bbb6284b60c793e60"
integrity sha512-2wmT+lB9JAHSYlBrmJGeYDQ4cGlIgxmaC3Bv85LJws/G/OkoFoxPezhMqtTp0JMCgsMadIoI9c43QvRz8WKLSw==
dependencies:
"@types/node" "*"
"@types/mime@*": "@types/mime@*":
version "2.0.2" version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.2.tgz#857a118d8634c84bba7ae14088e4508490cd5da5" resolved "https://registry.yarnpkg.com/@types/mime/-/mime-2.0.2.tgz#857a118d8634c84bba7ae14088e4508490cd5da5"
@ -370,6 +417,11 @@ clone-response@^1.0.2:
dependencies: dependencies:
mimic-response "^1.0.0" mimic-response "^1.0.0"
cluster-key-slot@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
color-convert@^2.0.1: color-convert@^2.0.1:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
@ -453,11 +505,26 @@ core-util-is@~1.0.0:
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
cors@^2.8.5:
version "2.8.5"
resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29"
integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==
dependencies:
object-assign "^4"
vary "^1"
crypto-random-string@^2.0.0: crypto-random-string@^2.0.0:
version "2.0.0" version "2.0.0"
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5" resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA== integrity sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==
crypto-random-string@^3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-3.2.0.tgz#d513ef0c2ac6ff7cad5769de585d9bf2ad5a2b4d"
integrity sha512-8vPu5bsKaq2uKRy3OL7h1Oo7RayAWB8sYexLKAqvCXVib8SxgbmoF1IN4QMKjBv8uI8mp5gPPMbiRah25GMrVQ==
dependencies:
type-fest "^0.8.1"
debug@2.6.9, debug@^2.2.0: debug@2.6.9, debug@^2.2.0:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
@ -479,6 +546,13 @@ debug@^3.1.0, debug@^3.2.6:
dependencies: dependencies:
ms "^2.1.1" ms "^2.1.1"
debug@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791"
integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==
dependencies:
ms "^2.1.1"
decompress-response@^3.3.0: decompress-response@^3.3.0:
version "3.3.0" version "3.3.0"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
@ -501,7 +575,7 @@ delayed-stream@~1.0.0:
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
denque@^1.4.1: denque@^1.1.0, denque@^1.4.1:
version "1.4.1" version "1.4.1"
resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf"
integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ== integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==
@ -822,6 +896,21 @@ ini@^1.3.5, ini@~1.3.0:
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927"
integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==
ioredis@^4.17.3:
version "4.17.3"
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.17.3.tgz#9938c60e4ca685f75326337177bdc2e73ae9c9dc"
integrity sha512-iRvq4BOYzNFkDnSyhx7cmJNOi1x/HWYe+A4VXHBu4qpwJaGT1Mp+D2bVGJntH9K/Z/GeOM/Nprb8gB3bmitz1Q==
dependencies:
cluster-key-slot "^1.1.0"
debug "^4.1.1"
denque "^1.1.0"
lodash.defaults "^4.2.0"
lodash.flatten "^4.4.0"
redis-commands "1.5.0"
redis-errors "^1.2.0"
redis-parser "^3.0.0"
standard-as-callback "^2.0.1"
ipaddr.js@1.9.1: ipaddr.js@1.9.1:
version "1.9.1" version "1.9.1"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3"
@ -947,6 +1036,16 @@ latest-version@^5.0.0:
dependencies: dependencies:
package-json "^6.3.0" package-json "^6.3.0"
lodash.defaults@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
lodash.flatten@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
lowercase-keys@^1.0.0, lowercase-keys@^1.0.1: lowercase-keys@^1.0.0, lowercase-keys@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
@ -1128,6 +1227,11 @@ mustache@^4.0.1:
resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.0.1.tgz#d99beb031701ad433338e7ea65e0489416c854a2" resolved "https://registry.yarnpkg.com/mustache/-/mustache-4.0.1.tgz#d99beb031701ad433338e7ea65e0489416c854a2"
integrity sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA== integrity sha512-yL5VE97+OXn4+Er3THSmTdCFCtx5hHWzrolvH+JObZnUYwuaG7XV+Ch4fR2cIrcYI0tFHxS7iyFYl14bW8y2sA==
nanoid@^3.1.10:
version "3.1.10"
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.10.tgz#69a8a52b77892de0d11cede96bc9762852145bc4"
integrity sha512-iZFMXKeXWkxzlfmMfM91gw7YhN2sdJtixY+eZh9V6QWJWTOiurhpKhBMgr82pfzgSqglQgqYSCowEYsz8D++6w==
negotiator@0.6.2: negotiator@0.6.2:
version "0.6.2" version "0.6.2"
resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb"
@ -1171,7 +1275,7 @@ normalize-url@^4.1.0:
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129"
integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ==
object-assign@^4.1.1: object-assign@^4, object-assign@^4.1.1:
version "4.1.1" version "4.1.1"
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
@ -1333,6 +1437,23 @@ readdirp@~3.4.0:
dependencies: dependencies:
picomatch "^2.2.1" picomatch "^2.2.1"
redis-commands@1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.5.0.tgz#80d2e20698fe688f227127ff9e5164a7dd17e785"
integrity sha512-6KxamqpZ468MeQC3bkWmCB1fp56XL64D4Kf0zJSwDZbVLLm7KFkoIcHrgRvQ+sk8dnhySs7+yBg94yIkAK7aJg==
redis-errors@^1.0.0, redis-errors@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=
redis-parser@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=
dependencies:
redis-errors "^1.0.0"
regexp-clone@1.0.0, regexp-clone@^1.0.0: regexp-clone@1.0.0, regexp-clone@^1.0.0:
version "1.0.0" version "1.0.0"
resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63" resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63"
@ -1482,6 +1603,11 @@ sparse-bitfield@^3.0.3:
dependencies: dependencies:
memory-pager "^1.0.2" memory-pager "^1.0.2"
standard-as-callback@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.0.1.tgz#ed8bb25648e15831759b6023bdb87e6b60b38126"
integrity sha512-NQOxSeB8gOI5WjSaxjBgog2QFw55FV8TkS6Y07BiB3VJ8xNTvUYm0wl0s8ObgQ5NhdpnNfigMIKjgPESzgr4tg==
"statuses@>= 1.5.0 < 2", statuses@~1.5.0: "statuses@>= 1.5.0 < 2", statuses@~1.5.0:
version "1.5.0" version "1.5.0"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
@ -1706,7 +1832,7 @@ uuid@3.3.2:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
vary@~1.1.2: vary@^1, vary@~1.1.2:
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw= integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=