From 391e2a0a0758959a57b023a9e3495fa16488819f Mon Sep 17 00:00:00 2001 From: Thakar-Advait Date: Sat, 29 Mar 2025 22:04:47 +0530 Subject: [PATCH 1/3] added backend for IndustryTrends --- backend/.env.sample | 3 + backend/.gitignore | 54 + backend/README.md | 143 ++ backend/app.js | 41 + backend/controllers/dev.controllers.js | 97 + backend/controllers/github.controllers.js | 191 ++ .../controllers/subscribers.controllers.js | 10 + backend/cron/dev.cron.js | 170 ++ backend/cron/github.cron.js | 306 +++ backend/cron/test.cron.js | 22 + backend/db/index.js | 20 + backend/index.js | 22 + backend/models/developers.models.js | 30 + backend/models/post.models.js | 50 + backend/models/repositories.models.js | 65 + backend/models/subscribers.models.js | 15 + backend/models/test.models.js | 18 + backend/package-lock.json | 1878 +++++++++++++++++ backend/package.json | 25 + backend/routes/dev.routes.js | 9 + backend/routes/github.routes.js | 11 + backend/routes/subscribers.routes.js | 8 + backend/utils/randomPeriod.utils.js | 5 + backend/utils/response.utils.js | 26 + backend/utils/urlBuilder.utils.js | 10 + 25 files changed, 3229 insertions(+) create mode 100644 backend/.env.sample create mode 100644 backend/.gitignore create mode 100644 backend/README.md create mode 100644 backend/app.js create mode 100644 backend/controllers/dev.controllers.js create mode 100644 backend/controllers/github.controllers.js create mode 100644 backend/controllers/subscribers.controllers.js create mode 100644 backend/cron/dev.cron.js create mode 100644 backend/cron/github.cron.js create mode 100644 backend/cron/test.cron.js create mode 100644 backend/db/index.js create mode 100644 backend/index.js create mode 100644 backend/models/developers.models.js create mode 100644 backend/models/post.models.js create mode 100644 backend/models/repositories.models.js create mode 100644 backend/models/subscribers.models.js create mode 100644 backend/models/test.models.js create mode 100644 backend/package-lock.json create mode 100644 backend/package.json create mode 100644 backend/routes/dev.routes.js create mode 100644 backend/routes/github.routes.js create mode 100644 backend/routes/subscribers.routes.js create mode 100644 backend/utils/randomPeriod.utils.js create mode 100644 backend/utils/response.utils.js create mode 100644 backend/utils/urlBuilder.utils.js diff --git a/backend/.env.sample b/backend/.env.sample new file mode 100644 index 00000000..d3a60fce --- /dev/null +++ b/backend/.env.sample @@ -0,0 +1,3 @@ +PORT = "your_server_port" +MONGO_URI = "your_mongo_uri" +DB_NAME = "your_db_name" \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 00000000..442b61ad --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,54 @@ +bin +obj +csx +.vs +edge +Publish + +*.user +*.suo +*.cscfg +*.Cache +project-lock.json + + +/packages +/TestResults + +/tools/NuGet.exe +/App_Data +/secrets +/data +.secrets +appsettings.json +local.settings.json + +node_modules +dist + +# Local python packages +.python_packages/ + +# Python Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Azurite artifacts +__blobstorage__ +__queuestorage__ +__azurite_db*__.json + +.vscode + +test + diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 00000000..3c2df0aa --- /dev/null +++ b/backend/README.md @@ -0,0 +1,143 @@ + +# 🚀 DevDisplay API: Your Gateway to Developer Trends + +## 🌟 Project Overview + +DevDisplay API is a powerful backend service that aggregates and serves trending developer content from GitHub and Dev.to. It provides developers and tech enthusiasts with scraped insights into the most exciting developments in the tech world. + +## ✨ Key Features + +- **Trending Insights**: Discover top developers, repositories, and posts +- **Regularly scraped data**: Latest data scraped from authentic sources +- **Flexible Time Frames**: Choose from daily, weekly, or monthly trending data +- **Multi-Source Aggregation**: Pulls data from GitHub and Dev.to +- **Newsletter Subscription**: Stay updated with the latest tech trends + +## 🛠 Tech Stack + +- **Backend**: Node.js & Express.js +- **Database**: MongoDB with Mongoose ORM +- **Data Sources**: GitHub.com, Dev.to + +## 📂 Project Structure + +``` +DevDisplay-API/ +│ +├── controllers/ +│ ├── dev.controllers.js +│ ├── github.controllers.js +│ └── subscribers.controllers.js +│ +├── db/ +│ └── index.js +| +├── cron/ +│ └── dev.cron.js +│ └── github.cron.js +│ └── test.cron.js +│ +├── models/ +│ ├── developers.models.js +│ ├── post.models.js +│ ├── repositories.models.js +│ └── subscribers.models.js +│ +├── routes/ +│ ├── dev.routes.js +│ ├── github.routes.js +│ └── subscribers.routes.js +│ +└── utils/ + ├── randomPeriod.utils.js + ├── response.utils.js + └── urlBuilder.utils.js + +``` + +## 🌐 API Endpoints + +### Dev.to Endpoints + +- `GET /devdisplay/v1/trending/dev/posts/:since` +- `GET /devdisplay/v1/trending/dev/getRandomPost` + +### GitHub Endpoints + +- `GET /devdisplay/v1/trending/github/repositories/:since` +- `GET /devdisplay/v1/trending/github/developers/:since` +- `GET /devdisplay/v1/trending/github/getRandomDev` +- `GET /devdisplay/v1/trending/github/getRandomRepo` + +### Subscription Endpoints + +- `POST /devdisplay/v1/subscribers` + +## 🚀 Quick Start Guide + +### Prerequisites + +- Node.js +- npm +- MongoDB + +### Installation Steps + +1. Clone the repository + +```bash +git clone https://github.com/Thakar-Advait/DevDisplay-API.git +cd DevDisplay-API + +``` + +2. Install Dependencies + +```bash +npm install + +``` + +3. Configure Environment Variables Create a `.env` file with the following variables: + +``` +PORT=3000 +MONGO_URI=your_mongodb_connection_string +DB_NAME=devdisplay + +``` + +4. Prepare MongoDB Collections Ensure your MongoDB database has these collections: + +- `daily_trending_devs` +- `daily_trending_posts` +- `daily_trending_repos` +- `weekly_trending_devs` +- `weekly_trending_posts` +- `weekly_trending_repos` +- `monthly_trending_devs` +- `monthly_trending_posts` +- `monthly_trending_repos` +- `tests` +5. Start the Server + +```bash +npm run dev + +``` + +## 🔒 Environment Configuration + +Refer to `.env.sample` for detailed environment variable setup. + +## 📄 License + +[Insert Your License Here] + +## 🙌 Acknowledgments + +Special thanks to GitHub and Dev.to for providing the incredible developer data that powers this API. + +---------- + +**Built with ❤️ by Developers, for Developers** diff --git a/backend/app.js b/backend/app.js new file mode 100644 index 00000000..aac921e6 --- /dev/null +++ b/backend/app.js @@ -0,0 +1,41 @@ +import express from "express"; +import { devRouter } from "./routes/dev.routes.js"; +import { githubRouter } from "./routes/github.routes.js"; +import { subscribersRouter } from "./routes/subscribers.routes.js"; +import { getSubscribers } from "./controllers/subscribers.controllers.js"; +import Subscribers from "./models/subscribers.models.js"; +const app = express(); + +// Middleware to parse incoming JSON requests +app.use(express.json()); + +// Use the devRouter for the "/api/v1/trending/dev" route +app.use("/devdisplay/v1/trending/dev", devRouter); + +// Use the githubRouter for the "/api/v1/trending/github" route +app.use("/devdisplay/v1/trending/github", githubRouter); + +app.use("/devdisplay/v1/subscribers", getSubscribers); + +app.get("/", (req, res) => { + res.status(200).json({ + message: "Welcome to the DevDisplay API!" + }); +}) + +app.get("/health", (req, res) => { + res.status(200).json({ + message: "DevDisplay API is healthy!" + }); +}) + +// Global error handling middleware (optional) +app.use((err, req, res, next) => { + console.error(err.stack); // Log the error stack for debugging + res.status(500).json({ + message: "Something went wrong!", + error: err.message + }); +}); + +export default app; diff --git a/backend/controllers/dev.controllers.js b/backend/controllers/dev.controllers.js new file mode 100644 index 00000000..714be8a3 --- /dev/null +++ b/backend/controllers/dev.controllers.js @@ -0,0 +1,97 @@ +import * as Dev_models from "../models/post.models.js"; +import * as responseClass from "../utils/response.utils.js"; +import { pickRandomPeriod } from "../utils/randomPeriod.utils.js" + +// Helper function to fetch posts based on time period +const fetchPostsForPeriod = async (timePeriod) => { + let model; + switch (timePeriod) { + case "daily": + model = Dev_models.Post_daily; + break; + case "weekly": + model = Dev_models.Post_weekly; + break; + case "monthly": + model = Dev_models.Post_monthly; + break; + default: + throw new responseClass.ApiError(400, "Invalid time period specified."); + } + + // Fetching posts for the chosen period + const response = await model.find(); + if (!response || response.length === 0) { + throw new responseClass.ApiError(404, `No trending posts found for time period: '${timePeriod}'`); + } + return response; +}; + +const fetchTrendingPosts = async (req, res) => { + // Extract the "since" parameter from the URL, defaulting to "daily" if not provided + const { since = 'daily' } = req.params; + + try { + // Fetch posts based on the time period + const posts = await fetchPostsForPeriod(since); + + // Return a consistent response with the fetched posts + return res.status(200).json(new responseClass.ApiResponse( + 200, + `Fetched trending posts for time period: '${since}'`, + posts + )); + + } catch (error) { + // Generic error handling for unexpected errors + console.error(error); // Optional: Add logging to track errors + return res.status(500).json(new responseClass.ApiResponse( + 500, + `Something went wrong while fetching trending posts for time period: '${since}' --> \n${error.message || error}` + )); + } +} + +const fetchRandomPost = async (req, res) => { + const since = pickRandomPeriod(); + try { + let posts; + switch (since) { + case "daily": + posts = await Dev_models.Post_daily.aggregate([ + { $sample: { size: 1 } } + ]) + break; + case "weekly": + posts = await Dev_models.Post_weekly.aggregate([ + { $sample: { size: 1 } } + ]) + break; + case "monthly": + posts = await Dev_models.Post_monthly.aggregate([ + { $sample: { size: 1 } } + ]) + break; + default: + posts = await Dev_models.Post_daily.aggregate([ + { $sample: { size: 1 } } + ]) + break; + } + if (posts.length > 0) { + console.log('Random Document:', posts[0]); + return res.status(200).json(new responseClass.ApiResponse(200, `Fetched random post for time period: '${since}'`, posts[0])); + } else { + console.log('No documents found.'); + return res.status(404).json(new responseClass.ApiResponse(404, `No random posts found for time period: '${since}'`)); + } + } catch (error) { + console.error(error); + return res.status(500).json(new responseClass.ApiResponse(500, `Something went wrong while fetching random post for time period: '${since}' --> \n${error.message || error}`)); + } +} + +export { + fetchTrendingPosts, + fetchRandomPost +}; diff --git a/backend/controllers/github.controllers.js b/backend/controllers/github.controllers.js new file mode 100644 index 00000000..33ab0bb0 --- /dev/null +++ b/backend/controllers/github.controllers.js @@ -0,0 +1,191 @@ +import * as Repo_models from "../models/repositories.models.js"; +import * as Dev_models from "../models/developers.models.js"; +import * as responseClass from "../utils/response.utils.js"; +import { pickRandomPeriod } from "../utils/randomPeriod.utils.js" + +// Helper function to fetch developers based on time period +const fetchDevsForPeriod = async (timePeriod) => { + let model; + switch (timePeriod) { + case "daily": + model = Dev_models.Dev_daily; + break; + case "weekly": + model = Dev_models.Dev_weekly; + break; + case "monthly": + model = Dev_models.Dev_monthly; + break; + default: + throw new responseClass.ApiError(400, "Invalid time period specified."); + } + + // Fetching developers for the chosen period + const response = await model.find(); + if (!response || response.length === 0) { + throw new responseClass.ApiError(404, `No trending developers found for time period: '${timePeriod}'`); + } + return response; +}; + +// Helper function to fetch repositories based on time period +const fetchReposForPeriod = async (timePeriod) => { + let model; + switch (timePeriod) { + case "daily": + model = Repo_models.Repo_daily; + break; + case "weekly": + model = Repo_models.Repo_weekly; + break; + case "monthly": + model = Repo_models.Repo_monthly; + break; + default: + throw new responseClass.ApiError(400, "Invalid time period specified."); + } + + // Fetching repositories for the chosen period + const response = await model.find(); + if (!response || response.length === 0) { + throw new responseClass.ApiError(404, `No trending repositories found for time period: '${timePeriod}'`); + } + return response; +}; + +// Controller function for fetching trending developers +const fetchTrendingDevs = async (req, res) => { + // Extract the "since" parameter from the URL + const { since = 'daily' } = req.params; // Default to 'daily' if no "since" is provided + + try { + // Fetch developers based on the time period + const devs = await fetchDevsForPeriod(since); + + // Return a consistent response + return res.status(200).json(new responseClass.ApiResponse( + 200, + `Fetched trending developers for time period: '${since}'`, + devs + )); + + } catch (error) { + // Generic error handling for unexpected errors + console.error(error); // Optional: Add logging to track errors + return res.status(500).json(new responseClass.ApiResponse( + 500, + `Something went wrong while fetching trending developers for time period: '${since}' --> \n${error.message || error}` + )); + } +} + +// Controller function for fetching trending repositories +const fetchTrendingRepos = async (req, res) => { + // Extract the "since" parameter from the URL + const { since = 'daily' } = req.params; // Default to 'daily' if no "since" is provided + + try { + // Fetch repositories based on the time period + const repos = await fetchReposForPeriod(since); + + // Return a consistent response + return res.status(200).json(new responseClass.ApiResponse( + 200, + `Fetched trending repositories for time period: '${since}'`, + repos + )); + + } catch (error) { + // Generic error handling for unexpected errors + console.error(error); // Optional: Add logging to track errors + return res.status(500).json(new responseClass.ApiResponse( + 500, + `Something went wrong while fetching trending repositories for time period: '${since}' --> \n${error.message || error}` + )); + } +} + +const fetchRandomDev = async (req, res) => { + const since = pickRandomPeriod(); + try { + let devs; + switch (since) { + case "daily": + devs = await Dev_models.Dev_daily.aggregate([ + { $sample: { size: 1 } } + ]) + break; + case "weekly": + devs = await Dev_models.Dev_weekly.aggregate([ + { $sample: { size: 1 } } + ]) + break; + case "monthly": + devs = await Dev_models.Dev_monthly.aggregate([ + { $sample: { size: 1 } } + ]) + break; + default: + devs = await Dev_models.Dev_daily.aggregate([ + { $sample: { size: 1 } } + ]) + break; + } + if (devs.length > 0) { + console.log('Random Document:', devs[0]); + return res.status(200).json(new responseClass.ApiResponse(200, `Fetched random developer for time period: '${since}'`, devs[0])); + } else { + console.log('No documents found.'); + return res.status(404).json(new responseClass.ApiResponse(404, `No random developer found for time period: '${since}'`)); + } + } catch (error) { + console.error(error); + return res.status(500).json(new responseClass.ApiResponse(500, `Something went wrong while fetching random developer for time period: '${since}' --> \n${error.message || error}`)); + } +} + +const fetchRandomRepo = async (req, res) => { + const since = pickRandomPeriod(); + try { + let repos; + switch (since) { + case "daily": + repos = await Repo_models.Repo_daily.aggregate([ + { $sample: { size: 1 } } + ]) + break; + case "weekly": + repos = await Repo_models.Repo_weekly.aggregate([ + { $sample: { size: 1 } } + ]) + break; + case "monthly": + repos = await Repo_models.Repo_monthly.aggregate([ + { $sample: { size: 1 } } + ]) + break; + default: + repos = await Repo_models.Repo_daily.aggregate([ + { $sample: { size: 1 } } + ]) + break; + } + if (repos.length > 0) { + console.log('Random Document:', repos[0]); + return res.status(200).json(new responseClass.ApiResponse(200, `Fetched random repo for time period: '${since}'`, repos[0])); + } else { + console.log('No documents found.'); + return res.status(404).json(new responseClass.ApiResponse(404, `No random repo found for time period: '${since}'`)); + } + } catch (error) { + console.error(error); + return res.status(500).json(new responseClass.ApiResponse(500, `Something went wrong while fetching random repo for time period: '${since}' --> \n${error.message || error}`)); + } +} + +export { + fetchTrendingDevs, + fetchTrendingRepos, + fetchRandomDev, + fetchRandomRepo +} diff --git a/backend/controllers/subscribers.controllers.js b/backend/controllers/subscribers.controllers.js new file mode 100644 index 00000000..222ee3a2 --- /dev/null +++ b/backend/controllers/subscribers.controllers.js @@ -0,0 +1,10 @@ +import Subscribers from "../models/subscribers.models.js"; + +export const getSubscribers = async (req, res) => { + try { + const subscribers = await Subscribers.find().where("status").equals("subscribed"); + res.status(200).json(subscribers); + } catch (error) { + res.status(500).json({ message: error.message }); + } +} \ No newline at end of file diff --git a/backend/cron/dev.cron.js b/backend/cron/dev.cron.js new file mode 100644 index 00000000..d7c43541 --- /dev/null +++ b/backend/cron/dev.cron.js @@ -0,0 +1,170 @@ +import cron from "node-cron"; +import * as cheerio from "cheerio"; +import axios from "axios"; +import buildUrl from "../utils/urlBuilder.utils.js"; +import * as postModels from "../models/post.models.js"; + +const base_url = "https://dev.to"; + +const fetchTrendingPosts = async ({ + since = "day" +} = {}) => { + const url = buildUrl(`${base_url}/top/${since}`); + console.log(url) + try { + const response = await axios.get(url); + + const $ = cheerio.load(response.data); + // Extract posts (titles, URLs, etc.) from the HTML + const $posts = $(".substories .crayons-story") + .map((i, post) => { + const $post = $(post); + const title = $post.find("a[class=crayons-story__hidden-navigation-link]").text().trim(); + const url = $post.find("a[class=crayons-story__hidden-navigation-link]").attr("href"); + const $body = $post.find(".crayons-story__body"); + const avatar = $body.find(".crayons-avatar > img").attr("src"); + const author = $body.find(".crayons-story__secondary").text().trim(); + const linkToProf = $body.find(".crayons-story__secondary").attr("href"); + const dateOfPost = $body.find(".crayons-story__tertiary > time").attr("datetime"); + const tags = $body.find(".crayons-story__indention").find(".crayons-story__tags > a.crayons-tag").get().map((tag) => { + const $tag = $(tag); + return $tag.text().split("#")[1].trim(); + }); + let reactions = "0 reactions" + const readTime = $body.find(".crayons-story__save > small").text().trim(); + // Fix the typo in the selector here: + const hasReaction = $body.find(".crayons-story__bottom > .crayons-story__details > a").find("div.multiple_reactions_aggregate") + hasReaction.length > 0 ? reactions = hasReaction.find(".aggregate_reactions_counter").text().trim() : "0 reactions" + + // Optionally, extract more post details (e.g., author, comments, etc.) + return { + title, + url: url, + avatar: avatar, + author: author, + linkToProf: base_url + linkToProf, + dateOfPost: dateOfPost, + tags: tags, + reactions: reactions, + readTime: readTime + }; + }) + .get(); // Convert Cheerio object to a regular array + + console.log(`Found ${$posts.length} posts`); + + // Return the scraped posts + return $posts; + } catch (error) { + console.log(`Something went wrong while fetching data from Dev Community: ${error}`); + } +} + +const trendingDailyPosts = cron.schedule("0 0 * * *", async () => { + try { + const postObj = await fetchTrendingPosts(); + try { + await postModels.Post_daily.deleteMany({}); + } catch (error) { + throw new Error(error); + } + postObj.forEach(async (post) => { + const newObj = new postModels.Post_daily({ + title: post.title, + url: post.url, + avatar: post.avatar, + author: post.author, + linkToProf: post.linkToProf, + dateOfPost: new Date(post.dateOfPost), + tags: post.tags, + reactions: post.reactions, + readTime: post.readTime + }) + + try { + const res = await newObj.save(); + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating daily trending posts: `,error) + } + }) + console.log(`\n`); + } catch (error) { + console.log(`Something went wrong in trendingDailyPosts function: \n${error}`) + } +}) + +const trendingWeeklyPosts = cron.schedule("0 0 * * 0", async () => { + try { + const postObj = await fetchTrendingPosts({ since: "week" }); + try { + await postModels.Post_weekly.deleteMany({}); + } catch (error) { + throw new Error(error); + } + postObj.forEach(async (post) => { + const newObj = new postModels.Post_weekly({ + title: post.title, + url: post.url, + avatar: post.avatar, + author: post.author, + linkToProf: post.linkToProf, + dateOfPost: new Date(post.dateOfPost), + tags: post.tags, + reactions: post.reactions, + readTime: post.readTime + }) + + try { + const res = await newObj.save(); + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating weekly trending posts: `,error) + } + }) + console.log(`\n`); + } catch (error) { + console.log(`Something went wrong in trendingWeeklyPosts function: \n${error}`) + } +}); + +const trendingMonthlyPosts = cron.schedule("0 0 1 * *", async () => { + try { + const postObj = await fetchTrendingPosts({ since: "month" }); + try { + await postModels.Post_monthly.deleteMany({}); + } catch (error) { + throw new Error(error); + } + postObj.forEach(async (post) => { + const newObj = new postModels.Post_monthly({ + title: post.title, + url: post.url, + avatar: post.avatar, + author: post.author, + linkToProf: post.linkToProf, + dateOfPost: new Date(post.dateOfPost), + tags: post.tags, + reactions: post.reactions, + readTime: post.readTime + }) + + try { + const res = await newObj.save(); + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating monthly trending posts: `,error) + } + }) + console.log(`\n`); + } catch (error) { + console.log(`Something went wrong in trendingMonthlyPosts function: \n${error}`) + } +}) + +export { + trendingDailyPosts, + trendingWeeklyPosts, + trendingMonthlyPosts +} + diff --git a/backend/cron/github.cron.js b/backend/cron/github.cron.js new file mode 100644 index 00000000..64258ce4 --- /dev/null +++ b/backend/cron/github.cron.js @@ -0,0 +1,306 @@ +import cron from "node-cron"; +import buildUrl from "../utils/urlBuilder.utils.js"; +import axios from "axios"; +import * as cheerio from "cheerio"; +import * as devModels from "../models/developers.models.js"; +import * as repoModels from "../models/repositories.models.js"; + +const base_url = "https://github.com" + +const fetchTrendingRepos = async ({ + language = "", + since = "daily" +} = {}) => { + const url = buildUrl(`${base_url}/trending`, { + language, + since + }) + + try { + const response = await axios.get(url); + + const $ = cheerio.load(response.data); + return ( + $('.Box article.Box-row') + .get() + .map((repo) => { + const $repo = $(repo); + const title = $repo.find(".h3").text().trim(); + const [username, repoName] = title.split("/").map(item => item.trim()); + const description = $repo.find("p").text().trim(); + const linkToRepo = $repo.find(".h3 > a").attr("href"); + const language = $repo.find('span[itemprop="programmingLanguage"]').text().trim(); + const stars = $repo.find('.mr-3 svg[aria-label="star"]').first().parent().text().trim(); + const forks = $repo.find('svg[aria-label="fork"]').first().parent().text().trim(); + const currentPeriodStars = $repo.find("span.d-inline-block.float-sm-right").text().trim(); + const builtBy = $repo + .find('span:contains("Built by")') + .find('[data-hovercard-type="user"]') + .map((i, user) => { + const altString = $(user).children('img').attr('alt'); + const avatarUrl = $(user).children('img').attr('src'); + return { + username: altString + ? altString.slice(1) + : /* istanbul ignore next */ null, + href: `${base_url}${user.attribs.href}`, + avatar: avatarUrl.split("?")[0], + }; + }) + .get(); + // const builtBy = builtByAnchors.map((contributor) => { + // return contributor.split("?")[1]; + // }) + return { + author: username, + title: repoName, + description: description, + linkToRepo: `${base_url}/${linkToRepo}`, + language: language, + stars: stars, + forks: forks, + currentPeriodStars: currentPeriodStars, + builtBy: builtBy + } + }) + ) + } catch (error) { + console.log(`Something went wrong while fetching the HTML data for repos: `, error) + } +} + +const fetchTrendingDevs = async ({ + language = "", + since = "daily" +} = {}) => { + const url = buildUrl(`${base_url}/trending/developers`, { + language, + since + }) + + try { + const response = await axios.get(url); + + const $ = cheerio.load(response.data); + return ( + $('.Box article.Box-row') + .get() + .map((dev) => { + const $dev = $(dev); + const avatar = $dev.find("img").attr("src") + const name = $dev.find(".h3 a").text().trim(); + const linkToDev = $dev.find(".h3 a").attr("href"); + const username = linkToDev.split("/")[1]; + return { + name: name, + avatar: avatar.split("?")[0], + linkToDev: `${base_url}/${linkToDev}`, + username: username + } + }) + ) + } catch (error) { + console.log(`Something went wrong while fetching the HTML data for devs: `, error) + } +} + +const trendingDailyRepos = cron.schedule("0 0 * * *", async () => { + try { + const repoObjects = await fetchTrendingRepos(); + try { + await repoModels.Repo_daily.deleteMany({}) + } catch (error) { + throw new Error(error); + } + repoObjects.forEach(async (repo) => { + const newObj = new repoModels.Repo_daily({ + author: repo.author, + title: repo.title, + description: repo.description, + link: repo.linkToRepo, + language: repo.language, + stars: repo.stars, + forks: repo.forks, + current_period_stars: repo.currentPeriodStars, + builtBy: repo.builtBy + }) + try { + const res = await newObj.save() + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating daily trending repositories: `, error) + } + console.log(`\n`) + }) + console.log(`Github's trending repositories fetched and pushed to db!`) + } catch (error) { + console.log(`Something went wrong in trendingDailyRepos function: \n${error}`) + } +}) + +const trendingDailyDevs = cron.schedule("0 0 * * *", async () => { + try { + const devObjects = await fetchTrendingDevs(); + try { + await devModels.Dev_daily.deleteMany({}) + } catch (error) { + throw new Error(error) + } + devObjects.forEach(async (dev) => { + const newObj = new devModels.Dev_daily({ + name: dev.name, + avatar: dev.avatar, + username: dev.username, + link: dev.linkToDev + }) + + try { + const res = await newObj.save(); + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating daily trending developers: `, error) + } + console.log(`\n`) + }) + console.log(`Github's trending developers fetched and pushed to db!`) + } catch (error) { + console.log(`Something went wrong in trendingDailyDevs function: \n${error}`) + } +}) + +const trendingWeeklyRepos = cron.schedule("0 0 * * 0", async () => { + try { + const repoObjects = await fetchTrendingRepos({ since: "weekly" }); + try { + await repoModels.Repo_weekly.deleteMany({}) + } catch (error) { + throw new Error(error) + } + repoObjects.forEach(async (repo) => { + const newObj = new repoModels.Repo_weekly({ + author: repo.author, + title: repo.title, + description: repo.description, + link: repo.linkToRepo, + language: repo.language, + stars: repo.stars, + forks: repo.forks, + current_period_stars: repo.currentPeriodStars, + builtBy: repo.builtBy + }) + try { + const res = await newObj.save() + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating weekly trending repositories: `, error) + } + console.log(`\n`) + }) + console.log(`Github's trending repositories fetched and pushed to db!`) + } catch (error) { + console.log(`Something went wrong in trendingWeeklyRepos function: \n${error}`) + } +}) + +const trendingWeeklyDevs = cron.schedule("0 0 * * 0", async () => { + try { + const devObjects = await fetchTrendingDevs({ since: "weekly" }); + try { + await devModels.Dev_weekly.deleteMany({}) + } catch (error) { + throw new Error(error) + } + devObjects.forEach(async (dev) => { + const newObj = new devModels.Dev_weekly({ + name: dev.name, + avatar: dev.avatar, + username: dev.username, + link: dev.linkToDev + }) + + try { + const res = await newObj.save(); + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating weekly trending developers: `, error) + } + console.log(`\n`) + }) + console.log(`Github's trending developers fetched and pushed to db!`) + } catch (error) { + console.log(`Something went wrong in trendingWeeklyDevs function: \n${error}`) + } +}) + +const trendingMonthlyRepos = cron.schedule("0 0 1 * *", async () => { + try { + const repoObjects = await fetchTrendingRepos({ since: "monthly" }); + try { + await repoModels.Repo_monthly.deleteMany({}) + } catch (error) { + throw new Error(error) + } + repoObjects.forEach(async (repo) => { + const newObj = new repoModels.Repo_monthly({ + author: repo.author, + title: repo.title, + description: repo.description, + link: repo.linkToRepo, + language: repo.language, + stars: repo.stars, + forks: repo.forks, + current_period_stars: repo.currentPeriodStars, + builtBy: repo.builtBy + }) + try { + const res = await newObj.save() + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating monthly trending repositories: `, error) + } + console.log(`\n`) + }) + console.log(`Github's trending repositories fetched and pushed to db!`) + } catch (error) { + console.log(`Something went wrong in trendingMonthlyRepos function: \n${error}`) + } +}) + +const trendingMonthlyDevs = cron.schedule("0 0 1 * *", async () => { + try { + const devObjects = await fetchTrendingDevs({ since: "monthly" }); + try { + await devModels.Dev_monthly.deleteMany({}) + } catch (error) { + throw new Error(error) + } + devObjects.forEach(async (dev) => { + const newObj = new devModels.Dev_monthly({ + name: dev.name, + avatar: dev.avatar, + username: dev.username, + link: dev.linkToDev + }) + + try { + const res = await newObj.save(); + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating monthly trending developers: `, error) + } + console.log(`\n`) + }) + console.log(`Github's trending developers fetched and pushed to db!`) + } catch (error) { + console.log(`Something went wrong in trendingMonthlyDevs function: \n${error}`) + } +}) + +export { + trendingDailyDevs, + trendingDailyRepos, + trendingMonthlyDevs, + trendingMonthlyRepos, + trendingWeeklyDevs, + trendingWeeklyRepos +} \ No newline at end of file diff --git a/backend/cron/test.cron.js b/backend/cron/test.cron.js new file mode 100644 index 00000000..951aa25f --- /dev/null +++ b/backend/cron/test.cron.js @@ -0,0 +1,22 @@ +import cron from "node-cron"; +import { Test } from "../models/test.models.js"; + +const testFunction = async () => { + const date = new Date(); + const time = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; + try { + const newObj = new Test({ + message: "Pinging MongoDB for tests...", + date: date, + time: time + }) + + await newObj.save() + console.log(`Successfully pinged MongoDB!`) + } catch (error) { + console.log(`Something went wrong while pinging MongoDB: ${error}`) + } +} + +const pingDB = cron.schedule("* * * * *", testFunction) +export default pingDB; \ No newline at end of file diff --git a/backend/db/index.js b/backend/db/index.js new file mode 100644 index 00000000..6c30c05e --- /dev/null +++ b/backend/db/index.js @@ -0,0 +1,20 @@ +import { connect } from "mongoose" + +const connectDb = async () => { + const db_uri = process.env.MONGO_URI; + const db_name = process.env.DB_NAME; + console.log(db_uri) + console.log(db_name) + try { + const connectionInstance = await connect(`${db_uri}/${db_name}`); + console.log(`${db_uri}/${db_name}`) + if (connectionInstance) { + console.log(`Connected to MongoDB: ${connectionInstance.connection.host}`) + } + } catch (error) { + console.log(`Something went wrong while connecting to DB: ${error}`) + process.exit(1) + } +} + +export default connectDb; \ No newline at end of file diff --git a/backend/index.js b/backend/index.js new file mode 100644 index 00000000..e5f938ab --- /dev/null +++ b/backend/index.js @@ -0,0 +1,22 @@ +import app from "./app.js"; +import connectDb from "./db/index.js"; +import dotenv from "dotenv"; +import pingDB from "./cron/test.cron.js"; +import * as devCronJobs from "./cron/dev.cron.js"; +import * as githubCronJobs from "./cron/github.cron.js"; + +dotenv.config({ + path: "./.env" +}); + +connectDb() + .then(() => { + const PORT = process.env.PORT; + app.listen(PORT, () => { + console.log(`Server started on http://localhost:${PORT}`) + }) + }) + .catch((error) => { + console.log(`Something went wrong while starting the server: ${error}`) + }) + diff --git a/backend/models/developers.models.js b/backend/models/developers.models.js new file mode 100644 index 00000000..511fcca4 --- /dev/null +++ b/backend/models/developers.models.js @@ -0,0 +1,30 @@ +import mongoose from "mongoose"; +import aggregatePaginate from "mongoose-aggregate-paginate-v2"; + +const devSchema = new mongoose.Schema({ + name: { + type: String, + lowercase: true, + trim: true, + required: true + }, + username: { + type: String, + lowercase: true, + trim: true, + required: true + }, + avatar: { + type: String, + required: true + }, + link: { + type: String, + } +}, { timestamps: true }); + +devSchema.plugin(aggregatePaginate) + +export const Dev_daily = mongoose.model("daily_trending_dev", devSchema) +export const Dev_weekly = mongoose.model("weekly_trending_dev", devSchema) +export const Dev_monthly = mongoose.model("monthly_trending_dev", devSchema) \ No newline at end of file diff --git a/backend/models/post.models.js b/backend/models/post.models.js new file mode 100644 index 00000000..ee9e915c --- /dev/null +++ b/backend/models/post.models.js @@ -0,0 +1,50 @@ +import mongoose from "mongoose"; +import aggregatePaginate from "mongoose-aggregate-paginate-v2"; + +const postSchema = new mongoose.Schema({ + title: { + type: String, + required: true, + lowercase: true, + trim: true + }, + url: { + type: String, + required: true + }, + avatar: { + type: String, + required: true + }, + author: { + type: String, + required: true, + lowercase: true, + trim: true + }, + linkToProf: { + type: String, + required: true + }, + dateOfPost: { + type: Date, + required: true + }, + tags: [{ + type: String, + lowercase: true, + trim: true + }], + reactions: { + type: String + }, + readTime: { + type: String + } +}, {timestamps: true}); + +postSchema.plugin(aggregatePaginate) + +export const Post_daily = mongoose.model("daily_trending_post", postSchema); +export const Post_weekly = mongoose.model("weekly_trending_post", postSchema); +export const Post_monthly = mongoose.model("monthly_trending_post", postSchema); diff --git a/backend/models/repositories.models.js b/backend/models/repositories.models.js new file mode 100644 index 00000000..227d417a --- /dev/null +++ b/backend/models/repositories.models.js @@ -0,0 +1,65 @@ +import mongoose from "mongoose"; +import aggregatePaginate from "mongoose-aggregate-paginate-v2"; + +const repoSchema = new mongoose.Schema({ + author: { + type: String, + lowercase: true, + trim: true, + required: true + }, + title: { + type: String, + lowercase: true, + trim: true, + required: true + }, + description: { + type: String, + lowercase: true, + trim: true, + required: true + }, + link: { + type: String, + required: true, + }, + language: { + type: String, + lowercase: true, + trim: true + }, + stars: { + type: String, + required: true + }, + forks: { + type: String, + required: true + }, + current_period_stars: { + type: String, + required: true + }, + builtBy: [{ + name: { + type: String, + lowercase: true, + trim: true + }, + username: { + type: String, + lowercase: true, + trim: true + }, + link: { + type: String, + } + }] +}, { timestamps: true }); + +repoSchema.plugin(aggregatePaginate) + +export const Repo_daily = mongoose.model("daily_trending_repo", repoSchema); +export const Repo_weekly = mongoose.model("weekly_trending_repo", repoSchema); +export const Repo_monthly = mongoose.model("monthly_trending_repo", repoSchema); \ No newline at end of file diff --git a/backend/models/subscribers.models.js b/backend/models/subscribers.models.js new file mode 100644 index 00000000..90fd4e6c --- /dev/null +++ b/backend/models/subscribers.models.js @@ -0,0 +1,15 @@ +import mongoose from "mongoose"; + +const subscriberSchema = new mongoose.Schema({ + email: { + type: String, + required: true + }, + status: { + type: String, + required: true + } +}, { timestamps: true }); + +const Subscribers = mongoose.model("Subscribers", subscriberSchema); +export default Subscribers; \ No newline at end of file diff --git a/backend/models/test.models.js b/backend/models/test.models.js new file mode 100644 index 00000000..ad971d32 --- /dev/null +++ b/backend/models/test.models.js @@ -0,0 +1,18 @@ +import mongoose from "mongoose"; + +const testSchema = new mongoose.Schema({ + message: { + type: String, + required: true + }, + date: { + type: Date, + required: true + }, + time: { + type: String, + required: true + } +}); + +export const Test = mongoose.model("test", testSchema); \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 00000000..862aa3aa --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,1878 @@ +{ + "name": "github-trending-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "github-trending-api", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^1.8.2", + "cheerio": "^1.0.0", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "mongoose": "^8.12.1", + "mongoose-aggregate-paginate-v2": "^1.1.4", + "node-cron": "^3.0.3" + }, + "devDependencies": { + "nodemon": "^3.1.9" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz", + "integrity": "sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", + "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bson": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz", + "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18.17" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mongodb": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.14.2.tgz", + "integrity": "sha512-kMEHNo0F3P6QKDq17zcDuPeaywK/YaJVCEQRzPF3TOM/Bl9MFg64YE5Tu7ifj37qZJMhwU1tl2Ioivws5gRG5Q==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.3", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.12.1.tgz", + "integrity": "sha512-UW22y8QFVYmrb36hm8cGncfn4ARc/XsYWQwRTaj0gxtQk1rDuhzDO1eBantS+hTTatfAIS96LlRCJrcNHvW5+Q==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.3", + "kareem": "2.6.3", + "mongodb": "~6.14.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose-aggregate-paginate-v2": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mongoose-aggregate-paginate-v2/-/mongoose-aggregate-paginate-v2-1.1.4.tgz", + "integrity": "sha512-CdQIar3wlS7g0H6JjSJIZzvzz05vFc+Xy9SosJmj46l3xIomgl3ZjDn/n4vDpEei9RBawgUk5zGTIP6fMKdMdA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mongoose/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==", + "license": "MIT" + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/mquery/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==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/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==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "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/send/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==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/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==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", + "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", + "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 00000000..0dab60ad --- /dev/null +++ b/backend/package.json @@ -0,0 +1,25 @@ +{ + "name": "github-trending-api", + "version": "1.0.0", + "description": "An application which scraps the trending repostitories and developers from Github.", + "main": "index.js", + "type": "module", + "scripts": { + "start": "node index.js", + "dev": "nodemon index.js" + }, + "author": "Advait Thakar", + "license": "ISC", + "dependencies": { + "axios": "^1.8.2", + "cheerio": "^1.0.0", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "mongoose": "^8.12.1", + "mongoose-aggregate-paginate-v2": "^1.1.4", + "node-cron": "^3.0.3" + }, + "devDependencies": { + "nodemon": "^3.1.9" + } +} diff --git a/backend/routes/dev.routes.js b/backend/routes/dev.routes.js new file mode 100644 index 00000000..c80a6513 --- /dev/null +++ b/backend/routes/dev.routes.js @@ -0,0 +1,9 @@ +import { Router } from "express"; +import * as devControllers from "../controllers/dev.controllers.js" + +const router = Router() + +router.get("/posts/:since", devControllers.fetchTrendingPosts) +router.get("/getRandomPost", devControllers.fetchRandomPost) + +export { router as devRouter }; \ No newline at end of file diff --git a/backend/routes/github.routes.js b/backend/routes/github.routes.js new file mode 100644 index 00000000..63052d7b --- /dev/null +++ b/backend/routes/github.routes.js @@ -0,0 +1,11 @@ +import { Router } from "express"; +import * as githubControllers from "../controllers/github.controllers.js"; + +const router = Router(); + +router.get("/repositories/:since", githubControllers.fetchTrendingRepos) +router.get("/getRandomRepo", githubControllers.fetchRandomRepo) +router.get("/developers/:since", githubControllers.fetchTrendingDevs) +router.get("/getRandomDev", githubControllers.fetchRandomDev) + +export { router as githubRouter }; \ No newline at end of file diff --git a/backend/routes/subscribers.routes.js b/backend/routes/subscribers.routes.js new file mode 100644 index 00000000..42a9d07d --- /dev/null +++ b/backend/routes/subscribers.routes.js @@ -0,0 +1,8 @@ +import { Router } from "express"; +import { getSubscribers } from "../controllers/subscribers.controllers.js"; + +const router = Router(); + +router.get("/", getSubscribers) + +export { router as subscribersRouter }; \ No newline at end of file diff --git a/backend/utils/randomPeriod.utils.js b/backend/utils/randomPeriod.utils.js new file mode 100644 index 00000000..c2b23790 --- /dev/null +++ b/backend/utils/randomPeriod.utils.js @@ -0,0 +1,5 @@ +export const pickRandomPeriod = () => { + const periods = ['daily', 'weekly', 'monthly']; + const randomIndex = Math.floor(Math.random() * periods.length); + return periods[randomIndex]; +} \ No newline at end of file diff --git a/backend/utils/response.utils.js b/backend/utils/response.utils.js new file mode 100644 index 00000000..39d40b35 --- /dev/null +++ b/backend/utils/response.utils.js @@ -0,0 +1,26 @@ +export class ApiResponse { + constructor(status, message, data) { + this.status = status; + this.message = message; + this.data = data; + this.success = status < 400 + } +} + +export class ApiError extends Error { + constructor(status, message, data = null, errors = [], stackTrace = null) { + super(message); + this.status = status; + this.message = message; + this.data = data; + this.errors = errors; + this.success = false; + + if(stackTrace) { + this.stackTrace = stackTrace; + } + else { + Error.captureStackTrace(this) + } + } +} \ No newline at end of file diff --git a/backend/utils/urlBuilder.utils.js b/backend/utils/urlBuilder.utils.js new file mode 100644 index 00000000..83a376a4 --- /dev/null +++ b/backend/utils/urlBuilder.utils.js @@ -0,0 +1,10 @@ +const buildUrl = (base_url, params = {}) => { + const queryString = Object.keys(params) + .filter((key) => params[key]) + .map((key) => `${key}=${params[key]}`) + .join("&"); + + return queryString === "" ? base_url : `${base_url}/?${queryString}`; +} + +export default buildUrl; \ No newline at end of file From 320673f0c3a2021334a11a8b1b9cfc36a0f2d46d Mon Sep 17 00:00:00 2001 From: Thakar-Advait Date: Sat, 29 Mar 2025 22:08:17 +0530 Subject: [PATCH 2/3] deleted the backend folder --- backend/.env.sample | 3 - backend/.gitignore | 54 - backend/README.md | 143 -- backend/app.js | 41 - backend/controllers/dev.controllers.js | 97 - backend/controllers/github.controllers.js | 191 -- .../controllers/subscribers.controllers.js | 10 - backend/cron/dev.cron.js | 170 -- backend/cron/github.cron.js | 306 --- backend/cron/test.cron.js | 22 - backend/db/index.js | 20 - backend/index.js | 22 - backend/models/developers.models.js | 30 - backend/models/post.models.js | 50 - backend/models/repositories.models.js | 65 - backend/models/subscribers.models.js | 15 - backend/models/test.models.js | 18 - backend/package-lock.json | 1878 ----------------- backend/package.json | 25 - backend/routes/dev.routes.js | 9 - backend/routes/github.routes.js | 11 - backend/routes/subscribers.routes.js | 8 - backend/utils/randomPeriod.utils.js | 5 - backend/utils/response.utils.js | 26 - backend/utils/urlBuilder.utils.js | 10 - 25 files changed, 3229 deletions(-) delete mode 100644 backend/.env.sample delete mode 100644 backend/.gitignore delete mode 100644 backend/README.md delete mode 100644 backend/app.js delete mode 100644 backend/controllers/dev.controllers.js delete mode 100644 backend/controllers/github.controllers.js delete mode 100644 backend/controllers/subscribers.controllers.js delete mode 100644 backend/cron/dev.cron.js delete mode 100644 backend/cron/github.cron.js delete mode 100644 backend/cron/test.cron.js delete mode 100644 backend/db/index.js delete mode 100644 backend/index.js delete mode 100644 backend/models/developers.models.js delete mode 100644 backend/models/post.models.js delete mode 100644 backend/models/repositories.models.js delete mode 100644 backend/models/subscribers.models.js delete mode 100644 backend/models/test.models.js delete mode 100644 backend/package-lock.json delete mode 100644 backend/package.json delete mode 100644 backend/routes/dev.routes.js delete mode 100644 backend/routes/github.routes.js delete mode 100644 backend/routes/subscribers.routes.js delete mode 100644 backend/utils/randomPeriod.utils.js delete mode 100644 backend/utils/response.utils.js delete mode 100644 backend/utils/urlBuilder.utils.js diff --git a/backend/.env.sample b/backend/.env.sample deleted file mode 100644 index d3a60fce..00000000 --- a/backend/.env.sample +++ /dev/null @@ -1,3 +0,0 @@ -PORT = "your_server_port" -MONGO_URI = "your_mongo_uri" -DB_NAME = "your_db_name" \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore deleted file mode 100644 index 442b61ad..00000000 --- a/backend/.gitignore +++ /dev/null @@ -1,54 +0,0 @@ -bin -obj -csx -.vs -edge -Publish - -*.user -*.suo -*.cscfg -*.Cache -project-lock.json - - -/packages -/TestResults - -/tools/NuGet.exe -/App_Data -/secrets -/data -.secrets -appsettings.json -local.settings.json - -node_modules -dist - -# Local python packages -.python_packages/ - -# Python Environments -.env -.venv -env/ -venv/ -ENV/ -env.bak/ -venv.bak/ - -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] -*$py.class - -# Azurite artifacts -__blobstorage__ -__queuestorage__ -__azurite_db*__.json - -.vscode - -test - diff --git a/backend/README.md b/backend/README.md deleted file mode 100644 index 3c2df0aa..00000000 --- a/backend/README.md +++ /dev/null @@ -1,143 +0,0 @@ - -# 🚀 DevDisplay API: Your Gateway to Developer Trends - -## 🌟 Project Overview - -DevDisplay API is a powerful backend service that aggregates and serves trending developer content from GitHub and Dev.to. It provides developers and tech enthusiasts with scraped insights into the most exciting developments in the tech world. - -## ✨ Key Features - -- **Trending Insights**: Discover top developers, repositories, and posts -- **Regularly scraped data**: Latest data scraped from authentic sources -- **Flexible Time Frames**: Choose from daily, weekly, or monthly trending data -- **Multi-Source Aggregation**: Pulls data from GitHub and Dev.to -- **Newsletter Subscription**: Stay updated with the latest tech trends - -## 🛠 Tech Stack - -- **Backend**: Node.js & Express.js -- **Database**: MongoDB with Mongoose ORM -- **Data Sources**: GitHub.com, Dev.to - -## 📂 Project Structure - -``` -DevDisplay-API/ -│ -├── controllers/ -│ ├── dev.controllers.js -│ ├── github.controllers.js -│ └── subscribers.controllers.js -│ -├── db/ -│ └── index.js -| -├── cron/ -│ └── dev.cron.js -│ └── github.cron.js -│ └── test.cron.js -│ -├── models/ -│ ├── developers.models.js -│ ├── post.models.js -│ ├── repositories.models.js -│ └── subscribers.models.js -│ -├── routes/ -│ ├── dev.routes.js -│ ├── github.routes.js -│ └── subscribers.routes.js -│ -└── utils/ - ├── randomPeriod.utils.js - ├── response.utils.js - └── urlBuilder.utils.js - -``` - -## 🌐 API Endpoints - -### Dev.to Endpoints - -- `GET /devdisplay/v1/trending/dev/posts/:since` -- `GET /devdisplay/v1/trending/dev/getRandomPost` - -### GitHub Endpoints - -- `GET /devdisplay/v1/trending/github/repositories/:since` -- `GET /devdisplay/v1/trending/github/developers/:since` -- `GET /devdisplay/v1/trending/github/getRandomDev` -- `GET /devdisplay/v1/trending/github/getRandomRepo` - -### Subscription Endpoints - -- `POST /devdisplay/v1/subscribers` - -## 🚀 Quick Start Guide - -### Prerequisites - -- Node.js -- npm -- MongoDB - -### Installation Steps - -1. Clone the repository - -```bash -git clone https://github.com/Thakar-Advait/DevDisplay-API.git -cd DevDisplay-API - -``` - -2. Install Dependencies - -```bash -npm install - -``` - -3. Configure Environment Variables Create a `.env` file with the following variables: - -``` -PORT=3000 -MONGO_URI=your_mongodb_connection_string -DB_NAME=devdisplay - -``` - -4. Prepare MongoDB Collections Ensure your MongoDB database has these collections: - -- `daily_trending_devs` -- `daily_trending_posts` -- `daily_trending_repos` -- `weekly_trending_devs` -- `weekly_trending_posts` -- `weekly_trending_repos` -- `monthly_trending_devs` -- `monthly_trending_posts` -- `monthly_trending_repos` -- `tests` -5. Start the Server - -```bash -npm run dev - -``` - -## 🔒 Environment Configuration - -Refer to `.env.sample` for detailed environment variable setup. - -## 📄 License - -[Insert Your License Here] - -## 🙌 Acknowledgments - -Special thanks to GitHub and Dev.to for providing the incredible developer data that powers this API. - ----------- - -**Built with ❤️ by Developers, for Developers** diff --git a/backend/app.js b/backend/app.js deleted file mode 100644 index aac921e6..00000000 --- a/backend/app.js +++ /dev/null @@ -1,41 +0,0 @@ -import express from "express"; -import { devRouter } from "./routes/dev.routes.js"; -import { githubRouter } from "./routes/github.routes.js"; -import { subscribersRouter } from "./routes/subscribers.routes.js"; -import { getSubscribers } from "./controllers/subscribers.controllers.js"; -import Subscribers from "./models/subscribers.models.js"; -const app = express(); - -// Middleware to parse incoming JSON requests -app.use(express.json()); - -// Use the devRouter for the "/api/v1/trending/dev" route -app.use("/devdisplay/v1/trending/dev", devRouter); - -// Use the githubRouter for the "/api/v1/trending/github" route -app.use("/devdisplay/v1/trending/github", githubRouter); - -app.use("/devdisplay/v1/subscribers", getSubscribers); - -app.get("/", (req, res) => { - res.status(200).json({ - message: "Welcome to the DevDisplay API!" - }); -}) - -app.get("/health", (req, res) => { - res.status(200).json({ - message: "DevDisplay API is healthy!" - }); -}) - -// Global error handling middleware (optional) -app.use((err, req, res, next) => { - console.error(err.stack); // Log the error stack for debugging - res.status(500).json({ - message: "Something went wrong!", - error: err.message - }); -}); - -export default app; diff --git a/backend/controllers/dev.controllers.js b/backend/controllers/dev.controllers.js deleted file mode 100644 index 714be8a3..00000000 --- a/backend/controllers/dev.controllers.js +++ /dev/null @@ -1,97 +0,0 @@ -import * as Dev_models from "../models/post.models.js"; -import * as responseClass from "../utils/response.utils.js"; -import { pickRandomPeriod } from "../utils/randomPeriod.utils.js" - -// Helper function to fetch posts based on time period -const fetchPostsForPeriod = async (timePeriod) => { - let model; - switch (timePeriod) { - case "daily": - model = Dev_models.Post_daily; - break; - case "weekly": - model = Dev_models.Post_weekly; - break; - case "monthly": - model = Dev_models.Post_monthly; - break; - default: - throw new responseClass.ApiError(400, "Invalid time period specified."); - } - - // Fetching posts for the chosen period - const response = await model.find(); - if (!response || response.length === 0) { - throw new responseClass.ApiError(404, `No trending posts found for time period: '${timePeriod}'`); - } - return response; -}; - -const fetchTrendingPosts = async (req, res) => { - // Extract the "since" parameter from the URL, defaulting to "daily" if not provided - const { since = 'daily' } = req.params; - - try { - // Fetch posts based on the time period - const posts = await fetchPostsForPeriod(since); - - // Return a consistent response with the fetched posts - return res.status(200).json(new responseClass.ApiResponse( - 200, - `Fetched trending posts for time period: '${since}'`, - posts - )); - - } catch (error) { - // Generic error handling for unexpected errors - console.error(error); // Optional: Add logging to track errors - return res.status(500).json(new responseClass.ApiResponse( - 500, - `Something went wrong while fetching trending posts for time period: '${since}' --> \n${error.message || error}` - )); - } -} - -const fetchRandomPost = async (req, res) => { - const since = pickRandomPeriod(); - try { - let posts; - switch (since) { - case "daily": - posts = await Dev_models.Post_daily.aggregate([ - { $sample: { size: 1 } } - ]) - break; - case "weekly": - posts = await Dev_models.Post_weekly.aggregate([ - { $sample: { size: 1 } } - ]) - break; - case "monthly": - posts = await Dev_models.Post_monthly.aggregate([ - { $sample: { size: 1 } } - ]) - break; - default: - posts = await Dev_models.Post_daily.aggregate([ - { $sample: { size: 1 } } - ]) - break; - } - if (posts.length > 0) { - console.log('Random Document:', posts[0]); - return res.status(200).json(new responseClass.ApiResponse(200, `Fetched random post for time period: '${since}'`, posts[0])); - } else { - console.log('No documents found.'); - return res.status(404).json(new responseClass.ApiResponse(404, `No random posts found for time period: '${since}'`)); - } - } catch (error) { - console.error(error); - return res.status(500).json(new responseClass.ApiResponse(500, `Something went wrong while fetching random post for time period: '${since}' --> \n${error.message || error}`)); - } -} - -export { - fetchTrendingPosts, - fetchRandomPost -}; diff --git a/backend/controllers/github.controllers.js b/backend/controllers/github.controllers.js deleted file mode 100644 index 33ab0bb0..00000000 --- a/backend/controllers/github.controllers.js +++ /dev/null @@ -1,191 +0,0 @@ -import * as Repo_models from "../models/repositories.models.js"; -import * as Dev_models from "../models/developers.models.js"; -import * as responseClass from "../utils/response.utils.js"; -import { pickRandomPeriod } from "../utils/randomPeriod.utils.js" - -// Helper function to fetch developers based on time period -const fetchDevsForPeriod = async (timePeriod) => { - let model; - switch (timePeriod) { - case "daily": - model = Dev_models.Dev_daily; - break; - case "weekly": - model = Dev_models.Dev_weekly; - break; - case "monthly": - model = Dev_models.Dev_monthly; - break; - default: - throw new responseClass.ApiError(400, "Invalid time period specified."); - } - - // Fetching developers for the chosen period - const response = await model.find(); - if (!response || response.length === 0) { - throw new responseClass.ApiError(404, `No trending developers found for time period: '${timePeriod}'`); - } - return response; -}; - -// Helper function to fetch repositories based on time period -const fetchReposForPeriod = async (timePeriod) => { - let model; - switch (timePeriod) { - case "daily": - model = Repo_models.Repo_daily; - break; - case "weekly": - model = Repo_models.Repo_weekly; - break; - case "monthly": - model = Repo_models.Repo_monthly; - break; - default: - throw new responseClass.ApiError(400, "Invalid time period specified."); - } - - // Fetching repositories for the chosen period - const response = await model.find(); - if (!response || response.length === 0) { - throw new responseClass.ApiError(404, `No trending repositories found for time period: '${timePeriod}'`); - } - return response; -}; - -// Controller function for fetching trending developers -const fetchTrendingDevs = async (req, res) => { - // Extract the "since" parameter from the URL - const { since = 'daily' } = req.params; // Default to 'daily' if no "since" is provided - - try { - // Fetch developers based on the time period - const devs = await fetchDevsForPeriod(since); - - // Return a consistent response - return res.status(200).json(new responseClass.ApiResponse( - 200, - `Fetched trending developers for time period: '${since}'`, - devs - )); - - } catch (error) { - // Generic error handling for unexpected errors - console.error(error); // Optional: Add logging to track errors - return res.status(500).json(new responseClass.ApiResponse( - 500, - `Something went wrong while fetching trending developers for time period: '${since}' --> \n${error.message || error}` - )); - } -} - -// Controller function for fetching trending repositories -const fetchTrendingRepos = async (req, res) => { - // Extract the "since" parameter from the URL - const { since = 'daily' } = req.params; // Default to 'daily' if no "since" is provided - - try { - // Fetch repositories based on the time period - const repos = await fetchReposForPeriod(since); - - // Return a consistent response - return res.status(200).json(new responseClass.ApiResponse( - 200, - `Fetched trending repositories for time period: '${since}'`, - repos - )); - - } catch (error) { - // Generic error handling for unexpected errors - console.error(error); // Optional: Add logging to track errors - return res.status(500).json(new responseClass.ApiResponse( - 500, - `Something went wrong while fetching trending repositories for time period: '${since}' --> \n${error.message || error}` - )); - } -} - -const fetchRandomDev = async (req, res) => { - const since = pickRandomPeriod(); - try { - let devs; - switch (since) { - case "daily": - devs = await Dev_models.Dev_daily.aggregate([ - { $sample: { size: 1 } } - ]) - break; - case "weekly": - devs = await Dev_models.Dev_weekly.aggregate([ - { $sample: { size: 1 } } - ]) - break; - case "monthly": - devs = await Dev_models.Dev_monthly.aggregate([ - { $sample: { size: 1 } } - ]) - break; - default: - devs = await Dev_models.Dev_daily.aggregate([ - { $sample: { size: 1 } } - ]) - break; - } - if (devs.length > 0) { - console.log('Random Document:', devs[0]); - return res.status(200).json(new responseClass.ApiResponse(200, `Fetched random developer for time period: '${since}'`, devs[0])); - } else { - console.log('No documents found.'); - return res.status(404).json(new responseClass.ApiResponse(404, `No random developer found for time period: '${since}'`)); - } - } catch (error) { - console.error(error); - return res.status(500).json(new responseClass.ApiResponse(500, `Something went wrong while fetching random developer for time period: '${since}' --> \n${error.message || error}`)); - } -} - -const fetchRandomRepo = async (req, res) => { - const since = pickRandomPeriod(); - try { - let repos; - switch (since) { - case "daily": - repos = await Repo_models.Repo_daily.aggregate([ - { $sample: { size: 1 } } - ]) - break; - case "weekly": - repos = await Repo_models.Repo_weekly.aggregate([ - { $sample: { size: 1 } } - ]) - break; - case "monthly": - repos = await Repo_models.Repo_monthly.aggregate([ - { $sample: { size: 1 } } - ]) - break; - default: - repos = await Repo_models.Repo_daily.aggregate([ - { $sample: { size: 1 } } - ]) - break; - } - if (repos.length > 0) { - console.log('Random Document:', repos[0]); - return res.status(200).json(new responseClass.ApiResponse(200, `Fetched random repo for time period: '${since}'`, repos[0])); - } else { - console.log('No documents found.'); - return res.status(404).json(new responseClass.ApiResponse(404, `No random repo found for time period: '${since}'`)); - } - } catch (error) { - console.error(error); - return res.status(500).json(new responseClass.ApiResponse(500, `Something went wrong while fetching random repo for time period: '${since}' --> \n${error.message || error}`)); - } -} - -export { - fetchTrendingDevs, - fetchTrendingRepos, - fetchRandomDev, - fetchRandomRepo -} diff --git a/backend/controllers/subscribers.controllers.js b/backend/controllers/subscribers.controllers.js deleted file mode 100644 index 222ee3a2..00000000 --- a/backend/controllers/subscribers.controllers.js +++ /dev/null @@ -1,10 +0,0 @@ -import Subscribers from "../models/subscribers.models.js"; - -export const getSubscribers = async (req, res) => { - try { - const subscribers = await Subscribers.find().where("status").equals("subscribed"); - res.status(200).json(subscribers); - } catch (error) { - res.status(500).json({ message: error.message }); - } -} \ No newline at end of file diff --git a/backend/cron/dev.cron.js b/backend/cron/dev.cron.js deleted file mode 100644 index d7c43541..00000000 --- a/backend/cron/dev.cron.js +++ /dev/null @@ -1,170 +0,0 @@ -import cron from "node-cron"; -import * as cheerio from "cheerio"; -import axios from "axios"; -import buildUrl from "../utils/urlBuilder.utils.js"; -import * as postModels from "../models/post.models.js"; - -const base_url = "https://dev.to"; - -const fetchTrendingPosts = async ({ - since = "day" -} = {}) => { - const url = buildUrl(`${base_url}/top/${since}`); - console.log(url) - try { - const response = await axios.get(url); - - const $ = cheerio.load(response.data); - // Extract posts (titles, URLs, etc.) from the HTML - const $posts = $(".substories .crayons-story") - .map((i, post) => { - const $post = $(post); - const title = $post.find("a[class=crayons-story__hidden-navigation-link]").text().trim(); - const url = $post.find("a[class=crayons-story__hidden-navigation-link]").attr("href"); - const $body = $post.find(".crayons-story__body"); - const avatar = $body.find(".crayons-avatar > img").attr("src"); - const author = $body.find(".crayons-story__secondary").text().trim(); - const linkToProf = $body.find(".crayons-story__secondary").attr("href"); - const dateOfPost = $body.find(".crayons-story__tertiary > time").attr("datetime"); - const tags = $body.find(".crayons-story__indention").find(".crayons-story__tags > a.crayons-tag").get().map((tag) => { - const $tag = $(tag); - return $tag.text().split("#")[1].trim(); - }); - let reactions = "0 reactions" - const readTime = $body.find(".crayons-story__save > small").text().trim(); - // Fix the typo in the selector here: - const hasReaction = $body.find(".crayons-story__bottom > .crayons-story__details > a").find("div.multiple_reactions_aggregate") - hasReaction.length > 0 ? reactions = hasReaction.find(".aggregate_reactions_counter").text().trim() : "0 reactions" - - // Optionally, extract more post details (e.g., author, comments, etc.) - return { - title, - url: url, - avatar: avatar, - author: author, - linkToProf: base_url + linkToProf, - dateOfPost: dateOfPost, - tags: tags, - reactions: reactions, - readTime: readTime - }; - }) - .get(); // Convert Cheerio object to a regular array - - console.log(`Found ${$posts.length} posts`); - - // Return the scraped posts - return $posts; - } catch (error) { - console.log(`Something went wrong while fetching data from Dev Community: ${error}`); - } -} - -const trendingDailyPosts = cron.schedule("0 0 * * *", async () => { - try { - const postObj = await fetchTrendingPosts(); - try { - await postModels.Post_daily.deleteMany({}); - } catch (error) { - throw new Error(error); - } - postObj.forEach(async (post) => { - const newObj = new postModels.Post_daily({ - title: post.title, - url: post.url, - avatar: post.avatar, - author: post.author, - linkToProf: post.linkToProf, - dateOfPost: new Date(post.dateOfPost), - tags: post.tags, - reactions: post.reactions, - readTime: post.readTime - }) - - try { - const res = await newObj.save(); - console.log(res) - } catch (error) { - console.log(`Something went wrong while updating daily trending posts: `,error) - } - }) - console.log(`\n`); - } catch (error) { - console.log(`Something went wrong in trendingDailyPosts function: \n${error}`) - } -}) - -const trendingWeeklyPosts = cron.schedule("0 0 * * 0", async () => { - try { - const postObj = await fetchTrendingPosts({ since: "week" }); - try { - await postModels.Post_weekly.deleteMany({}); - } catch (error) { - throw new Error(error); - } - postObj.forEach(async (post) => { - const newObj = new postModels.Post_weekly({ - title: post.title, - url: post.url, - avatar: post.avatar, - author: post.author, - linkToProf: post.linkToProf, - dateOfPost: new Date(post.dateOfPost), - tags: post.tags, - reactions: post.reactions, - readTime: post.readTime - }) - - try { - const res = await newObj.save(); - console.log(res) - } catch (error) { - console.log(`Something went wrong while updating weekly trending posts: `,error) - } - }) - console.log(`\n`); - } catch (error) { - console.log(`Something went wrong in trendingWeeklyPosts function: \n${error}`) - } -}); - -const trendingMonthlyPosts = cron.schedule("0 0 1 * *", async () => { - try { - const postObj = await fetchTrendingPosts({ since: "month" }); - try { - await postModels.Post_monthly.deleteMany({}); - } catch (error) { - throw new Error(error); - } - postObj.forEach(async (post) => { - const newObj = new postModels.Post_monthly({ - title: post.title, - url: post.url, - avatar: post.avatar, - author: post.author, - linkToProf: post.linkToProf, - dateOfPost: new Date(post.dateOfPost), - tags: post.tags, - reactions: post.reactions, - readTime: post.readTime - }) - - try { - const res = await newObj.save(); - console.log(res) - } catch (error) { - console.log(`Something went wrong while updating monthly trending posts: `,error) - } - }) - console.log(`\n`); - } catch (error) { - console.log(`Something went wrong in trendingMonthlyPosts function: \n${error}`) - } -}) - -export { - trendingDailyPosts, - trendingWeeklyPosts, - trendingMonthlyPosts -} - diff --git a/backend/cron/github.cron.js b/backend/cron/github.cron.js deleted file mode 100644 index 64258ce4..00000000 --- a/backend/cron/github.cron.js +++ /dev/null @@ -1,306 +0,0 @@ -import cron from "node-cron"; -import buildUrl from "../utils/urlBuilder.utils.js"; -import axios from "axios"; -import * as cheerio from "cheerio"; -import * as devModels from "../models/developers.models.js"; -import * as repoModels from "../models/repositories.models.js"; - -const base_url = "https://github.com" - -const fetchTrendingRepos = async ({ - language = "", - since = "daily" -} = {}) => { - const url = buildUrl(`${base_url}/trending`, { - language, - since - }) - - try { - const response = await axios.get(url); - - const $ = cheerio.load(response.data); - return ( - $('.Box article.Box-row') - .get() - .map((repo) => { - const $repo = $(repo); - const title = $repo.find(".h3").text().trim(); - const [username, repoName] = title.split("/").map(item => item.trim()); - const description = $repo.find("p").text().trim(); - const linkToRepo = $repo.find(".h3 > a").attr("href"); - const language = $repo.find('span[itemprop="programmingLanguage"]').text().trim(); - const stars = $repo.find('.mr-3 svg[aria-label="star"]').first().parent().text().trim(); - const forks = $repo.find('svg[aria-label="fork"]').first().parent().text().trim(); - const currentPeriodStars = $repo.find("span.d-inline-block.float-sm-right").text().trim(); - const builtBy = $repo - .find('span:contains("Built by")') - .find('[data-hovercard-type="user"]') - .map((i, user) => { - const altString = $(user).children('img').attr('alt'); - const avatarUrl = $(user).children('img').attr('src'); - return { - username: altString - ? altString.slice(1) - : /* istanbul ignore next */ null, - href: `${base_url}${user.attribs.href}`, - avatar: avatarUrl.split("?")[0], - }; - }) - .get(); - // const builtBy = builtByAnchors.map((contributor) => { - // return contributor.split("?")[1]; - // }) - return { - author: username, - title: repoName, - description: description, - linkToRepo: `${base_url}/${linkToRepo}`, - language: language, - stars: stars, - forks: forks, - currentPeriodStars: currentPeriodStars, - builtBy: builtBy - } - }) - ) - } catch (error) { - console.log(`Something went wrong while fetching the HTML data for repos: `, error) - } -} - -const fetchTrendingDevs = async ({ - language = "", - since = "daily" -} = {}) => { - const url = buildUrl(`${base_url}/trending/developers`, { - language, - since - }) - - try { - const response = await axios.get(url); - - const $ = cheerio.load(response.data); - return ( - $('.Box article.Box-row') - .get() - .map((dev) => { - const $dev = $(dev); - const avatar = $dev.find("img").attr("src") - const name = $dev.find(".h3 a").text().trim(); - const linkToDev = $dev.find(".h3 a").attr("href"); - const username = linkToDev.split("/")[1]; - return { - name: name, - avatar: avatar.split("?")[0], - linkToDev: `${base_url}/${linkToDev}`, - username: username - } - }) - ) - } catch (error) { - console.log(`Something went wrong while fetching the HTML data for devs: `, error) - } -} - -const trendingDailyRepos = cron.schedule("0 0 * * *", async () => { - try { - const repoObjects = await fetchTrendingRepos(); - try { - await repoModels.Repo_daily.deleteMany({}) - } catch (error) { - throw new Error(error); - } - repoObjects.forEach(async (repo) => { - const newObj = new repoModels.Repo_daily({ - author: repo.author, - title: repo.title, - description: repo.description, - link: repo.linkToRepo, - language: repo.language, - stars: repo.stars, - forks: repo.forks, - current_period_stars: repo.currentPeriodStars, - builtBy: repo.builtBy - }) - try { - const res = await newObj.save() - console.log(res) - } catch (error) { - console.log(`Something went wrong while updating daily trending repositories: `, error) - } - console.log(`\n`) - }) - console.log(`Github's trending repositories fetched and pushed to db!`) - } catch (error) { - console.log(`Something went wrong in trendingDailyRepos function: \n${error}`) - } -}) - -const trendingDailyDevs = cron.schedule("0 0 * * *", async () => { - try { - const devObjects = await fetchTrendingDevs(); - try { - await devModels.Dev_daily.deleteMany({}) - } catch (error) { - throw new Error(error) - } - devObjects.forEach(async (dev) => { - const newObj = new devModels.Dev_daily({ - name: dev.name, - avatar: dev.avatar, - username: dev.username, - link: dev.linkToDev - }) - - try { - const res = await newObj.save(); - console.log(res) - } catch (error) { - console.log(`Something went wrong while updating daily trending developers: `, error) - } - console.log(`\n`) - }) - console.log(`Github's trending developers fetched and pushed to db!`) - } catch (error) { - console.log(`Something went wrong in trendingDailyDevs function: \n${error}`) - } -}) - -const trendingWeeklyRepos = cron.schedule("0 0 * * 0", async () => { - try { - const repoObjects = await fetchTrendingRepos({ since: "weekly" }); - try { - await repoModels.Repo_weekly.deleteMany({}) - } catch (error) { - throw new Error(error) - } - repoObjects.forEach(async (repo) => { - const newObj = new repoModels.Repo_weekly({ - author: repo.author, - title: repo.title, - description: repo.description, - link: repo.linkToRepo, - language: repo.language, - stars: repo.stars, - forks: repo.forks, - current_period_stars: repo.currentPeriodStars, - builtBy: repo.builtBy - }) - try { - const res = await newObj.save() - console.log(res) - } catch (error) { - console.log(`Something went wrong while updating weekly trending repositories: `, error) - } - console.log(`\n`) - }) - console.log(`Github's trending repositories fetched and pushed to db!`) - } catch (error) { - console.log(`Something went wrong in trendingWeeklyRepos function: \n${error}`) - } -}) - -const trendingWeeklyDevs = cron.schedule("0 0 * * 0", async () => { - try { - const devObjects = await fetchTrendingDevs({ since: "weekly" }); - try { - await devModels.Dev_weekly.deleteMany({}) - } catch (error) { - throw new Error(error) - } - devObjects.forEach(async (dev) => { - const newObj = new devModels.Dev_weekly({ - name: dev.name, - avatar: dev.avatar, - username: dev.username, - link: dev.linkToDev - }) - - try { - const res = await newObj.save(); - console.log(res) - } catch (error) { - console.log(`Something went wrong while updating weekly trending developers: `, error) - } - console.log(`\n`) - }) - console.log(`Github's trending developers fetched and pushed to db!`) - } catch (error) { - console.log(`Something went wrong in trendingWeeklyDevs function: \n${error}`) - } -}) - -const trendingMonthlyRepos = cron.schedule("0 0 1 * *", async () => { - try { - const repoObjects = await fetchTrendingRepos({ since: "monthly" }); - try { - await repoModels.Repo_monthly.deleteMany({}) - } catch (error) { - throw new Error(error) - } - repoObjects.forEach(async (repo) => { - const newObj = new repoModels.Repo_monthly({ - author: repo.author, - title: repo.title, - description: repo.description, - link: repo.linkToRepo, - language: repo.language, - stars: repo.stars, - forks: repo.forks, - current_period_stars: repo.currentPeriodStars, - builtBy: repo.builtBy - }) - try { - const res = await newObj.save() - console.log(res) - } catch (error) { - console.log(`Something went wrong while updating monthly trending repositories: `, error) - } - console.log(`\n`) - }) - console.log(`Github's trending repositories fetched and pushed to db!`) - } catch (error) { - console.log(`Something went wrong in trendingMonthlyRepos function: \n${error}`) - } -}) - -const trendingMonthlyDevs = cron.schedule("0 0 1 * *", async () => { - try { - const devObjects = await fetchTrendingDevs({ since: "monthly" }); - try { - await devModels.Dev_monthly.deleteMany({}) - } catch (error) { - throw new Error(error) - } - devObjects.forEach(async (dev) => { - const newObj = new devModels.Dev_monthly({ - name: dev.name, - avatar: dev.avatar, - username: dev.username, - link: dev.linkToDev - }) - - try { - const res = await newObj.save(); - console.log(res) - } catch (error) { - console.log(`Something went wrong while updating monthly trending developers: `, error) - } - console.log(`\n`) - }) - console.log(`Github's trending developers fetched and pushed to db!`) - } catch (error) { - console.log(`Something went wrong in trendingMonthlyDevs function: \n${error}`) - } -}) - -export { - trendingDailyDevs, - trendingDailyRepos, - trendingMonthlyDevs, - trendingMonthlyRepos, - trendingWeeklyDevs, - trendingWeeklyRepos -} \ No newline at end of file diff --git a/backend/cron/test.cron.js b/backend/cron/test.cron.js deleted file mode 100644 index 951aa25f..00000000 --- a/backend/cron/test.cron.js +++ /dev/null @@ -1,22 +0,0 @@ -import cron from "node-cron"; -import { Test } from "../models/test.models.js"; - -const testFunction = async () => { - const date = new Date(); - const time = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; - try { - const newObj = new Test({ - message: "Pinging MongoDB for tests...", - date: date, - time: time - }) - - await newObj.save() - console.log(`Successfully pinged MongoDB!`) - } catch (error) { - console.log(`Something went wrong while pinging MongoDB: ${error}`) - } -} - -const pingDB = cron.schedule("* * * * *", testFunction) -export default pingDB; \ No newline at end of file diff --git a/backend/db/index.js b/backend/db/index.js deleted file mode 100644 index 6c30c05e..00000000 --- a/backend/db/index.js +++ /dev/null @@ -1,20 +0,0 @@ -import { connect } from "mongoose" - -const connectDb = async () => { - const db_uri = process.env.MONGO_URI; - const db_name = process.env.DB_NAME; - console.log(db_uri) - console.log(db_name) - try { - const connectionInstance = await connect(`${db_uri}/${db_name}`); - console.log(`${db_uri}/${db_name}`) - if (connectionInstance) { - console.log(`Connected to MongoDB: ${connectionInstance.connection.host}`) - } - } catch (error) { - console.log(`Something went wrong while connecting to DB: ${error}`) - process.exit(1) - } -} - -export default connectDb; \ No newline at end of file diff --git a/backend/index.js b/backend/index.js deleted file mode 100644 index e5f938ab..00000000 --- a/backend/index.js +++ /dev/null @@ -1,22 +0,0 @@ -import app from "./app.js"; -import connectDb from "./db/index.js"; -import dotenv from "dotenv"; -import pingDB from "./cron/test.cron.js"; -import * as devCronJobs from "./cron/dev.cron.js"; -import * as githubCronJobs from "./cron/github.cron.js"; - -dotenv.config({ - path: "./.env" -}); - -connectDb() - .then(() => { - const PORT = process.env.PORT; - app.listen(PORT, () => { - console.log(`Server started on http://localhost:${PORT}`) - }) - }) - .catch((error) => { - console.log(`Something went wrong while starting the server: ${error}`) - }) - diff --git a/backend/models/developers.models.js b/backend/models/developers.models.js deleted file mode 100644 index 511fcca4..00000000 --- a/backend/models/developers.models.js +++ /dev/null @@ -1,30 +0,0 @@ -import mongoose from "mongoose"; -import aggregatePaginate from "mongoose-aggregate-paginate-v2"; - -const devSchema = new mongoose.Schema({ - name: { - type: String, - lowercase: true, - trim: true, - required: true - }, - username: { - type: String, - lowercase: true, - trim: true, - required: true - }, - avatar: { - type: String, - required: true - }, - link: { - type: String, - } -}, { timestamps: true }); - -devSchema.plugin(aggregatePaginate) - -export const Dev_daily = mongoose.model("daily_trending_dev", devSchema) -export const Dev_weekly = mongoose.model("weekly_trending_dev", devSchema) -export const Dev_monthly = mongoose.model("monthly_trending_dev", devSchema) \ No newline at end of file diff --git a/backend/models/post.models.js b/backend/models/post.models.js deleted file mode 100644 index ee9e915c..00000000 --- a/backend/models/post.models.js +++ /dev/null @@ -1,50 +0,0 @@ -import mongoose from "mongoose"; -import aggregatePaginate from "mongoose-aggregate-paginate-v2"; - -const postSchema = new mongoose.Schema({ - title: { - type: String, - required: true, - lowercase: true, - trim: true - }, - url: { - type: String, - required: true - }, - avatar: { - type: String, - required: true - }, - author: { - type: String, - required: true, - lowercase: true, - trim: true - }, - linkToProf: { - type: String, - required: true - }, - dateOfPost: { - type: Date, - required: true - }, - tags: [{ - type: String, - lowercase: true, - trim: true - }], - reactions: { - type: String - }, - readTime: { - type: String - } -}, {timestamps: true}); - -postSchema.plugin(aggregatePaginate) - -export const Post_daily = mongoose.model("daily_trending_post", postSchema); -export const Post_weekly = mongoose.model("weekly_trending_post", postSchema); -export const Post_monthly = mongoose.model("monthly_trending_post", postSchema); diff --git a/backend/models/repositories.models.js b/backend/models/repositories.models.js deleted file mode 100644 index 227d417a..00000000 --- a/backend/models/repositories.models.js +++ /dev/null @@ -1,65 +0,0 @@ -import mongoose from "mongoose"; -import aggregatePaginate from "mongoose-aggregate-paginate-v2"; - -const repoSchema = new mongoose.Schema({ - author: { - type: String, - lowercase: true, - trim: true, - required: true - }, - title: { - type: String, - lowercase: true, - trim: true, - required: true - }, - description: { - type: String, - lowercase: true, - trim: true, - required: true - }, - link: { - type: String, - required: true, - }, - language: { - type: String, - lowercase: true, - trim: true - }, - stars: { - type: String, - required: true - }, - forks: { - type: String, - required: true - }, - current_period_stars: { - type: String, - required: true - }, - builtBy: [{ - name: { - type: String, - lowercase: true, - trim: true - }, - username: { - type: String, - lowercase: true, - trim: true - }, - link: { - type: String, - } - }] -}, { timestamps: true }); - -repoSchema.plugin(aggregatePaginate) - -export const Repo_daily = mongoose.model("daily_trending_repo", repoSchema); -export const Repo_weekly = mongoose.model("weekly_trending_repo", repoSchema); -export const Repo_monthly = mongoose.model("monthly_trending_repo", repoSchema); \ No newline at end of file diff --git a/backend/models/subscribers.models.js b/backend/models/subscribers.models.js deleted file mode 100644 index 90fd4e6c..00000000 --- a/backend/models/subscribers.models.js +++ /dev/null @@ -1,15 +0,0 @@ -import mongoose from "mongoose"; - -const subscriberSchema = new mongoose.Schema({ - email: { - type: String, - required: true - }, - status: { - type: String, - required: true - } -}, { timestamps: true }); - -const Subscribers = mongoose.model("Subscribers", subscriberSchema); -export default Subscribers; \ No newline at end of file diff --git a/backend/models/test.models.js b/backend/models/test.models.js deleted file mode 100644 index ad971d32..00000000 --- a/backend/models/test.models.js +++ /dev/null @@ -1,18 +0,0 @@ -import mongoose from "mongoose"; - -const testSchema = new mongoose.Schema({ - message: { - type: String, - required: true - }, - date: { - type: Date, - required: true - }, - time: { - type: String, - required: true - } -}); - -export const Test = mongoose.model("test", testSchema); \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json deleted file mode 100644 index 862aa3aa..00000000 --- a/backend/package-lock.json +++ /dev/null @@ -1,1878 +0,0 @@ -{ - "name": "github-trending-api", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "github-trending-api", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "axios": "^1.8.2", - "cheerio": "^1.0.0", - "dotenv": "^16.4.7", - "express": "^4.21.2", - "mongoose": "^8.12.1", - "mongoose-aggregate-paginate-v2": "^1.1.4", - "node-cron": "^3.0.3" - }, - "devDependencies": { - "nodemon": "^3.1.9" - } - }, - "node_modules/@mongodb-js/saslprep": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz", - "integrity": "sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==", - "license": "MIT", - "dependencies": { - "sparse-bitfield": "^3.0.3" - } - }, - "node_modules/@types/webidl-conversions": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", - "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", - "license": "MIT" - }, - "node_modules/@types/whatwg-url": { - "version": "11.0.5", - "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", - "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", - "license": "MIT", - "dependencies": { - "@types/webidl-conversions": "*" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "license": "MIT", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", - "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/body-parser": { - "version": "1.20.3", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", - "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.13.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "license": "ISC" - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/bson": { - "version": "6.10.3", - "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz", - "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=16.20.1" - } - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/cheerio": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", - "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", - "license": "MIT", - "dependencies": { - "cheerio-select": "^2.1.0", - "dom-serializer": "^2.0.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "encoding-sniffer": "^0.2.0", - "htmlparser2": "^9.1.0", - "parse5": "^7.1.2", - "parse5-htmlparser2-tree-adapter": "^7.0.0", - "parse5-parser-stream": "^7.1.2", - "undici": "^6.19.5", - "whatwg-mimetype": "^4.0.0" - }, - "engines": { - "node": ">=18.17" - }, - "funding": { - "url": "https://github.com/cheeriojs/cheerio?sponsor=1" - } - }, - "node_modules/cheerio-select": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", - "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-select": "^5.1.0", - "css-what": "^6.1.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", - "dev": true, - "license": "MIT" - }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", - "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "license": "MIT" - }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" - } - }, - "node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" - } - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "BSD-2-Clause" - }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", - "license": "BSD-2-Clause", - "dependencies": { - "domelementtype": "^2.3.0" - }, - "engines": { - "node": ">= 4" - }, - "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" - } - }, - "node_modules/domutils": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", - "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", - "license": "BSD-2-Clause", - "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" - }, - "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" - } - }, - "node_modules/dotenv": { - "version": "16.4.7", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", - "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dunder-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", - "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT" - }, - "node_modules/encodeurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", - "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/encoding-sniffer": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", - "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", - "license": "MIT", - "dependencies": { - "iconv-lite": "^0.6.3", - "whatwg-encoding": "^3.1.1" - }, - "funding": { - "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" - } - }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" - } - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-object-atoms": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", - "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT" - }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", - "proxy-addr": "~2.0.7", - "qs": "6.13.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "engines": { - "node": ">= 0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/finalhandler": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", - "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", - "license": "MIT", - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "statuses": "2.0.1", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/follow-redirects": { - "version": "1.15.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", - "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", - "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-proto": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", - "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/htmlparser2": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", - "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", - "funding": [ - "https://github.com/fb55/htmlparser2?sponsor=1", - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ], - "license": "MIT", - "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3", - "domutils": "^3.1.0", - "entities": "^4.5.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", - "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", - "license": "MIT", - "dependencies": { - "depd": "2.0.0", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/iconv-lite": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", - "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", - "license": "MIT", - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/kareem": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", - "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", - "license": "Apache-2.0", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/memory-pager": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", - "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", - "license": "MIT" - }, - "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/mongodb": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.14.2.tgz", - "integrity": "sha512-kMEHNo0F3P6QKDq17zcDuPeaywK/YaJVCEQRzPF3TOM/Bl9MFg64YE5Tu7ifj37qZJMhwU1tl2Ioivws5gRG5Q==", - "license": "Apache-2.0", - "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", - "bson": "^6.10.3", - "mongodb-connection-string-url": "^3.0.0" - }, - "engines": { - "node": ">=16.20.1" - }, - "peerDependencies": { - "@aws-sdk/credential-providers": "^3.188.0", - "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", - "gcp-metadata": "^5.2.0", - "kerberos": "^2.0.1", - "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", - "socks": "^2.7.1" - }, - "peerDependenciesMeta": { - "@aws-sdk/credential-providers": { - "optional": true - }, - "@mongodb-js/zstd": { - "optional": true - }, - "gcp-metadata": { - "optional": true - }, - "kerberos": { - "optional": true - }, - "mongodb-client-encryption": { - "optional": true - }, - "snappy": { - "optional": true - }, - "socks": { - "optional": true - } - } - }, - "node_modules/mongodb-connection-string-url": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", - "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", - "license": "Apache-2.0", - "dependencies": { - "@types/whatwg-url": "^11.0.2", - "whatwg-url": "^14.1.0 || ^13.0.0" - } - }, - "node_modules/mongoose": { - "version": "8.12.1", - "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.12.1.tgz", - "integrity": "sha512-UW22y8QFVYmrb36hm8cGncfn4ARc/XsYWQwRTaj0gxtQk1rDuhzDO1eBantS+hTTatfAIS96LlRCJrcNHvW5+Q==", - "license": "MIT", - "dependencies": { - "bson": "^6.10.3", - "kareem": "2.6.3", - "mongodb": "~6.14.0", - "mpath": "0.9.0", - "mquery": "5.0.0", - "ms": "2.1.3", - "sift": "17.1.3" - }, - "engines": { - "node": ">=16.20.1" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mongoose" - } - }, - "node_modules/mongoose-aggregate-paginate-v2": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/mongoose-aggregate-paginate-v2/-/mongoose-aggregate-paginate-v2-1.1.4.tgz", - "integrity": "sha512-CdQIar3wlS7g0H6JjSJIZzvzz05vFc+Xy9SosJmj46l3xIomgl3ZjDn/n4vDpEei9RBawgUk5zGTIP6fMKdMdA==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mongoose/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==", - "license": "MIT" - }, - "node_modules/mpath": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", - "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", - "license": "MIT", - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/mquery": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", - "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", - "license": "MIT", - "dependencies": { - "debug": "4.x" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/mquery/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==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/mquery/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==", - "license": "MIT" - }, - "node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-cron": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", - "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", - "license": "ISC", - "dependencies": { - "uuid": "8.3.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/nodemon": { - "version": "3.1.9", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", - "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^3.1.2", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/nodemon/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==", - "dev": true, - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/nodemon/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==", - "dev": true, - "license": "MIT" - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/nth-check": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", - "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", - "license": "BSD-2-Clause", - "dependencies": { - "boolbase": "^1.0.0" - }, - "funding": { - "url": "https://github.com/fb55/nth-check?sponsor=1" - } - }, - "node_modules/object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/parse5": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", - "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", - "license": "MIT", - "dependencies": { - "entities": "^4.5.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-htmlparser2-tree-adapter": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", - "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", - "license": "MIT", - "dependencies": { - "domhandler": "^5.0.3", - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parse5-parser-stream": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", - "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", - "license": "MIT", - "dependencies": { - "parse5": "^7.0.0" - }, - "funding": { - "url": "https://github.com/inikulin/parse5?sponsor=1" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "license": "MIT", - "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, - "license": "MIT" - }, - "node_modules/punycode": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", - "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/qs": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", - "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", - "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/raw-body/node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "0.19.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", - "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", - "license": "MIT", - "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/send/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==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/send/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==", - "license": "MIT" - }, - "node_modules/serve-static": { - "version": "1.16.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", - "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.19.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/sift": { - "version": "17.1.3", - "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", - "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", - "license": "MIT" - }, - "node_modules/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/sparse-bitfield": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", - "license": "MIT", - "dependencies": { - "memory-pager": "^1.0.2" - } - }, - "node_modules/statuses": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", - "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "node_modules/tr46": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", - "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", - "license": "MIT", - "dependencies": { - "punycode": "^2.3.1" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "license": "MIT", - "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, - "node_modules/undici": { - "version": "6.21.1", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", - "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", - "license": "MIT", - "engines": { - "node": ">=18.17" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/webidl-conversions": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", - "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - } - }, - "node_modules/whatwg-encoding": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", - "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", - "license": "MIT", - "dependencies": { - "iconv-lite": "0.6.3" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-mimetype": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", - "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", - "license": "MIT", - "engines": { - "node": ">=18" - } - }, - "node_modules/whatwg-url": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", - "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", - "license": "MIT", - "dependencies": { - "tr46": "^5.0.0", - "webidl-conversions": "^7.0.0" - }, - "engines": { - "node": ">=18" - } - } - } -} diff --git a/backend/package.json b/backend/package.json deleted file mode 100644 index 0dab60ad..00000000 --- a/backend/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "github-trending-api", - "version": "1.0.0", - "description": "An application which scraps the trending repostitories and developers from Github.", - "main": "index.js", - "type": "module", - "scripts": { - "start": "node index.js", - "dev": "nodemon index.js" - }, - "author": "Advait Thakar", - "license": "ISC", - "dependencies": { - "axios": "^1.8.2", - "cheerio": "^1.0.0", - "dotenv": "^16.4.7", - "express": "^4.21.2", - "mongoose": "^8.12.1", - "mongoose-aggregate-paginate-v2": "^1.1.4", - "node-cron": "^3.0.3" - }, - "devDependencies": { - "nodemon": "^3.1.9" - } -} diff --git a/backend/routes/dev.routes.js b/backend/routes/dev.routes.js deleted file mode 100644 index c80a6513..00000000 --- a/backend/routes/dev.routes.js +++ /dev/null @@ -1,9 +0,0 @@ -import { Router } from "express"; -import * as devControllers from "../controllers/dev.controllers.js" - -const router = Router() - -router.get("/posts/:since", devControllers.fetchTrendingPosts) -router.get("/getRandomPost", devControllers.fetchRandomPost) - -export { router as devRouter }; \ No newline at end of file diff --git a/backend/routes/github.routes.js b/backend/routes/github.routes.js deleted file mode 100644 index 63052d7b..00000000 --- a/backend/routes/github.routes.js +++ /dev/null @@ -1,11 +0,0 @@ -import { Router } from "express"; -import * as githubControllers from "../controllers/github.controllers.js"; - -const router = Router(); - -router.get("/repositories/:since", githubControllers.fetchTrendingRepos) -router.get("/getRandomRepo", githubControllers.fetchRandomRepo) -router.get("/developers/:since", githubControllers.fetchTrendingDevs) -router.get("/getRandomDev", githubControllers.fetchRandomDev) - -export { router as githubRouter }; \ No newline at end of file diff --git a/backend/routes/subscribers.routes.js b/backend/routes/subscribers.routes.js deleted file mode 100644 index 42a9d07d..00000000 --- a/backend/routes/subscribers.routes.js +++ /dev/null @@ -1,8 +0,0 @@ -import { Router } from "express"; -import { getSubscribers } from "../controllers/subscribers.controllers.js"; - -const router = Router(); - -router.get("/", getSubscribers) - -export { router as subscribersRouter }; \ No newline at end of file diff --git a/backend/utils/randomPeriod.utils.js b/backend/utils/randomPeriod.utils.js deleted file mode 100644 index c2b23790..00000000 --- a/backend/utils/randomPeriod.utils.js +++ /dev/null @@ -1,5 +0,0 @@ -export const pickRandomPeriod = () => { - const periods = ['daily', 'weekly', 'monthly']; - const randomIndex = Math.floor(Math.random() * periods.length); - return periods[randomIndex]; -} \ No newline at end of file diff --git a/backend/utils/response.utils.js b/backend/utils/response.utils.js deleted file mode 100644 index 39d40b35..00000000 --- a/backend/utils/response.utils.js +++ /dev/null @@ -1,26 +0,0 @@ -export class ApiResponse { - constructor(status, message, data) { - this.status = status; - this.message = message; - this.data = data; - this.success = status < 400 - } -} - -export class ApiError extends Error { - constructor(status, message, data = null, errors = [], stackTrace = null) { - super(message); - this.status = status; - this.message = message; - this.data = data; - this.errors = errors; - this.success = false; - - if(stackTrace) { - this.stackTrace = stackTrace; - } - else { - Error.captureStackTrace(this) - } - } -} \ No newline at end of file diff --git a/backend/utils/urlBuilder.utils.js b/backend/utils/urlBuilder.utils.js deleted file mode 100644 index 83a376a4..00000000 --- a/backend/utils/urlBuilder.utils.js +++ /dev/null @@ -1,10 +0,0 @@ -const buildUrl = (base_url, params = {}) => { - const queryString = Object.keys(params) - .filter((key) => params[key]) - .map((key) => `${key}=${params[key]}`) - .join("&"); - - return queryString === "" ? base_url : `${base_url}/?${queryString}`; -} - -export default buildUrl; \ No newline at end of file From ed4c1bc5f2affc75f0a4bc46afb12c5a897ecbbc Mon Sep 17 00:00:00 2001 From: Thakar-Advait Date: Sat, 29 Mar 2025 22:11:32 +0530 Subject: [PATCH 3/3] added backend for IndustryTrends --- backend/.env.sample | 3 + backend/.gitignore | 54 + backend/README.md | 143 ++ backend/app.js | 41 + backend/controllers/dev.controllers.js | 97 + backend/controllers/github.controllers.js | 191 ++ .../controllers/subscribers.controllers.js | 10 + backend/cron/dev.cron.js | 170 ++ backend/cron/github.cron.js | 306 +++ backend/cron/test.cron.js | 22 + backend/db/index.js | 20 + backend/index.js | 22 + backend/models/developers.models.js | 30 + backend/models/post.models.js | 50 + backend/models/repositories.models.js | 65 + backend/models/subscribers.models.js | 15 + backend/models/test.models.js | 18 + backend/package-lock.json | 1878 +++++++++++++++++ backend/package.json | 25 + backend/routes/dev.routes.js | 9 + backend/routes/github.routes.js | 11 + backend/routes/subscribers.routes.js | 8 + backend/utils/randomPeriod.utils.js | 5 + backend/utils/response.utils.js | 26 + backend/utils/urlBuilder.utils.js | 10 + 25 files changed, 3229 insertions(+) create mode 100644 backend/.env.sample create mode 100644 backend/.gitignore create mode 100644 backend/README.md create mode 100644 backend/app.js create mode 100644 backend/controllers/dev.controllers.js create mode 100644 backend/controllers/github.controllers.js create mode 100644 backend/controllers/subscribers.controllers.js create mode 100644 backend/cron/dev.cron.js create mode 100644 backend/cron/github.cron.js create mode 100644 backend/cron/test.cron.js create mode 100644 backend/db/index.js create mode 100644 backend/index.js create mode 100644 backend/models/developers.models.js create mode 100644 backend/models/post.models.js create mode 100644 backend/models/repositories.models.js create mode 100644 backend/models/subscribers.models.js create mode 100644 backend/models/test.models.js create mode 100644 backend/package-lock.json create mode 100644 backend/package.json create mode 100644 backend/routes/dev.routes.js create mode 100644 backend/routes/github.routes.js create mode 100644 backend/routes/subscribers.routes.js create mode 100644 backend/utils/randomPeriod.utils.js create mode 100644 backend/utils/response.utils.js create mode 100644 backend/utils/urlBuilder.utils.js diff --git a/backend/.env.sample b/backend/.env.sample new file mode 100644 index 00000000..d3a60fce --- /dev/null +++ b/backend/.env.sample @@ -0,0 +1,3 @@ +PORT = "your_server_port" +MONGO_URI = "your_mongo_uri" +DB_NAME = "your_db_name" \ No newline at end of file diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 00000000..442b61ad --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1,54 @@ +bin +obj +csx +.vs +edge +Publish + +*.user +*.suo +*.cscfg +*.Cache +project-lock.json + + +/packages +/TestResults + +/tools/NuGet.exe +/App_Data +/secrets +/data +.secrets +appsettings.json +local.settings.json + +node_modules +dist + +# Local python packages +.python_packages/ + +# Python Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Azurite artifacts +__blobstorage__ +__queuestorage__ +__azurite_db*__.json + +.vscode + +test + diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 00000000..3c2df0aa --- /dev/null +++ b/backend/README.md @@ -0,0 +1,143 @@ + +# 🚀 DevDisplay API: Your Gateway to Developer Trends + +## 🌟 Project Overview + +DevDisplay API is a powerful backend service that aggregates and serves trending developer content from GitHub and Dev.to. It provides developers and tech enthusiasts with scraped insights into the most exciting developments in the tech world. + +## ✨ Key Features + +- **Trending Insights**: Discover top developers, repositories, and posts +- **Regularly scraped data**: Latest data scraped from authentic sources +- **Flexible Time Frames**: Choose from daily, weekly, or monthly trending data +- **Multi-Source Aggregation**: Pulls data from GitHub and Dev.to +- **Newsletter Subscription**: Stay updated with the latest tech trends + +## 🛠 Tech Stack + +- **Backend**: Node.js & Express.js +- **Database**: MongoDB with Mongoose ORM +- **Data Sources**: GitHub.com, Dev.to + +## 📂 Project Structure + +``` +DevDisplay-API/ +│ +├── controllers/ +│ ├── dev.controllers.js +│ ├── github.controllers.js +│ └── subscribers.controllers.js +│ +├── db/ +│ └── index.js +| +├── cron/ +│ └── dev.cron.js +│ └── github.cron.js +│ └── test.cron.js +│ +├── models/ +│ ├── developers.models.js +│ ├── post.models.js +│ ├── repositories.models.js +│ └── subscribers.models.js +│ +├── routes/ +│ ├── dev.routes.js +│ ├── github.routes.js +│ └── subscribers.routes.js +│ +└── utils/ + ├── randomPeriod.utils.js + ├── response.utils.js + └── urlBuilder.utils.js + +``` + +## 🌐 API Endpoints + +### Dev.to Endpoints + +- `GET /devdisplay/v1/trending/dev/posts/:since` +- `GET /devdisplay/v1/trending/dev/getRandomPost` + +### GitHub Endpoints + +- `GET /devdisplay/v1/trending/github/repositories/:since` +- `GET /devdisplay/v1/trending/github/developers/:since` +- `GET /devdisplay/v1/trending/github/getRandomDev` +- `GET /devdisplay/v1/trending/github/getRandomRepo` + +### Subscription Endpoints + +- `POST /devdisplay/v1/subscribers` + +## 🚀 Quick Start Guide + +### Prerequisites + +- Node.js +- npm +- MongoDB + +### Installation Steps + +1. Clone the repository + +```bash +git clone https://github.com/Thakar-Advait/DevDisplay-API.git +cd DevDisplay-API + +``` + +2. Install Dependencies + +```bash +npm install + +``` + +3. Configure Environment Variables Create a `.env` file with the following variables: + +``` +PORT=3000 +MONGO_URI=your_mongodb_connection_string +DB_NAME=devdisplay + +``` + +4. Prepare MongoDB Collections Ensure your MongoDB database has these collections: + +- `daily_trending_devs` +- `daily_trending_posts` +- `daily_trending_repos` +- `weekly_trending_devs` +- `weekly_trending_posts` +- `weekly_trending_repos` +- `monthly_trending_devs` +- `monthly_trending_posts` +- `monthly_trending_repos` +- `tests` +5. Start the Server + +```bash +npm run dev + +``` + +## 🔒 Environment Configuration + +Refer to `.env.sample` for detailed environment variable setup. + +## 📄 License + +[Insert Your License Here] + +## 🙌 Acknowledgments + +Special thanks to GitHub and Dev.to for providing the incredible developer data that powers this API. + +---------- + +**Built with ❤️ by Developers, for Developers** diff --git a/backend/app.js b/backend/app.js new file mode 100644 index 00000000..aac921e6 --- /dev/null +++ b/backend/app.js @@ -0,0 +1,41 @@ +import express from "express"; +import { devRouter } from "./routes/dev.routes.js"; +import { githubRouter } from "./routes/github.routes.js"; +import { subscribersRouter } from "./routes/subscribers.routes.js"; +import { getSubscribers } from "./controllers/subscribers.controllers.js"; +import Subscribers from "./models/subscribers.models.js"; +const app = express(); + +// Middleware to parse incoming JSON requests +app.use(express.json()); + +// Use the devRouter for the "/api/v1/trending/dev" route +app.use("/devdisplay/v1/trending/dev", devRouter); + +// Use the githubRouter for the "/api/v1/trending/github" route +app.use("/devdisplay/v1/trending/github", githubRouter); + +app.use("/devdisplay/v1/subscribers", getSubscribers); + +app.get("/", (req, res) => { + res.status(200).json({ + message: "Welcome to the DevDisplay API!" + }); +}) + +app.get("/health", (req, res) => { + res.status(200).json({ + message: "DevDisplay API is healthy!" + }); +}) + +// Global error handling middleware (optional) +app.use((err, req, res, next) => { + console.error(err.stack); // Log the error stack for debugging + res.status(500).json({ + message: "Something went wrong!", + error: err.message + }); +}); + +export default app; diff --git a/backend/controllers/dev.controllers.js b/backend/controllers/dev.controllers.js new file mode 100644 index 00000000..714be8a3 --- /dev/null +++ b/backend/controllers/dev.controllers.js @@ -0,0 +1,97 @@ +import * as Dev_models from "../models/post.models.js"; +import * as responseClass from "../utils/response.utils.js"; +import { pickRandomPeriod } from "../utils/randomPeriod.utils.js" + +// Helper function to fetch posts based on time period +const fetchPostsForPeriod = async (timePeriod) => { + let model; + switch (timePeriod) { + case "daily": + model = Dev_models.Post_daily; + break; + case "weekly": + model = Dev_models.Post_weekly; + break; + case "monthly": + model = Dev_models.Post_monthly; + break; + default: + throw new responseClass.ApiError(400, "Invalid time period specified."); + } + + // Fetching posts for the chosen period + const response = await model.find(); + if (!response || response.length === 0) { + throw new responseClass.ApiError(404, `No trending posts found for time period: '${timePeriod}'`); + } + return response; +}; + +const fetchTrendingPosts = async (req, res) => { + // Extract the "since" parameter from the URL, defaulting to "daily" if not provided + const { since = 'daily' } = req.params; + + try { + // Fetch posts based on the time period + const posts = await fetchPostsForPeriod(since); + + // Return a consistent response with the fetched posts + return res.status(200).json(new responseClass.ApiResponse( + 200, + `Fetched trending posts for time period: '${since}'`, + posts + )); + + } catch (error) { + // Generic error handling for unexpected errors + console.error(error); // Optional: Add logging to track errors + return res.status(500).json(new responseClass.ApiResponse( + 500, + `Something went wrong while fetching trending posts for time period: '${since}' --> \n${error.message || error}` + )); + } +} + +const fetchRandomPost = async (req, res) => { + const since = pickRandomPeriod(); + try { + let posts; + switch (since) { + case "daily": + posts = await Dev_models.Post_daily.aggregate([ + { $sample: { size: 1 } } + ]) + break; + case "weekly": + posts = await Dev_models.Post_weekly.aggregate([ + { $sample: { size: 1 } } + ]) + break; + case "monthly": + posts = await Dev_models.Post_monthly.aggregate([ + { $sample: { size: 1 } } + ]) + break; + default: + posts = await Dev_models.Post_daily.aggregate([ + { $sample: { size: 1 } } + ]) + break; + } + if (posts.length > 0) { + console.log('Random Document:', posts[0]); + return res.status(200).json(new responseClass.ApiResponse(200, `Fetched random post for time period: '${since}'`, posts[0])); + } else { + console.log('No documents found.'); + return res.status(404).json(new responseClass.ApiResponse(404, `No random posts found for time period: '${since}'`)); + } + } catch (error) { + console.error(error); + return res.status(500).json(new responseClass.ApiResponse(500, `Something went wrong while fetching random post for time period: '${since}' --> \n${error.message || error}`)); + } +} + +export { + fetchTrendingPosts, + fetchRandomPost +}; diff --git a/backend/controllers/github.controllers.js b/backend/controllers/github.controllers.js new file mode 100644 index 00000000..33ab0bb0 --- /dev/null +++ b/backend/controllers/github.controllers.js @@ -0,0 +1,191 @@ +import * as Repo_models from "../models/repositories.models.js"; +import * as Dev_models from "../models/developers.models.js"; +import * as responseClass from "../utils/response.utils.js"; +import { pickRandomPeriod } from "../utils/randomPeriod.utils.js" + +// Helper function to fetch developers based on time period +const fetchDevsForPeriod = async (timePeriod) => { + let model; + switch (timePeriod) { + case "daily": + model = Dev_models.Dev_daily; + break; + case "weekly": + model = Dev_models.Dev_weekly; + break; + case "monthly": + model = Dev_models.Dev_monthly; + break; + default: + throw new responseClass.ApiError(400, "Invalid time period specified."); + } + + // Fetching developers for the chosen period + const response = await model.find(); + if (!response || response.length === 0) { + throw new responseClass.ApiError(404, `No trending developers found for time period: '${timePeriod}'`); + } + return response; +}; + +// Helper function to fetch repositories based on time period +const fetchReposForPeriod = async (timePeriod) => { + let model; + switch (timePeriod) { + case "daily": + model = Repo_models.Repo_daily; + break; + case "weekly": + model = Repo_models.Repo_weekly; + break; + case "monthly": + model = Repo_models.Repo_monthly; + break; + default: + throw new responseClass.ApiError(400, "Invalid time period specified."); + } + + // Fetching repositories for the chosen period + const response = await model.find(); + if (!response || response.length === 0) { + throw new responseClass.ApiError(404, `No trending repositories found for time period: '${timePeriod}'`); + } + return response; +}; + +// Controller function for fetching trending developers +const fetchTrendingDevs = async (req, res) => { + // Extract the "since" parameter from the URL + const { since = 'daily' } = req.params; // Default to 'daily' if no "since" is provided + + try { + // Fetch developers based on the time period + const devs = await fetchDevsForPeriod(since); + + // Return a consistent response + return res.status(200).json(new responseClass.ApiResponse( + 200, + `Fetched trending developers for time period: '${since}'`, + devs + )); + + } catch (error) { + // Generic error handling for unexpected errors + console.error(error); // Optional: Add logging to track errors + return res.status(500).json(new responseClass.ApiResponse( + 500, + `Something went wrong while fetching trending developers for time period: '${since}' --> \n${error.message || error}` + )); + } +} + +// Controller function for fetching trending repositories +const fetchTrendingRepos = async (req, res) => { + // Extract the "since" parameter from the URL + const { since = 'daily' } = req.params; // Default to 'daily' if no "since" is provided + + try { + // Fetch repositories based on the time period + const repos = await fetchReposForPeriod(since); + + // Return a consistent response + return res.status(200).json(new responseClass.ApiResponse( + 200, + `Fetched trending repositories for time period: '${since}'`, + repos + )); + + } catch (error) { + // Generic error handling for unexpected errors + console.error(error); // Optional: Add logging to track errors + return res.status(500).json(new responseClass.ApiResponse( + 500, + `Something went wrong while fetching trending repositories for time period: '${since}' --> \n${error.message || error}` + )); + } +} + +const fetchRandomDev = async (req, res) => { + const since = pickRandomPeriod(); + try { + let devs; + switch (since) { + case "daily": + devs = await Dev_models.Dev_daily.aggregate([ + { $sample: { size: 1 } } + ]) + break; + case "weekly": + devs = await Dev_models.Dev_weekly.aggregate([ + { $sample: { size: 1 } } + ]) + break; + case "monthly": + devs = await Dev_models.Dev_monthly.aggregate([ + { $sample: { size: 1 } } + ]) + break; + default: + devs = await Dev_models.Dev_daily.aggregate([ + { $sample: { size: 1 } } + ]) + break; + } + if (devs.length > 0) { + console.log('Random Document:', devs[0]); + return res.status(200).json(new responseClass.ApiResponse(200, `Fetched random developer for time period: '${since}'`, devs[0])); + } else { + console.log('No documents found.'); + return res.status(404).json(new responseClass.ApiResponse(404, `No random developer found for time period: '${since}'`)); + } + } catch (error) { + console.error(error); + return res.status(500).json(new responseClass.ApiResponse(500, `Something went wrong while fetching random developer for time period: '${since}' --> \n${error.message || error}`)); + } +} + +const fetchRandomRepo = async (req, res) => { + const since = pickRandomPeriod(); + try { + let repos; + switch (since) { + case "daily": + repos = await Repo_models.Repo_daily.aggregate([ + { $sample: { size: 1 } } + ]) + break; + case "weekly": + repos = await Repo_models.Repo_weekly.aggregate([ + { $sample: { size: 1 } } + ]) + break; + case "monthly": + repos = await Repo_models.Repo_monthly.aggregate([ + { $sample: { size: 1 } } + ]) + break; + default: + repos = await Repo_models.Repo_daily.aggregate([ + { $sample: { size: 1 } } + ]) + break; + } + if (repos.length > 0) { + console.log('Random Document:', repos[0]); + return res.status(200).json(new responseClass.ApiResponse(200, `Fetched random repo for time period: '${since}'`, repos[0])); + } else { + console.log('No documents found.'); + return res.status(404).json(new responseClass.ApiResponse(404, `No random repo found for time period: '${since}'`)); + } + } catch (error) { + console.error(error); + return res.status(500).json(new responseClass.ApiResponse(500, `Something went wrong while fetching random repo for time period: '${since}' --> \n${error.message || error}`)); + } +} + +export { + fetchTrendingDevs, + fetchTrendingRepos, + fetchRandomDev, + fetchRandomRepo +} diff --git a/backend/controllers/subscribers.controllers.js b/backend/controllers/subscribers.controllers.js new file mode 100644 index 00000000..222ee3a2 --- /dev/null +++ b/backend/controllers/subscribers.controllers.js @@ -0,0 +1,10 @@ +import Subscribers from "../models/subscribers.models.js"; + +export const getSubscribers = async (req, res) => { + try { + const subscribers = await Subscribers.find().where("status").equals("subscribed"); + res.status(200).json(subscribers); + } catch (error) { + res.status(500).json({ message: error.message }); + } +} \ No newline at end of file diff --git a/backend/cron/dev.cron.js b/backend/cron/dev.cron.js new file mode 100644 index 00000000..d7c43541 --- /dev/null +++ b/backend/cron/dev.cron.js @@ -0,0 +1,170 @@ +import cron from "node-cron"; +import * as cheerio from "cheerio"; +import axios from "axios"; +import buildUrl from "../utils/urlBuilder.utils.js"; +import * as postModels from "../models/post.models.js"; + +const base_url = "https://dev.to"; + +const fetchTrendingPosts = async ({ + since = "day" +} = {}) => { + const url = buildUrl(`${base_url}/top/${since}`); + console.log(url) + try { + const response = await axios.get(url); + + const $ = cheerio.load(response.data); + // Extract posts (titles, URLs, etc.) from the HTML + const $posts = $(".substories .crayons-story") + .map((i, post) => { + const $post = $(post); + const title = $post.find("a[class=crayons-story__hidden-navigation-link]").text().trim(); + const url = $post.find("a[class=crayons-story__hidden-navigation-link]").attr("href"); + const $body = $post.find(".crayons-story__body"); + const avatar = $body.find(".crayons-avatar > img").attr("src"); + const author = $body.find(".crayons-story__secondary").text().trim(); + const linkToProf = $body.find(".crayons-story__secondary").attr("href"); + const dateOfPost = $body.find(".crayons-story__tertiary > time").attr("datetime"); + const tags = $body.find(".crayons-story__indention").find(".crayons-story__tags > a.crayons-tag").get().map((tag) => { + const $tag = $(tag); + return $tag.text().split("#")[1].trim(); + }); + let reactions = "0 reactions" + const readTime = $body.find(".crayons-story__save > small").text().trim(); + // Fix the typo in the selector here: + const hasReaction = $body.find(".crayons-story__bottom > .crayons-story__details > a").find("div.multiple_reactions_aggregate") + hasReaction.length > 0 ? reactions = hasReaction.find(".aggregate_reactions_counter").text().trim() : "0 reactions" + + // Optionally, extract more post details (e.g., author, comments, etc.) + return { + title, + url: url, + avatar: avatar, + author: author, + linkToProf: base_url + linkToProf, + dateOfPost: dateOfPost, + tags: tags, + reactions: reactions, + readTime: readTime + }; + }) + .get(); // Convert Cheerio object to a regular array + + console.log(`Found ${$posts.length} posts`); + + // Return the scraped posts + return $posts; + } catch (error) { + console.log(`Something went wrong while fetching data from Dev Community: ${error}`); + } +} + +const trendingDailyPosts = cron.schedule("0 0 * * *", async () => { + try { + const postObj = await fetchTrendingPosts(); + try { + await postModels.Post_daily.deleteMany({}); + } catch (error) { + throw new Error(error); + } + postObj.forEach(async (post) => { + const newObj = new postModels.Post_daily({ + title: post.title, + url: post.url, + avatar: post.avatar, + author: post.author, + linkToProf: post.linkToProf, + dateOfPost: new Date(post.dateOfPost), + tags: post.tags, + reactions: post.reactions, + readTime: post.readTime + }) + + try { + const res = await newObj.save(); + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating daily trending posts: `,error) + } + }) + console.log(`\n`); + } catch (error) { + console.log(`Something went wrong in trendingDailyPosts function: \n${error}`) + } +}) + +const trendingWeeklyPosts = cron.schedule("0 0 * * 0", async () => { + try { + const postObj = await fetchTrendingPosts({ since: "week" }); + try { + await postModels.Post_weekly.deleteMany({}); + } catch (error) { + throw new Error(error); + } + postObj.forEach(async (post) => { + const newObj = new postModels.Post_weekly({ + title: post.title, + url: post.url, + avatar: post.avatar, + author: post.author, + linkToProf: post.linkToProf, + dateOfPost: new Date(post.dateOfPost), + tags: post.tags, + reactions: post.reactions, + readTime: post.readTime + }) + + try { + const res = await newObj.save(); + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating weekly trending posts: `,error) + } + }) + console.log(`\n`); + } catch (error) { + console.log(`Something went wrong in trendingWeeklyPosts function: \n${error}`) + } +}); + +const trendingMonthlyPosts = cron.schedule("0 0 1 * *", async () => { + try { + const postObj = await fetchTrendingPosts({ since: "month" }); + try { + await postModels.Post_monthly.deleteMany({}); + } catch (error) { + throw new Error(error); + } + postObj.forEach(async (post) => { + const newObj = new postModels.Post_monthly({ + title: post.title, + url: post.url, + avatar: post.avatar, + author: post.author, + linkToProf: post.linkToProf, + dateOfPost: new Date(post.dateOfPost), + tags: post.tags, + reactions: post.reactions, + readTime: post.readTime + }) + + try { + const res = await newObj.save(); + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating monthly trending posts: `,error) + } + }) + console.log(`\n`); + } catch (error) { + console.log(`Something went wrong in trendingMonthlyPosts function: \n${error}`) + } +}) + +export { + trendingDailyPosts, + trendingWeeklyPosts, + trendingMonthlyPosts +} + diff --git a/backend/cron/github.cron.js b/backend/cron/github.cron.js new file mode 100644 index 00000000..64258ce4 --- /dev/null +++ b/backend/cron/github.cron.js @@ -0,0 +1,306 @@ +import cron from "node-cron"; +import buildUrl from "../utils/urlBuilder.utils.js"; +import axios from "axios"; +import * as cheerio from "cheerio"; +import * as devModels from "../models/developers.models.js"; +import * as repoModels from "../models/repositories.models.js"; + +const base_url = "https://github.com" + +const fetchTrendingRepos = async ({ + language = "", + since = "daily" +} = {}) => { + const url = buildUrl(`${base_url}/trending`, { + language, + since + }) + + try { + const response = await axios.get(url); + + const $ = cheerio.load(response.data); + return ( + $('.Box article.Box-row') + .get() + .map((repo) => { + const $repo = $(repo); + const title = $repo.find(".h3").text().trim(); + const [username, repoName] = title.split("/").map(item => item.trim()); + const description = $repo.find("p").text().trim(); + const linkToRepo = $repo.find(".h3 > a").attr("href"); + const language = $repo.find('span[itemprop="programmingLanguage"]').text().trim(); + const stars = $repo.find('.mr-3 svg[aria-label="star"]').first().parent().text().trim(); + const forks = $repo.find('svg[aria-label="fork"]').first().parent().text().trim(); + const currentPeriodStars = $repo.find("span.d-inline-block.float-sm-right").text().trim(); + const builtBy = $repo + .find('span:contains("Built by")') + .find('[data-hovercard-type="user"]') + .map((i, user) => { + const altString = $(user).children('img').attr('alt'); + const avatarUrl = $(user).children('img').attr('src'); + return { + username: altString + ? altString.slice(1) + : /* istanbul ignore next */ null, + href: `${base_url}${user.attribs.href}`, + avatar: avatarUrl.split("?")[0], + }; + }) + .get(); + // const builtBy = builtByAnchors.map((contributor) => { + // return contributor.split("?")[1]; + // }) + return { + author: username, + title: repoName, + description: description, + linkToRepo: `${base_url}/${linkToRepo}`, + language: language, + stars: stars, + forks: forks, + currentPeriodStars: currentPeriodStars, + builtBy: builtBy + } + }) + ) + } catch (error) { + console.log(`Something went wrong while fetching the HTML data for repos: `, error) + } +} + +const fetchTrendingDevs = async ({ + language = "", + since = "daily" +} = {}) => { + const url = buildUrl(`${base_url}/trending/developers`, { + language, + since + }) + + try { + const response = await axios.get(url); + + const $ = cheerio.load(response.data); + return ( + $('.Box article.Box-row') + .get() + .map((dev) => { + const $dev = $(dev); + const avatar = $dev.find("img").attr("src") + const name = $dev.find(".h3 a").text().trim(); + const linkToDev = $dev.find(".h3 a").attr("href"); + const username = linkToDev.split("/")[1]; + return { + name: name, + avatar: avatar.split("?")[0], + linkToDev: `${base_url}/${linkToDev}`, + username: username + } + }) + ) + } catch (error) { + console.log(`Something went wrong while fetching the HTML data for devs: `, error) + } +} + +const trendingDailyRepos = cron.schedule("0 0 * * *", async () => { + try { + const repoObjects = await fetchTrendingRepos(); + try { + await repoModels.Repo_daily.deleteMany({}) + } catch (error) { + throw new Error(error); + } + repoObjects.forEach(async (repo) => { + const newObj = new repoModels.Repo_daily({ + author: repo.author, + title: repo.title, + description: repo.description, + link: repo.linkToRepo, + language: repo.language, + stars: repo.stars, + forks: repo.forks, + current_period_stars: repo.currentPeriodStars, + builtBy: repo.builtBy + }) + try { + const res = await newObj.save() + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating daily trending repositories: `, error) + } + console.log(`\n`) + }) + console.log(`Github's trending repositories fetched and pushed to db!`) + } catch (error) { + console.log(`Something went wrong in trendingDailyRepos function: \n${error}`) + } +}) + +const trendingDailyDevs = cron.schedule("0 0 * * *", async () => { + try { + const devObjects = await fetchTrendingDevs(); + try { + await devModels.Dev_daily.deleteMany({}) + } catch (error) { + throw new Error(error) + } + devObjects.forEach(async (dev) => { + const newObj = new devModels.Dev_daily({ + name: dev.name, + avatar: dev.avatar, + username: dev.username, + link: dev.linkToDev + }) + + try { + const res = await newObj.save(); + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating daily trending developers: `, error) + } + console.log(`\n`) + }) + console.log(`Github's trending developers fetched and pushed to db!`) + } catch (error) { + console.log(`Something went wrong in trendingDailyDevs function: \n${error}`) + } +}) + +const trendingWeeklyRepos = cron.schedule("0 0 * * 0", async () => { + try { + const repoObjects = await fetchTrendingRepos({ since: "weekly" }); + try { + await repoModels.Repo_weekly.deleteMany({}) + } catch (error) { + throw new Error(error) + } + repoObjects.forEach(async (repo) => { + const newObj = new repoModels.Repo_weekly({ + author: repo.author, + title: repo.title, + description: repo.description, + link: repo.linkToRepo, + language: repo.language, + stars: repo.stars, + forks: repo.forks, + current_period_stars: repo.currentPeriodStars, + builtBy: repo.builtBy + }) + try { + const res = await newObj.save() + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating weekly trending repositories: `, error) + } + console.log(`\n`) + }) + console.log(`Github's trending repositories fetched and pushed to db!`) + } catch (error) { + console.log(`Something went wrong in trendingWeeklyRepos function: \n${error}`) + } +}) + +const trendingWeeklyDevs = cron.schedule("0 0 * * 0", async () => { + try { + const devObjects = await fetchTrendingDevs({ since: "weekly" }); + try { + await devModels.Dev_weekly.deleteMany({}) + } catch (error) { + throw new Error(error) + } + devObjects.forEach(async (dev) => { + const newObj = new devModels.Dev_weekly({ + name: dev.name, + avatar: dev.avatar, + username: dev.username, + link: dev.linkToDev + }) + + try { + const res = await newObj.save(); + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating weekly trending developers: `, error) + } + console.log(`\n`) + }) + console.log(`Github's trending developers fetched and pushed to db!`) + } catch (error) { + console.log(`Something went wrong in trendingWeeklyDevs function: \n${error}`) + } +}) + +const trendingMonthlyRepos = cron.schedule("0 0 1 * *", async () => { + try { + const repoObjects = await fetchTrendingRepos({ since: "monthly" }); + try { + await repoModels.Repo_monthly.deleteMany({}) + } catch (error) { + throw new Error(error) + } + repoObjects.forEach(async (repo) => { + const newObj = new repoModels.Repo_monthly({ + author: repo.author, + title: repo.title, + description: repo.description, + link: repo.linkToRepo, + language: repo.language, + stars: repo.stars, + forks: repo.forks, + current_period_stars: repo.currentPeriodStars, + builtBy: repo.builtBy + }) + try { + const res = await newObj.save() + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating monthly trending repositories: `, error) + } + console.log(`\n`) + }) + console.log(`Github's trending repositories fetched and pushed to db!`) + } catch (error) { + console.log(`Something went wrong in trendingMonthlyRepos function: \n${error}`) + } +}) + +const trendingMonthlyDevs = cron.schedule("0 0 1 * *", async () => { + try { + const devObjects = await fetchTrendingDevs({ since: "monthly" }); + try { + await devModels.Dev_monthly.deleteMany({}) + } catch (error) { + throw new Error(error) + } + devObjects.forEach(async (dev) => { + const newObj = new devModels.Dev_monthly({ + name: dev.name, + avatar: dev.avatar, + username: dev.username, + link: dev.linkToDev + }) + + try { + const res = await newObj.save(); + console.log(res) + } catch (error) { + console.log(`Something went wrong while updating monthly trending developers: `, error) + } + console.log(`\n`) + }) + console.log(`Github's trending developers fetched and pushed to db!`) + } catch (error) { + console.log(`Something went wrong in trendingMonthlyDevs function: \n${error}`) + } +}) + +export { + trendingDailyDevs, + trendingDailyRepos, + trendingMonthlyDevs, + trendingMonthlyRepos, + trendingWeeklyDevs, + trendingWeeklyRepos +} \ No newline at end of file diff --git a/backend/cron/test.cron.js b/backend/cron/test.cron.js new file mode 100644 index 00000000..951aa25f --- /dev/null +++ b/backend/cron/test.cron.js @@ -0,0 +1,22 @@ +import cron from "node-cron"; +import { Test } from "../models/test.models.js"; + +const testFunction = async () => { + const date = new Date(); + const time = `${date.getHours()}:${date.getMinutes()}:${date.getSeconds()}`; + try { + const newObj = new Test({ + message: "Pinging MongoDB for tests...", + date: date, + time: time + }) + + await newObj.save() + console.log(`Successfully pinged MongoDB!`) + } catch (error) { + console.log(`Something went wrong while pinging MongoDB: ${error}`) + } +} + +const pingDB = cron.schedule("* * * * *", testFunction) +export default pingDB; \ No newline at end of file diff --git a/backend/db/index.js b/backend/db/index.js new file mode 100644 index 00000000..6c30c05e --- /dev/null +++ b/backend/db/index.js @@ -0,0 +1,20 @@ +import { connect } from "mongoose" + +const connectDb = async () => { + const db_uri = process.env.MONGO_URI; + const db_name = process.env.DB_NAME; + console.log(db_uri) + console.log(db_name) + try { + const connectionInstance = await connect(`${db_uri}/${db_name}`); + console.log(`${db_uri}/${db_name}`) + if (connectionInstance) { + console.log(`Connected to MongoDB: ${connectionInstance.connection.host}`) + } + } catch (error) { + console.log(`Something went wrong while connecting to DB: ${error}`) + process.exit(1) + } +} + +export default connectDb; \ No newline at end of file diff --git a/backend/index.js b/backend/index.js new file mode 100644 index 00000000..e5f938ab --- /dev/null +++ b/backend/index.js @@ -0,0 +1,22 @@ +import app from "./app.js"; +import connectDb from "./db/index.js"; +import dotenv from "dotenv"; +import pingDB from "./cron/test.cron.js"; +import * as devCronJobs from "./cron/dev.cron.js"; +import * as githubCronJobs from "./cron/github.cron.js"; + +dotenv.config({ + path: "./.env" +}); + +connectDb() + .then(() => { + const PORT = process.env.PORT; + app.listen(PORT, () => { + console.log(`Server started on http://localhost:${PORT}`) + }) + }) + .catch((error) => { + console.log(`Something went wrong while starting the server: ${error}`) + }) + diff --git a/backend/models/developers.models.js b/backend/models/developers.models.js new file mode 100644 index 00000000..511fcca4 --- /dev/null +++ b/backend/models/developers.models.js @@ -0,0 +1,30 @@ +import mongoose from "mongoose"; +import aggregatePaginate from "mongoose-aggregate-paginate-v2"; + +const devSchema = new mongoose.Schema({ + name: { + type: String, + lowercase: true, + trim: true, + required: true + }, + username: { + type: String, + lowercase: true, + trim: true, + required: true + }, + avatar: { + type: String, + required: true + }, + link: { + type: String, + } +}, { timestamps: true }); + +devSchema.plugin(aggregatePaginate) + +export const Dev_daily = mongoose.model("daily_trending_dev", devSchema) +export const Dev_weekly = mongoose.model("weekly_trending_dev", devSchema) +export const Dev_monthly = mongoose.model("monthly_trending_dev", devSchema) \ No newline at end of file diff --git a/backend/models/post.models.js b/backend/models/post.models.js new file mode 100644 index 00000000..ee9e915c --- /dev/null +++ b/backend/models/post.models.js @@ -0,0 +1,50 @@ +import mongoose from "mongoose"; +import aggregatePaginate from "mongoose-aggregate-paginate-v2"; + +const postSchema = new mongoose.Schema({ + title: { + type: String, + required: true, + lowercase: true, + trim: true + }, + url: { + type: String, + required: true + }, + avatar: { + type: String, + required: true + }, + author: { + type: String, + required: true, + lowercase: true, + trim: true + }, + linkToProf: { + type: String, + required: true + }, + dateOfPost: { + type: Date, + required: true + }, + tags: [{ + type: String, + lowercase: true, + trim: true + }], + reactions: { + type: String + }, + readTime: { + type: String + } +}, {timestamps: true}); + +postSchema.plugin(aggregatePaginate) + +export const Post_daily = mongoose.model("daily_trending_post", postSchema); +export const Post_weekly = mongoose.model("weekly_trending_post", postSchema); +export const Post_monthly = mongoose.model("monthly_trending_post", postSchema); diff --git a/backend/models/repositories.models.js b/backend/models/repositories.models.js new file mode 100644 index 00000000..227d417a --- /dev/null +++ b/backend/models/repositories.models.js @@ -0,0 +1,65 @@ +import mongoose from "mongoose"; +import aggregatePaginate from "mongoose-aggregate-paginate-v2"; + +const repoSchema = new mongoose.Schema({ + author: { + type: String, + lowercase: true, + trim: true, + required: true + }, + title: { + type: String, + lowercase: true, + trim: true, + required: true + }, + description: { + type: String, + lowercase: true, + trim: true, + required: true + }, + link: { + type: String, + required: true, + }, + language: { + type: String, + lowercase: true, + trim: true + }, + stars: { + type: String, + required: true + }, + forks: { + type: String, + required: true + }, + current_period_stars: { + type: String, + required: true + }, + builtBy: [{ + name: { + type: String, + lowercase: true, + trim: true + }, + username: { + type: String, + lowercase: true, + trim: true + }, + link: { + type: String, + } + }] +}, { timestamps: true }); + +repoSchema.plugin(aggregatePaginate) + +export const Repo_daily = mongoose.model("daily_trending_repo", repoSchema); +export const Repo_weekly = mongoose.model("weekly_trending_repo", repoSchema); +export const Repo_monthly = mongoose.model("monthly_trending_repo", repoSchema); \ No newline at end of file diff --git a/backend/models/subscribers.models.js b/backend/models/subscribers.models.js new file mode 100644 index 00000000..90fd4e6c --- /dev/null +++ b/backend/models/subscribers.models.js @@ -0,0 +1,15 @@ +import mongoose from "mongoose"; + +const subscriberSchema = new mongoose.Schema({ + email: { + type: String, + required: true + }, + status: { + type: String, + required: true + } +}, { timestamps: true }); + +const Subscribers = mongoose.model("Subscribers", subscriberSchema); +export default Subscribers; \ No newline at end of file diff --git a/backend/models/test.models.js b/backend/models/test.models.js new file mode 100644 index 00000000..ad971d32 --- /dev/null +++ b/backend/models/test.models.js @@ -0,0 +1,18 @@ +import mongoose from "mongoose"; + +const testSchema = new mongoose.Schema({ + message: { + type: String, + required: true + }, + date: { + type: Date, + required: true + }, + time: { + type: String, + required: true + } +}); + +export const Test = mongoose.model("test", testSchema); \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json new file mode 100644 index 00000000..862aa3aa --- /dev/null +++ b/backend/package-lock.json @@ -0,0 +1,1878 @@ +{ + "name": "github-trending-api", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "github-trending-api", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^1.8.2", + "cheerio": "^1.0.0", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "mongoose": "^8.12.1", + "mongoose-aggregate-paginate-v2": "^1.1.4", + "node-cron": "^3.0.3" + }, + "devDependencies": { + "nodemon": "^3.1.9" + } + }, + "node_modules/@mongodb-js/saslprep": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.2.0.tgz", + "integrity": "sha512-+ywrb0AqkfaYuhHs6LxKWgqbh3I72EpEgESCw37o+9qPx9WTCkgDm2B+eMrwehGtHBWHFU4GXvnSCNiFhhausg==", + "license": "MIT", + "dependencies": { + "sparse-bitfield": "^3.0.3" + } + }, + "node_modules/@types/webidl-conversions": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz", + "integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==", + "license": "MIT" + }, + "node_modules/@types/whatwg-url": { + "version": "11.0.5", + "resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz", + "integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==", + "license": "MIT", + "dependencies": { + "@types/webidl-conversions": "*" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz", + "integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "license": "ISC" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/bson": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/bson/-/bson-6.10.3.tgz", + "integrity": "sha512-MTxGsqgYTwfshYWTRdmZRC+M7FnG1b4y7RO7p2k3X24Wq0yv1m77Wsj0BzlPzd/IowgESfsruQCUToa7vbOpPQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16.20.1" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "license": "MIT", + "dependencies": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + }, + "engines": { + "node": ">=18.17" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", + "license": "MIT" + }, + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">= 6" + }, + "funding": { + "url": "https://github.com/sponsors/fb55" + } + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "16.4.7", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", + "integrity": "sha512-47qPchRCykZC03FhkYAhrvwU4xDBFIj1QPqaarj6mdM/hgUzfPHcpkHJOn3mJAufFeeAxAzeGsr5X0M4k6fLZQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "license": "MIT", + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.3", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.7.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.3.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.12", + "proxy-addr": "~2.0.7", + "qs": "6.13.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.19.0", + "serve-static": "1.16.2", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.9", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz", + "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.2.tgz", + "integrity": "sha512-hGfm/slu0ZabnNt4oaRZ6uREyfCj6P4fT/n6A1rGV+Z0VdGXjfOhVUpkn6qVQONHGIFwmveGXyDs75+nr6FM8w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/kareem": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz", + "integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==", + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "license": "MIT" + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mongodb": { + "version": "6.14.2", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.14.2.tgz", + "integrity": "sha512-kMEHNo0F3P6QKDq17zcDuPeaywK/YaJVCEQRzPF3TOM/Bl9MFg64YE5Tu7ifj37qZJMhwU1tl2Ioivws5gRG5Q==", + "license": "Apache-2.0", + "dependencies": { + "@mongodb-js/saslprep": "^1.1.9", + "bson": "^6.10.3", + "mongodb-connection-string-url": "^3.0.0" + }, + "engines": { + "node": ">=16.20.1" + }, + "peerDependencies": { + "@aws-sdk/credential-providers": "^3.188.0", + "@mongodb-js/zstd": "^1.1.0 || ^2.0.0", + "gcp-metadata": "^5.2.0", + "kerberos": "^2.0.1", + "mongodb-client-encryption": ">=6.0.0 <7", + "snappy": "^7.2.2", + "socks": "^2.7.1" + }, + "peerDependenciesMeta": { + "@aws-sdk/credential-providers": { + "optional": true + }, + "@mongodb-js/zstd": { + "optional": true + }, + "gcp-metadata": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "snappy": { + "optional": true + }, + "socks": { + "optional": true + } + } + }, + "node_modules/mongodb-connection-string-url": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.2.tgz", + "integrity": "sha512-rMO7CGo/9BFwyZABcKAWL8UJwH/Kc2x0g72uhDWzG48URRax5TCIcJ7Rc3RZqffZzO/Gwff/jyKwCU9TN8gehA==", + "license": "Apache-2.0", + "dependencies": { + "@types/whatwg-url": "^11.0.2", + "whatwg-url": "^14.1.0 || ^13.0.0" + } + }, + "node_modules/mongoose": { + "version": "8.12.1", + "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.12.1.tgz", + "integrity": "sha512-UW22y8QFVYmrb36hm8cGncfn4ARc/XsYWQwRTaj0gxtQk1rDuhzDO1eBantS+hTTatfAIS96LlRCJrcNHvW5+Q==", + "license": "MIT", + "dependencies": { + "bson": "^6.10.3", + "kareem": "2.6.3", + "mongodb": "~6.14.0", + "mpath": "0.9.0", + "mquery": "5.0.0", + "ms": "2.1.3", + "sift": "17.1.3" + }, + "engines": { + "node": ">=16.20.1" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mongoose" + } + }, + "node_modules/mongoose-aggregate-paginate-v2": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/mongoose-aggregate-paginate-v2/-/mongoose-aggregate-paginate-v2-1.1.4.tgz", + "integrity": "sha512-CdQIar3wlS7g0H6JjSJIZzvzz05vFc+Xy9SosJmj46l3xIomgl3ZjDn/n4vDpEei9RBawgUk5zGTIP6fMKdMdA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mongoose/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==", + "license": "MIT" + }, + "node_modules/mpath": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz", + "integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/mquery": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", + "integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", + "license": "MIT", + "dependencies": { + "debug": "4.x" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/mquery/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==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/mquery/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==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-cron": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-3.0.3.tgz", + "integrity": "sha512-dOal67//nohNgYWb+nWmg5dkFdIwDm8EpeGYMekPMrngV3637lqnX0lbUcCtgibHTz6SEz7DAIjKvKDFYCnO1A==", + "license": "ISC", + "dependencies": { + "uuid": "8.3.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/nodemon": { + "version": "3.1.9", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.9.tgz", + "integrity": "sha512-hdr1oIb2p6ZSxu3PB2JWWYS7ZQ0qvaZsc3hK8DR8f02kRzc8rjYmxAIvdz+aYC+8F2IjNaB7HMcSDg8nQpJxyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/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==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "license": "BSD-2-Clause", + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "license": "MIT", + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "license": "MIT", + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "license": "MIT", + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.0.6" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "license": "MIT", + "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/send/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==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/send/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==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sift": { + "version": "17.1.3", + "resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz", + "integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==", + "license": "MIT" + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "license": "MIT", + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.0.0.tgz", + "integrity": "sha512-tk2G5R2KRwBd+ZN0zaEXpmzdKyOYksXwywulIX95MBODjSzMIuQnQ3m8JxgbhnL1LeVo7lqQKsYa1O3Htl7K5g==", + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici": { + "version": "6.21.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.1.tgz", + "integrity": "sha512-q/1rj5D0/zayJB2FraXdaWxbhWiNKDvu8naDT2dl1yTlvJp4BLtOcp2a5BvgGNQpYYJzau7tf1WgKv3b+7mqpQ==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webidl-conversions": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", + "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.1.1.tgz", + "integrity": "sha512-mDGf9diDad/giZ/Sm9Xi2YcyzaFpbdLpJPr+E9fSkyQ7KpQD4SdFcugkRQYzhmfI4KeV4Qpnn2sKPdo+kmsgRQ==", + "license": "MIT", + "dependencies": { + "tr46": "^5.0.0", + "webidl-conversions": "^7.0.0" + }, + "engines": { + "node": ">=18" + } + } + } +} diff --git a/backend/package.json b/backend/package.json new file mode 100644 index 00000000..0dab60ad --- /dev/null +++ b/backend/package.json @@ -0,0 +1,25 @@ +{ + "name": "github-trending-api", + "version": "1.0.0", + "description": "An application which scraps the trending repostitories and developers from Github.", + "main": "index.js", + "type": "module", + "scripts": { + "start": "node index.js", + "dev": "nodemon index.js" + }, + "author": "Advait Thakar", + "license": "ISC", + "dependencies": { + "axios": "^1.8.2", + "cheerio": "^1.0.0", + "dotenv": "^16.4.7", + "express": "^4.21.2", + "mongoose": "^8.12.1", + "mongoose-aggregate-paginate-v2": "^1.1.4", + "node-cron": "^3.0.3" + }, + "devDependencies": { + "nodemon": "^3.1.9" + } +} diff --git a/backend/routes/dev.routes.js b/backend/routes/dev.routes.js new file mode 100644 index 00000000..c80a6513 --- /dev/null +++ b/backend/routes/dev.routes.js @@ -0,0 +1,9 @@ +import { Router } from "express"; +import * as devControllers from "../controllers/dev.controllers.js" + +const router = Router() + +router.get("/posts/:since", devControllers.fetchTrendingPosts) +router.get("/getRandomPost", devControllers.fetchRandomPost) + +export { router as devRouter }; \ No newline at end of file diff --git a/backend/routes/github.routes.js b/backend/routes/github.routes.js new file mode 100644 index 00000000..63052d7b --- /dev/null +++ b/backend/routes/github.routes.js @@ -0,0 +1,11 @@ +import { Router } from "express"; +import * as githubControllers from "../controllers/github.controllers.js"; + +const router = Router(); + +router.get("/repositories/:since", githubControllers.fetchTrendingRepos) +router.get("/getRandomRepo", githubControllers.fetchRandomRepo) +router.get("/developers/:since", githubControllers.fetchTrendingDevs) +router.get("/getRandomDev", githubControllers.fetchRandomDev) + +export { router as githubRouter }; \ No newline at end of file diff --git a/backend/routes/subscribers.routes.js b/backend/routes/subscribers.routes.js new file mode 100644 index 00000000..42a9d07d --- /dev/null +++ b/backend/routes/subscribers.routes.js @@ -0,0 +1,8 @@ +import { Router } from "express"; +import { getSubscribers } from "../controllers/subscribers.controllers.js"; + +const router = Router(); + +router.get("/", getSubscribers) + +export { router as subscribersRouter }; \ No newline at end of file diff --git a/backend/utils/randomPeriod.utils.js b/backend/utils/randomPeriod.utils.js new file mode 100644 index 00000000..c2b23790 --- /dev/null +++ b/backend/utils/randomPeriod.utils.js @@ -0,0 +1,5 @@ +export const pickRandomPeriod = () => { + const periods = ['daily', 'weekly', 'monthly']; + const randomIndex = Math.floor(Math.random() * periods.length); + return periods[randomIndex]; +} \ No newline at end of file diff --git a/backend/utils/response.utils.js b/backend/utils/response.utils.js new file mode 100644 index 00000000..39d40b35 --- /dev/null +++ b/backend/utils/response.utils.js @@ -0,0 +1,26 @@ +export class ApiResponse { + constructor(status, message, data) { + this.status = status; + this.message = message; + this.data = data; + this.success = status < 400 + } +} + +export class ApiError extends Error { + constructor(status, message, data = null, errors = [], stackTrace = null) { + super(message); + this.status = status; + this.message = message; + this.data = data; + this.errors = errors; + this.success = false; + + if(stackTrace) { + this.stackTrace = stackTrace; + } + else { + Error.captureStackTrace(this) + } + } +} \ No newline at end of file diff --git a/backend/utils/urlBuilder.utils.js b/backend/utils/urlBuilder.utils.js new file mode 100644 index 00000000..83a376a4 --- /dev/null +++ b/backend/utils/urlBuilder.utils.js @@ -0,0 +1,10 @@ +const buildUrl = (base_url, params = {}) => { + const queryString = Object.keys(params) + .filter((key) => params[key]) + .map((key) => `${key}=${params[key]}`) + .join("&"); + + return queryString === "" ? base_url : `${base_url}/?${queryString}`; +} + +export default buildUrl; \ No newline at end of file