From 16c17ac0ca52f7a89cbce4284ecdd1acefd2201c Mon Sep 17 00:00:00 2001 From: capitanwesler Date: Thu, 4 Mar 2021 23:45:14 +0000 Subject: [PATCH] =?UTF-8?q?=F0=9F=91=BEMigrating=20routes=20and=20function?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 3 +- package.json | 3 +- src/controllers/AnimeController.ts | 154 ++++++++++++++++--- src/controllers/DirectoryController.ts | 184 ++++++++++++++++++++++ src/controllers/UtilsController.ts | 143 +++++++++++++++++ src/database/connection.ts | 20 +++ src/database/models/anime.model.ts | 35 +++++ src/middlewares/middleware.ts | 31 ++-- src/routes.ts | 106 ++++++++----- src/server.ts | 11 +- yarn.lock | 204 ++++++++++++++----------- 11 files changed, 728 insertions(+), 166 deletions(-) create mode 100644 src/controllers/DirectoryController.ts create mode 100644 src/controllers/UtilsController.ts create mode 100644 src/database/connection.ts create mode 100644 src/database/models/anime.model.ts diff --git a/.env.example b/.env.example index 9a69c24..772484b 100644 --- a/.env.example +++ b/.env.example @@ -1 +1,2 @@ -PORT=5000 \ No newline at end of file +PORT= +DATABASE= \ No newline at end of file diff --git a/package.json b/package.json index 4e8de86..a54a132 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,8 @@ "got": "^11.8.1", "helmet": "^4.4.1", "mongodb": "^3.6.4", - "rss-to-json": "^1.1.2", + "mongoose": "^5.11.18", + "rss-parser": "^3.12.0", "tough-cookie": "^4.0.0", "ts-node-dev": "^1.1.1" }, diff --git a/src/controllers/AnimeController.ts b/src/controllers/AnimeController.ts index 60b893c..a048a62 100644 --- a/src/controllers/AnimeController.ts +++ b/src/controllers/AnimeController.ts @@ -1,7 +1,14 @@ -import { Request, Response } from 'express'; +import { NextFunction, Request, Response } from 'express'; import { requestGot } from '../utils/requestCall'; +import { transformUrlServer } from '../utils/transformerUrl'; import urls from '../utils/urls'; +/* + AnimeController - a class to manage the schedule, + top, all the animes, return the last episodes + with async to return promises. +*/ + interface Schedule { title: string; mal_id: number; @@ -27,19 +34,46 @@ interface Top { score: string; } +interface Episode { + id: string; + title: string; + image: string; + episode: number; + servers: { id: string; url: string; direct: boolean }; +} + +interface Movie { + id: string; + title: string; + type: string; + page: string; + banner: string; + image: string; + synopsis: string; + status: string; + rate: string; + genres: string[]; + episodes: object[]; +} + export default class AnimeController { - async schedule(req: Request, res: Response) { + async schedule(req: Request, res: Response, next: NextFunction) { const { day } = req.params; + let data: any; - const data: any = await requestGot(`${urls.BASE_JIKAN}schedule/${day}`, { - parse: true, - scrapy: false, - }); + try { + data = await requestGot(`${urls.BASE_JIKAN}schedule/${day}`, { + parse: true, + scrapy: false, + }); + } catch (err) { + return next(err); + } - const animeList: Schedule[] = data[day].map((doc: Schedule) => ({ - title: doc.title, - mal_id: doc.mal_id, - image: doc.image_url, + const animeList: Schedule[] = data[day].map((item: Schedule) => ({ + title: item.title, + mal_id: item.mal_id, + image: item.image_url, })); if (animeList.length > 0) { @@ -51,7 +85,7 @@ export default class AnimeController { } } - async top(req: Request, res: Response) { + async top(req: Request, res: Response, next: NextFunction) { const { type, subtype, page } = req.params; let data: any; @@ -68,9 +102,8 @@ export default class AnimeController { scrapy: false, }); } - } catch (error) { - console.log(error); - return res.status(404); + } catch (err) { + return next(err); } const top: Top[] = data.top.map((item: Top) => ({ @@ -91,11 +124,17 @@ export default class AnimeController { } } - async getAllAnimes(req: Request, res: Response) { - const data: any = await requestGot(`${urls.BASE_ANIMEFLV}api/animes/list`, { - parse: true, - scrapy: false, - }); + async getAllAnimes(req: Request, res: Response, next: NextFunction) { + let data: any; + + try { + data = await requestGot(`${urls.BASE_ANIMEFLV}api/animes/list`, { + parse: true, + scrapy: false, + }); + } catch (err) { + return next(err); + } const animes: Anime[] = data.map((item: any) => ({ index: item[0], @@ -111,4 +150,81 @@ export default class AnimeController { res.status(500).json({ message: 'Aruppi lost in the shell' }); } } + + async getLastEpisodes(req: Request, res: Response, next: NextFunction) { + let data: any; + let episodes: Episode[] = []; + + try { + data = await requestGot(`${urls.BASE_ANIMEFLV_JELU}LatestEpisodesAdded`, { + parse: true, + scrapy: false, + }); + } catch (err) { + return next(err); + } + + for (const episode of data.episodes) { + const formattedEpisode: Episode = { + id: episode.id, + title: episode.title, + image: episode.poster, + episode: episode.episode, + servers: await transformUrlServer(episode.servers), + }; + + episodes.push(formattedEpisode); + } + + if (episodes.length > 0) { + res.status(200).json({ + episodes, + }); + } else { + res.status(500).json({ message: 'Aruppi lost in the shell' }); + } + } + + async getContent(req: Request, res: Response, next: NextFunction) { + const { type, page, url } = req.params; + let data: any; + + try { + data = await requestGot( + `${urls.BASE_ANIMEFLV_JELU}${ + url.charAt(0).toUpperCase() + url.slice(1) + }/${type}/${page}`, + { + parse: true, + scrapy: false, + }, + ); + } catch (err) { + return next(err); + } + + const animes: Movie[] = data[url.toLowerCase()].map((item: any) => { + return { + id: item.id, + title: item.title, + type: url.toLowerCase(), + page: page, + banner: item.banner, + image: item.poster, + synopsis: item.synopsis, + status: item.debut, + rate: item.rating, + genres: item.genres.map((genre: any) => genre), + episodes: item.episodes.map((episode: any) => episode), + }; + }); + + if (animes.length > 0) { + res.status(200).json({ + animes, + }); + } else { + res.status(500).json({ message: 'Aruppi lost in the shell' }); + } + } } diff --git a/src/controllers/DirectoryController.ts b/src/controllers/DirectoryController.ts new file mode 100644 index 0000000..df6cbde --- /dev/null +++ b/src/controllers/DirectoryController.ts @@ -0,0 +1,184 @@ +import { NextFunction, Request, Response } from 'express'; +import { requestGot } from '../utils/requestCall'; +import AnimeModel, { Anime } from '../database/models/anime.model'; +import { + animeExtraInfo, + getAnimeVideoPromo, + getAnimeCharacters, + getRelatedAnimesFLV, + getRelatedAnimesMAL, +} from '../utils/util'; +import urls from '../utils/urls'; + +/* + DirectoryController - async functions controlling the directory + in the database of MongoDB, functions like getAllDirectory from the DB + other functions with realation to the directory, like the season and stuff. +*/ + +interface TypeAnime { + title: string; + image: string; + genres: string[]; +} + +interface Season { + title: string; + image: string; + malink: string; +} + +interface Archive { + year: string; + seasons: string[]; +} + +export default class DirectoryController { + async getAllDirectory(req: Request, res: Response, next: NextFunction) { + const { genres } = req.params; + + try { + if (genres === 'sfw') { + res.status(200).json( + await AnimeModel.find({ + genres: { $nin: ['ecchi', 'Ecchi'] }, + }), + ); + } else { + res.status(200).json(await AnimeModel.find()); + } + } catch (err) { + return next(err); + } + } + + async getSeason(req: Request, res: Response, next: NextFunction) { + const { year, type } = req.params; + let data: any; + + try { + data = await requestGot(`${urls.BASE_JIKAN}season/${year}/${type}`, { + scrapy: false, + parse: true, + }); + } catch (err) { + return next(err); + } + + const season: TypeAnime[] = data.anime.map((item: any) => { + return { + title: item.title, + image: item.image_url, + genres: item.genres.map((genre: any) => genre.name), + }; + }); + + if (season.length > 0) { + res.status(200).json({ + season, + }); + } else { + res.status(500).json({ message: 'Aruppi lost in the shell' }); + } + } + + async allSeasons(req: Request, res: Response, next: NextFunction) { + let data: any; + + try { + data = await requestGot(`${urls.BASE_JIKAN}season/archive`, { + parse: true, + scrapy: false, + }); + } catch (err) { + return next(err); + } + + const archive: Archive[] = data.archive.map((item: any) => { + return { + year: item.year, + seasons: item.seasons, + }; + }); + + if (archive.length > 0) { + res.status(200).json({ archive }); + } else { + res.status(500).json({ message: 'Aruppi lost in the shell' }); + } + } + + async laterSeasons(req: Request, res: Response, next: NextFunction) { + let data: any; + + try { + data = await requestGot(`${urls.BASE_JIKAN}season/later`, { + parse: true, + scrapy: false, + }); + } catch (err) { + return next(err); + } + + const future: Season[] = data.anime.map((item: any) => { + return { + title: item.title, + image: item.image_url, + malink: item.url, + }; + }); + + if (future.length > 0) { + res.status(200).json({ future }); + } else { + res.status(500).json({ message: 'Aruppi lost in the shell' }); + } + } + + async getMoreInfo(req: Request, res: Response, next: NextFunction) { + const { title } = req.params; + let resultAnime: Anime | null; + + try { + resultAnime = await AnimeModel.findOne({ + $or: [{ title: { $eq: title } }, { title: { $eq: `${title} (TV)` } }], + }); + } catch (err) { + return next(err); + } + + if (resultAnime) { + if (!resultAnime?.jkanime) { + res.status(200).json({ + title: resultAnime.title || null, + poster: resultAnime.poster || null, + synopsis: resultAnime.description || null, + status: resultAnime.state || null, + type: resultAnime.type || null, + rating: resultAnime.score || null, + genres: resultAnime.genres || null, + moreInfo: await animeExtraInfo(resultAnime.mal_id), + promo: await getAnimeVideoPromo(resultAnime.mal_id), + characters: await getAnimeCharacters(resultAnime.mal_id), + related: await getRelatedAnimesFLV(resultAnime.id), + }); + } else { + res.status(200).json({ + title: resultAnime.title || null, + poster: resultAnime.poster || null, + synopsis: resultAnime.description || null, + status: resultAnime.state || null, + type: resultAnime.type || null, + rating: resultAnime.score || null, + genres: resultAnime.genres || null, + moreInfo: await animeExtraInfo(resultAnime.mal_id), + promo: await getAnimeVideoPromo(resultAnime.mal_id), + characters: await getAnimeCharacters(resultAnime.mal_id), + related: await getRelatedAnimesMAL(resultAnime.mal_id), + }); + } + } else { + res.status(500).json({ message: 'Aruppi lost in the shell' }); + } + } +} diff --git a/src/controllers/UtilsController.ts b/src/controllers/UtilsController.ts new file mode 100644 index 0000000..0799041 --- /dev/null +++ b/src/controllers/UtilsController.ts @@ -0,0 +1,143 @@ +import { NextFunction, Request, Response } from 'express'; +import Parser from 'rss-parser'; +import urls from '../utils/urls'; +import { obtainPreviewNews } from '../utils/obtainPreviews'; + +/* + UtilsController - controller to parse the + feed and get news, all with scraping and + parsing RSS. +*/ + +type CustomFeed = { + foo: string; +}; +type CustomItem = { + bar: number; + itunes: { duration: string; image: string }; + 'content:encoded': string; + 'content:encodedSnippet': string; +}; + +const parser: Parser = new Parser({ + customFields: { + feed: ['foo'], + item: ['bar'], + }, +}); + +interface News { + title?: string; + url?: string; + author?: string; + thumbnail?: string; + content?: string; +} + +interface Podcast { + title?: string; + duration?: string; + created?: Date | string; + mp3?: string; +} + +interface rssPage { + url: string; + author: string; + content: string; +} + +export default class UtilsController { + async getAnitakume(req: Request, res: Response, next: NextFunction) { + let feed: CustomFeed & Parser.Output; + + try { + feed = await parser.parseURL(urls.BASE_IVOOX); + } catch (err) { + return next(err); + } + + const podcast: Podcast[] = []; + const monthNames = [ + 'Enero', + 'Febrero', + 'Marzo', + 'Abril', + 'Mayo', + 'Junio', + 'Julio', + 'Agosto', + 'Septiembre', + 'Octubre', + 'Noviembre', + 'Diciembre', + ]; + + feed.items.forEach(item => { + const date: Date = new Date(item.pubDate!); + + const formattedObject: Podcast = { + title: item.title, + duration: item.itunes.duration, + created: `${date.getDate()} de ${ + monthNames[date.getMonth()] + } de ${date.getFullYear()}`, + mp3: item.enclosure?.url, + }; + + podcast.push(formattedObject); + }); + + if (podcast.length > 0) { + res.status(200).json({ podcast }); + } else { + res.status(500).json({ message: 'Aruppi lost in the shell' }); + } + } + + async getNews(req: Request, res: Response, next: NextFunction) { + const news: News[] = []; + const pagesRss: rssPage[] = [ + { url: urls.BASE_KUDASAI, author: 'Kudasai', content: 'content_encoded' }, + { + url: urls.BASE_PALOMITRON, + author: 'Palomitron', + content: 'description', + }, + { + url: urls.BASE_RAMENPARADOS, + author: 'Ramen para dos', + content: 'content', + }, + { + url: urls.BASE_CRUNCHYROLL, + author: 'Crunchyroll', + content: 'content_encoded', + }, + ]; + + try { + for (const rssPage of pagesRss) { + const feed = await parser.parseURL(rssPage.url); + + feed.items.forEach(item => { + const formattedObject: News = { + title: item.title, + url: item.link, + author: feed.title?.includes('Crunchyroll') + ? 'Crunchyroll' + : feed.title, + thumbnail: obtainPreviewNews(item['content:encoded']), + content: item['content:encodedSnippet'], + }; + + news.push(formattedObject); + }); + } + } catch (err) { + return next(err); + } + + res.json({ news }); + } +} diff --git a/src/database/connection.ts b/src/database/connection.ts new file mode 100644 index 0000000..3a257cb --- /dev/null +++ b/src/database/connection.ts @@ -0,0 +1,20 @@ +import mongoose from 'mongoose'; + +/* + Create the connection to the database + of mongodb. +*/ + +export const createConnection = (database: string | undefined) => { + mongoose.connect(`mongodb://${database}/anime-directory`, { + useNewUrlParser: true, + useUnifiedTopology: true, + }); + + mongoose.connection.on('error', err => { + console.log('err', err); + }); + mongoose.connection.on('connected', (err, res) => { + console.log('Database connected: mongoose.'); + }); +}; diff --git a/src/database/models/anime.model.ts b/src/database/models/anime.model.ts new file mode 100644 index 0000000..3293d9d --- /dev/null +++ b/src/database/models/anime.model.ts @@ -0,0 +1,35 @@ +import { Document, model, Types, Schema } from 'mongoose'; + +/* + This is the model for each anime + of the directory, the anime model. +*/ + +export interface Anime extends Document { + id: string; + title: string; + mal_id: number; + poster: string; + type: string; + genres: Types.Array; + state: string; + score: string; + jkanime: boolean; + description: string; +} + +// Schema for the anime +const AnimeSchema: Schema = new Schema({ + id: { type: String }, + title: { type: String }, + mal_id: { type: Number }, + poster: { type: String }, + type: { type: String }, + genres: [{ type: String }], + state: { type: String }, + score: { type: String }, + jkanime: { type: Boolean }, + description: { type: String }, +}); + +export default model('Anime', AnimeSchema); diff --git a/src/middlewares/middleware.ts b/src/middlewares/middleware.ts index 2b583d2..4f654a4 100644 --- a/src/middlewares/middleware.ts +++ b/src/middlewares/middleware.ts @@ -6,6 +6,12 @@ import { RequestHandler, } from 'express'; +/* + Error handler and notFound handler + for all the API like a middleware with + the function next of express. +*/ + export const errorHandler: ErrorRequestHandler = ( err: any, req: Request, @@ -13,8 +19,7 @@ export const errorHandler: ErrorRequestHandler = ( next: NextFunction, ) => { const statusCode = res.statusCode !== 200 ? res.statusCode : 500; - res.status(statusCode); - res.json({ + res.status(statusCode).json({ message: err.message, stack: process.env.NODE_ENV === 'production' ? '🥞' : err.stack, }); @@ -30,14 +35,14 @@ export const notFound: any = ( next(error); }; -export const requestLoggerMiddleWare: RequestHandler = (req, res, next) => { - console.log(`${req.method} ${req.originalUrl}`); - const start: number = new Date().getTime(); - res.on('finish', () => { - const elapsed: number = new Date().getTime() - start; - console.info( - `${req.method} ${req.originalUrl} ${req.statusCode} ${elapsed}ms`, - ); - }); - next(); -}; +// export const requestLoggerMiddleWare: RequestHandler = (req, res, next) => { +// console.log(`${req.method} ${req.originalUrl}`); +// const start: number = new Date().getTime(); +// res.on('finish', () => { +// const elapsed: number = new Date().getTime() - start; +// console.info( +// `${req.method} ${req.originalUrl} ${req.statusCode} ${elapsed}ms`, +// ); +// }); +// next(); +// }; diff --git a/src/routes.ts b/src/routes.ts index 837a867..cd16a60 100644 --- a/src/routes.ts +++ b/src/routes.ts @@ -1,62 +1,92 @@ import { Router, Request, Response } from 'express'; import AnimeController from './controllers/AnimeController'; +import DirectoryController from './controllers/DirectoryController'; +import UtilsController from './controllers/UtilsController'; const routes = Router(); const animeController = new AnimeController(); +const directoryController = new DirectoryController(); +const utilsController = new UtilsController(); routes.get('/', (req: Request, res: Response) => { // We dont want to enforce the redirect storing, so just check res.set('Cache-Control', 'no-cache,proxy-revalidate'); - res.redirect('/api/'); + res.redirect('/api/v4/'); }); -routes.get('/api/', (req: Request, res: Response) => { +/* + Routes - JSON + Message with the JSON of all the routes in the + /api, parameters and some examples, how to call the + endpoints of the /api. +*/ + +routes.get('/api/v4/', (req: Request, res: Response) => { res.set('Cache-Control', 'no-store'); res.json({ - message: 'Aruppi API - 🎏', + message: 'Aruppi /api - 🎏', author: 'Jéluchu', version: '4.0.0', - credits: 'The bitch loves APIs that offers data to Aruppi App', + credits: 'The bitch loves /apis that offers data to Aruppi App', entries: [ { - Schedule: '/api/schedule/:day', - Top: '/api/top/:type/:page/:subtype', - AllAnimes: '/api/ allAnimes', - RandomAnime: '/api/randomAnime', - Anitakume: '/api/anitakume', - News: '/api/news', - Season: '/api/season/:year/:type', - 'All Seasons': '/api/allSeasons', - 'All Directory': '/api/allDirectory/:type', - Genres: '/api/getByGenres/:genre?/:order?/:page?', - 'Futures Seasons': '/api/laterSeasons', - LastEpisodes: '/api/lastEpisodes', - Movies: '/api/movies/:type/:page', - Ovas: '/api/ova/:type/:page', - Specials: '/api/special/:type/:page', - Tv: '/api/tv/:type/:page', - MoreInfo: '/api/moreInfo/:title', - GetEpisodes: '/api/getEpisodes/:title', - GetAnimeServers: '/api/getAnimeServers/:id', - Search: '/api/search/:title', - Images: '/api/images/:query', - Videos: '/api/videos/:channelId', - 'Type Videos': '/api/sectionedVideos/:type', - Radios: '/api/radio', - 'All Themes': '/api/allThemes', - Themes: '/api/themes/:title', - 'Year Themes': '/api/themesYear/:year?', - 'Random Theme': '/api/randomTheme', - 'Artists Theme': '/api/artists/:id?', - 'Famous Platforms': '/api/destAnimePlatforms', - 'Legal Platforms': '/api/platforms/:id?', + Schedule: '/api/v4/schedule/:day', + Top: '/api/v4/top/:type/:page/:subtype', + AllAnimes: '/api/v4/allAnimes', + RandomAnime: '/api/v4/randomAnime', + Anitakume: '/api/v4/anitakume', + News: '/api/v4/news', + Season: '/api/v4/season/:year/:type', + 'All Seasons': '/api/v4/allSeasons', + 'All Directory': '/api/v4/allDirectory/:type', + Genres: '/api/v4/getByGenres/:genre?/:order?/:page?', + 'Futures Seasons': '/api/v4/laterSeasons', + LastEpisodes: '/api/v4/lastEpisodes', + Movies: '/api/v4/movies/:type/:page', + Ovas: '/api/v4/ova/:type/:page', + Specials: '/api/v4/special/:type/:page', + Tv: '/api/v4/tv/:type/:page', + MoreInfo: '/api/v4/moreInfo/:title', + GetEpisodes: '/api/v4/getEpisodes/:title', + GetAnimeServers: '/api/v4/getAnimeServers/:id', + Search: '/api/v4/search/:title', + Images: '/api/v4/images/:query', + Videos: '/api/v4/videos/:channelId', + 'Type Videos': '/api/v4/sectionedVideos/:type', + Radios: '/api/v4/radio', + 'All Themes': '/api/v4/allThemes', + Themes: '/api/v4/themes/:title', + 'Year Themes': '/api/v4/themesYear/:year?', + 'Random Theme': '/api/v4/randomTheme', + 'Artists Theme': '/api/v4/artists/:id?', + 'Famous Platforms': '/api/v4/destAnimePlatforms', + 'Legal Platforms': '/api/v4/platforms/:id?', }, ], }); }); -routes.get('/api/schedule/:day', animeController.schedule); -routes.get('/api/top/:type/:page/:subtype?/', animeController.top); -routes.get('/api/allAnimes', animeController.getAllAnimes); +/* Routes of the app below */ + +/* Anime Controller */ +routes.get('/api/v4/schedule/:day', animeController.schedule); +routes.get('/api/v4/top/:type/:page/:subtype?/', animeController.top); +routes.get('/api/v4/allAnimes', animeController.getAllAnimes); +routes.get('/api/v4/lastEpisodes', animeController.getLastEpisodes); +routes.get('/api/v4/:url/:type/:page', animeController.getContent); + +/* Directory Controller */ +routes.get( + '/api/v4/allDirectory/:genres?', + directoryController.getAllDirectory, +); +routes.get('/api/v4/season/:year/:type', directoryController.getSeason); +routes.get('/api/v4/allSeasons', directoryController.allSeasons); +routes.get('/api/v4/laterSeasons', directoryController.laterSeasons); +routes.get('/api/v4/moreInfo/:title', directoryController.getMoreInfo); + +/* Utils Controller */ +routes.get('/api/v4/anitakume', utilsController.getAnitakume); +routes.get('/api/v4/news', utilsController.getNews); export default routes; diff --git a/src/server.ts b/src/server.ts index e5e0d8d..472d20d 100644 --- a/src/server.ts +++ b/src/server.ts @@ -3,18 +3,25 @@ import cors from 'cors'; import helmet from 'helmet'; import dotenv from 'dotenv'; import { errorHandler, notFound } from './middlewares/middleware'; +import { createConnection } from './database/connection'; import routes from './routes'; const app: Application = express(); dotenv.config(); +createConnection(process.env.DATABASE); app.use(cors()); app.use(helmet()); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(routes); -app.use(errorHandler); app.use(notFound); +app.use(errorHandler); -// Starting to listen the server in port +/* + Starting the server on the .env process + you can define the PORT where the server + is going to listen in the server. + ex: PORT=3000. +*/ app.listen(process.env.PORT || 3000); diff --git a/yarn.lock b/yarn.lock index ae72777..869c6b0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -64,6 +64,13 @@ "@types/connect" "*" "@types/node" "*" +"@types/bson@*": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@types/bson/-/bson-4.0.3.tgz#30889d2ffde6262abbe38659364c631454999fbf" + integrity sha512-mVRvYnTOZJz3ccpxhr3wgxVmSeiYinW+zlzQz3SXWaJmD1DuL05Jeq7nKw3SnbKmbleW5qrLG5vdyWe/A9sXhw== + dependencies: + "@types/node" "*" + "@types/cacheable-request@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/@types/cacheable-request/-/cacheable-request-6.0.1.tgz#5d22f3dded1fd3a84c0bbeb5039a7419c2c91976" @@ -146,6 +153,14 @@ resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.2.tgz#93e25bf9ee75fe0fd80b594bc4feb0e862111b5a" integrity sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw== +"@types/mongodb@^3.5.27": + version "3.6.8" + resolved "https://registry.yarnpkg.com/@types/mongodb/-/mongodb-3.6.8.tgz#f80f0a3022c44d8db659a936434403ca3f4661df" + integrity sha512-8qNbL5/GFrljXc/QijcuQcUMYZ1iWNcqnJ6tneROwbfU0LsAjQ9bmq3aHi5lWXM4cyBPd2F/n9INAk/pZZttHw== + dependencies: + "@types/bson" "*" + "@types/node" "*" + "@types/node@*", "@types/node@^14.14.31": version "14.14.31" resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.31.tgz#72286bd33d137aa0d152d47ec7c1762563d34055" @@ -381,13 +396,6 @@ asynckit@^0.4.0: resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= -axios@^0.21.0: - version "0.21.1" - resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.1.tgz#22563481962f4d6bde9a76d516ef0e5d3c09b2b8" - integrity sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA== - dependencies: - follow-redirects "^1.10.0" - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" @@ -398,13 +406,6 @@ binary-extensions@^2.0.0: resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== -bindings@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" - integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== - dependencies: - file-uri-to-path "1.0.0" - bl@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/bl/-/bl-2.2.1.tgz#8c11a7b730655c5d56898cdc871224f40fd901d5" @@ -413,6 +414,11 @@ bl@^2.2.1: readable-stream "^2.3.5" safe-buffer "^5.1.1" +bluebird@3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" + integrity sha512-MKiLiV+I1AA596t9w1sQJ8jkiSr5+ZKi0WKrYGUn6d1Fx+Ij4tIj+m2WMQSGczs5jZVxV339chE8iwk6F64wjA== + body-parser@1.19.0, body-parser@^1.19.0: version "1.19.0" resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.19.0.tgz#96b2709e57c9c4e09a6fd66a8fd979844f69f08a" @@ -712,6 +718,13 @@ debug@2.6.9, debug@^2.6.9: dependencies: ms "2.0.0" +debug@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== + dependencies: + ms "2.0.0" + debug@^4.0.1, debug@^4.1.0, debug@^4.1.1: version "4.3.1" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" @@ -859,7 +872,7 @@ end-of-stream@^1.1.0: dependencies: once "^1.4.0" -entities@^2.0.0: +entities@^2.0.0, entities@^2.0.3: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== @@ -1210,11 +1223,6 @@ file-entry-cache@^5.0.1: dependencies: flat-cache "^2.0.1" -file-uri-to-path@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz#553a7b8446ff6f684359c445f1e37a05dacc33dd" - integrity sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw== - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -1264,11 +1272,6 @@ flatted@^2.0.0: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== -follow-redirects@^1.10.0: - version "1.13.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.2.tgz#dd73c8effc12728ba5cf4259d760ea5fb83e3147" - integrity sha512-6mPTgLxYm3r6Bkkg0vNM0HTjfGrOEtsfbhagQvbxDEsEkpNhw582upBaoRZylzen6krEmxXJgt9Ju6HiI4O7BA== - form-data@^2.5.0: version "2.5.1" resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.5.1.tgz#f2cbec57b5e59e23716e128fe44d4e5dd23895f4" @@ -1409,21 +1412,6 @@ helmet@^4.4.1: resolved "https://registry.yarnpkg.com/helmet/-/helmet-4.4.1.tgz#a17e1444d81d7a83ddc6e6f9bc6e2055b994efe7" integrity sha512-G8tp0wUMI7i8wkMk2xLcEvESg5PiCitFMYgGRc/PwULB0RVhTP5GFdxOwvJwp9XVha8CuS8mnhmE8I/8dx/pbw== -hoek@5.x.x: - version "5.0.4" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.4.tgz#0f7fa270a1cafeb364a4b2ddfaa33f864e4157da" - integrity sha512-Alr4ZQgoMlnere5FZJsIyfIjORBqZll5POhDsF4q64dPuJR6rNxXdDxtHSQq8OXRurhmx+PWYEE8bXRROY8h0w== - -hoek@6.x.x: - version "6.1.3" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-6.1.3.tgz#73b7d33952e01fe27a38b0457294b79dd8da242c" - integrity sha512-YXXAAhmF9zpQbC7LEcREFtXfGq5K1fmd+4PHkBq8NUqmzW3G+Dq10bI/i0KucLRwss3YYFQ0fSfoxBZYiGUqtQ== - -hoek@^4.2.1: - version "4.2.1" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.1.tgz#9634502aa12c445dd5a7c5734b572bb8738aacbb" - integrity sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA== - hosted-git-info@^2.1.4: version "2.8.8" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.8.tgz#7539bd4bc1e0e0a895815a2e0262420b12858488" @@ -1644,27 +1632,11 @@ isarray@^1.0.0, isarray@~1.0.0: resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= -isemail@3.x.x: - version "3.2.0" - resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.2.0.tgz#59310a021931a9fb06bbb51e155ce0b3f236832c" - integrity sha512-zKqkK+O+dGqevc93KNsbZ/TqTUFd46MwWjYOoMrjIMZ51eU7DtQG3Wmd9SQQT7i7RVnuTPEiYEWHU3MSbxC1Tg== - dependencies: - punycode "2.x.x" - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -joi@^13.1.2: - version "13.7.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-13.7.0.tgz#cfd85ebfe67e8a1900432400b4d03bbd93fb879f" - integrity sha512-xuY5VkHfeOYK3Hdi91ulocfuFopwgbSORmIwzcwHKESQhC7w1kD5jaVSPnqDxS2I8t3RZ9omCKAxNwXN5zG1/Q== - dependencies: - hoek "5.x.x" - isemail "3.x.x" - topo "3.x.x" - js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" @@ -1700,6 +1672,11 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +kareem@2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/kareem/-/kareem-2.3.2.tgz#78c4508894985b8d38a0dc15e1a8e11078f2ca93" + integrity sha512-STHz9P7X2L4Kwn72fA4rGyqyXdmrMSdxqHx9IXon/FXluXieaFA6KJ2upcHAHxQPQ0LeM/OjLrhFxifHewOALQ== + keyv@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.0.3.tgz#4f3aa98de254803cafcd2896734108daa35e4254" @@ -1884,7 +1861,7 @@ mkdirp@^1.0.4: resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== -mongodb@^3.6.4: +mongodb@3.6.4, mongodb@^3.6.4: version "3.6.4" resolved "https://registry.yarnpkg.com/mongodb/-/mongodb-3.6.4.tgz#ca59fd65b06831308262372ef9df6b78f9da97be" integrity sha512-Y+Ki9iXE9jI+n9bVtbTOOdK0B95d6wVGSucwtBkvQ+HIvVdTCfpVRp01FDC24uhC/Q2WXQ8Lpq3/zwtB5Op9Qw== @@ -1897,6 +1874,45 @@ mongodb@^3.6.4: optionalDependencies: saslprep "^1.0.0" +mongoose-legacy-pluralize@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/mongoose-legacy-pluralize/-/mongoose-legacy-pluralize-1.0.2.tgz#3ba9f91fa507b5186d399fb40854bff18fb563e4" + integrity sha512-Yo/7qQU4/EyIS8YDFSeenIvXxZN+ld7YdV9LqFVQJzTLye8unujAWPZ4NWKfFA+RNjh+wvTWKY9Z3E5XM6ZZiQ== + +mongoose@^5.11.18: + version "5.11.18" + resolved "https://registry.yarnpkg.com/mongoose/-/mongoose-5.11.18.tgz#76fdcefeb2afc7409705fc44b58045953bba5ee1" + integrity sha512-RsrPR9nhkXZbO3ml0DcmdbfeMvFNhgFrP81S6o1P+lFnDTNEKYnGNRCIL+ojD69wj7H5jJaAdZ0SJ5IlKxCHqw== + dependencies: + "@types/mongodb" "^3.5.27" + bson "^1.1.4" + kareem "2.3.2" + mongodb "3.6.4" + mongoose-legacy-pluralize "1.0.2" + mpath "0.8.3" + mquery "3.2.4" + ms "2.1.2" + regexp-clone "1.0.0" + safe-buffer "5.2.1" + sift "7.0.1" + sliced "1.0.1" + +mpath@0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/mpath/-/mpath-0.8.3.tgz#828ac0d187f7f42674839d74921970979abbdd8f" + integrity sha512-eb9rRvhDltXVNL6Fxd2zM9D4vKBxjVVQNLNijlj7uoXUy19zNDsIif5zR+pWmPCWNKwAtqyo4JveQm4nfD5+eA== + +mquery@3.2.4: + version "3.2.4" + resolved "https://registry.yarnpkg.com/mquery/-/mquery-3.2.4.tgz#9c5c2e285ea6c6f20673f3528973c99ee1aaa1a0" + integrity sha512-uOLpp7iRX0BV1Uu6YpsqJ5b42LwYnmu0WeF/f8qgD/On3g0XDaQM6pfn0m6UxO6SM8DioZ9Bk6xxbWIGHm2zHg== + dependencies: + bluebird "3.5.1" + debug "3.1.0" + regexp-clone "^1.0.0" + safe-buffer "5.1.2" + sliced "1.0.1" + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -1917,11 +1933,6 @@ mute-stream@0.0.7: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" integrity sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s= -nan@^2.13.2: - version "2.14.2" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.2.tgz#f5376400695168f4cc694ac9393d0c9585eeea19" - integrity sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ== - natural-compare@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" @@ -1937,14 +1948,6 @@ nice-try@^1.0.4: resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== -node-expat@^2.3.18: - version "2.3.18" - resolved "https://registry.yarnpkg.com/node-expat/-/node-expat-2.3.18.tgz#d9e6949cecda15e131f14259b73dc7b9ed7bc560" - integrity sha512-9dIrDxXePa9HSn+hhlAg1wXkvqOjxefEbMclGxk2cEnq/Y3U7Qo5HNNqeo3fQ4bVmLhcdt3YN1TZy7WMZy4MHw== - dependencies: - bindings "^1.5.0" - nan "^2.13.2" - normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8" @@ -2245,7 +2248,7 @@ pump@^3.0.0: end-of-stream "^1.1.0" once "^1.3.1" -punycode@2.x.x, punycode@^2.1.0, punycode@^2.1.1: +punycode@^2.1.0, punycode@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== @@ -2342,6 +2345,11 @@ redent@^1.0.0: indent-string "^2.1.0" strip-indent "^1.0.1" +regexp-clone@1.0.0, regexp-clone@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/regexp-clone/-/regexp-clone-1.0.0.tgz#222db967623277056260b992626354a04ce9bf63" + integrity sha512-TuAasHQNamyyJ2hb97IuBEif4qBHGjPHBS64sZwytpLEqtBQ1gPJTnOaQ6qmpET16cK14kkjbazl6+p0RRv0yw== + regexpp@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f" @@ -2424,13 +2432,13 @@ rimraf@^2.6.1: dependencies: glob "^7.1.3" -rss-to-json@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/rss-to-json/-/rss-to-json-1.1.2.tgz#279ba4e82a10f44ea6277e6594bd2910d000816c" - integrity sha512-88J6yuApSgYL7eU7fNMDNVp3K7za/Bp2cj9Mjh0flDqChX6WG8f6OLJHqBeuxcvQjxTVALUPz82MGJMAATZplg== +rss-parser@^3.12.0: + version "3.12.0" + resolved "https://registry.yarnpkg.com/rss-parser/-/rss-parser-3.12.0.tgz#b8888699ea46304a74363fbd8144671b2997984c" + integrity sha512-aqD3E8iavcCdkhVxNDIdg1nkBI17jgqF+9OqPS1orwNaOgySdpvq6B+DoONLhzjzwV8mWg37sb60e4bmLK117A== dependencies: - axios "^0.21.0" - xml2json "^0.12.0" + entities "^2.0.3" + xml2js "^0.4.19" run-async@^2.2.0: version "2.4.1" @@ -2456,7 +2464,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@^5.1.1, safe-buffer@^5.1.2: +safe-buffer@5.2.1, safe-buffer@^5.1.1, safe-buffer@^5.1.2: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -2473,6 +2481,11 @@ saslprep@^1.0.0: dependencies: sparse-bitfield "^3.0.3" +sax@>=0.6.0: + version "1.2.4" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== + "semver@2 || 3 || 4 || 5", semver@^5.1.0, semver@^5.5.0, semver@^5.5.1: version "5.7.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7" @@ -2531,6 +2544,11 @@ shebang-regex@^1.0.0: resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= +sift@7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/sift/-/sift-7.0.1.tgz#47d62c50b159d316f1372f8b53f9c10cd21a4b08" + integrity sha512-oqD7PMJ+uO6jV9EQCl0LrRw1OwsiPsiFQR5AR30heR+4Dl7jBBbDLnNvWiak20tzZlSE1H7RB30SX/1j/YYT7g== + signal-exit@^3.0.0, signal-exit@^3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" @@ -2550,6 +2568,11 @@ slice-ansi@^2.1.0: astral-regex "^1.0.0" is-fullwidth-code-point "^2.0.0" +sliced@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/sliced/-/sliced-1.0.1.tgz#0b3a662b5d04c3177b1926bea82b03f837a2ef41" + integrity sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E= + source-map-support@^0.5.12, source-map-support@^0.5.17: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -2730,13 +2753,6 @@ toidentifier@1.0.0: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw== -topo@3.x.x: - version "3.0.3" - resolved "https://registry.yarnpkg.com/topo/-/topo-3.0.3.tgz#d5a67fb2e69307ebeeb08402ec2a2a6f5f7ad95c" - integrity sha512-IgpPtvD4kjrJ7CRA3ov2FhWQADwv+Tdqbsf1ZnPUSAtCJ9e1Z44MmoSGDXGk4IppoZA7jd/QRkNddlLJWlUZsQ== - dependencies: - hoek "6.x.x" - tough-cookie@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4" @@ -2901,14 +2917,18 @@ write@1.0.3: dependencies: mkdirp "^0.5.1" -xml2json@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/xml2json/-/xml2json-0.12.0.tgz#b2ae450b267033b76d896f86e022fa7bff678572" - integrity sha512-EPJHRWJnJUYbJlzR4pBhZODwWdi2IaYGtDdteJi0JpZ4OD31IplWALuit8r73dJuM4iHZdDVKY1tLqY2UICejg== +xml2js@^0.4.19: + version "0.4.23" + resolved "https://registry.yarnpkg.com/xml2js/-/xml2js-0.4.23.tgz#a0c69516752421eb2ac758ee4d4ccf58843eac66" + integrity sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug== dependencies: - hoek "^4.2.1" - joi "^13.1.2" - node-expat "^2.3.18" + sax ">=0.6.0" + xmlbuilder "~11.0.0" + +xmlbuilder@~11.0.0: + version "11.0.1" + resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-11.0.1.tgz#be9bae1c8a046e76b31127726347d0ad7002beb3" + integrity sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA== xtend@^4.0.0: version "4.0.2"