From b00dafdc183d48487ef74a572f82c14809034b4b Mon Sep 17 00:00:00 2001 From: mrcanelas Date: Sun, 13 Apr 2025 16:04:21 -0300 Subject: [PATCH] feat(analytics): replace Mixpanel with Swagger Stats and add rate limiting - refactor(analytics): remove Mixpanel implementation - feat(analytics): implement Swagger Stats for metrics and analytics - feat(security): add rate limiting with express-rate-limit - feat(security): implement CORS for access control - feat(security): add request-ip for IP identification - fix(analytics): fix unique users count via MongoDB BREAKING CHANGE: Removed Mixpanel dependency. Metrics are now collected via Swagger Stats and stored in MongoDB. New available routes: - /stats - general statistics - /stats/ui - Swagger Stats web interface - /stats/metrics - detailed metrics --- addon/index.js | 55 ++- addon/middleware/analytics.middleware.js | 13 - addon/utils/analytics.js | 300 +++++++------ package-lock.json | 517 +++++++++++++++++------ package.json | 7 +- 5 files changed, 588 insertions(+), 304 deletions(-) delete mode 100644 addon/middleware/analytics.middleware.js diff --git a/addon/index.js b/addon/index.js index e9e049d..7f78804 100644 --- a/addon/index.js +++ b/addon/index.js @@ -14,9 +14,22 @@ const { parseConfig, getRpdbPoster, checkIfExists } = require("./utils/parseProp const { getRequestToken, getSessionId } = require("./lib/getSession"); const { getFavorites, getWatchList } = require("./lib/getPersonalLists"); const { blurImage } = require('./utils/imageProcessor'); -const analyticsMiddleware = require('./middleware/analytics.middleware'); +const cors = require('cors'); +const rateLimit = require('express-rate-limit'); +const requestIp = require('request-ip'); -addon.use(analyticsMiddleware()); +const router = express.Router(); +router.use(cors()); + +const limiter = rateLimit({ + windowMs: 60 * 60 * 1000, + max: 300, + headers: false, + keyGenerator: (req) => requestIp.getClientIp(req) +}); + +router.use(limiter); +addon.use(analytics.middleware); addon.use(favicon(path.join(__dirname, '../public/favicon.png'))); addon.use(express.static(path.join(__dirname, '../public'))); addon.use(express.static(path.join(__dirname, '../dist'))); @@ -66,31 +79,10 @@ addon.get("/session_id", async function (req, res) { respond(res, sessionId); }); -addon.get('/stats', async (req, res) => { - try { - const uniqueUsers = await analytics.getUniqueUserCount(); - - res.json(uniqueUsers); - } catch (error) { - res.status(500).json({ error: 'Erro ao obter estatísticas' }); - } -}); - addon.use('/configure', express.static(path.join(__dirname, '../dist'))); addon.use('/configure', (req, res, next) => { const config = parseConfig(req.params.catalogChoices); - const analytics = require('./utils/analytics'); - - analytics.trackConfigUpdate({ - language: config.language || DEFAULT_LANGUAGE, - catalogs: config.catalogs || [], - integrations: { - rpdb: !!config.rpdbkey, - tmdb: !!config.sessionId - } - }); - next(); }); @@ -242,4 +234,21 @@ addon.get("/api/image/blur", async function (req, res) { } }); +addon.get('/stats', async (req, res) => { + try { + const [uniqueUsers, swaggerStats] = await Promise.all([ + analytics.getUniqueUsers(), + analytics.getStats() + ]); + + res.json({ + ...uniqueUsers, + ...swaggerStats + }); + } catch (error) { + console.error('Erro ao obter estatísticas:', error); + res.status(500).json({ error: 'Erro ao obter estatísticas' }); + } +}); + module.exports = addon; \ No newline at end of file diff --git a/addon/middleware/analytics.middleware.js b/addon/middleware/analytics.middleware.js deleted file mode 100644 index bcff4d7..0000000 --- a/addon/middleware/analytics.middleware.js +++ /dev/null @@ -1,13 +0,0 @@ -const analytics = require('../utils/analytics'); - -const analyticsMiddleware = () => { - return async (req, res, next) => { - const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; - - analytics.trackUsers(ip); - - next(); - }; -}; - -module.exports = analyticsMiddleware; \ No newline at end of file diff --git a/addon/utils/analytics.js b/addon/utils/analytics.js index f44f059..96af728 100644 --- a/addon/utils/analytics.js +++ b/addon/utils/analytics.js @@ -1,149 +1,193 @@ -require('dotenv').config() -const Mixpanel = require('mixpanel'); -const axios = require('axios'); - -const MIXPANEL_TOKEN = process.env.MIXPANEL_TOKEN || ''; -const MIXPANEL_API_SECRET = process.env.MIXPANEL_API_SECRET || ''; +require('dotenv').config(); +const swaggerStats = require('swagger-stats'); +const express = require('express'); +const { MongoClient } = require('mongodb'); +const packageJson = require("../../package.json"); class Analytics { static instance; constructor() { if (!Analytics.instance) { - if (!MIXPANEL_TOKEN) { - console.warn("Mixpanel não inicializado: Token não definido."); - return; + this.app = express(); + + // Validar URI do MongoDB + const mongodbUri = process.env.MONGODB_METRICS; + if (!mongodbUri || !mongodbUri.startsWith('mongodb://') && !mongodbUri.startsWith('mongodb+srv://')) { + throw new Error('URI do MongoDB inválida ou não configurada. Verifique a variável de ambiente MONGODB_METRICS'); } - - this.mixpanel = Mixpanel.init(MIXPANEL_TOKEN); + + this.middleware = swaggerStats.getMiddleware({ + name: 'TMDB Addon', + version: '3.1.3', + timelineBucketDuration: 60000, + uriPath: '/stats/ui', + authentication: false, + onAuthenticate: (req, username, password) => { + return true; + }, + elasticsearch: false, + mongodb: { + uri: mongodbUri, + collectionPrefix: 'stats_' + }, + swaggerSpec: { + info: { + title: 'TMDB Addon API', + version: '3.1.3' + } + } + }); + this.app.use(this.middleware); + + // Configurar conexão com MongoDB + this.client = new MongoClient(mongodbUri, { + useNewUrlParser: true, + useUnifiedTopology: true + }); + + // Conectar ao MongoDB e criar coleção se não existir + this.client.connect().then(async () => { + const db = this.client.db(); + const collections = await db.listCollections().toArray(); + const collectionExists = collections.some(c => c.name === 'stats_requests'); + + if (!collectionExists) { + console.log('Criando coleção stats_requests...'); + await db.createCollection('stats_requests'); + // Inserir um documento vazio para inicializar a coleção + await db.collection('stats_requests').insertOne({ + ip: '127.0.0.1', + ts: new Date(), + status: 200 + }); + } + }).catch(err => { + console.error('Erro ao conectar ao MongoDB:', err); + }); + Analytics.instance = this; } return Analytics.instance; } - trackInstall(data) { - if (!this.mixpanel) { - return; - } - - this.mixpanel.track('Addon Installed', { - ...data, - timestamp: new Date().toISOString() - }); - } - - trackCatalogAccess(data) { - if (!this.mixpanel) { - return; - } - - this.mixpanel.track('Catalog Accessed', { - ...data, - timestamp: new Date().toISOString() - }); - } - - trackMetadataRequest(data) { - if (!this.mixpanel) { - return; - } - - this.mixpanel.track('Metadata Requested', { - ...data, - timestamp: new Date().toISOString() - }); - } - - trackConfigUpdate(data) { - if (!this.mixpanel) { - return; - } - - this.mixpanel.track('Configuration Updated', { - ...data, - timestamp: new Date().toISOString() - }); - } - - trackError(data) { - if (!this.mixpanel) { - return; - } - - this.mixpanel.track('Error Occurred', { - ...data, - timestamp: new Date().toISOString() - }); - } - - trackAPIPerformance(data) { - if (!this.mixpanel) { - return; - } - - this.mixpanel.track('API Performance', { - ...data, - timestamp: new Date().toISOString() - }); - } - - trackActiveUsers(count) { - if (!this.mixpanel) { - return; - } - - this.mixpanel.track('Active Users', { - count, - timestamp: new Date().toISOString() - }); - } - - trackUsers(ip) { - if (!this.mixpanel) { - - return; - } - - this.mixpanel.people.set(ip, { - $last_seen: new Date().toISOString() - }); - - this.mixpanel.track('Users', { - ip, - timestamp: new Date().toISOString() - }); - } - - async getUniqueUserCount() { + async getUniqueUsers() { try { - if (!MIXPANEL_API_SECRET) { + if (!this.client) { + console.error('Cliente MongoDB não está inicializado'); return { uniqueUserCount: 0 }; } - - const endDate = new Date(); - const startDate = new Date('2011-07-10'); - - const response = await axios.get('https://data-eu.mixpanel.com/api/2.0/export', { - params: { - event: JSON.stringify(['Users']), - from_date: startDate.toISOString().split('T')[0], - to_date: endDate.toISOString().split('T')[0] + + const db = this.client.db(); + const collection = db.collection('stats_requests'); + + // Verificar se a coleção existe + const collections = await db.listCollections().toArray(); + const collectionExists = collections.some(c => c.name === 'stats_requests'); + + if (!collectionExists) { + console.log('Coleção stats_requests não existe ainda'); + return { uniqueUserCount: 0 }; + } + + const result = await collection.aggregate([ + { + $group: { + _id: "$ip", + lastSeen: { $max: "$ts" } + } }, - headers: { - 'Authorization': `Basic ${Buffer.from(MIXPANEL_API_SECRET).toString('base64')}` + { + $count: "uniqueUserCount" } - }); - - const lines = response.data.trim().split('\n'); - const events = lines.map(line => JSON.parse(line)); - - const uniqueUsers = new Set(events.map(event => event.properties.distinct_id)); - - return { uniqueUserCount: uniqueUsers.size }; + ]).toArray(); + + return { uniqueUserCount: result[0]?.uniqueUserCount || 0 }; } catch (error) { - console.error('Erro ao buscar usuários únicos:', error.response ? error.response.data : error.message); + console.error('Erro ao obter usuários únicos:', error); return { uniqueUserCount: 0 }; } - } + } + + async getStats() { + try { + if (!this.client) { + console.error('Cliente MongoDB não está inicializado'); + return { + requests: { total: 0 }, + responses: { '2xx': 0, '4xx': 0, '5xx': 0 }, + timeline: { last60: 0 } + }; + } + + const db = this.client.db(); + const collection = db.collection('stats_requests'); + + // Verificar se a coleção existe + const collections = await db.listCollections().toArray(); + const collectionExists = collections.some(c => c.name === 'stats_requests'); + + if (!collectionExists) { + console.log('Coleção stats_requests não existe ainda'); + return { + requests: { total: 0 }, + responses: { '2xx': 0, '4xx': 0, '5xx': 0 }, + timeline: { last60: 0 } + }; + } + + const stats = await collection.aggregate([ + { + $group: { + _id: null, + totalRequests: { $sum: 1 }, + successRequests: { + $sum: { + $cond: [{ $gte: ["$status", 200] }, 1, 0] + } + }, + errorRequests: { + $sum: { + $cond: [{ $gte: ["$status", 400] }, 1, 0] + } + }, + last60: { + $sum: { + $cond: [ + { $gte: ["$ts", new Date(Date.now() - 60000)] }, + 1, + 0 + ] + } + } + } + } + ]).toArray(); + + const result = stats[0] || { + totalRequests: 0, + successRequests: 0, + errorRequests: 0, + last60: 0 + }; + + return { + requests: { total: result.totalRequests }, + responses: { + '2xx': result.successRequests, + '4xx': result.errorRequests, + '5xx': 0 + }, + timeline: { last60: result.last60 } + }; + } catch (error) { + console.error('Erro ao obter estatísticas:', error); + return { + requests: { total: 0 }, + responses: { '2xx': 0, '4xx': 0, '5xx': 0 }, + timeline: { last60: 0 } + }; + } + } } -module.exports = new Analytics(); \ No newline at end of file +module.exports = new Analytics(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index c6af7d6..7c3ff7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -28,21 +28,24 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", - "dotenv": "^16.4.5", + "cors": "^2.8.5", + "dotenv": "^16.5.0", "express": "^4.21.2", + "express-rate-limit": "^7.5.0", "fanart.tv-api": "^1.0.3", "framer-motion": "^12.0.6", "lucide-react": "^0.462.0", - "mixpanel": "^0.18.0", "moviedb-promise": "^4.0.7", "next-themes": "^0.3.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-kofi": "^0.0.2", "react-router-dom": "^6.26.2", + "request-ip": "^3.3.0", "serve-favicon": "^2.5.0", "sharp": "^0.33.5", "sonner": "^1.5.0", + "swagger-stats": "^0.99.0", "tailwind-merge": "^2.5.2", "transliteration": "^2.3.5", "url-exists": "^1.0.3" @@ -3672,38 +3675,6 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/agent-base/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/agent-base/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3893,6 +3864,17 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -3910,6 +3892,12 @@ "node": ">=8" } }, + "node_modules/bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", + "peer": true + }, "node_modules/bl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", @@ -4750,11 +4738,35 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "node_modules/cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -4871,9 +4883,9 @@ "dev": true }, "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "engines": { "node": ">=12" }, @@ -5287,6 +5299,20 @@ "url": "https://opencollective.com/express" } }, + "node_modules/express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://github.com/sponsors/express-rate-limit" + }, + "peerDependencies": { + "express": "^4.11 || 5 || ^5.0.0-beta.1" + } + }, "node_modules/express/node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -5805,39 +5831,6 @@ "npm": ">=1.3.7" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/https-proxy-agent/node_modules/debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/https-proxy-agent/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -6098,6 +6091,17 @@ "node": ">=0.6.0" } }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -6351,15 +6355,12 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mixpanel": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/mixpanel/-/mixpanel-0.18.0.tgz", - "integrity": "sha512-VyUoiLB/S/7abYYHGD5x0LijeuJCUabG8Hb+FvYU3Y99xHf1Qh+s4/pH9lt50fRitAHncWbU1FE01EknUfVVjQ==", - "dependencies": { - "https-proxy-agent": "5.0.0" - }, + "node_modules/moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "engines": { - "node": ">=10.0" + "node": "*" } }, "node_modules/mongodb": { @@ -6516,7 +6517,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -6898,6 +6898,18 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/prom-client": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.2.0.tgz", + "integrity": "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA==", + "peer": true, + "dependencies": { + "tdigest": "^0.1.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/promise-throttle": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/promise-throttle/-/promise-throttle-1.1.2.tgz", @@ -7206,6 +7218,11 @@ "node": ">= 6" } }, + "node_modules/request-ip": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-3.3.0.tgz", + "integrity": "sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==" + }, "node_modules/request/node_modules/qs": { "version": "6.5.3", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", @@ -7763,6 +7780,107 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swagger-stats": { + "version": "0.99.7", + "resolved": "https://registry.npmjs.org/swagger-stats/-/swagger-stats-0.99.7.tgz", + "integrity": "sha512-niP70m99Cwpz/Fyfk8ydul1jM0pOKD6UofSaDzW2Op6o6WYFsuAl/BhVbmLkZWOAZ7IloDVvFj6vaU5zA0xydg==", + "dependencies": { + "axios": "^1.4.0", + "basic-auth": "^2.0.1", + "cookies": "^0.8.0", + "debug": "^4.3.4", + "moment": "^2.29.4", + "path-to-regexp": "^6.2.1", + "qs": "^6.11.2", + "send": "^0.18.0", + "uuid": "^9.0.0" + }, + "peerDependencies": { + "prom-client": ">= 10 <= 14" + } + }, + "node_modules/swagger-stats/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/swagger-stats/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/swagger-stats/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/swagger-stats/node_modules/path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" + }, + "node_modules/swagger-stats/node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/swagger-stats/node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/swagger-stats/node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/swagger-stats/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/tailwind-merge": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", @@ -7834,6 +7952,15 @@ "node": ">=4" } }, + "node_modules/tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "peer": true, + "dependencies": { + "bintrees": "1.0.2" + } + }, "node_modules/thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -7925,6 +8052,14 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "engines": { + "node": ">=0.6.x" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -10363,29 +10498,6 @@ "dev": true, "requires": {} }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "requires": { - "ms": "^2.1.3" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -10526,6 +10638,14 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + } + }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -10540,6 +10660,12 @@ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", "dev": true }, + "bintrees": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bintrees/-/bintrees-1.0.2.tgz", + "integrity": "sha512-VOMgTMwjAaUG580SXn3LacVgjurrbMme7ZZNYGSSV7mmtY6QQRh0Eg3pwIcntQ77DErK1L0NxkbetjcoXzVwKw==", + "peer": true + }, "bl": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", @@ -11065,11 +11191,29 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, + "cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "requires": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + } + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -11158,9 +11302,9 @@ "dev": true }, "dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==" + "version": "16.5.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", + "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==" }, "dunder-proto": { "version": "1.0.1", @@ -11471,6 +11615,12 @@ } } }, + "express-rate-limit": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-7.5.0.tgz", + "integrity": "sha512-eB5zbQh5h+VenMPM3fh+nw1YExi5nMr6HUCR62ELSP11huvxm/Uir1H1QEyTkk5QX6A58pX6NmaTMceKZ0Eodg==", + "requires": {} + }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", @@ -11812,30 +11962,6 @@ "sshpk": "^1.7.0" } }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "requires": { - "agent-base": "6", - "debug": "4" - }, - "dependencies": { - "debug": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", - "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", - "requires": { - "ms": "^2.1.3" - } - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } - } - }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -12031,6 +12157,14 @@ "verror": "1.10.0" } }, + "keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "requires": { + "tsscmp": "1.0.6" + } + }, "keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -12224,13 +12358,10 @@ "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true }, - "mixpanel": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/mixpanel/-/mixpanel-0.18.0.tgz", - "integrity": "sha512-VyUoiLB/S/7abYYHGD5x0LijeuJCUabG8Hb+FvYU3Y99xHf1Qh+s4/pH9lt50fRitAHncWbU1FE01EknUfVVjQ==", - "requires": { - "https-proxy-agent": "5.0.0" - } + "moment": { + "version": "2.30.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", + "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==" }, "mongodb": { "version": "3.7.3", @@ -12333,8 +12464,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-hash": { "version": "3.0.0", @@ -12574,6 +12704,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "prom-client": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/prom-client/-/prom-client-14.2.0.tgz", + "integrity": "sha512-sF308EhTenb/pDRPakm+WgiN+VdM/T1RaHj1x+MvAuT8UiQP8JmOEbxVqtkbfR4LrvOg5n7ic01kRBDGXjYikA==", + "peer": true, + "requires": { + "tdigest": "^0.1.1" + } + }, "promise-throttle": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/promise-throttle/-/promise-throttle-1.1.2.tgz", @@ -12788,6 +12927,11 @@ } } }, + "request-ip": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/request-ip/-/request-ip-3.3.0.tgz", + "integrity": "sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==" + }, "require-at": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/require-at/-/require-at-1.0.6.tgz", @@ -13180,6 +13324,89 @@ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", "dev": true }, + "swagger-stats": { + "version": "0.99.7", + "resolved": "https://registry.npmjs.org/swagger-stats/-/swagger-stats-0.99.7.tgz", + "integrity": "sha512-niP70m99Cwpz/Fyfk8ydul1jM0pOKD6UofSaDzW2Op6o6WYFsuAl/BhVbmLkZWOAZ7IloDVvFj6vaU5zA0xydg==", + "requires": { + "axios": "^1.4.0", + "basic-auth": "^2.0.1", + "cookies": "^0.8.0", + "debug": "^4.3.4", + "moment": "^2.29.4", + "path-to-regexp": "^6.2.1", + "qs": "^6.11.2", + "send": "^0.18.0", + "uuid": "^9.0.0" + }, + "dependencies": { + "debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", + "requires": { + "ms": "^2.1.3" + } + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "path-to-regexp": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", + "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==" + }, + "send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + } + } + }, + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==" + } + } + }, "tailwind-merge": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.6.0.tgz", @@ -13236,6 +13463,15 @@ } } }, + "tdigest": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/tdigest/-/tdigest-0.1.2.tgz", + "integrity": "sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==", + "peer": true, + "requires": { + "bintrees": "1.0.2" + } + }, "thenify": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", @@ -13303,6 +13539,11 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", diff --git a/package.json b/package.json index 172f6b3..8cd3f2a 100644 --- a/package.json +++ b/package.json @@ -46,21 +46,24 @@ "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "cmdk": "^1.0.0", - "dotenv": "^16.4.5", + "cors": "^2.8.5", + "dotenv": "^16.5.0", "express": "^4.21.2", + "express-rate-limit": "^7.5.0", "fanart.tv-api": "^1.0.3", "framer-motion": "^12.0.6", "lucide-react": "^0.462.0", - "mixpanel": "^0.18.0", "moviedb-promise": "^4.0.7", "next-themes": "^0.3.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-kofi": "^0.0.2", "react-router-dom": "^6.26.2", + "request-ip": "^3.3.0", "serve-favicon": "^2.5.0", "sharp": "^0.33.5", "sonner": "^1.5.0", + "swagger-stats": "^0.99.0", "tailwind-merge": "^2.5.2", "transliteration": "^2.3.5", "url-exists": "^1.0.3"