diff --git a/.gitignore b/.gitignore index 66ccaac..c152118 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,8 @@ /node_modules/ /token*.json /env/.env -/database*/ +database/*.db /test.ts /sessions/ *.swp +/prisma/*.db diff --git a/.prettierrc b/.prettierrc index e838b01..c47e246 100644 --- a/.prettierrc +++ b/.prettierrc @@ -1,5 +1,5 @@ { - "semi": false, + "semi": true, "singleQuote": true, "bracketSameLine": true, "printWidth": 180, diff --git a/README.md b/README.md index 15bb2b7..acf0567 100644 --- a/README.md +++ b/README.md @@ -9,10 +9,6 @@ This website is meant to help students to find peers that are working on the sam Also see `./src/env.ts` for more configuration -## Changing listed projects -- The projects shown on the front page are listed in `./env/projectIDs.json`. Should the curriculum change, you can edit that file. Remember to restart the server and wait for the server to pull all the data from the intra api. -- A list of all the projects and their corresponding ID in the 42 network (as of march 2022) can be found in `./env/allProjectIDs.json` - ## Updating the secrets / API tokens ```shell cd find-peers @@ -25,26 +21,13 @@ docker compose up -d docker logs --tail 10000 -f find-peers ``` -## Monitoring -At the (unauthenticated) route `/status/pull` you can see a summary of the pull status of every campus -It contains the key `hoursAgo` for every campus, which is the amount of hours since the last successful pull (syncing the 42 DB of the users' completed projects) of that campus -This value should not be higher than 2 * the pull timeout (currently 24 hours) - ## Configuration files | File path | Description | Managed by server | |----------------------------------------------|---------------------------------------------------------------------------------------|-------------------| -| `./env/projectIDs.json` | List of all the projects and their corresponding ID to be displayed on the front page | no | -| `./env/allProjectIDs.json` | List of all projects in the 42 network (as of march 2022) | no | | `./env/.env-example` | Example file for api tokens, rename to `.env` to activate | no | -| `./env/campusIDs.json` | List of all campuses and their corresponding ID that are fetched from the 42 API | no | -| `./database/` | All database files, mount this when running in a docker container | yes | -| `./database/sessions/` | All session files currently active | yes | -| `./database/users.json` | Userdata associated with session | yes | -| `./database//lastpull.txt` | Unix timestamp when the project users of that campus were last successfully updated | yes | -| `./database//projectUsers.json` | Status of users for each project | yes | ## Running -The 'database' of this project is a folder called 'database' at the root of the project. +The database of this project is in a folder called 'prisma' at the root of the project. ### Docker and Docker-compose This is in production @@ -61,5 +44,9 @@ docker logs --tail 10000 -f find-peers - Install Nodejs >= 18.x - Install dependencies\ `npm install` +- Generate the Prisma client\ +`npx prisma generate` +- Run Prisma migration\ +`npx prisma migrate dev` - Start development server\ `npm run dev` diff --git a/env/.env-example b/env/.env-example index 583e9ed..255e3aa 100644 --- a/env/.env-example +++ b/env/.env-example @@ -24,6 +24,3 @@ SYNC_SECRET= # Rate limiter, you can find this number on in the line "Your application has a secondly rate limit set at " on the application page SYNC_MAX_REQUESTS_PER_SECOND= - -# DataDog API key, used for logging metrics -DD_API_KEY= diff --git a/env/allProjectIDs.json b/env/allProjectIDs.json deleted file mode 100644 index 3db1c09..0000000 --- a/env/allProjectIDs.json +++ /dev/null @@ -1,7627 +0,0 @@ -[ - { - "id": 2504, - "slug": "codam-startup-internship-codam-startup-internship-contract-upload", - "name": "Codam Startup Internship - Contract Upload" - }, - { - "id": 2246, - "slug": "hive-startup-internship-hive-startup-internship-contract-upload", - "name": "Hive Startup Internship - Contract Upload" - }, - { - "id": 2240, - "slug": "hive-internship-hive-internship-contract-upload", - "name": "Hive Internship - Contract Upload" - }, - { - "id": 2066, - "slug": "rushes-libunit", - "name": "Libunit" - }, - { - "id": 1874, - "slug": "apprentissage-1-an-contract-upload", - "name": "Contract Upload " - }, - { - "id": 1866, - "slug": "apprentissage-2-ans-2eme-annee-contract-upload", - "name": "Contract Upload" - }, - { - "id": 1858, - "slug": "apprentissage-2-ans-1ere-annee-contract-upload", - "name": "Contract Upload" - }, - { - "id": 1800, - "slug": "piscine-java-day-00", - "name": "Day 00" - }, - { - "id": 1787, - "slug": "piscine-python-data-science-day-00", - "name": "Day 00" - }, - { - "id": 1710, - "slug": "apcsp-prep-apcsp-programming", - "name": "APCSP-Programming" - }, - { - "id": 1663, - "slug": "startup-internship-startup-internship-contract-upload", - "name": "Startup Internship - Contract Upload" - }, - { - "id": 1657, - "slug": "part_time-ii-part_time-ii-contract-upload", - "name": "Part_Time II - Contract Upload" - }, - { - "id": 1651, - "slug": "part_time-i-part_time-i-contract-upload", - "name": "Part_Time I - Contract Upload" - }, - { - "id": 1645, - "slug": "work-experience-ii-work-experience-ii-contract-upload", - "name": "Work Experience II - Contract Upload" - }, - { - "id": 1640, - "slug": "work-experience-i-work-experience-i-contract-upload", - "name": "Work Experience I - Contract Upload" - }, - { - "id": 1636, - "slug": "open-project-open-project-define-your-subject", - "name": "Open Project - Define your Subject" - }, - { - "id": 1620, - "slug": "42cursus-piscine-python-django-day-00", - "name": "Day 00" - }, - { - "id": 1608, - "slug": "42cursus-piscine-ruby-on-rails-day-00", - "name": "Day 00" - }, - { - "id": 1596, - "slug": "42cursus-piscine-swift-ios-day-00", - "name": "Day 00" - }, - { - "id": 1584, - "slug": "42cursus-piscine-php-symfony-day-00", - "name": "Day 00" - }, - { - "id": 1572, - "slug": "42cursus-piscine-ocaml-day-00", - "name": "Day 00" - }, - { - "id": 1560, - "slug": "42cursus-piscine-unity-day-00", - "name": "Day 00" - }, - { - "id": 1368, - "slug": "data-structures-in-python-part-1-linked-lists", - "name": "Part 1: Linked Lists" - }, - { - "id": 1367, - "slug": "java-runestone-academy-ap-java", - "name": "Runestone Academy - AP Java" - }, - { - "id": 1354, - "slug": "pygame-intro-to-oop", - "name": "Intro to OOP" - }, - { - "id": 1296, - "slug": "api-s-with-node-js-api-creation", - "name": "API Creation" - }, - { - "id": 1286, - "slug": "machine-learning-using-python-ml_01", - "name": "ML_01" - }, - { - "id": 1240, - "slug": "go-programming-go-00", - "name": "Go 00" - }, - { - "id": 1221, - "slug": "javascript-and-graphics-in-p5js-p5js-00", - "name": "p5js-00" - }, - { - "id": 1192, - "slug": "data-mining-the-49ers-web-scraping-with-beautiful-soup", - "name": "Web Scraping with Beautiful Soup" - }, - { - "id": 1148, - "slug": "piscine-php-symfony-day-00", - "name": "Day 00" - }, - { - "id": 1131, - "slug": "javascript-web01-html_css", - "name": "Web01 - HTML_CSS" - }, - { - "id": 1110, - "slug": "hack-your-own-adventure-map-your-own-adventure", - "name": "Map Your Own Adventure" - }, - { - "id": 1108, - "slug": "algorithmic-puzzles-matchbox", - "name": "Matchbox" - }, - { - "id": 1104, - "slug": "apcsp-internet-simulator-internet-simulator-binary-encodings", - "name": "Internet Simulator: Binary Encodings" - }, - { - "id": 1027, - "slug": "apcsp-explore-task-apcsp-explore-practice", - "name": "APCSP - Explore Practice" - }, - { - "id": 971, - "slug": "wethinkcode_-social-tech-lab-idea-pitch", - "name": "Idea Pitch" - }, - { - "id": 962, - "slug": "joburg-first-internship-contract-upload", - "name": "Contract Upload" - }, - { - "id": 850, - "slug": "piscine-starfleet-day-00", - "name": "Day 00" - }, - { - "id": 834, - "slug": "hercules-nemean-lion", - "name": "Nemean Lion" - }, - { - "id": 831, - "slug": "matrice-matrice-cpa", - "name": "Matrice CPA" - }, - { - "id": 792, - "slug": "piscine-ruby-on-rails-day-00", - "name": "Day 00" - }, - { - "id": 746, - "slug": "piscine-python-django-day-00", - "name": "Day 00" - }, - { - "id": 743, - "slug": "piscine-swift-ios-day-00", - "name": "Day 00" - }, - { - "id": 659, - "slug": "electronics-electronics-selection-test", - "name": "Electronics Selection Test" - }, - { - "id": 634, - "slug": "bootcamp-day-09-00", - "name": "00" - }, - { - "id": 615, - "slug": "open-project-ii", - "name": "Open Project II" - }, - { - "id": 614, - "slug": "open-project-ii-complete-the-project", - "name": "Complete the project" - }, - { - "id": 613, - "slug": "open-project-ii-define-your-subject", - "name": "Define your subject" - }, - { - "id": 591, - "slug": "piscine-c-formation-jour-06", - "name": "Jour 06" - }, - { - "id": 590, - "slug": "piscine-c-formation-jour-05", - "name": "Jour 05" - }, - { - "id": 589, - "slug": "piscine-c-formation-jour-04", - "name": "Jour 04" - }, - { - "id": 588, - "slug": "piscine-c-formation-jour-03", - "name": "Jour 03" - }, - { - "id": 587, - "slug": "piscine-c-formation-jour-02", - "name": "Jour 02" - }, - { - "id": 583, - "slug": "piscine-c-formation-jour-12", - "name": "Jour 12" - }, - { - "id": 582, - "slug": "piscine-c-formation-jour-13", - "name": "Jour 13" - }, - { - "id": 581, - "slug": "piscine-c-formation-jour-10", - "name": "Jour 10" - }, - { - "id": 580, - "slug": "piscine-c-formation-jour-11", - "name": "Jour 11" - }, - { - "id": 551, - "slug": "piscine-c-formation-jour-08", - "name": "Jour 08" - }, - { - "id": 549, - "slug": "piscine-c-formation-jour-07", - "name": "Jour 07" - }, - { - "id": 513, - "slug": "42partnerships-initiation-web-day-00-shell", - "name": "Day 00 - Shell" - }, - { - "id": 507, - "slug": "42partnerships-initiation-ruby-day-00-shell", - "name": "Day 00 - Shell" - }, - { - "id": 505, - "slug": "piscine-c-a-distance-bsq", - "name": "BSQ" - }, - { - "id": 498, - "slug": "piscine-c-a-distance-evalexpr", - "name": "EvalExpr" - }, - { - "id": 496, - "slug": "piscine-c-a-distance-match-n-match", - "name": "Match-N-Match" - }, - { - "id": 494, - "slug": "piscine-c-a-distance-sastantua", - "name": "Sastantua" - }, - { - "id": 489, - "slug": "piscine-c-a-distance-jour-13", - "name": "Jour 13" - }, - { - "id": 487, - "slug": "piscine-c-a-distance-jour-12", - "name": "Jour 12" - }, - { - "id": 485, - "slug": "piscine-c-a-distance-jour-11", - "name": "Jour 11" - }, - { - "id": 483, - "slug": "piscine-c-a-distance-jour-10", - "name": "Jour 10" - }, - { - "id": 459, - "slug": "piscine-c-decloisonnee-pide-jour-09-01", - "name": "01" - }, - { - "id": 458, - "slug": "piscine-c-decloisonnee-pide-jour-09-00", - "name": "00" - }, - { - "id": 457, - "slug": "piscine-c-a-distance-jour-09", - "name": "Jour 09" - }, - { - "id": 434, - "slug": "piscine-c-piadi-jour-09-01", - "name": "01" - }, - { - "id": 433, - "slug": "piscine-c-piadi-jour-09-00", - "name": "00" - }, - { - "id": 431, - "slug": "piscine-c-a-distance-jour-08", - "name": "Jour 08" - }, - { - "id": 430, - "slug": "piscine-c-a-distance-jour-07", - "name": "Jour 07" - }, - { - "id": 427, - "slug": "piscine-c-a-distance-jour-06", - "name": "Jour 06" - }, - { - "id": 425, - "slug": "piscine-c-a-distance-jour-05", - "name": "Jour 05" - }, - { - "id": 424, - "slug": "piscine-c-a-distance-jour-04", - "name": "Jour 04" - }, - { - "id": 422, - "slug": "piscine-c-a-distance-jour-03", - "name": "Jour 03" - }, - { - "id": 420, - "slug": "piscine-c-a-distance-jour-02", - "name": "Jour 02" - }, - { - "id": 418, - "slug": "piscine-c-a-distance-jour-01", - "name": "Jour 01" - }, - { - "id": 416, - "slug": "piscine-c-a-distance-jour-00", - "name": "Jour 00" - }, - { - "id": 409, - "slug": "ft_hangouts", - "name": "ft_hangouts" - }, - { - "id": 381, - "slug": "piscine-unity-day-00", - "name": "Day 00" - }, - { - "id": 372, - "slug": "piscine-ocaml-day-00", - "name": "Day 00" - }, - { - "id": 370, - "slug": "piscine-ocaml", - "name": "Piscine OCaml" - }, - { - "id": 215, - "slug": "communication-trainer-trainees-sessions", - "name": "Trainees sessions" - }, - { - "id": 214, - "slug": "communication-trainer", - "name": "Communication Trainer" - }, - { - "id": 212, - "slug": "final-internship", - "name": "Final Internship" - }, - { - "id": 208, - "slug": "final-internship-contract-upload", - "name": "Contract Upload" - }, - { - "id": 184, - "slug": "part-time", - "name": "Part-time" - }, - { - "id": 180, - "slug": "part-time-contract-upload", - "name": "Contract Upload" - }, - { - "id": 179, - "slug": "strace", - "name": "strace" - }, - { - "id": 178, - "slug": "42run", - "name": "42run" - }, - { - "id": 175, - "slug": "piscine-c-day-09-00", - "name": "00" - }, - { - "id": 174, - "slug": "bsq", - "name": "BSQ" - }, - { - "id": 173, - "slug": "piscine-c-evalexpr", - "name": "EvalExpr" - }, - { - "id": 172, - "slug": "piscine-c-match-n-match", - "name": "Match-N-Match" - }, - { - "id": 171, - "slug": "piscine-c-sastantua", - "name": "Sastantua" - }, - { - "id": 170, - "slug": "piscine-c-rush-02", - "name": "Rush 02" - }, - { - "id": 169, - "slug": "piscine-c-rush-01", - "name": "Rush 01" - }, - { - "id": 168, - "slug": "piscine-c-rush-00", - "name": "Rush 00" - }, - { - "id": 167, - "slug": "piscine-c-day-09", - "name": "Day 09" - }, - { - "id": 166, - "slug": "piscine-c-day-13", - "name": "Day 13" - }, - { - "id": 165, - "slug": "piscine-c-day-12", - "name": "Day 12" - }, - { - "id": 164, - "slug": "piscine-c-day-11", - "name": "Day 11" - }, - { - "id": 163, - "slug": "piscine-c-day-10", - "name": "Day 10" - }, - { - "id": 162, - "slug": "piscine-c-day-08", - "name": "Day 08" - }, - { - "id": 161, - "slug": "piscine-c-day-07", - "name": "Day 07" - }, - { - "id": 160, - "slug": "piscine-c-day-06", - "name": "Day 06" - }, - { - "id": 159, - "slug": "piscine-c-day-05", - "name": "Day 05" - }, - { - "id": 158, - "slug": "piscine-c-day-04", - "name": "Day 04" - }, - { - "id": 157, - "slug": "piscine-c-day-03", - "name": "Day 03" - }, - { - "id": 156, - "slug": "piscine-c-day-02", - "name": "Day 02" - }, - { - "id": 155, - "slug": "piscine-c-day-01", - "name": "Day 01" - }, - { - "id": 154, - "slug": "piscine-c-day-00", - "name": "Day 00" - }, - { - "id": 135, - "slug": "scop", - "name": "Scop" - }, - { - "id": 122, - "slug": "taskmaster", - "name": "Taskmaster" - }, - { - "id": 119, - "slug": "first-internship-contract-upload", - "name": "Contract Upload" - }, - { - "id": 118, - "slug": "first-internship", - "name": "First Internship" - }, - { - "id": 111, - "slug": "computorv1", - "name": "ComputorV1" - }, - { - "id": 107, - "slug": "gomoku", - "name": "Gomoku" - }, - { - "id": 98, - "slug": "expert-system", - "name": "Expert System" - }, - { - "id": 97, - "slug": "n-puzzle", - "name": "N-puzzle" - }, - { - "id": 95, - "slug": "nibbler", - "name": "Nibbler" - }, - { - "id": 89, - "slug": "web-initiation-d00-shell", - "name": "D00 - Shell" - }, - { - "id": 86, - "slug": "open-project-i-define-your-subject", - "name": "Define your subject" - }, - { - "id": 85, - "slug": "open-project-i", - "name": "Open Project I" - }, - { - "id": 63, - "slug": "42-piscine-c-formation-piscine-cpp-day-00", - "name": "Day 00" - }, - { - "id": 49, - "slug": "42-piscine-c-formation-piscine-php-day-00", - "name": "Day 00" - }, - { - "id": 25, - "slug": "rushes-c-hotrace", - "name": "Hotrace" - }, - { - "id": 2505, - "slug": "codam-startup-internship-codam-startup-internship-duration", - "name": "Codam Startup Internship - Duration" - }, - { - "id": 2247, - "slug": "hive-startup-internship-hive-startup-internship-duration", - "name": "Hive Startup Internship - Duration" - }, - { - "id": 2241, - "slug": "hive-internship-hive-internship-duration", - "name": "Hive Internship - Duration" - }, - { - "id": 2070, - "slug": "rushes-hotrace", - "name": "Hotrace" - }, - { - "id": 1877, - "slug": "apprentissage-1-an-apprentissage-1-an-1", - "name": "Apprentissage 1 an - 1" - }, - { - "id": 1869, - "slug": "apprentissage-2-ans-2eme-annee-apprentissage-2-ans-2eme-annee-1", - "name": "Apprentissage 2 ans - 2ème année - 1" - }, - { - "id": 1861, - "slug": "apprentissage-2-ans-1ere-annee-apprentissage-2-ans-1ere-annee-1", - "name": "Apprentissage 2 ans - 1ère année - 1" - }, - { - "id": 1801, - "slug": "piscine-java-day-01", - "name": "Day 01" - }, - { - "id": 1788, - "slug": "piscine-python-data-science-day-01", - "name": "Day 01" - }, - { - "id": 1664, - "slug": "startup-experience-startup-experience-duration", - "name": "Startup Experience - Duration" - }, - { - "id": 1658, - "slug": "part_time-ii-part_time-ii-duration", - "name": "Part_Time II - Duration" - }, - { - "id": 1652, - "slug": "part_time-i-part_time-i-duration", - "name": "Part_Time I - Duration" - }, - { - "id": 1646, - "slug": "work-experience-ii-work-experience-ii-duration", - "name": "Work Experience II - Duration" - }, - { - "id": 1639, - "slug": "work-experience-i-work-experience-i-duration", - "name": "Work Experience I - Duration" - }, - { - "id": 1637, - "slug": "open-project-open-project-complete-the-project", - "name": "Open Project - Complete the project" - }, - { - "id": 1621, - "slug": "42cursus-piscine-python-django-day-01", - "name": "Day 01" - }, - { - "id": 1609, - "slug": "42cursus-piscine-ruby-on-rails-day-01", - "name": "Day 01" - }, - { - "id": 1597, - "slug": "42cursus-piscine-swift-ios-day-01", - "name": "Day 01" - }, - { - "id": 1585, - "slug": "42cursus-piscine-php-symfony-day-01", - "name": "Day 01" - }, - { - "id": 1573, - "slug": "42cursus-piscine-ocaml-day-01", - "name": "Day 01" - }, - { - "id": 1561, - "slug": "42cursus-piscine-unity-day-01", - "name": "Day 01" - }, - { - "id": 1369, - "slug": "data-structures-in-python-part-2-queues-and-stacks", - "name": "Part 2: Queues and Stacks" - }, - { - "id": 1297, - "slug": "api-s-with-node-js-mongodb-setup", - "name": "MongoDB Setup" - }, - { - "id": 1293, - "slug": "java-oop-essentials-in-java", - "name": "OOP Essentials in Java" - }, - { - "id": 1292, - "slug": "pygame-showcase-arcade", - "name": "Showcase: Arcade" - }, - { - "id": 1287, - "slug": "machine-learning-using-python-ml_02", - "name": "ML_02" - }, - { - "id": 1222, - "slug": "javascript-and-graphics-in-p5js-p5js-01", - "name": "p5js-01" - }, - { - "id": 1193, - "slug": "data-mining-the-49ers-mapping-geographical-data-in-plotly", - "name": "Mapping Geographical Data in Plotly" - }, - { - "id": 1149, - "slug": "piscine-php-symfony-day-01", - "name": "Day 01" - }, - { - "id": 1143, - "slug": "parseltongue-piscine-parseltongue-part-1", - "name": "Parseltongue - Part 1" - }, - { - "id": 1105, - "slug": "apcsp-internet-simulator-internet-simulator-network-architecture", - "name": "Internet Simulator: Network Architecture" - }, - { - "id": 1028, - "slug": "deprecated-apcsp-explore-apcsp-explore-portfolio", - "name": "APCSP - Explore Portfolio" - }, - { - "id": 1007, - "slug": "apcsp-prep-apcsp-create-task", - "name": "APCSP - Create Task" - }, - { - "id": 991, - "slug": "algorithmic-puzzles-crypto-intro", - "name": "Crypto intro" - }, - { - "id": 972, - "slug": "wethinkcode_-social-tech-lab-phase-2", - "name": "Phase 2" - }, - { - "id": 963, - "slug": "joburg-first-internship-duration", - "name": "Duration" - }, - { - "id": 869, - "slug": "matrice-matrice-cea", - "name": "Matrice CEA" - }, - { - "id": 851, - "slug": "piscine-starfleet-exam-00", - "name": "Exam 00" - }, - { - "id": 835, - "slug": "hercules-lernaean-hydra", - "name": "Lernaean Hydra" - }, - { - "id": 793, - "slug": "piscine-ruby-on-rails-day-01", - "name": "Day 01" - }, - { - "id": 744, - "slug": "piscine-swift-ios-day-01", - "name": "Day 01" - }, - { - "id": 730, - "slug": "piscine-python-django-day-01", - "name": "Day 01" - }, - { - "id": 703, - "slug": "rushes-factrace", - "name": "Factrace" - }, - { - "id": 666, - "slug": "electronics-electronics-project", - "name": "Electronics Project" - }, - { - "id": 635, - "slug": "bootcamp-day-09-01", - "name": "01" - }, - { - "id": 520, - "slug": "42partnerships-initiation-web-day-01-html-css", - "name": "Day 01 - HTML & CSS" - }, - { - "id": 508, - "slug": "42partnerships-initiation-ruby-day-01-ruby", - "name": "Day 01 - Ruby" - }, - { - "id": 460, - "slug": "piscine-c-decloisonnee-pide-jour-09-02", - "name": "02" - }, - { - "id": 435, - "slug": "piscine-c-piadi-jour-09-02", - "name": "02" - }, - { - "id": 382, - "slug": "piscine-unity-day-01", - "name": "Day 01" - }, - { - "id": 374, - "slug": "piscine-ocaml-day-01", - "name": "Day 01" - }, - { - "id": 216, - "slug": "communication-trainer-training-the-community", - "name": "Training the community" - }, - { - "id": 209, - "slug": "final-internship-duration", - "name": "Duration" - }, - { - "id": 185, - "slug": "piscine-c-day-09-01", - "name": "01" - }, - { - "id": 181, - "slug": "part-time-duration", - "name": "Duration" - }, - { - "id": 140, - "slug": "first-internship-duration", - "name": "Duration" - }, - { - "id": 90, - "slug": "web-initiation-d01-html-css-js", - "name": "D01 - HTML, CSS & JS" - }, - { - "id": 87, - "slug": "open-project-i-complete-the-project", - "name": "Complete the project" - }, - { - "id": 64, - "slug": "42-piscine-c-formation-piscine-cpp-day-01", - "name": "Day 01" - }, - { - "id": 50, - "slug": "42-piscine-c-formation-piscine-php-day-01", - "name": "Day 01" - }, - { - "id": 2506, - "slug": "codam-startup-internship-codam-startup-internship-mid-evaluation", - "name": "Codam Startup Internship - Mid Evaluation" - }, - { - "id": 2248, - "slug": "hive-startup-internship-hive-startup-internship-entrepreneurship-mid-evaluation", - "name": "Hive Startup Internship - Entrepreneurship mid evaluation" - }, - { - "id": 2242, - "slug": "hive-internship-hive-internship-company-mid-evaluation", - "name": "Hive Internship - Company mid evaluation" - }, - { - "id": 2102, - "slug": "42cursus-rushes-alcu", - "name": "AlCu" - }, - { - "id": 1878, - "slug": "apprentissage-1-an-apprentissage-1-an-2", - "name": "Apprentissage 1 an - 2" - }, - { - "id": 1870, - "slug": "apprentissage-2-ans-2eme-annee-apprentissage-2-ans-2eme-annee-2", - "name": "Apprentissage 2 ans - 2ème année - 2" - }, - { - "id": 1862, - "slug": "apprentissage-2-ans-1ere-annee-apprentissage-2-ans-1ere-annee-2", - "name": "Apprentissage 2 ans - 1ère année - 2" - }, - { - "id": 1802, - "slug": "piscine-java-day-02", - "name": "Day 02" - }, - { - "id": 1789, - "slug": "piscine-python-data-science-day-02", - "name": "Day 02" - }, - { - "id": 1665, - "slug": "startup-internship-startup-internship-tutor-mid-evaluation", - "name": "Startup Internship - Tutor Mid Evaluation" - }, - { - "id": 1659, - "slug": "part_time-ii-part_time-ii-company-mid-evaluation", - "name": "Part_Time II - Company Mid Evaluation" - }, - { - "id": 1654, - "slug": "part_time-i-part_time-i-company-mid-evaluation", - "name": "Part_Time I Company Mid Evaluation" - }, - { - "id": 1647, - "slug": "work-experience-ii-work-experience-ii-company-mid-evaluation", - "name": "Work Experience II - Company Mid Evaluation" - }, - { - "id": 1641, - "slug": "work-experience-i-work-experience-i-company-mid-evaluation", - "name": "Work Experience I - Company Mid Evaluation" - }, - { - "id": 1622, - "slug": "42cursus-piscine-python-django-day-02", - "name": "Day 02" - }, - { - "id": 1610, - "slug": "42cursus-piscine-ruby-on-rails-day-02", - "name": "Day 02" - }, - { - "id": 1598, - "slug": "42cursus-piscine-swift-ios-day-02", - "name": "Day 02" - }, - { - "id": 1586, - "slug": "42cursus-piscine-php-symfony-day-02", - "name": "Day 02" - }, - { - "id": 1574, - "slug": "42cursus-piscine-ocaml-day-02", - "name": "Day 02" - }, - { - "id": 1562, - "slug": "42cursus-piscine-unity-day-02", - "name": "Day 02" - }, - { - "id": 1370, - "slug": "data-structures-plants-vs-nonplants", - "name": "Plants vs NonPlants!" - }, - { - "id": 1298, - "slug": "api-s-with-node-js-hosting-on-heroku", - "name": "Hosting on Heroku" - }, - { - "id": 1288, - "slug": "machine-learning-using-python-ml_03", - "name": "ML_03" - }, - { - "id": 1224, - "slug": "javascript-and-graphics-in-p5js-p5js-02", - "name": "p5js-02" - }, - { - "id": 1194, - "slug": "data-mining-the-49ers-api-queries-to-mysportsfeeds", - "name": "API Queries to MySportsFeeds" - }, - { - "id": 1150, - "slug": "piscine-php-symfony-day-02", - "name": "Day 02" - }, - { - "id": 1145, - "slug": "parseltongue-piscine-parseltongue-part-2", - "name": "Parseltongue - Part 2" - }, - { - "id": 1106, - "slug": "electronics-electronics-project-level-up", - "name": "Electronics Project - Level UP" - }, - { - "id": 1092, - "slug": "startup-internship-entrepreneurship-mid-evaluation", - "name": "Entrepreneurship mid evaluation" - }, - { - "id": 988, - "slug": "apcsp-prep-apcsp-explore-task", - "name": "APCSP - Explore Task" - }, - { - "id": 973, - "slug": "wethinkcode_-social-tech-lab-phase-3", - "name": "Phase 3" - }, - { - "id": 964, - "slug": "joburg-first-internship-company-mid-evaluation", - "name": "Company mid evaluation" - }, - { - "id": 852, - "slug": "piscine-starfleet-day-01", - "name": "Day 01" - }, - { - "id": 836, - "slug": "hercules-ceryneian-hind", - "name": "Ceryneian Hind" - }, - { - "id": 828, - "slug": "part-time-company-mid-evaluation", - "name": "Company mid evaluation" - }, - { - "id": 827, - "slug": "final-internship-company-mid-evaluation", - "name": "Company mid evaluation" - }, - { - "id": 826, - "slug": "first-internship-company-mid-evaluation", - "name": "Company mid evaluation" - }, - { - "id": 794, - "slug": "piscine-ruby-on-rails-day-02", - "name": "Day 02" - }, - { - "id": 745, - "slug": "piscine-swift-ios-day-02", - "name": "Day 02" - }, - { - "id": 731, - "slug": "piscine-python-django-day-02", - "name": "Day 02" - }, - { - "id": 697, - "slug": "rushes-lldb", - "name": "LLDB" - }, - { - "id": 636, - "slug": "day-09-02", - "name": "02" - }, - { - "id": 521, - "slug": "42partnerships-initiation-web-day-02-php", - "name": "Day 02 - PHP" - }, - { - "id": 509, - "slug": "42partnerships-initiation-ruby-day-02-ruby", - "name": "Day 02 - Ruby" - }, - { - "id": 461, - "slug": "piscine-c-decloisonnee-pide-jour-09-03", - "name": "03" - }, - { - "id": 436, - "slug": "piscine-c-piadi-jour-09-03", - "name": "03" - }, - { - "id": 383, - "slug": "piscine-unity-day-02", - "name": "Day 02" - }, - { - "id": 375, - "slug": "piscine-ocaml-day-02", - "name": "Day 02" - }, - { - "id": 186, - "slug": "piscine-c-day-09-02", - "name": "02" - }, - { - "id": 92, - "slug": "web-initiation-d02-ratchet-parse", - "name": "D02 - Ratchet & Parse" - }, - { - "id": 65, - "slug": "42-piscine-c-formation-piscine-cpp-day-02", - "name": "Day 02" - }, - { - "id": 51, - "slug": "42-piscine-c-formation-piscine-php-day-02", - "name": "Day 02" - }, - { - "id": 2507, - "slug": "codam-startup-internship-codam-startup-internship-final-evaluation", - "name": "Codam Startup Internship - Final Evaluation" - }, - { - "id": 2249, - "slug": "hive-startup-internship-hive-startup-internship-entrepreneurship-final-evaluation", - "name": "Hive Startup Internship - Entrepreneurship final evaluation" - }, - { - "id": 2243, - "slug": "hive-internship-hive-internship-company-final-evaluation", - "name": "Hive Internship - Company final evaluation" - }, - { - "id": 2122, - "slug": "rushes-wong-kar-wai", - "name": "Wong kar Wai" - }, - { - "id": 1879, - "slug": "apprentissage-1-an-apprentissage-1-an-3", - "name": "Apprentissage 1 an - 3" - }, - { - "id": 1871, - "slug": "apprentissage-2-ans-2eme-annee-apprentissage-2-ans-2eme-annee-3", - "name": "Apprentissage 2 ans - 2ème année - 3" - }, - { - "id": 1863, - "slug": "apprentissage-2-ans-1ere-annee-apprentissage-2-ans-1ere-annee-3", - "name": "Apprentissage 2 ans - 1ère année - 3" - }, - { - "id": 1803, - "slug": "piscine-java-day-03", - "name": "Day 03" - }, - { - "id": 1790, - "slug": "piscine-python-data-science-day-03", - "name": "Day 03" - }, - { - "id": 1748, - "slug": "deprecated-out-with-the-old-owo-deprecated-philosophers-owo", - "name": "[DEPRECATED] Philosophers (OwO)" - }, - { - "id": 1697, - "slug": "apcsp-prep-apcsp-digital-portfolio", - "name": "APCSP - Digital Portfolio" - }, - { - "id": 1666, - "slug": "startup-internship-startup-internship-tutor-final-evaluation", - "name": "Startup Internship - Tutor Final Evaluation" - }, - { - "id": 1660, - "slug": "part_time-ii-part_time-ii-company-final-evaluation", - "name": "Part_Time II - Company Final Evaluation" - }, - { - "id": 1653, - "slug": "part_time-i-part_time-i-company-final-evaluation", - "name": "Part_Time I Company Final Evaluation" - }, - { - "id": 1648, - "slug": "work-experience-ii-work-experience-ii-company-final-evaluation", - "name": "Work Experience II - Company Final Evaluation" - }, - { - "id": 1642, - "slug": "work-experience-i-work-experience-i-company-final-evaluation", - "name": "Work Experience I - Company Final Evaluation" - }, - { - "id": 1623, - "slug": "42cursus-piscine-python-django-day-03", - "name": "Day 03" - }, - { - "id": 1611, - "slug": "42cursus-piscine-ruby-on-rails-day-03", - "name": "Day 03" - }, - { - "id": 1599, - "slug": "42cursus-piscine-swift-ios-day-03", - "name": "Day 03" - }, - { - "id": 1587, - "slug": "42cursus-piscine-php-symfony-day-03", - "name": "Day 03" - }, - { - "id": 1575, - "slug": "42cursus-piscine-ocaml-day-03", - "name": "Day 03" - }, - { - "id": 1563, - "slug": "42cursus-piscine-unity-day-03", - "name": "Day 03" - }, - { - "id": 1357, - "slug": "algorithmic-puzzles-fractal", - "name": "Fractal" - }, - { - "id": 1289, - "slug": "machine-learning-using-python-ml_04", - "name": "ML_04" - }, - { - "id": 1225, - "slug": "javascript-and-graphics-in-p5js-p5js-03", - "name": "p5js-03" - }, - { - "id": 1201, - "slug": "javascript-jquery", - "name": "jQuery" - }, - { - "id": 1195, - "slug": "data-mining-the-49ers-statistical-data-visualization-with-seaborn", - "name": "Statistical Data Visualization with Seaborn" - }, - { - "id": 1151, - "slug": "piscine-php-symfony-day-03", - "name": "Day 03" - }, - { - "id": 1144, - "slug": "parseltongue-piscine-parseltongue-part-3", - "name": "Parseltongue - Part 3" - }, - { - "id": 1096, - "slug": "matrice-matrice-arts-numerique", - "name": "Matrice Arts & Numérique" - }, - { - "id": 1093, - "slug": "startup-internship-entrepreneurship-final-evaluation", - "name": "Entrepreneurship final evaluation" - }, - { - "id": 975, - "slug": "wethinkcode_-social-tech-lab-final-jury", - "name": "Final Jury" - }, - { - "id": 965, - "slug": "joburg-first-internship-company-final-evaluation", - "name": "Company final evaluation" - }, - { - "id": 853, - "slug": "piscine-starfleet-day-02", - "name": "Day 02" - }, - { - "id": 837, - "slug": "hercules-erymanthian-boar", - "name": "Erymanthian Boar" - }, - { - "id": 795, - "slug": "piscine-ruby-on-rails-day-03", - "name": "Day 03" - }, - { - "id": 747, - "slug": "piscine-swift-ios-day-03", - "name": "Day 03" - }, - { - "id": 732, - "slug": "piscine-python-django-day-03", - "name": "Day 03" - }, - { - "id": 684, - "slug": "rushes-puissance-4", - "name": "Puissance 4" - }, - { - "id": 637, - "slug": "day-09-03", - "name": "03" - }, - { - "id": 510, - "slug": "42partnerships-initiation-ruby-day-03-ruby", - "name": "Day 03 - Ruby" - }, - { - "id": 462, - "slug": "piscine-c-decloisonnee-pide-jour-09-04", - "name": "04" - }, - { - "id": 437, - "slug": "piscine-c-piadi-jour-09-04", - "name": "04" - }, - { - "id": 384, - "slug": "piscine-unity-day-03", - "name": "Day 03" - }, - { - "id": 377, - "slug": "piscine-ocaml-day-03", - "name": "Day 03" - }, - { - "id": 210, - "slug": "final-internship-company-final-evaluation", - "name": "Company final evaluation" - }, - { - "id": 187, - "slug": "piscine-c-day-09-03", - "name": "03" - }, - { - "id": 182, - "slug": "part-time-company-final-evaluation", - "name": "Company final evaluation" - }, - { - "id": 120, - "slug": "first-internship-company-final-evaluation", - "name": "Company final evaluation" - }, - { - "id": 66, - "slug": "42-piscine-c-formation-piscine-cpp-day-03", - "name": "Day 03" - }, - { - "id": 52, - "slug": "42-piscine-c-formation-piscine-php-day-03", - "name": "Day 03" - }, - { - "id": 2508, - "slug": "codam-startup-internship-codam-startup-internship-peer-video", - "name": "Codam Startup Internship - Peer Video" - }, - { - "id": 2250, - "slug": "hive-startup-internship-hive-startup-internship-peer-video", - "name": "Hive Startup Internship - Peer Video" - }, - { - "id": 2244, - "slug": "hive-internship-hive-internship-peer-video", - "name": "Hive Internship - Peer video" - }, - { - "id": 2136, - "slug": "42cursus-rushes-yasl", - "name": "yasl" - }, - { - "id": 1880, - "slug": "apprentissage-1-an-apprentissage-1-an-4", - "name": "Apprentissage 1 an - 4" - }, - { - "id": 1872, - "slug": "apprentissage-2-ans-2eme-annee-apprentissage-2-ans-2eme-annee-4", - "name": "Apprentissage 2 ans - 2ème année - 4" - }, - { - "id": 1864, - "slug": "apprentissage-2-ans-1ere-annee-apprentissage-2-ans-1ere-annee-4", - "name": "Apprentissage 2 ans - 1ère année - 4" - }, - { - "id": 1804, - "slug": "piscine-java-day-04", - "name": "Day 04" - }, - { - "id": 1791, - "slug": "piscine-python-data-science-day-04", - "name": "Day 04" - }, - { - "id": 1712, - "slug": "machine-learning-ibm-machine-learning-00", - "name": "IBM Machine Learning 00" - }, - { - "id": 1667, - "slug": "startup-internship-startup-internship-peer-video", - "name": "Startup Internship - Peer Video" - }, - { - "id": 1661, - "slug": "part_time-ii-part_time-ii-peer-video", - "name": "Part_Time II - Peer Video" - }, - { - "id": 1655, - "slug": "part_time-i-part_time-i-peer-video", - "name": "Part_Time I Peer Video" - }, - { - "id": 1649, - "slug": "work-experience-ii-work-experience-ii-peer-video", - "name": "Work Experience II - Peer Video" - }, - { - "id": 1643, - "slug": "work-experience-i-work-experience-i-peer-video", - "name": "Work Experience I - Peer Video" - }, - { - "id": 1624, - "slug": "42cursus-piscine-python-django-day-04", - "name": "Day 04" - }, - { - "id": 1612, - "slug": "42cursus-piscine-ruby-on-rails-day-04", - "name": "Day 04" - }, - { - "id": 1600, - "slug": "42cursus-piscine-swift-ios-day-04", - "name": "Day 04" - }, - { - "id": 1588, - "slug": "42cursus-piscine-php-symfony-day-04", - "name": "Day 04" - }, - { - "id": 1576, - "slug": "42cursus-piscine-ocaml-day-04", - "name": "Day 04" - }, - { - "id": 1564, - "slug": "42cursus-piscine-unity-day-04", - "name": "Day 04" - }, - { - "id": 1366, - "slug": "apcsp-prep-apcsp-vocabulary", - "name": "APCSP - Vocabulary" - }, - { - "id": 1358, - "slug": "algorithmic-puzzles-connect-4", - "name": "Connect-4" - }, - { - "id": 1294, - "slug": "javascript-web02-freecodecamp-js", - "name": "Web02 - FreeCodeCamp JS" - }, - { - "id": 1229, - "slug": "javascript-and-graphics-in-p5js-p5js-04", - "name": "p5js-04" - }, - { - "id": 1152, - "slug": "piscine-php-symfony-day-04", - "name": "Day 04" - }, - { - "id": 1146, - "slug": "parseltongue-piscine-parseltongue-part-4", - "name": "Parseltongue - Part 4" - }, - { - "id": 1094, - "slug": "startup-internship-peer-video", - "name": "Peer Video" - }, - { - "id": 966, - "slug": "joburg-first-internship-peer-video", - "name": "Peer Video" - }, - { - "id": 855, - "slug": "piscine-starfleet-day-03", - "name": "Day 03" - }, - { - "id": 838, - "slug": "hercules-augean-stables", - "name": "Augean stables" - }, - { - "id": 796, - "slug": "piscine-ruby-on-rails-day-04", - "name": "Day 04" - }, - { - "id": 748, - "slug": "piscine-swift-ios-day-04", - "name": "Day 04" - }, - { - "id": 733, - "slug": "piscine-python-django-day-04", - "name": "Day 04" - }, - { - "id": 685, - "slug": "rushes-domino", - "name": "Domino" - }, - { - "id": 638, - "slug": "day-09-04", - "name": "04" - }, - { - "id": 463, - "slug": "piscine-c-decloisonnee-pide-jour-09-05", - "name": "05" - }, - { - "id": 438, - "slug": "piscine-c-piadi-jour-09-05", - "name": "05" - }, - { - "id": 385, - "slug": "piscine-unity-day-04", - "name": "Day 04" - }, - { - "id": 379, - "slug": "piscine-ocaml-day-04", - "name": "Day 04" - }, - { - "id": 211, - "slug": "final-internship-peer-video", - "name": "Peer Video" - }, - { - "id": 188, - "slug": "piscine-c-day-09-04", - "name": "04" - }, - { - "id": 183, - "slug": "part-time-peer-video", - "name": "Peer Video" - }, - { - "id": 121, - "slug": "first-internship-peer-video", - "name": "Peer Video" - }, - { - "id": 67, - "slug": "42-piscine-c-formation-piscine-cpp-day-04", - "name": "Day 04" - }, - { - "id": 53, - "slug": "42-piscine-c-formation-piscine-php-day-04", - "name": "Day 04" - }, - { - "id": 2173, - "slug": "42cursus-rushes-wordle", - "name": "wordle" - }, - { - "id": 2092, - "slug": "apprentissage-2-ans-1ere-annee-apprentissage-2-ans-1ere-annee-begin-evaluation", - "name": "Apprentissage 2 ans - 1ère année - Begin evaluation" - }, - { - "id": 2091, - "slug": "apprentissage-2-ans-2eme-annee-apprentissage-2-ans-2eme-annee-mid-evaluation", - "name": "Apprentissage 2 ans - 2ème année - Mid evaluation" - }, - { - "id": 2090, - "slug": "apprentissage-1-an-apprentissage-1-an-begin-evaluation", - "name": "Apprentissage 1 an - Begin evaluation" - }, - { - "id": 1805, - "slug": "piscine-java-day-05", - "name": "Day 05" - }, - { - "id": 1792, - "slug": "piscine-python-data-science-day-05", - "name": "Day 05" - }, - { - "id": 1749, - "slug": "deprecated-out-with-the-old-owo-deprecated-webserv-owo", - "name": "[DEPRECATED] webserv (OwO)" - }, - { - "id": 1713, - "slug": "machine-learning-ibm-machine-learning-01", - "name": "IBM Machine Learning 01" - }, - { - "id": 1694, - "slug": "javascript-web-basics-01-recipe", - "name": "Web Basics 01 - Recipe" - }, - { - "id": 1681, - "slug": "apcsp-programming-pygame", - "name": "Pygame" - }, - { - "id": 1625, - "slug": "42cursus-piscine-python-django-day-05", - "name": "Day 05" - }, - { - "id": 1613, - "slug": "42cursus-piscine-ruby-on-rails-day-05", - "name": "Day 05" - }, - { - "id": 1601, - "slug": "42cursus-piscine-swift-ios-day-05", - "name": "Day 05" - }, - { - "id": 1589, - "slug": "42cursus-piscine-php-symfony-day-05", - "name": "Day 05" - }, - { - "id": 1577, - "slug": "42cursus-piscine-ocaml-day-05", - "name": "Day 05" - }, - { - "id": 1565, - "slug": "42cursus-piscine-unity-day-05", - "name": "Day 05" - }, - { - "id": 1363, - "slug": "apcsp-prep-apcsp-practice-exam", - "name": "APCSP - Practice Exam" - }, - { - "id": 1359, - "slug": "algorithmic-puzzles-game-of-life", - "name": "Game Of Life" - }, - { - "id": 1313, - "slug": "python-showcase-command-line-games", - "name": "Showcase: Command-Line Games" - }, - { - "id": 1231, - "slug": "javascript-and-graphics-in-p5js-p5js-05", - "name": "p5js-05" - }, - { - "id": 1153, - "slug": "piscine-php-symfony-rush00", - "name": "Rush00" - }, - { - "id": 857, - "slug": "piscine-starfleet-exam-02", - "name": "Exam 02" - }, - { - "id": 839, - "slug": "hercules-stymphalian-birds", - "name": "Stymphalian Birds" - }, - { - "id": 797, - "slug": "piscine-ruby-on-rails-rush00", - "name": "Rush00" - }, - { - "id": 749, - "slug": "piscine-swift-ios-rush00", - "name": "Rush00" - }, - { - "id": 734, - "slug": "piscine-python-django-rush00", - "name": "Rush00" - }, - { - "id": 639, - "slug": "day-09-05", - "name": "05" - }, - { - "id": 512, - "slug": "42partnerships-initiation-ruby-rush-00-rpg_txt", - "name": "Rush 00 - rpg_txt" - }, - { - "id": 464, - "slug": "piscine-c-decloisonnee-pide-jour-09-06", - "name": "06" - }, - { - "id": 439, - "slug": "piscine-c-piadi-jour-09-06", - "name": "06" - }, - { - "id": 399, - "slug": "piscine-ocaml-rush00", - "name": "Rush00" - }, - { - "id": 386, - "slug": "piscine-unity-rush00", - "name": "Rush00" - }, - { - "id": 189, - "slug": "piscine-c-day-09-05", - "name": "05" - }, - { - "id": 69, - "slug": "piscine-cpp-rush00", - "name": "Rush00" - }, - { - "id": 59, - "slug": "piscine-php-rush00", - "name": "Rush00" - }, - { - "id": 18, - "slug": "rushes-introduction-to-ios", - "name": "Introduction to iOS" - }, - { - "id": 2174, - "slug": "rushes-connect4", - "name": "Connect4" - }, - { - "id": 2093, - "slug": "apprentissage-2-ans-1ere-annee-apprentissage-2-ans-1ere-annee-annual-evaluation", - "name": "Apprentissage 2 ans - 1ère année - Annual Evaluation" - }, - { - "id": 1876, - "slug": "apprentissage-1-an-apprentissage-1-an-final-evaluation", - "name": "Apprentissage 1 an - Final evaluation" - }, - { - "id": 1868, - "slug": "apprentissage-2-ans-2eme-annee-apprentissage-2-ans-2eme-annee-final-evaluation", - "name": "Apprentissage 2 ans - 2ème année - Final evaluation" - }, - { - "id": 1806, - "slug": "piscine-java-day-06", - "name": "Day 06" - }, - { - "id": 1793, - "slug": "piscine-python-data-science-day-06", - "name": "Day 06" - }, - { - "id": 1751, - "slug": "deprecated-out-with-the-old-owo-deprecated-ft_transcendence-owo", - "name": " [DEPRECATED] ft_transcendence (OwO)" - }, - { - "id": 1695, - "slug": "javascript-web-basics-02-pinterest", - "name": "Web Basics 02 - Pinterest" - }, - { - "id": 1626, - "slug": "42cursus-piscine-python-django-day-06", - "name": "Day 06" - }, - { - "id": 1614, - "slug": "42cursus-piscine-ruby-on-rails-day-06", - "name": "Day 06" - }, - { - "id": 1602, - "slug": "42cursus-piscine-swift-ios-day-06", - "name": "Day 06" - }, - { - "id": 1590, - "slug": "42cursus-piscine-php-symfony-day-06", - "name": "Day 06" - }, - { - "id": 1578, - "slug": "42cursus-piscine-ocaml-day-06", - "name": "Day 06" - }, - { - "id": 1566, - "slug": "42cursus-piscine-unity-day-06", - "name": "Day 06" - }, - { - "id": 1365, - "slug": "apcsp-prep-apcsp-internet-simulator", - "name": "APCSP - Internet Simulator" - }, - { - "id": 1360, - "slug": "algorithmic-puzzles-sonicpi", - "name": "SonicPi" - }, - { - "id": 1154, - "slug": "piscine-php-symfony-day-05", - "name": "Day 05" - }, - { - "id": 858, - "slug": "piscine-starfleet-rush-00", - "name": "Rush 00" - }, - { - "id": 840, - "slug": "hercules-cretan-bull", - "name": "Cretan Bull" - }, - { - "id": 798, - "slug": "piscine-ruby-on-rails-day-05", - "name": "Day 05" - }, - { - "id": 750, - "slug": "piscine-swift-ios-day-05", - "name": "Day 05" - }, - { - "id": 735, - "slug": "piscine-python-django-day-05", - "name": "Day 05" - }, - { - "id": 640, - "slug": "day-09-06", - "name": "06" - }, - { - "id": 465, - "slug": "piscine-c-decloisonnee-pide-jour-09-07", - "name": "07" - }, - { - "id": 440, - "slug": "piscine-c-piadi-jour-09-07", - "name": "07" - }, - { - "id": 388, - "slug": "piscine-unity-day-05", - "name": "Day 05" - }, - { - "id": 380, - "slug": "piscine-ocaml-day-05", - "name": "Day 05" - }, - { - "id": 190, - "slug": "piscine-c-day-09-06", - "name": "06" - }, - { - "id": 54, - "slug": "42-piscine-c-formation-piscine-php-day-05", - "name": "Day 05" - }, - { - "id": 14, - "slug": "rushes-introduction-to-wordpress", - "name": "Introduction to Wordpress" - }, - { - "id": 2210, - "slug": "rushes-retro-mfa", - "name": "Retro-MFA" - }, - { - "id": 1807, - "slug": "piscine-java-day-07", - "name": "Day 07" - }, - { - "id": 1794, - "slug": "piscine-python-data-science-day-07", - "name": "Day 07" - }, - { - "id": 1766, - "slug": "deprecated-out-with-the-old-owo-deprecated-cpp-module-00-owo", - "name": "[DEPRECATED] CPP Module 00 (OwO)" - }, - { - "id": 1627, - "slug": "42cursus-piscine-python-django-day-07", - "name": "Day 07" - }, - { - "id": 1615, - "slug": "42cursus-piscine-ruby-on-rails-day-07", - "name": "Day 07" - }, - { - "id": 1603, - "slug": "42cursus-piscine-swift-ios-day-07", - "name": "Day 07" - }, - { - "id": 1591, - "slug": "42cursus-piscine-php-symfony-day-07", - "name": "Day 07" - }, - { - "id": 1579, - "slug": "42cursus-piscine-ocaml-day-07", - "name": "Day 07" - }, - { - "id": 1567, - "slug": "42cursus-piscine-unity-day-07", - "name": "Day 07" - }, - { - "id": 1155, - "slug": "piscine-php-symfony-day-06", - "name": "Day 06" - }, - { - "id": 859, - "slug": "piscine-starfleet-day-04", - "name": "Day 04" - }, - { - "id": 841, - "slug": "hercules-mares-of-diomedes", - "name": "Mares of Diomedes" - }, - { - "id": 799, - "slug": "piscine-ruby-on-rails-day-06", - "name": "Day 06" - }, - { - "id": 751, - "slug": "piscine-swift-ios-day-06", - "name": "Day 06" - }, - { - "id": 736, - "slug": "piscine-python-django-day-06", - "name": "Day 06" - }, - { - "id": 641, - "slug": "day-09-07", - "name": "07" - }, - { - "id": 466, - "slug": "piscine-c-decloisonnee-pide-jour-09-08", - "name": "08" - }, - { - "id": 441, - "slug": "piscine-c-piadi-jour-09-08", - "name": "08" - }, - { - "id": 395, - "slug": "piscine-ocaml-day-06", - "name": "Day 06" - }, - { - "id": 389, - "slug": "piscine-unity-day-06", - "name": "Day 06" - }, - { - "id": 191, - "slug": "piscine-c-day-09-07", - "name": "07" - }, - { - "id": 141, - "slug": "rushes-arkanoid", - "name": "Arkanoid" - }, - { - "id": 70, - "slug": "42-piscine-c-formation-piscine-cpp-day-05", - "name": "Day 05" - }, - { - "id": 55, - "slug": "42-piscine-c-formation-piscine-php-day-06", - "name": "Day 06" - }, - { - "id": 2214, - "slug": "rushes-ft_shmup", - "name": "ft_shmup " - }, - { - "id": 1808, - "slug": "piscine-java-day-08", - "name": "Day 08" - }, - { - "id": 1795, - "slug": "piscine-python-data-science-day-08", - "name": "Day 08" - }, - { - "id": 1775, - "slug": "deprecated-out-with-the-old-owo-deprecated-cpp-module-01-owo", - "name": "[DEPRECATED] CPP Module 01 (OwO)" - }, - { - "id": 1628, - "slug": "42cursus-piscine-python-django-day-08", - "name": "Day 08" - }, - { - "id": 1616, - "slug": "42cursus-piscine-ruby-on-rails-day-08", - "name": "Day 08" - }, - { - "id": 1604, - "slug": "42cursus-piscine-swift-ios-day-08", - "name": "Day 08" - }, - { - "id": 1592, - "slug": "42cursus-piscine-php-symfony-day-08", - "name": "Day 08" - }, - { - "id": 1580, - "slug": "42cursus-piscine-ocaml-day-08", - "name": "Day 08" - }, - { - "id": 1568, - "slug": "42cursus-piscine-unity-day-08", - "name": "Day 08" - }, - { - "id": 1156, - "slug": "piscine-php-symfony-day-07", - "name": "Day 07" - }, - { - "id": 860, - "slug": "piscine-starfleet-exam-03", - "name": "Exam 03" - }, - { - "id": 842, - "slug": "hercules-girdle-of-hippolyta", - "name": "Girdle of Hippolyta" - }, - { - "id": 800, - "slug": "piscine-ruby-on-rails-day-07", - "name": "Day 07" - }, - { - "id": 752, - "slug": "piscine-swift-ios-day-07", - "name": "Day 07" - }, - { - "id": 737, - "slug": "piscine-python-django-day-07", - "name": "Day 07" - }, - { - "id": 642, - "slug": "day-09-08", - "name": "08" - }, - { - "id": 467, - "slug": "piscine-c-decloisonnee-pide-jour-09-09", - "name": "09" - }, - { - "id": 442, - "slug": "piscine-c-piadi-jour-09-09", - "name": "09" - }, - { - "id": 396, - "slug": "piscine-ocaml-day-07", - "name": "Day 07" - }, - { - "id": 390, - "slug": "piscine-unity-day-07", - "name": "Day 07" - }, - { - "id": 192, - "slug": "piscine-c-day-09-08", - "name": "08" - }, - { - "id": 93, - "slug": "rushes-wong_kar_wai", - "name": "wong_kar_wai" - }, - { - "id": 71, - "slug": "42-piscine-c-formation-piscine-cpp-day-06", - "name": "Day 06" - }, - { - "id": 56, - "slug": "42-piscine-c-formation-piscine-php-day-07", - "name": "Day 07" - }, - { - "id": 1809, - "slug": "piscine-java-day-09", - "name": "Day 09" - }, - { - "id": 1796, - "slug": "piscine-python-data-science-day-09", - "name": "Day 09" - }, - { - "id": 1776, - "slug": "out-with-the-old-owo-cpp-module-02-owo", - "name": "CPP Module 02 (OwO)" - }, - { - "id": 1629, - "slug": "42cursus-piscine-python-django-day-09", - "name": "Day 09" - }, - { - "id": 1617, - "slug": "42cursus-piscine-ruby-on-rails-day-09", - "name": "Day 09" - }, - { - "id": 1605, - "slug": "42cursus-piscine-swift-ios-day-09", - "name": "Day 09" - }, - { - "id": 1593, - "slug": "42cursus-piscine-php-symfony-day-09", - "name": "Day 09" - }, - { - "id": 1581, - "slug": "42cursus-piscine-ocaml-day-09", - "name": "Day 09" - }, - { - "id": 1569, - "slug": "42cursus-piscine-unity-day-09", - "name": "Day 09" - }, - { - "id": 1157, - "slug": "piscine-php-symfony-day-08", - "name": "Day 08" - }, - { - "id": 862, - "slug": "piscine-starfleet-day-05", - "name": "Day 05" - }, - { - "id": 843, - "slug": "hercules-cattle-of-geryon", - "name": "Cattle of Geryon" - }, - { - "id": 801, - "slug": "piscine-ruby-on-rails-day-08", - "name": "Day 08" - }, - { - "id": 753, - "slug": "piscine-swift-ios-day-08", - "name": "Day 08" - }, - { - "id": 738, - "slug": "piscine-python-django-day-08", - "name": "Day 08" - }, - { - "id": 663, - "slug": "rushes-carnifex-lisp", - "name": "Carnifex (LISP)" - }, - { - "id": 643, - "slug": "day-09-09", - "name": "09" - }, - { - "id": 468, - "slug": "piscine-c-decloisonnee-pide-jour-09-10", - "name": "10" - }, - { - "id": 443, - "slug": "piscine-c-piadi-jour-09-10", - "name": "10" - }, - { - "id": 397, - "slug": "piscine-ocaml-day-08", - "name": "Day 08" - }, - { - "id": 394, - "slug": "piscine-unity", - "name": "Piscine Unity" - }, - { - "id": 391, - "slug": "piscine-unity-day-08", - "name": "Day 08" - }, - { - "id": 193, - "slug": "piscine-c-day-09-09", - "name": "09" - }, - { - "id": 72, - "slug": "42-piscine-c-formation-piscine-cpp-day-07", - "name": "Day 07" - }, - { - "id": 57, - "slug": "42-piscine-c-formation-piscine-php-day-08", - "name": "Day 08" - }, - { - "id": 1810, - "slug": "piscine-java-rush-00", - "name": "Rush 00" - }, - { - "id": 1797, - "slug": "piscine-python-data-science-rush-00", - "name": "Rush 00" - }, - { - "id": 1777, - "slug": "out-with-the-old-owo-cpp-module-03-owo", - "name": "CPP Module 03 (OwO)" - }, - { - "id": 1706, - "slug": "42cursus-piscine-unity-rush-00", - "name": "Rush 00" - }, - { - "id": 1704, - "slug": "42cursus-piscine-swift-ios-rush-01", - "name": "Rush 01" - }, - { - "id": 1703, - "slug": "42cursus-piscine-php-symfony-rush-00", - "name": "Rush 00" - }, - { - "id": 1700, - "slug": "42cursus-piscine-ruby-on-rails-rush-00", - "name": "Rush 00" - }, - { - "id": 1698, - "slug": "42cursus-piscine-ocaml-rush-00", - "name": "Rush 00" - }, - { - "id": 1630, - "slug": "42cursus-piscine-python-django-rush-00", - "name": "Rush 00" - }, - { - "id": 1272, - "slug": "matrice-matrice-sante", - "name": "Matrice Santé" - }, - { - "id": 1160, - "slug": "piscine-php-symfony-rush01", - "name": "Rush01" - }, - { - "id": 1158, - "slug": "piscine-php-symfony-day-09", - "name": "Day 09" - }, - { - "id": 863, - "slug": "piscine-starfleet-day-06", - "name": "Day 06" - }, - { - "id": 844, - "slug": "hercules-apples-of-the-hesperides", - "name": "Apples of the Hesperides" - }, - { - "id": 803, - "slug": "piscine-ruby-on-rails-rush01", - "name": "Rush01" - }, - { - "id": 802, - "slug": "piscine-ruby-on-rails-day-09", - "name": "Day 09" - }, - { - "id": 755, - "slug": "piscine-swift-ios-rush01", - "name": "Rush01" - }, - { - "id": 754, - "slug": "piscine-swift-ios-day-09", - "name": "Day 09" - }, - { - "id": 740, - "slug": "piscine-python-django-day-09", - "name": "Day 09" - }, - { - "id": 644, - "slug": "day-09-10", - "name": "10" - }, - { - "id": 469, - "slug": "piscine-c-decloisonnee-pide-jour-09-11", - "name": "11" - }, - { - "id": 444, - "slug": "piscine-c-piadi-jour-09-11", - "name": "11" - }, - { - "id": 401, - "slug": "piscine-unity-day-09", - "name": "Day 09" - }, - { - "id": 400, - "slug": "piscine-ocaml-rush01", - "name": "Rush01" - }, - { - "id": 398, - "slug": "piscine-ocaml-day-09", - "name": "Day 09" - }, - { - "id": 393, - "slug": "piscine-unity-rush01", - "name": "Rush01" - }, - { - "id": 194, - "slug": "piscine-c-day-09-10", - "name": "10" - }, - { - "id": 114, - "slug": "rushes-cluedo-prolog", - "name": "Cluedo (Prolog)" - }, - { - "id": 73, - "slug": "42-piscine-c-formation-piscine-cpp-day-08", - "name": "Day 08" - }, - { - "id": 58, - "slug": "42-piscine-c-formation-piscine-php-day-09", - "name": "Day 09" - }, - { - "id": 1815, - "slug": "piscine-python-data-science-rush-01", - "name": "Rush 01" - }, - { - "id": 1811, - "slug": "piscine-java-rush-01", - "name": "Rush 01" - }, - { - "id": 1778, - "slug": "out-with-the-old-owo-cpp-module-04-owo", - "name": "CPP Module 04 (OwO)" - }, - { - "id": 1707, - "slug": "42cursus-piscine-unity-rush-01", - "name": "Rush 01" - }, - { - "id": 1705, - "slug": "42cursus-piscine-swift-ios-rush-00", - "name": "Rush 00" - }, - { - "id": 1702, - "slug": "42cursus-piscine-php-symfony-rush-01", - "name": "Rush 01" - }, - { - "id": 1701, - "slug": "42cursus-piscine-ruby-on-rails-rush-01", - "name": "Rush 01" - }, - { - "id": 1699, - "slug": "42cursus-piscine-ocaml-rush-01", - "name": "Rush 01" - }, - { - "id": 1631, - "slug": "42cursus-piscine-python-django-rush-01", - "name": "Rush 01" - }, - { - "id": 1618, - "slug": "piscine-ruby-on-rails-rush-00", - "name": "Rush 00" - }, - { - "id": 1607, - "slug": "piscine-swift-ios-rush-01", - "name": "Rush 01" - }, - { - "id": 1594, - "slug": "piscine-php-symfony-rush-00", - "name": "Rush 00" - }, - { - "id": 1582, - "slug": "piscine-ocaml-rush-00", - "name": "Rush 00" - }, - { - "id": 865, - "slug": "piscine-starfleet-day-07", - "name": "Day 07" - }, - { - "id": 845, - "slug": "hercules-capturing-cerberus", - "name": "Capturing Cerberus" - }, - { - "id": 741, - "slug": "piscine-python-django-rush01", - "name": "Rush01" - }, - { - "id": 662, - "slug": "rushes-yasl", - "name": "YASL" - }, - { - "id": 645, - "slug": "day-09-11", - "name": "11" - }, - { - "id": 470, - "slug": "piscine-c-decloisonnee-pide-jour-09-12", - "name": "12" - }, - { - "id": 445, - "slug": "piscine-c-piadi-jour-09-12", - "name": "12" - }, - { - "id": 195, - "slug": "piscine-c-day-09-11", - "name": "11" - }, - { - "id": 94, - "slug": "root-me-app-systeme", - "name": "Root-me | App-Systeme" - }, - { - "id": 60, - "slug": "piscine-php-rush01", - "name": "Rush01" - }, - { - "id": 1779, - "slug": "out-with-the-old-owo-cpp-module-05-owo", - "name": "CPP Module 05 (OwO)" - }, - { - "id": 1619, - "slug": "piscine-ruby-on-rails-rush-01", - "name": "Rush 01" - }, - { - "id": 1606, - "slug": "piscine-swift-ios-rush-00", - "name": "Rush 00" - }, - { - "id": 1595, - "slug": "piscine-php-symfony-rush-01", - "name": "Rush 01" - }, - { - "id": 1583, - "slug": "piscine-ocaml-rush-01", - "name": "Rush 01" - }, - { - "id": 867, - "slug": "piscine-starfleet-exam-05", - "name": "Exam 05" - }, - { - "id": 646, - "slug": "day-09-12", - "name": "12" - }, - { - "id": 602, - "slug": "rushes-rage-against-the-api", - "name": "Rage Against The aPi" - }, - { - "id": 471, - "slug": "piscine-c-decloisonnee-pide-jour-09-13", - "name": "13" - }, - { - "id": 446, - "slug": "piscine-c-piadi-jour-09-13", - "name": "13" - }, - { - "id": 403, - "slug": "corewar-championship", - "name": "Corewar Championship" - }, - { - "id": 196, - "slug": "piscine-c-day-09-12", - "name": "12" - }, - { - "id": 76, - "slug": "piscine-cpp-rush01", - "name": "Rush01" - }, - { - "id": 1780, - "slug": "out-with-the-old-owo-cpp-module-06-owo", - "name": "CPP Module 06 (OwO)" - }, - { - "id": 868, - "slug": "piscine-starfleet-rush-01", - "name": "Rush 01" - }, - { - "id": 647, - "slug": "day-09-13", - "name": "13" - }, - { - "id": 472, - "slug": "piscine-c-decloisonnee-pide-jour-09-14", - "name": "14" - }, - { - "id": 447, - "slug": "piscine-c-piadi-jour-09-14", - "name": "14" - }, - { - "id": 197, - "slug": "piscine-c-day-09-13", - "name": "13" - }, - { - "id": 28, - "slug": "rushes-alcu", - "name": "AlCu" - }, - { - "id": 1781, - "slug": "deprecated-out-with-the-old-owo-deprecated-cpp-module-07-owo", - "name": "[DEPRECATED] CPP Module 07 (OwO)" - }, - { - "id": 648, - "slug": "day-09-14", - "name": "14" - }, - { - "id": 599, - "slug": "rushes-mexican-standoff", - "name": "Mexican Standoff" - }, - { - "id": 473, - "slug": "piscine-c-decloisonnee-pide-jour-09-15", - "name": "15" - }, - { - "id": 448, - "slug": "piscine-c-piadi-jour-09-15", - "name": "15" - }, - { - "id": 198, - "slug": "piscine-c-day-09-14", - "name": "14" - }, - { - "id": 1767, - "slug": "deprecated-out-with-the-old-owo-deprecated-cpp-module-08-owo", - "name": "[DEPRECATED] CPP Module 08 (OwO)" - }, - { - "id": 1300, - "slug": "matrice-matrice-air-data", - "name": "Matrice Air Data" - }, - { - "id": 649, - "slug": "day-09-15", - "name": "15" - }, - { - "id": 474, - "slug": "piscine-c-decloisonnee-pide-jour-09-16", - "name": "16" - }, - { - "id": 449, - "slug": "piscine-c-piadi-jour-09-16", - "name": "16" - }, - { - "id": 405, - "slug": "piscine-c-exam01", - "name": "Exam01" - }, - { - "id": 199, - "slug": "piscine-c-day-09-15", - "name": "15" - }, - { - "id": 108, - "slug": "rushes-rush-network-and-system-administration-0", - "name": "Rush Network and System Administration #0" - }, - { - "id": 1634, - "slug": "matrice-matrice-solutions-urbaines", - "name": "Matrice Solutions Urbaines" - }, - { - "id": 650, - "slug": "day-09-16", - "name": "16" - }, - { - "id": 586, - "slug": "piscine-c-formation-exam-final", - "name": "Exam Final" - }, - { - "id": 475, - "slug": "piscine-c-decloisonnee-pide-jour-09-17", - "name": "17" - }, - { - "id": 450, - "slug": "piscine-c-piadi-jour-09-17", - "name": "17" - }, - { - "id": 200, - "slug": "piscine-c-day-09-16", - "name": "16" - }, - { - "id": 109, - "slug": "rushes-rush-network-and-system-administration-1", - "name": "Rush Network and System Administration #1" - }, - { - "id": 1716, - "slug": "matrice-matrice-concept-car", - "name": "Matrice Concept car" - }, - { - "id": 651, - "slug": "day-09-17", - "name": "17" - }, - { - "id": 476, - "slug": "piscine-c-decloisonnee-pide-jour-09-18", - "name": "18" - }, - { - "id": 451, - "slug": "piscine-c-piadi-jour-09-18", - "name": "18" - }, - { - "id": 201, - "slug": "piscine-c-day-09-17", - "name": "17" - }, - { - "id": 32, - "slug": "rushes-c-minitalk", - "name": "Minitalk" - }, - { - "id": 652, - "slug": "day-09-18", - "name": "18" - }, - { - "id": 477, - "slug": "piscine-c-decloisonnee-pide-jour-09-19", - "name": "19" - }, - { - "id": 452, - "slug": "piscine-c-piadi-jour-09-19", - "name": "19" - }, - { - "id": 407, - "slug": "piscine-c-exam-final", - "name": "Exam Final" - }, - { - "id": 202, - "slug": "piscine-c-day-09-18", - "name": "18" - }, - { - "id": 30, - "slug": "rushes-c-pipex", - "name": "Pipex" - }, - { - "id": 823, - "slug": "42-formation-pole-emploi-rushes-libunit", - "name": "libunit" - }, - { - "id": 653, - "slug": "day-09-19", - "name": "19" - }, - { - "id": 478, - "slug": "piscine-c-decloisonnee-pide-jour-09-20", - "name": "20" - }, - { - "id": 453, - "slug": "piscine-c-piadi-jour-09-20", - "name": "20" - }, - { - "id": 203, - "slug": "piscine-c-day-09-19", - "name": "19" - }, - { - "id": 887, - "slug": "rushes-ft_contrast", - "name": "ft_contrast" - }, - { - "id": 654, - "slug": "day-09-20", - "name": "20" - }, - { - "id": 479, - "slug": "piscine-c-decloisonnee-pide-jour-09-21", - "name": "21" - }, - { - "id": 454, - "slug": "piscine-c-piadi-jour-09-21", - "name": "21" - }, - { - "id": 204, - "slug": "piscine-c-day-09-20", - "name": "20" - }, - { - "id": 1025, - "slug": "rushes-frozen", - "name": "Frozen" - }, - { - "id": 655, - "slug": "day-09-21", - "name": "21" - }, - { - "id": 480, - "slug": "piscine-c-decloisonnee-pide-jour-09-22", - "name": "22" - }, - { - "id": 455, - "slug": "piscine-c-piadi-jour-09-22", - "name": "22" - }, - { - "id": 406, - "slug": "piscine-c-exam02", - "name": "Exam02" - }, - { - "id": 205, - "slug": "piscine-c-day-09-21", - "name": "21" - }, - { - "id": 657, - "slug": "day-09-22", - "name": "22" - }, - { - "id": 538, - "slug": "rushes-ft_minirogue", - "name": "ft_minirogue" - }, - { - "id": 481, - "slug": "piscine-c-decloisonnee-pide-jour-09-23", - "name": "23" - }, - { - "id": 456, - "slug": "piscine-c-piadi-jour-09-23", - "name": "23" - }, - { - "id": 206, - "slug": "piscine-c-day-09-22", - "name": "22" - }, - { - "id": 1095, - "slug": "rushes-numpy", - "name": "Numpy" - }, - { - "id": 658, - "slug": "day-09-23", - "name": "23" - }, - { - "id": 207, - "slug": "piscine-c-day-09-23", - "name": "23" - }, - { - "id": 1112, - "slug": "rushes-ft_tar", - "name": "ft_tar" - }, - { - "id": 404, - "slug": "piscine-c-exam00", - "name": "Exam00" - }, - { - "id": 1163, - "slug": "rushes-ft_pastebin", - "name": "ft_pastebin" - }, - { - "id": 1176, - "slug": "rushes-reverse-engineering", - "name": "Reverse Engineering" - }, - { - "id": 410, - "slug": "bomberman", - "name": "Bomberman" - }, - { - "id": 2110, - "slug": "rushes-wordle", - "name": "Wordle" - }, - { - "id": 411, - "slug": "electronics", - "name": "Electronics" - }, - { - "id": 2121, - "slug": "rushes-abstract-games", - "name": "Abstract Games" - }, - { - "id": 414, - "slug": "ft_linear_regression", - "name": "ft_linear_regression" - }, - { - "id": 2153, - "slug": "rushes-music-collection", - "name": "Music Collection" - }, - { - "id": 519, - "slug": "42partnerships-initiation-web", - "name": "Initiation Web" - }, - { - "id": 2176, - "slug": "rushes-sound-synthesis", - "name": "Sound Synthesis" - }, - { - "id": 506, - "slug": "42partnerships-initiation-ruby", - "name": "Initiation Ruby" - }, - { - "id": 2178, - "slug": "rushes-game-of-life", - "name": "Game of Life" - }, - { - "id": 522, - "slug": "krpsim", - "name": "KrpSim" - }, - { - "id": 523, - "slug": "21sh", - "name": "21sh" - }, - { - "id": 534, - "slug": "rubik", - "name": "Rubik" - }, - { - "id": 535, - "slug": "humangl", - "name": "HumanGL" - }, - { - "id": 536, - "slug": "swifty-companion", - "name": "Swifty Companion" - }, - { - "id": 537, - "slug": "camagru", - "name": "Camagru" - }, - { - "id": 539, - "slug": "ft_ping", - "name": "ft_ping" - }, - { - "id": 540, - "slug": "fillit", - "name": "Fillit" - }, - { - "id": 541, - "slug": "piscine-c-formation-jour-00", - "name": "Jour 00" - }, - { - "id": 542, - "slug": "piscine-c-formation-jour-01", - "name": "Jour 01" - }, - { - "id": 548, - "slug": "ft_traceroute", - "name": "ft_traceroute" - }, - { - "id": 593, - "slug": "ft_nmap", - "name": "ft_nmap" - }, - { - "id": 594, - "slug": "piscine-c-a-distance-libft-old", - "name": "Libft-old" - }, - { - "id": 595, - "slug": "piscine-c-a-distance-fillit", - "name": "Fillit" - }, - { - "id": 596, - "slug": "matcha", - "name": "Matcha" - }, - { - "id": 597, - "slug": "hypertube", - "name": "Hypertube" - }, - { - "id": 601, - "slug": "ft_turing", - "name": "ft_turing" - }, - { - "id": 603, - "slug": "snow-crash", - "name": "Snow Crash" - }, - { - "id": 604, - "slug": "darkly", - "name": "Darkly" - }, - { - "id": 606, - "slug": "bootcamp-day-01", - "name": "Day 01" - }, - { - "id": 608, - "slug": "bootcamp-day-02", - "name": "Day 02" - }, - { - "id": 611, - "slug": "bootcamp-sastantua", - "name": "Sastantua" - }, - { - "id": 612, - "slug": "piscine-c-a-distance-c-exam-training", - "name": "C Exam Training" - }, - { - "id": 616, - "slug": "bootcamp-day-04", - "name": "Day 04" - }, - { - "id": 617, - "slug": "bootcamp-day-05", - "name": "Day 05" - }, - { - "id": 618, - "slug": "bootcamp-day-06", - "name": "Day 06" - }, - { - "id": 620, - "slug": "bootcamp-day-08", - "name": "Day 08" - }, - { - "id": 621, - "slug": "bootcamp-day-07", - "name": "Day 07" - }, - { - "id": 622, - "slug": "bootcamp-match-n-match", - "name": "Match-N-Match" - }, - { - "id": 623, - "slug": "bootcamp-colle-00", - "name": "Colle 00" - }, - { - "id": 624, - "slug": "bootcamp-colle-01", - "name": "Colle 01" - }, - { - "id": 625, - "slug": "bootcamp-day-11", - "name": "Day 11" - }, - { - "id": 626, - "slug": "bootcamp-day-10", - "name": "Day 10" - }, - { - "id": 627, - "slug": "bootcamp-day-12", - "name": "Day 12" - }, - { - "id": 628, - "slug": "bootcamp-day-13", - "name": "Day 13" - }, - { - "id": 629, - "slug": "bootcamp-colle-02", - "name": "Colle 02" - }, - { - "id": 630, - "slug": "bootcamp-evalexpr", - "name": "EvalExpr" - }, - { - "id": 631, - "slug": "bootcamp-bsq", - "name": "BSQ" - }, - { - "id": 633, - "slug": "bootcamp-day-09", - "name": "Day 09" - }, - { - "id": 661, - "slug": "swifty-proteins", - "name": "Swifty Proteins" - }, - { - "id": 665, - "slug": "ft_ality", - "name": "ft_ality" - }, - { - "id": 668, - "slug": "piscine-c-formation-exam06", - "name": "Exam06" - }, - { - "id": 680, - "slug": "bootcamp-wtc-exam-01", - "name": "Bootcamp-WTC-Exam-01" - }, - { - "id": 669, - "slug": "bootcamp-wtc-exam-00", - "name": "Bootcamp-WTC-Exam-00" - }, - { - "id": 670, - "slug": "bootcamp-day-00", - "name": "Day 00" - }, - { - "id": 672, - "slug": "bootcamp-day-03", - "name": "Day 03" - }, - { - "id": 675, - "slug": "formation-pole-emploi-libft-old", - "name": "Libft-old" - }, - { - "id": 677, - "slug": "xv", - "name": "XV" - }, - { - "id": 678, - "slug": "in-the-shadows", - "name": "In the Shadows" - }, - { - "id": 679, - "slug": "particle-system", - "name": "Particle System" - }, - { - "id": 681, - "slug": "bootcamp-wtc-final-exam", - "name": "Bootcamp-WTC-Final-Exam" - }, - { - "id": 683, - "slug": "friends-with-benefits", - "name": "Friends with Benefits" - }, - { - "id": 687, - "slug": "init", - "name": "init" - }, - { - "id": 688, - "slug": "roger-skyline-2", - "name": "roger-skyline-2" - }, - { - "id": 694, - "slug": "cloud-1", - "name": "cloud-1" - }, - { - "id": 695, - "slug": "ft_linux", - "name": "ft_linux" - }, - { - "id": 696, - "slug": "little-penguin-1", - "name": "little-penguin-1" - }, - { - "id": 698, - "slug": "bootcamp-wtc-exam-02", - "name": "Bootcamp-WTC-Exam-02" - }, - { - "id": 699, - "slug": "rainfall", - "name": "RainFall" - }, - { - "id": 700, - "slug": "dr-quine", - "name": "Dr Quine" - }, - { - "id": 701, - "slug": "woody-woodpacker", - "name": "Woody Woodpacker" - }, - { - "id": 702, - "slug": "matt-daemon", - "name": "Matt Daemon" - }, - { - "id": 709, - "slug": "process-and-memory", - "name": "Process and Memory" - }, - { - "id": 710, - "slug": "drivers-and-interrupts", - "name": "Drivers and Interrupts" - }, - { - "id": 711, - "slug": "filesystem", - "name": "Filesystem" - }, - { - "id": 714, - "slug": "kfs-2", - "name": "KFS-2" - }, - { - "id": 716, - "slug": "kfs-1", - "name": "KFS-1" - }, - { - "id": 717, - "slug": "kfs-3", - "name": "KFS-3" - }, - { - "id": 719, - "slug": "music-room", - "name": "Music Room" - }, - { - "id": 727, - "slug": "piscine-python-django", - "name": "Piscine Python Django" - }, - { - "id": 742, - "slug": "piscine-swift-ios", - "name": "Piscine Swift iOS" - }, - { - "id": 756, - "slug": "piscine-reloaded", - "name": "Piscine Reloaded" - }, - { - "id": 791, - "slug": "piscine-ruby-on-rails", - "name": "Piscine Ruby on Rails" - }, - { - "id": 817, - "slug": "42-formation-pole-emploi-42-commandements", - "name": "42 Commandements" - }, - { - "id": 818, - "slug": "red-tetris", - "name": "Red Tetris" - }, - { - "id": 819, - "slug": "h42n42", - "name": "H42N42" - }, - { - "id": 820, - "slug": "famine", - "name": "Famine" - }, - { - "id": 824, - "slug": "kfs-4", - "name": "KFS-4" - }, - { - "id": 825, - "slug": "kfs-5", - "name": "KFS-5" - }, - { - "id": 830, - "slug": "matrice", - "name": "Matrice" - }, - { - "id": 833, - "slug": "hercules", - "name": "Hercules" - }, - { - "id": 846, - "slug": "computorv2", - "name": "ComputorV2" - }, - { - "id": 847, - "slug": "docker-1", - "name": "docker-1" - }, - { - "id": 849, - "slug": "piscine-interview", - "name": "Piscine Interview" - }, - { - "id": 870, - "slug": "avaj-launcher", - "name": "avaj-launcher" - }, - { - "id": 871, - "slug": "swingy", - "name": "swingy" - }, - { - "id": 872, - "slug": "fix-me", - "name": "fix-me" - }, - { - "id": 873, - "slug": "kfs-6", - "name": "KFS-6" - }, - { - "id": 874, - "slug": "kfs-7", - "name": "KFS-7" - }, - { - "id": 876, - "slug": "kfs-8", - "name": "KFS-8" - }, - { - "id": 877, - "slug": "kfs-9", - "name": "KFS-9" - }, - { - "id": 882, - "slug": "kfs-x", - "name": "KFS-X" - }, - { - "id": 888, - "slug": "ft_db", - "name": "ft_db" - }, - { - "id": 891, - "slug": "crea-piscine-after-effects-day-00", - "name": "Piscine After Effects Day 00" - }, - { - "id": 892, - "slug": "crea-piscine-after-effects-day-01", - "name": "Piscine After Effects Day 01" - }, - { - "id": 893, - "slug": "crea-piscine-after-effects-day-02", - "name": "Piscine After Effects Day 02" - }, - { - "id": 895, - "slug": "crea-piscine-after-effects-day-03", - "name": "Piscine After Effects Day 03" - }, - { - "id": 897, - "slug": "crea-piscine-after-effects-day-04", - "name": "Piscine After Effects Day 04" - }, - { - "id": 901, - "slug": "crea-piscine-after-effects-rush-00", - "name": "Piscine After Effects Rush 00" - }, - { - "id": 902, - "slug": "curriculum-vitae", - "name": "Curriculum Vitae" - }, - { - "id": 903, - "slug": "technical-interview-intra-api-interview", - "name": "Intra API Interview" - }, - { - "id": 904, - "slug": "technical-interview-sys-admin-technical-tests", - "name": "Sys admin Technical Tests" - }, - { - "id": 905, - "slug": "pestilence", - "name": "Pestilence" - }, - { - "id": 907, - "slug": "war", - "name": "War" - }, - { - "id": 908, - "slug": "death", - "name": "Death" - }, - { - "id": 909, - "slug": "boot2root", - "name": "Boot2Root" - }, - { - "id": 910, - "slug": "durex", - "name": "Durex" - }, - { - "id": 912, - "slug": "override", - "name": "Override" - }, - { - "id": 914, - "slug": "kift", - "name": "KIFT" - }, - { - "id": 919, - "slug": "x-mansion-x-mansion-namido-d00", - "name": "D00" - }, - { - "id": 920, - "slug": "x-mansion-x-mansion-namido-d01", - "name": "D01" - }, - { - "id": 921, - "slug": "x-mansion-x-mansion-namido-d02", - "name": "D02" - }, - { - "id": 922, - "slug": "x-mansion-x-mansion-namido-d03", - "name": "D03" - }, - { - "id": 923, - "slug": "x-mansion-x-mansion-namido-d04-advanced", - "name": "D04 Advanced" - }, - { - "id": 924, - "slug": "x-mansion-x-mansion-namido-d05", - "name": "D05" - }, - { - "id": 925, - "slug": "x-mansion-x-mansion-namido-d06", - "name": "D06" - }, - { - "id": 926, - "slug": "x-mansion-x-mansion-namido-d07", - "name": "D07" - }, - { - "id": 927, - "slug": "x-mansion-x-mansion-namido-d08", - "name": "D08" - }, - { - "id": 933, - "slug": "x-mansion-x-mansion-namido-d04-basics", - "name": "D04 Basics" - }, - { - "id": 942, - "slug": "42-piscine-c-harassment_policy", - "name": "harassment_policy" - }, - { - "id": 945, - "slug": "reverse-game-of-life", - "name": "Reverse Game of Life" - }, - { - "id": 948, - "slug": "greenlight", - "name": "greenlight" - }, - { - "id": 953, - "slug": "check-your-dorms", - "name": "Check Your Dorms" - }, - { - "id": 960, - "slug": "stairway_to_42", - "name": "Stairway_to_42" - }, - { - "id": 961, - "slug": "wethinkcode_-first-internship", - "name": "First-Internship" - }, - { - "id": 970, - "slug": "wethinkcode_-social-tech-lab", - "name": "Social-Tech-Lab" - }, - { - "id": 977, - "slug": "ft_zenko", - "name": "ft_zenko" - }, - { - "id": 978, - "slug": "ft_vox", - "name": "ft_vox" - }, - { - "id": 980, - "slug": "walking-marvin", - "name": "Walking Marvin" - }, - { - "id": 983, - "slug": "ft_ssl_rsa", - "name": "ft_ssl_rsa" - }, - { - "id": 985, - "slug": "ft_ssl_des", - "name": "ft_ssl_des" - }, - { - "id": 1009, - "slug": "start-here-hello-42", - "name": "START HERE - Hello 42!" - }, - { - "id": 1012, - "slug": "c-exam-alone-in-the-dark-intermediate", - "name": "C Exam Alone In The Dark - Intermediate" - }, - { - "id": 1023, - "slug": "ft_debut", - "name": "ft_debut" - }, - { - "id": 1037, - "slug": "bistromatic", - "name": "Bistromatic" - }, - { - "id": 1049, - "slug": "unit-factory-harassment-tolerance-policy", - "name": "UNIT Factory Harassment & Tolerance Policy" - }, - { - "id": 1052, - "slug": "simplyelectronic", - "name": "SimplyElectronic" - }, - { - "id": 1055, - "slug": "startup-internship", - "name": "Startup-Internship" - }, - { - "id": 1057, - "slug": "blackhole-peer-help", - "name": "Blackhole - Peer Help" - }, - { - "id": 1058, - "slug": "grimly", - "name": "Grimly" - }, - { - "id": 1059, - "slug": "shell-0", - "name": "Shell 0" - }, - { - "id": 1060, - "slug": "shell-1", - "name": "Shell 1" - }, - { - "id": 1061, - "slug": "shell-2", - "name": "Shell 2" - }, - { - "id": 1062, - "slug": "shell-3", - "name": "Shell 3" - }, - { - "id": 1063, - "slug": "ruby-00", - "name": "Ruby 00" - }, - { - "id": 1064, - "slug": "ruby-01", - "name": "Ruby 01" - }, - { - "id": 1065, - "slug": "ruby-02", - "name": "Ruby 02" - }, - { - "id": 1066, - "slug": "ruby-03", - "name": "Ruby 03" - }, - { - "id": 1067, - "slug": "ruby-04", - "name": "Ruby 04" - }, - { - "id": 1068, - "slug": "ruby-05", - "name": "Ruby 05" - }, - { - "id": 1070, - "slug": "ruby-06", - "name": "Ruby 06" - }, - { - "id": 1071, - "slug": "ruby-07", - "name": "Ruby 07" - }, - { - "id": 1072, - "slug": "projet-ruby", - "name": "Projet Ruby" - }, - { - "id": 1073, - "slug": "web-00", - "name": "Web 00" - }, - { - "id": 1074, - "slug": "web-01", - "name": "Web 01" - }, - { - "id": 1075, - "slug": "web-02", - "name": "Web 02" - }, - { - "id": 1076, - "slug": "web-03", - "name": "Web 03" - }, - { - "id": 1077, - "slug": "web-04", - "name": "Web 04" - }, - { - "id": 1078, - "slug": "web-05", - "name": "Web 05" - }, - { - "id": 1079, - "slug": "projet-web", - "name": "Projet Web" - }, - { - "id": 1080, - "slug": "h2s-project-authorship-t2", - "name": "H2S Project Authorship - T2" - }, - { - "id": 1081, - "slug": "dslr", - "name": "DSLR" - }, - { - "id": 1084, - "slug": "ccmn", - "name": "CCMN" - }, - { - "id": 1087, - "slug": "shaderpixel", - "name": "ShaderPixel" - }, - { - "id": 1107, - "slug": "algorithmic-puzzles", - "name": "Algorithmic Puzzles" - }, - { - "id": 1109, - "slug": "hack-your-own-adventure", - "name": "Hack Your Own Adventure" - }, - { - "id": 1111, - "slug": "h2s-project-authorship-t1", - "name": "H2S Project Authorship - T1" - }, - { - "id": 1113, - "slug": "h2s-mentorship-project-auditing", - "name": "H2S Mentorship - Project Auditing" - }, - { - "id": 1117, - "slug": "guimp", - "name": "GUImp" - }, - { - "id": 1118, - "slug": "hackerrank-university-codesprint-4", - "name": "HackerRank University CodeSprint 4" - }, - { - "id": 1119, - "slug": "userspace_digressions", - "name": "userspace_digressions" - }, - { - "id": 1124, - "slug": "netflix-hackathon", - "name": "Netflix Hackathon" - }, - { - "id": 1125, - "slug": "piscine-photoshop-day-00", - "name": "Piscine Photoshop Day 00" - }, - { - "id": 1126, - "slug": "piscine-photoshop-day-01", - "name": "Piscine Photoshop Day 01" - }, - { - "id": 1127, - "slug": "piscine-photoshop-day-02", - "name": "Piscine Photoshop Day 02" - }, - { - "id": 1128, - "slug": "piscine-photoshop-day-03", - "name": "Piscine Photoshop Day 03" - }, - { - "id": 1129, - "slug": "piscine-photoshop-day-04", - "name": "Piscine Photoshop Day 04" - }, - { - "id": 1130, - "slug": "piscine-photoshop-rush-00", - "name": "Piscine Photoshop Rush 00" - }, - { - "id": 1138, - "slug": "atlantis-day-00", - "name": "Atlantis - Day 00" - }, - { - "id": 1139, - "slug": "atlantis-day-01", - "name": "Atlantis - Day 01" - }, - { - "id": 1140, - "slug": "atlantis-day-02", - "name": "Atlantis - Day 02" - }, - { - "id": 1141, - "slug": "python", - "name": "Python" - }, - { - "id": 1147, - "slug": "piscine-php-symfony", - "name": "Piscine PHP Symfony" - }, - { - "id": 1161, - "slug": "atlantis-chatterbot", - "name": "Atlantis - Chatterbot" - }, - { - "id": 1162, - "slug": "multilayer-perceptron", - "name": "Multilayer Perceptron" - }, - { - "id": 1165, - "slug": "ft_sommelier", - "name": "ft_sommelier" - }, - { - "id": 1166, - "slug": "np1", - "name": "NP1" - }, - { - "id": 1175, - "slug": "javascript", - "name": "Javascript" - }, - { - "id": 1182, - "slug": "doom-nukem", - "name": "Doom Nukem" - }, - { - "id": 1183, - "slug": "hackathon-born2hack", - "name": "Hackathon Born2Hack" - }, - { - "id": 1184, - "slug": "teen-idol", - "name": "Teen Idol" - }, - { - "id": 1185, - "slug": "yellow-brick-road", - "name": "Yellow Brick Road" - }, - { - "id": 1189, - "slug": "h2s-project-editor-t1", - "name": "H2S Project Editor T1" - }, - { - "id": 1190, - "slug": "roger-skyline-1", - "name": "roger-skyline-1" - }, - { - "id": 1191, - "slug": "data-mining", - "name": "Data Mining" - }, - { - "id": 1198, - "slug": "dapp-init", - "name": "Dapp-init" - }, - { - "id": 1199, - "slug": "uf_bird", - "name": "uf_bird" - }, - { - "id": 1200, - "slug": "p5js", - "name": "p5JS" - }, - { - "id": 1203, - "slug": "total-perspective-vortex", - "name": "Total-perspective-vortex" - }, - { - "id": 1204, - "slug": "b_libft", - "name": "b_libft" - }, - { - "id": 1205, - "slug": "b_printf", - "name": "b_printf" - }, - { - "id": 1206, - "slug": "b_ls", - "name": "b_ls" - }, - { - "id": 1207, - "slug": "cs-joy", - "name": "CS-Joy" - }, - { - "id": 1208, - "slug": "java", - "name": "Java" - }, - { - "id": 1209, - "slug": "piscine-python-django-day00", - "name": "Piscine Python Django Day00" - }, - { - "id": 1210, - "slug": "piscine-python-django-day01", - "name": "Piscine Python Django Day01" - }, - { - "id": 1211, - "slug": "piscine-python-django-day02", - "name": "Piscine Python Django Day02" - }, - { - "id": 1212, - "slug": "piscine-python-django-day03", - "name": "Piscine Python Django Day03" - }, - { - "id": 1213, - "slug": "piscine-python-django-day04", - "name": "Piscine Python Django Day04" - }, - { - "id": 1214, - "slug": "piscine-python-django-rush-00", - "name": "piscine-python-django-rush-00" - }, - { - "id": 1215, - "slug": "piscine-python-django-day05", - "name": "Piscine Python Django Day05" - }, - { - "id": 1216, - "slug": "piscine-python-django-day06", - "name": "Piscine Python Django Day06" - }, - { - "id": 1217, - "slug": "piscine-python-django-day07", - "name": "Piscine Python Django Day07" - }, - { - "id": 1218, - "slug": "piscine-python-django-day08", - "name": "Piscine Python Django Day08" - }, - { - "id": 1219, - "slug": "day-09", - "name": "Day 09" - }, - { - "id": 1220, - "slug": "piscine-python-django-rush-01", - "name": "Piscine Python Django Rush 01" - }, - { - "id": 1227, - "slug": "blackhole-peer-helper", - "name": "Blackhole - Peer Helper" - }, - { - "id": 1228, - "slug": "plagiart", - "name": "Plagiart" - }, - { - "id": 1230, - "slug": "wildcard", - "name": "Wildcard" - }, - { - "id": 1237, - "slug": "hackhighschool-mentorship-program", - "name": "HackHighSchool Mentorship Program" - }, - { - "id": 1238, - "slug": "linkedin", - "name": "LinkedIn" - }, - { - "id": 1239, - "slug": "go-programming", - "name": "Go Programming" - }, - { - "id": 1241, - "slug": "piscine-illustrator-day-00", - "name": "Piscine Illustrator Day 00" - }, - { - "id": 1242, - "slug": "piscine-illustrator-day-01", - "name": "Piscine Illustrator Day 01" - }, - { - "id": 1243, - "slug": "piscine-illustrator-day-02", - "name": "Piscine Illustrator Day 02" - }, - { - "id": 1244, - "slug": "piscine-illustrator-day-04", - "name": "Piscine Illustrator Day 04" - }, - { - "id": 1245, - "slug": "piscine-illustrator-day-03", - "name": "Piscine Illustrator Day 03" - }, - { - "id": 1246, - "slug": "piscine-illustrator-rush00", - "name": "Piscine Illustrator Rush00" - }, - { - "id": 1255, - "slug": "c-piscine-shell-00", - "name": "C Piscine Shell 00" - }, - { - "id": 1256, - "slug": "c-piscine-shell-01", - "name": "C Piscine Shell 01" - }, - { - "id": 1257, - "slug": "c-piscine-c-00", - "name": "C Piscine C 00" - }, - { - "id": 1258, - "slug": "c-piscine-c-01", - "name": "C Piscine C 01" - }, - { - "id": 1259, - "slug": "c-piscine-c-02", - "name": "C Piscine C 02" - }, - { - "id": 1260, - "slug": "c-piscine-c-03", - "name": "C Piscine C 03" - }, - { - "id": 1261, - "slug": "c-piscine-c-04", - "name": "C Piscine C 04" - }, - { - "id": 1262, - "slug": "c-piscine-c-05", - "name": "C Piscine C 05" - }, - { - "id": 1263, - "slug": "c-piscine-c-06", - "name": "C Piscine C 06" - }, - { - "id": 1264, - "slug": "c-piscine-c-08", - "name": "C Piscine C 08" - }, - { - "id": 1265, - "slug": "c-piscine-c-09", - "name": "C Piscine C 09" - }, - { - "id": 1266, - "slug": "c-piscine-c-10", - "name": "C Piscine C 10" - }, - { - "id": 1267, - "slug": "c-piscine-c-11", - "name": "C Piscine C 11" - }, - { - "id": 1268, - "slug": "c-piscine-c-12", - "name": "C Piscine C 12" - }, - { - "id": 1270, - "slug": "c-piscine-c-07", - "name": "C Piscine C 07" - }, - { - "id": 1271, - "slug": "c-piscine-c-13", - "name": "C Piscine C 13" - }, - { - "id": 1283, - "slug": "machine-learning", - "name": "Machine Learning" - }, - { - "id": 1285, - "slug": "h2s-project-editor-t2", - "name": "H2S Project Editor T2" - }, - { - "id": 1291, - "slug": "pygame", - "name": "Pygame" - }, - { - "id": 1295, - "slug": "node-js", - "name": "Node.js" - }, - { - "id": 1299, - "slug": "42gui", - "name": "42GUI" - }, - { - "id": 1301, - "slug": "c-piscine-exam-00", - "name": "C Piscine Exam 00" - }, - { - "id": 1302, - "slug": "c-piscine-exam-01", - "name": "C Piscine Exam 01" - }, - { - "id": 1303, - "slug": "c-piscine-exam-02", - "name": "C Piscine Exam 02" - }, - { - "id": 1304, - "slug": "c-piscine-final-exam", - "name": "C Piscine Final Exam" - }, - { - "id": 1305, - "slug": "c-piscine-bsq", - "name": "C Piscine BSQ" - }, - { - "id": 1306, - "slug": "genesis-b", - "name": "Genesis B" - }, - { - "id": 1308, - "slug": "c-piscine-rush-00", - "name": "C Piscine Rush 00" - }, - { - "id": 1309, - "slug": "c-piscine-rush-02", - "name": "C Piscine Rush 02" - }, - { - "id": 1310, - "slug": "c-piscine-rush-01", - "name": "C Piscine Rush 01" - }, - { - "id": 1314, - "slug": "42cursus-libft", - "name": "Libft" - }, - { - "id": 1315, - "slug": "minirt", - "name": "miniRT" - }, - { - "id": 1316, - "slug": "42cursus-ft_printf", - "name": "ft_printf" - }, - { - "id": 1320, - "slug": "exam-rank-02", - "name": "Exam Rank 02" - }, - { - "id": 1321, - "slug": "exam-rank-03", - "name": "Exam Rank 03" - }, - { - "id": 1322, - "slug": "exam-rank-04", - "name": "Exam Rank 04" - }, - { - "id": 1323, - "slug": "exam-rank-05", - "name": "Exam Rank 05" - }, - { - "id": 1324, - "slug": "exam-rank-06", - "name": "Exam Rank 06" - }, - { - "id": 1326, - "slug": "cub3d", - "name": "cub3d" - }, - { - "id": 1327, - "slug": "42cursus-get_next_line", - "name": "get_next_line" - }, - { - "id": 1328, - "slug": "ft_server", - "name": "ft_server" - }, - { - "id": 1329, - "slug": "ft_services", - "name": "ft_services" - }, - { - "id": 1330, - "slug": "libasm", - "name": "libasm" - }, - { - "id": 1331, - "slug": "42cursus-minishell", - "name": "minishell" - }, - { - "id": 1332, - "slug": "webserv", - "name": "webserv" - }, - { - "id": 1334, - "slug": "42cursus-philosophers", - "name": "Philosophers" - }, - { - "id": 1335, - "slug": "ft_containers", - "name": "ft_containers" - }, - { - "id": 1336, - "slug": "ft_irc", - "name": "ft_irc" - }, - { - "id": 1337, - "slug": "ft_transcendence", - "name": "ft_transcendence" - }, - { - "id": 1347, - "slug": "d00-html", - "name": "D00 - HTML" - }, - { - "id": 1348, - "slug": "d01-css", - "name": "D01 - CSS" - }, - { - "id": 1349, - "slug": "d03-javascript", - "name": "D03 - Javascript" - }, - { - "id": 1350, - "slug": "d04-advanced-javascript", - "name": "D04 - Advanced Javascript" - }, - { - "id": 1351, - "slug": "d02-css-js", - "name": "D02 - CSS/JS" - }, - { - "id": 1352, - "slug": "rush", - "name": "RUSH" - }, - { - "id": 1353, - "slug": "data-structures", - "name": "Data Structures" - }, - { - "id": 1361, - "slug": "apcsp-prep", - "name": "APCSP Prep" - }, - { - "id": 1372, - "slug": "python-101-d00", - "name": "Python-101 D00" - }, - { - "id": 1373, - "slug": "python-101-d01", - "name": "Python-101 D01" - }, - { - "id": 1374, - "slug": "python-101-d02", - "name": "Python-101 D02" - }, - { - "id": 1376, - "slug": "python-101-d03", - "name": "Python-101 D03" - }, - { - "id": 1377, - "slug": "python-101-d04", - "name": "Python-101 D04" - }, - { - "id": 1378, - "slug": "python-101-rush", - "name": "Python-101 Rush" - }, - { - "id": 1379, - "slug": "42cursus-ft_hangouts", - "name": "ft_hangouts" - }, - { - "id": 1381, - "slug": "42cursus-taskmaster", - "name": "taskmaster" - }, - { - "id": 1382, - "slug": "42cursus-computorv1", - "name": "computorv1" - }, - { - "id": 1383, - "slug": "42cursus-gomoku", - "name": "gomoku" - }, - { - "id": 1384, - "slug": "42cursus-expert-system", - "name": "expert-system" - }, - { - "id": 1385, - "slug": "42cursus-n-puzzle", - "name": "n-puzzle" - }, - { - "id": 1386, - "slug": "42cursus-nibbler", - "name": "nibbler" - }, - { - "id": 1387, - "slug": "42cursus-42run", - "name": "42run" - }, - { - "id": 1388, - "slug": "42cursus-strace", - "name": "strace" - }, - { - "id": 1389, - "slug": "42cursus-bomberman", - "name": "bomberman" - }, - { - "id": 1390, - "slug": "42cursus-scop", - "name": "scop" - }, - { - "id": 1391, - "slug": "42cursus-ft_linear_regression", - "name": "ft_linear_regression" - }, - { - "id": 1392, - "slug": "42cursus-krpsim", - "name": "krpsim" - }, - { - "id": 1393, - "slug": "42cursus-rubik", - "name": "rubik" - }, - { - "id": 1394, - "slug": "42cursus-humangl", - "name": "humangl" - }, - { - "id": 1395, - "slug": "42cursus-swifty-companion", - "name": "swifty-companion" - }, - { - "id": 1396, - "slug": "42cursus-camagru", - "name": "camagru" - }, - { - "id": 1397, - "slug": "42cursus-ft_ping", - "name": "ft_ping" - }, - { - "id": 1399, - "slug": "42cursus-ft_traceroute", - "name": "ft_traceroute" - }, - { - "id": 1400, - "slug": "42cursus-ft_nmap", - "name": "ft_nmap" - }, - { - "id": 1401, - "slug": "42cursus-matcha", - "name": "matcha" - }, - { - "id": 1402, - "slug": "42cursus-hypertube", - "name": "hypertube" - }, - { - "id": 1403, - "slug": "42cursus-ft_turing", - "name": "ft_turing" - }, - { - "id": 1404, - "slug": "42cursus-snow-crash", - "name": "snow-crash" - }, - { - "id": 1405, - "slug": "42cursus-darkly", - "name": "darkly" - }, - { - "id": 1406, - "slug": "42cursus-swifty-proteins", - "name": "swifty-proteins" - }, - { - "id": 1407, - "slug": "42cursus-ft_ality", - "name": "ft_ality" - }, - { - "id": 1408, - "slug": "42cursus-xv", - "name": "xv" - }, - { - "id": 1409, - "slug": "42cursus-in-the-shadows", - "name": "in-the-shadows" - }, - { - "id": 1410, - "slug": "42cursus-particle-system", - "name": "particle-system" - }, - { - "id": 1411, - "slug": "42cursus-gbmu", - "name": "gbmu" - }, - { - "id": 1414, - "slug": "42cursus-cloud-1", - "name": "cloud-1" - }, - { - "id": 1415, - "slug": "42cursus-ft_linux", - "name": "ft_linux" - }, - { - "id": 1416, - "slug": "42cursus-little-penguin-1", - "name": "little-penguin-1" - }, - { - "id": 1417, - "slug": "42cursus-rainfall", - "name": "rainfall" - }, - { - "id": 1418, - "slug": "42cursus-dr-quine", - "name": "dr-quine" - }, - { - "id": 1419, - "slug": "42cursus-woody-woodpacker", - "name": "woody-woodpacker" - }, - { - "id": 1420, - "slug": "42cursus-matt-daemon", - "name": "matt-daemon" - }, - { - "id": 1421, - "slug": "42cursus-process-and-memory", - "name": "process-and-memory" - }, - { - "id": 1422, - "slug": "42cursus-drivers-and-interrupts", - "name": "drivers-and-interrupts" - }, - { - "id": 1423, - "slug": "42cursus-filesystem", - "name": "filesystem" - }, - { - "id": 1424, - "slug": "42cursus-kfs-2", - "name": "kfs-2" - }, - { - "id": 1425, - "slug": "42cursus-kfs-1", - "name": "kfs-1" - }, - { - "id": 1426, - "slug": "42cursus-kfs-3", - "name": "kfs-3" - }, - { - "id": 1427, - "slug": "42cursus-music-room", - "name": "music-room" - }, - { - "id": 1428, - "slug": "42cursus-red-tetris", - "name": "red-tetris" - }, - { - "id": 1429, - "slug": "42cursus-h42n42", - "name": "h42n42" - }, - { - "id": 1430, - "slug": "42cursus-famine", - "name": "famine" - }, - { - "id": 1431, - "slug": "42cursus-kfs-4", - "name": "kfs-4" - }, - { - "id": 1432, - "slug": "42cursus-kfs-5", - "name": "kfs-5" - }, - { - "id": 1433, - "slug": "42cursus-computorv2", - "name": "computorv2" - }, - { - "id": 1435, - "slug": "42cursus-avaj-launcher", - "name": "avaj-launcher" - }, - { - "id": 1436, - "slug": "42cursus-swingy", - "name": "swingy" - }, - { - "id": 1437, - "slug": "42cursus-fix-me", - "name": "fix-me" - }, - { - "id": 1438, - "slug": "42cursus-kfs-6", - "name": "kfs-6" - }, - { - "id": 1439, - "slug": "42cursus-kfs-7", - "name": "kfs-7" - }, - { - "id": 1440, - "slug": "42cursus-kfs-8", - "name": "kfs-8" - }, - { - "id": 1441, - "slug": "42cursus-kfs-9", - "name": "kfs-9" - }, - { - "id": 1442, - "slug": "42cursus-kfs-x", - "name": "kfs-x" - }, - { - "id": 1443, - "slug": "42cursus-pestilence", - "name": "pestilence" - }, - { - "id": 1444, - "slug": "42cursus-war", - "name": "war" - }, - { - "id": 1445, - "slug": "42cursus-death", - "name": "death" - }, - { - "id": 1446, - "slug": "42cursus-boot2root", - "name": "boot2root" - }, - { - "id": 1447, - "slug": "42cursus-ft_shield", - "name": "ft_shield" - }, - { - "id": 1448, - "slug": "42cursus-override", - "name": "override" - }, - { - "id": 1449, - "slug": "42cursus-ft_vox", - "name": "ft_vox" - }, - { - "id": 1450, - "slug": "42cursus-ft_ssl_rsa", - "name": "ft_ssl_rsa" - }, - { - "id": 1451, - "slug": "42cursus-ft_ssl_md5", - "name": "ft_ssl_md5" - }, - { - "id": 1452, - "slug": "42cursus-ft_ssl_des", - "name": "ft_ssl_des" - }, - { - "id": 1453, - "slug": "42cursus-dslr", - "name": "dslr" - }, - { - "id": 1454, - "slug": "42cursus-shaderpixel", - "name": "shaderpixel" - }, - { - "id": 1455, - "slug": "42cursus-guimp", - "name": "guimp" - }, - { - "id": 1456, - "slug": "42cursus-userspace_digressions", - "name": "userspace_digressions" - }, - { - "id": 1457, - "slug": "42cursus-multilayer-perceptron", - "name": "multilayer-perceptron" - }, - { - "id": 1458, - "slug": "42cursus-doom-nukem", - "name": "doom-nukem" - }, - { - "id": 1460, - "slug": "42cursus-total-perspective-vortex", - "name": "total-perspective-vortex" - }, - { - "id": 1461, - "slug": "42cursus-abstract-vm", - "name": "abstract-vm" - }, - { - "id": 1462, - "slug": "42cursus-mod1", - "name": "mod1" - }, - { - "id": 1463, - "slug": "42cursus-zappy", - "name": "zappy" - }, - { - "id": 1464, - "slug": "42cursus-lem-ipc", - "name": "lem-ipc" - }, - { - "id": 1466, - "slug": "42cursus-ft_script", - "name": "ft_script" - }, - { - "id": 1467, - "slug": "nm", - "name": "nm" - }, - { - "id": 1468, - "slug": "42cursus-malloc", - "name": "malloc" - }, - { - "id": 1469, - "slug": "42cursus-ft_select", - "name": "ft_select" - }, - { - "id": 1470, - "slug": "42cursus-lem_in", - "name": "lem_in" - }, - { - "id": 1471, - "slug": "42cursus-push_swap", - "name": "push_swap" - }, - { - "id": 1475, - "slug": "42cursus-corewar", - "name": "corewar" - }, - { - "id": 1476, - "slug": "42cursus-fract-ol", - "name": "fract-ol" - }, - { - "id": 1479, - "slug": "42cursus-ft_ls", - "name": "ft_ls" - }, - { - "id": 1480, - "slug": "eu-aceito", - "name": "Eu aceito" - }, - { - "id": 1481, - "slug": "42cursus-piscine-php-symfony", - "name": "Piscine PHP Symfony" - }, - { - "id": 1482, - "slug": "42cursus-piscine-ruby-on-rails", - "name": "Piscine Ruby on Rails" - }, - { - "id": 1483, - "slug": "deprecated-piscine-python-django", - "name": "[DEPRECATED] Piscine Python Django" - }, - { - "id": 1484, - "slug": "deprecated-piscine-ocaml", - "name": "[DEPRECATED] Piscine OCaml" - }, - { - "id": 1485, - "slug": "deprecated-piscine-unity", - "name": "[DEPRECATED] Piscine Unity" - }, - { - "id": 1486, - "slug": "deprecated-piscine-swift-ios", - "name": "[DEPRECATED] Piscine Swift iOS" - }, - { - "id": 1338, - "slug": "cpp-module-00", - "name": "CPP Module 00" - }, - { - "id": 1339, - "slug": "cpp-module-01", - "name": "CPP Module 01" - }, - { - "id": 1340, - "slug": "cpp-module-02", - "name": "CPP Module 02" - }, - { - "id": 1341, - "slug": "cpp-module-03", - "name": "CPP Module 03" - }, - { - "id": 1342, - "slug": "cpp-module-04", - "name": "CPP Module 04" - }, - { - "id": 1343, - "slug": "cpp-module-05", - "name": "CPP Module 05" - }, - { - "id": 1344, - "slug": "cpp-module-06", - "name": "CPP Module 06" - }, - { - "id": 1345, - "slug": "cpp-module-07", - "name": "CPP Module 07" - }, - { - "id": 1346, - "slug": "cpp-module-08", - "name": "CPP Module 08" - }, - { - "id": 1633, - "slug": "linkedin-profile-task", - "name": "LinkedIn Profile Task" - }, - { - "id": 1635, - "slug": "open-project", - "name": "Open Project" - }, - { - "id": 1638, - "slug": "work-experience-i", - "name": "Work Experience I" - }, - { - "id": 1644, - "slug": "work-experience-ii", - "name": "Work Experience II" - }, - { - "id": 1650, - "slug": "part_time-i", - "name": "Part_Time I" - }, - { - "id": 1656, - "slug": "part_time-ii", - "name": "Part_Time II" - }, - { - "id": 1662, - "slug": "42cursus-startup-experience", - "name": "Startup Experience" - }, - { - "id": 1673, - "slug": "electronics-old", - "name": "Electronics-Old" - }, - { - "id": 1674, - "slug": "discovery-pedagogy", - "name": "Discovery-Pedagogy" - }, - { - "id": 1676, - "slug": "day00-html", - "name": "Day00 - HTML" - }, - { - "id": 1677, - "slug": "day01-css", - "name": "Day01 - CSS" - }, - { - "id": 1678, - "slug": "day02-js", - "name": "Day02 - JS" - }, - { - "id": 1679, - "slug": "rush-final", - "name": "Rush final" - }, - { - "id": 1683, - "slug": "old-philosophers", - "name": "Old-Philosophers" - }, - { - "id": 1684, - "slug": "old-irc", - "name": "Old-IRC" - }, - { - "id": 1685, - "slug": "deprecated-old-cpp-module-00", - "name": "[DEPRECATED] Old-CPP Module 00" - }, - { - "id": 1686, - "slug": "deprecated-old-cpp-module-01", - "name": "[DEPRECATED] Old-CPP Module 01" - }, - { - "id": 1687, - "slug": "deprecated-old-cpp-module-02", - "name": "[DEPRECATED] Old-CPP Module 02" - }, - { - "id": 1688, - "slug": "deprecated-old-cpp-module-03", - "name": "[DEPRECATED] Old-CPP Module 03" - }, - { - "id": 1689, - "slug": "deprecated-old-cpp-module-04", - "name": "[DEPRECATED] Old-CPP Module 04" - }, - { - "id": 1690, - "slug": "deprecated-old-cpp-module-05", - "name": "[DEPRECATED] Old-CPP Module 05" - }, - { - "id": 1691, - "slug": "deprecated-old-cpp-module-06", - "name": "[DEPRECATED] Old-CPP Module 06" - }, - { - "id": 1692, - "slug": "deprecated-old-cpp-module-07", - "name": "[DEPRECATED] Old-CPP Module 07" - }, - { - "id": 1693, - "slug": "deprecated-old-cpp-module-08", - "name": "[DEPRECATED] Old-CPP Module 08" - }, - { - "id": 1668, - "slug": "deprecated-apcsp_00", - "name": "[Deprecated]APCSP_00" - }, - { - "id": 1733, - "slug": "iotapp", - "name": "IoTApp" - }, - { - "id": 1758, - "slug": "pre-open-01", - "name": "Pre Open 01" - }, - { - "id": 1759, - "slug": "pre-open-00", - "name": "Pre Open 00" - }, - { - "id": 1760, - "slug": "pre-open-02", - "name": "Pre Open 02" - }, - { - "id": 1761, - "slug": "42-seoul-ex1", - "name": "42 seoul ex1" - }, - { - "id": 1762, - "slug": "42-seoul-my-little-tv", - "name": "42 seoul my little tv" - }, - { - "id": 1764, - "slug": "42-squads", - "name": "42 Squads" - }, - { - "id": 1786, - "slug": "deprecated-piscine-python-data-science", - "name": "[DEPRECATED] Piscine Python Data Science" - }, - { - "id": 1799, - "slug": "piscine-java", - "name": "Piscine Java" - }, - { - "id": 1814, - "slug": "darkly-web", - "name": "darkly - web" - }, - { - "id": 1816, - "slug": "module-00-ds-test", - "name": "Module 00 DS test" - }, - { - "id": 1817, - "slug": "basecamp-shell-00", - "name": "Basecamp Shell 00" - }, - { - "id": 1818, - "slug": "basecamp-shell-01", - "name": "Basecamp Shell 01" - }, - { - "id": 1819, - "slug": "basecamp-eu-aceito", - "name": "Basecamp Eu Aceito" - }, - { - "id": 1820, - "slug": "basecamp-c-00", - "name": "Basecamp C 00" - }, - { - "id": 1821, - "slug": "basecamp-c-01", - "name": "Basecamp C 01" - }, - { - "id": 1822, - "slug": "basecamp-c-02", - "name": "Basecamp C 02" - }, - { - "id": 1823, - "slug": "basecamp-c-03", - "name": "Basecamp C 03" - }, - { - "id": 1824, - "slug": "basecamp-c-04", - "name": "Basecamp C 04" - }, - { - "id": 1825, - "slug": "basecamp-c-05", - "name": "Basecamp C 05" - }, - { - "id": 1826, - "slug": "basecamp-c-06", - "name": "Basecamp C 06" - }, - { - "id": 1827, - "slug": "basecamp-c-07", - "name": "Basecamp C 07" - }, - { - "id": 1828, - "slug": "basecamp-c-08", - "name": "Basecamp C 08" - }, - { - "id": 1829, - "slug": "basecamp-c-09", - "name": "Basecamp C 09" - }, - { - "id": 1830, - "slug": "basecamp-c-10", - "name": "Basecamp C 10" - }, - { - "id": 1831, - "slug": "basecamp-c-11", - "name": "Basecamp C 11" - }, - { - "id": 1832, - "slug": "basecamp-c-12", - "name": "Basecamp C 12" - }, - { - "id": 1833, - "slug": "basecamp-c-13", - "name": "Basecamp C 13" - }, - { - "id": 1834, - "slug": "basecamp-rush-00", - "name": "Basecamp Rush 00" - }, - { - "id": 1835, - "slug": "basecamp-rush-01", - "name": "Basecamp Rush 01" - }, - { - "id": 1837, - "slug": "basecamp-exam-00", - "name": "Basecamp Exam 00" - }, - { - "id": 1838, - "slug": "basecamp-exam-01", - "name": "Basecamp Exam 01" - }, - { - "id": 1839, - "slug": "basecamp-final-exam", - "name": "Basecamp Final Exam" - }, - { - "id": 1840, - "slug": "ft_malcolm", - "name": "ft_malcolm" - }, - { - "id": 1848, - "slug": "electronique", - "name": "Electronique" - }, - { - "id": 1853, - "slug": "doom_nukem", - "name": "doom_nukem" - }, - { - "id": 1854, - "slug": "42cursus-42sh", - "name": "42sh" - }, - { - "id": 1855, - "slug": "42cursus-rt", - "name": "rt" - }, - { - "id": 1857, - "slug": "42cursus-apprentissage-2-ans-1ere-annee", - "name": "Apprentissage 2 ans - 1ère année" - }, - { - "id": 1865, - "slug": "apprentissage-2-ans-2eme-annee", - "name": " Apprentissage 2 ans - 2ème année" - }, - { - "id": 1873, - "slug": "apprentissage-1-an", - "name": "Apprentissage 1 an" - }, - { - "id": 1881, - "slug": "ft_mini_ls", - "name": "ft_mini_ls" - }, - { - "id": 1882, - "slug": "hello_node", - "name": "hello_node" - }, - { - "id": 1883, - "slug": "freddie-mercury", - "name": "freddie-mercury" - }, - { - "id": 1884, - "slug": "hello_vue", - "name": "hello_vue" - }, - { - "id": 1885, - "slug": "pokedex_vue", - "name": "pokedex_vue" - }, - { - "id": 1886, - "slug": "test-bassecamp-c-00", - "name": "test-bassecamp-c-00" - }, - { - "id": 1887, - "slug": "test-basecamp-i-accept", - "name": "test-basecamp-i-accept" - }, - { - "id": 1888, - "slug": "test-basecamp-shell-00", - "name": "test-basecamp-shell-00" - }, - { - "id": 1889, - "slug": "germany-basecamp-i-accept-old", - "name": "Germany Basecamp I Accept (OLD)" - }, - { - "id": 1890, - "slug": "germany-basecamp-germany-basecamp-shell-00", - "name": "Germany Basecamp Shell 00" - }, - { - "id": 1891, - "slug": "germany-basecamp-shell-01", - "name": "Germany Basecamp Shell 01" - }, - { - "id": 1892, - "slug": "germany-basecamp-c-00", - "name": "Germany Basecamp C 00" - }, - { - "id": 1893, - "slug": "germany-basecamp-c-01", - "name": "Germany Basecamp C 01" - }, - { - "id": 1894, - "slug": "germany-basecamp-c-02", - "name": "Germany Basecamp C 02" - }, - { - "id": 1895, - "slug": "germany-basecamp-c-03", - "name": "Germany Basecamp C 03" - }, - { - "id": 1896, - "slug": "germany-basecamp-c-04", - "name": "Germany Basecamp C 04" - }, - { - "id": 1897, - "slug": "germany-basecamp-c-05", - "name": "Germany Basecamp C 05" - }, - { - "id": 1898, - "slug": "germany-basecamp-c-06", - "name": "Germany Basecamp C 06" - }, - { - "id": 1899, - "slug": "germany-basecamp-c-07", - "name": "Germany Basecamp C 07" - }, - { - "id": 1900, - "slug": "germany-basecamp-c-08", - "name": "Germany Basecamp C 08" - }, - { - "id": 1901, - "slug": "germany-basecamp-c-09", - "name": "Germany Basecamp C 09" - }, - { - "id": 1903, - "slug": "germany-basecamp-c-10", - "name": "Germany Basecamp C 10" - }, - { - "id": 1904, - "slug": "germany-basecamp-c-11", - "name": "Germany Basecamp C 11" - }, - { - "id": 1905, - "slug": "germany-basecamp-c-12", - "name": "Germany Basecamp C 12" - }, - { - "id": 1906, - "slug": "germany-basecamp-c-13", - "name": "Germany Basecamp C 13" - }, - { - "id": 1907, - "slug": "germany-basecamp-rush-00", - "name": "Germany Basecamp Rush 00" - }, - { - "id": 1908, - "slug": "germany-basecamp-rush-01", - "name": "Germany Basecamp Rush 01" - }, - { - "id": 1910, - "slug": "germany-basecamp-exam-01", - "name": "Germany Basecamp Exam 01" - }, - { - "id": 1911, - "slug": "germany-basecamp-final-exam", - "name": "Germany Basecamp Final Exam" - }, - { - "id": 1912, - "slug": "brussels-basecamp-shell-00", - "name": "Brussels Basecamp Shell 00" - }, - { - "id": 1913, - "slug": "brussels-basecamp-shell-01", - "name": "Brussels Basecamp Shell 01" - }, - { - "id": 1914, - "slug": "brussels-basecamp-c-00", - "name": "Brussels Basecamp C 00" - }, - { - "id": 1915, - "slug": "brussels-basecamp-c-01", - "name": "Brussels Basecamp C 01" - }, - { - "id": 1916, - "slug": "brussels-basecamp-c-09", - "name": "Brussels Basecamp C 09" - }, - { - "id": 1918, - "slug": "brussels-basecamp-c-03", - "name": "Brussels Basecamp C 03" - }, - { - "id": 1917, - "slug": "brussels-basecamp-c-02", - "name": "Brussels Basecamp C 02" - }, - { - "id": 1920, - "slug": "brussels-basecamp-c-05", - "name": "Brussels Basecamp C 05" - }, - { - "id": 1919, - "slug": "brussels-basecamp-c-04", - "name": "Brussels Basecamp C 04" - }, - { - "id": 1921, - "slug": "brussels-basecamp-c-06", - "name": "Brussels Basecamp C 06" - }, - { - "id": 1922, - "slug": "brussels-basecamp-c-07", - "name": "Brussels Basecamp C 07" - }, - { - "id": 1923, - "slug": "brussels-basecamp-c-08", - "name": "Brussels Basecamp C 08" - }, - { - "id": 1924, - "slug": "brussels-basecamp-rush-00", - "name": "Brussels Basecamp Rush 00" - }, - { - "id": 1925, - "slug": "brussels-basecamp-rush-01", - "name": "Brussels Basecamp Rush 01" - }, - { - "id": 1926, - "slug": "brussels-basecamp-exam-00", - "name": "Brussels Basecamp Exam 00" - }, - { - "id": 1927, - "slug": "brussels-basecamp-exam-01", - "name": "Brussels Basecamp Exam 01" - }, - { - "id": 1928, - "slug": "brussels-basecamp-final-exam", - "name": "Brussels Basecamp Final Exam" - }, - { - "id": 1929, - "slug": "brussels-basecamp-c-10", - "name": "Brussels Basecamp C 10" - }, - { - "id": 1930, - "slug": "brussels-basecamp-c-11", - "name": "Brussels Basecamp C 11" - }, - { - "id": 1931, - "slug": "brussels-basecamp-c-12", - "name": "Brussels Basecamp C 12" - }, - { - "id": 1932, - "slug": "brussels-basecamp-c-13", - "name": "Brussels Basecamp C 13" - }, - { - "id": 1933, - "slug": "tweets", - "name": "tweets" - }, - { - "id": 1934, - "slug": "churn", - "name": "churn" - }, - { - "id": 1936, - "slug": "brussels-basecamp-i-accept", - "name": "Brussels Basecamp I Accept" - }, - { - "id": 1937, - "slug": "brussels-basecamp-intro-git", - "name": "Brussels Basecamp Intro Git" - }, - { - "id": 1938, - "slug": "piscine-101-shell-00", - "name": "Piscine 101 Shell 00" - }, - { - "id": 1943, - "slug": "myspotify", - "name": "mySpotify" - }, - { - "id": 1945, - "slug": "fwa", - "name": "FWA" - }, - { - "id": 1946, - "slug": "restful", - "name": "Restful" - }, - { - "id": 1947, - "slug": "microservices", - "name": "MicroServices" - }, - { - "id": 1948, - "slug": "piscine-101-python-02", - "name": "Piscine 101 Python 02" - }, - { - "id": 1949, - "slug": "piscine-101-python-04", - "name": "Piscine 101 Python 04" - }, - { - "id": 1950, - "slug": "piscine-101-python-rush", - "name": "Piscine 101 Python Rush" - }, - { - "id": 1951, - "slug": "cinema", - "name": "Cinema" - }, - { - "id": 1952, - "slug": "springboot", - "name": "SpringBoot" - }, - { - "id": 1953, - "slug": "piscine-101-python-01", - "name": "Piscine 101 Python 01" - }, - { - "id": 1954, - "slug": "piscine-101-python-03", - "name": "Piscine 101 Python 03" - }, - { - "id": 1955, - "slug": "uber", - "name": "Uber" - }, - { - "id": 1956, - "slug": "understanding-customer", - "name": " Understanding customer" - }, - { - "id": 1957, - "slug": "city-life", - "name": "City Life" - }, - { - "id": 1958, - "slug": "messagequeue", - "name": "MessageQueue" - }, - { - "id": 1959, - "slug": "amazon", - "name": "Amazon" - }, - { - "id": 1960, - "slug": "fried-eggs", - "name": "Fried eggs" - }, - { - "id": 1962, - "slug": "ft_newton", - "name": "ft_newton" - }, - { - "id": 1963, - "slug": "libunit", - "name": "libunit" - }, - { - "id": 1964, - "slug": "hive-basecamp-day-00", - "name": "Hive Basecamp - Day 00" - }, - { - "id": 1965, - "slug": "hive-basecamp-day-01", - "name": "Hive Basecamp - Day 01" - }, - { - "id": 1966, - "slug": "hive-basecamp-day-02", - "name": "Hive Basecamp - Day 02" - }, - { - "id": 1967, - "slug": "hive-basecamp-day-03", - "name": "Hive Basecamp - Day 03" - }, - { - "id": 1968, - "slug": "hive-basecamp-day-04", - "name": "Hive Basecamp - Day 04" - }, - { - "id": 1969, - "slug": "hive-basecamp-day-05", - "name": "Hive Basecamp - Day 05" - }, - { - "id": 1970, - "slug": "hive-basecamp-day-06", - "name": "Hive Basecamp - Day 06" - }, - { - "id": 1971, - "slug": "hive-basecamp-day-07", - "name": "Hive Basecamp - Day 07" - }, - { - "id": 1972, - "slug": "hive-basecamp-day-08", - "name": "Hive Basecamp - Day 08" - }, - { - "id": 1973, - "slug": "hive-basecamp-day-10", - "name": "Hive Basecamp - Day 10" - }, - { - "id": 1974, - "slug": "hive-basecamp-day-11", - "name": "Hive Basecamp - Day 11" - }, - { - "id": 1975, - "slug": "hive-basecamp-exam-00", - "name": "Hive Basecamp - Exam 00" - }, - { - "id": 1976, - "slug": "hive-basecamp-exam-01", - "name": "Hive Basecamp - Exam 01" - }, - { - "id": 1977, - "slug": "hive-basecamp-final-exam", - "name": "Hive Basecamp - Final exam" - }, - { - "id": 1978, - "slug": "hive-basecamp-rush-00", - "name": "Hive Basecamp - Rush 00" - }, - { - "id": 1979, - "slug": "hive-basecamp-rush-01", - "name": "Hive Basecamp - Rush 01" - }, - { - "id": 1980, - "slug": "hive-basecamp-sastantua", - "name": "Hive Basecamp - Sastantua" - }, - { - "id": 1981, - "slug": "hive-basecamp-match-n-match", - "name": "Hive Basecamp - Match-N-Match" - }, - { - "id": 1983, - "slug": "inception", - "name": "Inception" - }, - { - "id": 1984, - "slug": "deprecated-python-module-00", - "name": "[DEPRECATED] Python Module 00" - }, - { - "id": 1986, - "slug": "go-piscine-go-00-advanced", - "name": "Go Piscine Go 00 Advanced" - }, - { - "id": 1987, - "slug": "hive-basecamp-day-13", - "name": "Hive Basecamp - Day 13" - }, - { - "id": 1988, - "slug": "hive-basecamp-day-12", - "name": "Hive Basecamp - Day 12" - }, - { - "id": 1989, - "slug": "hive-basecamp-exam-02", - "name": "Hive Basecamp - Exam 02" - }, - { - "id": 1990, - "slug": "hive-basecamp-evalexpr", - "name": "Hive Basecamp - EvalExpr" - }, - { - "id": 1991, - "slug": "hive-basecamp-rush-02", - "name": "Hive Basecamp - Rush 02" - }, - { - "id": 1992, - "slug": "hive-basecamp-bsq", - "name": "Hive Basecamp - BSQ" - }, - { - "id": 1993, - "slug": "seoul-labs-member", - "name": "SEOUL LABS MEMBER" - }, - { - "id": 1994, - "slug": "born2beroot", - "name": "Born2beroot" - }, - { - "id": 1996, - "slug": "deprecated-python-module-02", - "name": "[DEPRECATED] Python Module 02" - }, - { - "id": 1997, - "slug": "deprecated-python-module-03", - "name": "[DEPRECATED] Python Module 03" - }, - { - "id": 1998, - "slug": "deprecated-python-module-04", - "name": "[DEPRECATED] Python Module 04" - }, - { - "id": 1999, - "slug": "deprecated-ml-module-00", - "name": "[DEPRECATED] ML Module 00" - }, - { - "id": 2000, - "slug": "deprecated-ml-module-01", - "name": "[DEPRECATED] ML Module 01" - }, - { - "id": 2001, - "slug": "deprecated-ml-module-02", - "name": "[DEPRECATED] ML Module 02" - }, - { - "id": 2002, - "slug": "deprecated-ml-module-03", - "name": "[DEPRECATED] ML Module 03" - }, - { - "id": 2003, - "slug": "deprecated-ml-module-04", - "name": "[DEPRECATED] ML Module 04" - }, - { - "id": 2004, - "slug": "pipex", - "name": "pipex" - }, - { - "id": 2005, - "slug": "minitalk", - "name": "minitalk" - }, - { - "id": 2006, - "slug": "42seoul_test", - "name": "42SEOUL_TEST" - }, - { - "id": 2007, - "slug": "netpractice", - "name": "NetPractice" - }, - { - "id": 2008, - "slug": "42cursus-fdf", - "name": "FdF" - }, - { - "id": 2009, - "slug": "so_long", - "name": "so_long" - }, - { - "id": 2010, - "slug": "road-to-mercari-gopher-dojo-00", - "name": "Road-to-Mercari-Gopher-Dojo-00" - }, - { - "id": 2012, - "slug": "cellule0-0-shell", - "name": "Cellule0.0 - Shell" - }, - { - "id": 2014, - "slug": "cellule0-2-shell", - "name": "Cellule0.2 - Shell" - }, - { - "id": 2015, - "slug": "cellule0-3-shell", - "name": "Cellule0.3 - Shell" - }, - { - "id": 2016, - "slug": "cellule0-4-shell", - "name": "Cellule0.4 - Shell" - }, - { - "id": 2017, - "slug": "cellule0-5-shell", - "name": "Cellule0.5 - Shell" - }, - { - "id": 2018, - "slug": "cellule1-0-web", - "name": "Cellule1.0 - Web" - }, - { - "id": 2019, - "slug": "cellule1-1-web", - "name": "Cellule1.1 - Web" - }, - { - "id": 2020, - "slug": "cellule1-2-web", - "name": "Cellule1.2 - Web" - }, - { - "id": 2021, - "slug": "cellule1-3-web", - "name": "Cellule1.3 - Web" - }, - { - "id": 2022, - "slug": "cellule1-4-web", - "name": "Cellule1.4 - Web" - }, - { - "id": 2023, - "slug": "cellule1-5-web", - "name": "Cellule1.5 - Web" - }, - { - "id": 2024, - "slug": "cellule1-6-web", - "name": "Cellule1.6 - Web" - }, - { - "id": 2025, - "slug": "cellule2-0-web", - "name": "Cellule2.0 - Web" - }, - { - "id": 2026, - "slug": "cellule2-1-web", - "name": "Cellule2.1 - Web" - }, - { - "id": 2027, - "slug": "cellule2-2-web", - "name": "Cellule2.2 - Web" - }, - { - "id": 2028, - "slug": "cellule2-3-web", - "name": "Cellule2.3 - Web" - }, - { - "id": 2029, - "slug": "cellule3-0-web", - "name": "Cellule3.0 - Web" - }, - { - "id": 2030, - "slug": "cellule3-1-web", - "name": "Cellule3.1 - Web" - }, - { - "id": 2031, - "slug": "cellule3-2-web", - "name": "Cellule3.2 - Web" - }, - { - "id": 2032, - "slug": "cellule3-3-web", - "name": "Cellule3.3 - Web" - }, - { - "id": 2033, - "slug": "cellule3-4-web", - "name": "Cellule3.4 - Web" - }, - { - "id": 2034, - "slug": "cellule4-0-rush", - "name": "Cellule4.0 - Rush" - }, - { - "id": 2035, - "slug": "road-to-mercari-gopher-dojo-02", - "name": "Road-to-Mercari-Gopher-Dojo-02" - }, - { - "id": 2037, - "slug": "road-to-mercari-gopher-dojo-01", - "name": "Road-to-Mercari-Gopher-Dojo-01" - }, - { - "id": 2041, - "slug": "c-01", - "name": "C 01" - }, - { - "id": 2042, - "slug": "c-00", - "name": "C 00" - }, - { - "id": 2043, - "slug": "c-02", - "name": "C 02" - }, - { - "id": 2044, - "slug": "road-to-mercari-gopher-dojo-03", - "name": "Road-to-Mercari-Gopher-Dojo-03" - }, - { - "id": 2046, - "slug": "ft_communication", - "name": "ft_communication" - }, - { - "id": 2047, - "slug": "refactor_bsq", - "name": "refactor_bsq" - }, - { - "id": 2049, - "slug": "shell-00", - "name": "SHELL 00" - }, - { - "id": 2050, - "slug": "shell-01", - "name": "SHELL 01" - }, - { - "id": 2051, - "slug": "basecamp-warm-up-rio-eu-aceito", - "name": "Eu Aceito!" - }, - { - "id": 2052, - "slug": "c-04", - "name": "C 04" - }, - { - "id": 2053, - "slug": "c-05", - "name": "C 05" - }, - { - "id": 2054, - "slug": "c-06", - "name": "C 06" - }, - { - "id": 2055, - "slug": "rush-00", - "name": "RUSH 00" - }, - { - "id": 2056, - "slug": "rush-01", - "name": "RUSH 01" - }, - { - "id": 2057, - "slug": "c-07", - "name": "C 07" - }, - { - "id": 2058, - "slug": "c-08", - "name": "C 08" - }, - { - "id": 2059, - "slug": "c-09", - "name": "C 09" - }, - { - "id": 2060, - "slug": "c-10", - "name": "C 10" - }, - { - "id": 2061, - "slug": "c-11", - "name": "C 11" - }, - { - "id": 2062, - "slug": "c-12", - "name": "C 12" - }, - { - "id": 2063, - "slug": "c-13", - "name": "C 13" - }, - { - "id": 2064, - "slug": "inception-of-things", - "name": "Inception-of-Things" - }, - { - "id": 2065, - "slug": "42cursus-rushes", - "name": "Rushes" - }, - { - "id": 2068, - "slug": "cow-neck-tid", - "name": "Cow-Neck-TID" - }, - { - "id": 2071, - "slug": "bgp-at-doors-of-autonomous-systems-is-simple", - "name": " Bgp At Doors of Autonomous Systems is Simple" - }, - { - "id": 2072, - "slug": "exam-42-zip", - "name": "Exam 42 Zip" - }, - { - "id": 2073, - "slug": "ft_communication_v2", - "name": "ft_communication_v2" - }, - { - "id": 2074, - "slug": "road-to-mercari-gopher-dojoo-01", - "name": "Road-to-Mercari-Gopher-Dojoo-01" - }, - { - "id": 2075, - "slug": "road-to-dmm-bootcamp-go", - "name": "Road-to-DMM-Bootcamp-Go" - }, - { - "id": 2076, - "slug": "ready-set-boole", - "name": "ready set boole" - }, - { - "id": 2077, - "slug": "matrix", - "name": "matrix" - }, - { - "id": 2078, - "slug": "wolfsburg-i-accept", - "name": "Wolfsburg I Accept" - }, - { - "id": 2082, - "slug": "germany-basecamp-i-accept", - "name": "Germany Basecamp I Accept" - }, - { - "id": 2085, - "slug": "basecamp-rio-eu-aceito", - "name": "Eu Aceito!!" - }, - { - "id": 2088, - "slug": "ft_self-analysis", - "name": "ft_self-analysis" - }, - { - "id": 2097, - "slug": "tinky-winkey", - "name": "tinky-winkey" - }, - { - "id": 1909, - "slug": "germany-basecamp-exam-00", - "name": "Germany Basecamp Exam 00" - }, - { - "id": 2098, - "slug": "ft_kalman", - "name": "ft_kalman" - }, - { - "id": 2099, - "slug": "term3d", - "name": "term3d" - }, - { - "id": 2100, - "slug": "road-to-mixi-mini-sns-00", - "name": "Road-to-MIXI-Mini-SNS-00" - }, - { - "id": 2101, - "slug": "road-to-mixi-mini-sns-01", - "name": "Road-to-MIXI-Mini-SNS-01" - }, - { - "id": 2103, - "slug": "ft_helpme", - "name": "ft_helpme" - }, - { - "id": 2104, - "slug": "libft-00", - "name": "Libft-00" - }, - { - "id": 2105, - "slug": "libft-01", - "name": "Libft-01" - }, - { - "id": 2106, - "slug": "libft-02", - "name": "Libft-02" - }, - { - "id": 2107, - "slug": "road-to-cyberagent-ca-tech-dojo-go", - "name": "Road-to-CyberAgent-CA-Tech-Dojo-Go" - }, - { - "id": 2108, - "slug": "libft-03", - "name": "Libft-03" - }, - { - "id": 2109, - "slug": "libft-04", - "name": "Libft-04" - }, - { - "id": 2112, - "slug": "ft_ssl_md5", - "name": "ft_ssl_md5" - }, - { - "id": 2113, - "slug": "ft_newton42", - "name": "ft_newton42" - }, - { - "id": 2114, - "slug": "ft_abstract_vm", - "name": "ft_abstract_vm" - }, - { - "id": 2115, - "slug": "42_matrix", - "name": "42_matrix" - }, - { - "id": 2116, - "slug": "42-ready-set-boole", - "name": "42 Ready Set boole" - }, - { - "id": 2117, - "slug": "ft_malcolm_42", - "name": "ft_malcolm_42" - }, - { - "id": 2118, - "slug": "42_ft_shield", - "name": "42_ft_shield" - }, - { - "id": 2124, - "slug": "piscine_reloaded", - "name": "Piscine_Reloaded" - }, - { - "id": 2126, - "slug": "ft_minecraft", - "name": "ft_minecraft" - }, - { - "id": 2135, - "slug": "go-piscine-go-00", - "name": "Go Piscine Go 00" - }, - { - "id": 2137, - "slug": "go-piscine-go-01", - "name": "Go Piscine Go 01" - }, - { - "id": 2138, - "slug": "go-piscine-go-02", - "name": "Go Piscine Go 02" - }, - { - "id": 2139, - "slug": "go-piscine-go-03", - "name": "Go Piscine Go 03" - }, - { - "id": 2140, - "slug": "go-piscine-go-04", - "name": "Go Piscine Go 04" - }, - { - "id": 2141, - "slug": "go-piscine-go-05", - "name": "Go Piscine Go 05" - }, - { - "id": 2142, - "slug": "go-piscine-go-06", - "name": "Go Piscine Go 06" - }, - { - "id": 2143, - "slug": "go-piscine-go-07", - "name": "Go Piscine Go 07" - }, - { - "id": 2144, - "slug": "go-piscine-go-08", - "name": "Go Piscine Go 08" - }, - { - "id": 2145, - "slug": "go-piscine-go-09", - "name": "Go Piscine Go 09" - }, - { - "id": 2146, - "slug": "go-piscine-go-10", - "name": "Go Piscine Go 10" - }, - { - "id": 2148, - "slug": "go-piscine-rush-00", - "name": "Go Piscine Rush 00" - }, - { - "id": 2149, - "slug": "go-piscine-rush-01", - "name": "Go Piscine Rush 01" - }, - { - "id": 2150, - "slug": "go-piscine-rush-02", - "name": "Go Piscine Rush 02" - }, - { - "id": 2151, - "slug": "go-piscine-go-bsq", - "name": "Go Piscine Go BSQ" - }, - { - "id": 2175, - "slug": "sql-workshop", - "name": "Sql-workshop" - }, - { - "id": 2013, - "slug": "cellule0-1-shell", - "name": "Cellule0.1 - Shell" - }, - { - "id": 2177, - "slug": "i-accept", - "name": "I accept" - }, - { - "id": 2179, - "slug": "piscine-ror", - "name": "Piscine RoR" - }, - { - "id": 2189, - "slug": "piscine-django", - "name": "Piscine Django" - }, - { - "id": 2199, - "slug": "piscine-symfony", - "name": "Piscine Symfony" - }, - { - "id": 2209, - "slug": "refactor_tetris", - "name": "refactor_tetris" - }, - { - "id": 2212, - "slug": "codam_exam_test", - "name": "codam_exam_test" - }, - { - "id": 2217, - "slug": "cursus-eu-aceito-rio", - "name": "Cursus Eu Aceito Rio" - }, - { - "id": 2218, - "slug": "p2p-101", - "name": "P2P 101" - }, - { - "id": 2225, - "slug": "ftl_quantum", - "name": "Ftl_quantum" - }, - { - "id": 2162, - "slug": "ft_onion", - "name": "ft_onion" - }, - { - "id": 2171, - "slug": "corsair", - "name": "coRSAir" - }, - { - "id": 2159, - "slug": "ft_blockchain", - "name": "ft_blockchain" - }, - { - "id": 2166, - "slug": "tsunami", - "name": "tsunami" - }, - { - "id": 2157, - "slug": "arachnida", - "name": "arachnida" - }, - { - "id": 2163, - "slug": "ft_otp", - "name": "ft_otp" - }, - { - "id": 2169, - "slug": "recovery", - "name": "recovery" - }, - { - "id": 2168, - "slug": "extraction", - "name": "extraction" - }, - { - "id": 2167, - "slug": "iron_dome", - "name": "iron_dome" - }, - { - "id": 2165, - "slug": "vaccine", - "name": "vaccine" - }, - { - "id": 2164, - "slug": "inquisitor", - "name": "inquisitor" - }, - { - "id": 2172, - "slug": "stockholm", - "name": "stockholm" - }, - { - "id": 2238, - "slug": "exam_test_42", - "name": "exam_test_42" - }, - { - "id": 2239, - "slug": "hive-internship", - "name": "Hive Internship" - }, - { - "id": 2245, - "slug": "hive-startup-internship", - "name": "Hive Startup Internship" - }, - { - "id": 2262, - "slug": "angular-piscine-week-1-day-01", - "name": "Angular Piscine Week 1 | Day 01" - }, - { - "id": 2263, - "slug": "3sc4p3", - "name": "3sc4p3" - }, - { - "id": 2267, - "slug": "python-for-data-science", - "name": "Python for Data Science" - }, - { - "id": 2268, - "slug": "python-0-starting", - "name": "Python - 0 - Starting" - }, - { - "id": 2269, - "slug": "python-1-array", - "name": "Python - 1 - Array" - }, - { - "id": 2270, - "slug": "python-2-datatable", - "name": "Python - 2 - DataTable" - }, - { - "id": 2271, - "slug": "python-3-oop", - "name": "Python - 3 - OOP" - }, - { - "id": 2272, - "slug": "python-4-dod", - "name": "Python - 4 - Dod" - }, - { - "id": 2284, - "slug": "unity-1-3d-physics-tags-layers-and-scene", - "name": "Unity - 1 - 3D physics, Tags, Layers and Scene" - }, - { - "id": 2285, - "slug": "unity-6-navmesh-light-sound-and-camera", - "name": "Unity - 6 - Navmesh, light, sound and camera" - }, - { - "id": 2286, - "slug": "unity-0-the-basics-unity-tools", - "name": "Unity - 0 - The basics Unity tools" - }, - { - "id": 2287, - "slug": "unity-5-singleton-playerprefs-and-coroutines", - "name": "Unity - 5 - Singleton, playerPrefs and coroutines" - }, - { - "id": 2288, - "slug": "unity", - "name": "Unity" - }, - { - "id": 2289, - "slug": "unity-2-2d-environment-tiles-and-sprites", - "name": "Unity - 2 - 2D environment, tiles and sprites" - }, - { - "id": 2290, - "slug": "unity-3-advanced-inputs-and-2d-gui", - "name": "Unity - 3 - Advanced inputs and 2D GUI" - }, - { - "id": 2292, - "slug": "unity-4-animations-and-sound", - "name": "Unity - 4 - Animations and Sound" - }, - { - "id": 2295, - "slug": "piscine-data-science", - "name": "Piscine Data Science" - }, - { - "id": 2306, - "slug": "data-science-0", - "name": "Data Science - 0" - }, - { - "id": 2309, - "slug": "cpp-module-09", - "name": "CPP Module 09" - }, - { - "id": 2310, - "slug": "42cursus-alone-in-the-dark", - "name": "Alone in the Dark" - }, - { - "id": 2311, - "slug": "data-science-2", - "name": "Data Science - 2" - }, - { - "id": 2294, - "slug": "mini-piscine-python-django-day-00", - "name": "Mini-Piscine Python Django Day 00" - }, - { - "id": 2297, - "slug": "mini-piscine-python-django-day-01", - "name": "Mini-Piscine Python Django Day 01" - }, - { - "id": 2298, - "slug": "mini-piscine-python-django-day-02", - "name": "Mini-Piscine Python Django Day 02" - }, - { - "id": 2299, - "slug": "mini-piscine-python-django-day-03", - "name": "Mini-Piscine Python Django Day 03" - }, - { - "id": 2300, - "slug": "mini-piscine-python-django-day-04", - "name": "Mini-Piscine Python Django Day 04" - }, - { - "id": 2301, - "slug": "mini-piscine-python-django-day-05", - "name": "Mini-Piscine Python Django Day 05" - }, - { - "id": 2302, - "slug": "mini-piscine-python-django-day-06", - "name": "Mini-Piscine Python Django Day 06" - }, - { - "id": 2303, - "slug": "mini-piscine-python-django-day-07", - "name": "Mini-Piscine Python Django Day 07" - }, - { - "id": 2304, - "slug": "mini-piscine-python-django-day-08", - "name": "Mini-Piscine Python Django Day 08" - }, - { - "id": 2305, - "slug": "mini-piscine-python-django-day-09", - "name": "Mini-Piscine Python Django Day 09" - }, - { - "id": 2180, - "slug": "ror-0-initiation", - "name": "RoR - 0 - Initiation" - }, - { - "id": 2181, - "slug": "ror-0-starting", - "name": " RoR - 0 - Starting" - }, - { - "id": 2182, - "slug": "ror-0-oob", - "name": "RoR - 0 - Oob" - }, - { - "id": 2183, - "slug": "ror-1-gems", - "name": "RoR - 1 - Gems" - }, - { - "id": 2184, - "slug": "ror-1-base-rails", - "name": "RoR - 1 - Base Rails" - }, - { - "id": 2185, - "slug": "ror-2-sql", - "name": "RoR - 2 - SQL" - }, - { - "id": 2186, - "slug": "ror-3-sessions", - "name": "RoR - 3 - Sessions" - }, - { - "id": 2187, - "slug": "ror-3-advanced", - "name": "RoR - 3 - Advanced" - }, - { - "id": 2188, - "slug": "ror-3-final", - "name": "RoR - 3 - Final" - }, - { - "id": 2200, - "slug": "symfony-0-initiation", - "name": "Symfony - 0 - Initiation" - }, - { - "id": 2201, - "slug": "symfony-0-starting", - "name": "Symfony - 0 - Starting" - }, - { - "id": 2202, - "slug": "symfony-0-oob", - "name": "Symfony - 0 - Oob" - }, - { - "id": 2203, - "slug": "symfony-1-composer", - "name": "Symfony - 1 - Composer" - }, - { - "id": 2204, - "slug": "symfony-1-base-symfony", - "name": "Symfony - 1 - Base Symfony " - }, - { - "id": 2205, - "slug": "symfony-2-sql", - "name": "Symfony - 2 - SQL" - }, - { - "id": 2206, - "slug": "symfony-3-sessions", - "name": "Symfony - 3 - Sessions" - }, - { - "id": 2207, - "slug": "symfony-3-advanced", - "name": "Symfony - 3 - Advanced" - }, - { - "id": 2208, - "slug": "symfony-3-final", - "name": "Symfony - 3 - Final" - }, - { - "id": 2190, - "slug": "django-0-initiation", - "name": "Django - 0 - Initiation" - }, - { - "id": 2191, - "slug": "django-0-starting", - "name": "Django - 0 - Starting" - }, - { - "id": 2192, - "slug": "django-0-oob", - "name": "Django - 0 - Oob" - }, - { - "id": 2193, - "slug": "django-1-lib", - "name": "Django - 1 - Lib" - }, - { - "id": 2194, - "slug": "django-1-base-django", - "name": "Django - 1 - Base Django" - }, - { - "id": 2195, - "slug": "django-2-sql", - "name": "Django - 2 - SQL" - }, - { - "id": 2196, - "slug": "django-3-sessions", - "name": "Django - 3 - Sessions" - }, - { - "id": 2197, - "slug": "django-3-advanced", - "name": "Django - 3 - Advanced" - }, - { - "id": 2198, - "slug": "django-3-final", - "name": "Django - 3 - Final" - }, - { - "id": 2315, - "slug": "c-piscine-reloaded", - "name": "C-piscine-reloaded" - }, - { - "id": 2316, - "slug": "data-science-3", - "name": "Data Science - 3" - }, - { - "id": 2317, - "slug": "data-science-4", - "name": "Data Science - 4" - }, - { - "id": 2318, - "slug": "data-science-1", - "name": "Data Science - 1" - }, - { - "id": 2319, - "slug": "tweet", - "name": "tweet" - }, - { - "id": 2320, - "slug": "java-module-00", - "name": "Java Module 00" - }, - { - "id": 2321, - "slug": "java-module-01", - "name": "Java Module 01" - }, - { - "id": 2322, - "slug": "java-module-02", - "name": "Java Module 02" - }, - { - "id": 2323, - "slug": "microshell-00", - "name": "microshell-00" - }, - { - "id": 2324, - "slug": "microshell-01", - "name": "microshell-01" - }, - { - "id": 2325, - "slug": "microshell-02", - "name": "microshell-02" - }, - { - "id": 2326, - "slug": "cybersecurity-arachnida-web", - "name": "Cybersecurity - arachnida - Web" - }, - { - "id": 2327, - "slug": "cybersecurity-ft_otp-otp", - "name": "Cybersecurity - ft_otp - OTP" - }, - { - "id": 2328, - "slug": "cybersecurity-ft_onion-web", - "name": "Cybersecurity - ft_onion - Web" - }, - { - "id": 2329, - "slug": "java-module-03", - "name": "Java Module 03" - }, - { - "id": 2330, - "slug": "java-module-04", - "name": "Java Module 04" - }, - { - "id": 2331, - "slug": "java-module-05", - "name": "Java Module 05" - }, - { - "id": 2332, - "slug": "java-module-06", - "name": "Java Module 06" - }, - { - "id": 2333, - "slug": "java-module-07", - "name": "Java Module 07" - }, - { - "id": 2334, - "slug": "java-module-08", - "name": "Java Module 08" - }, - { - "id": 2335, - "slug": "java-module-09", - "name": "Java Module 09" - }, - { - "id": 2336, - "slug": "cybersecurity-reverse-me-rev", - "name": "Cybersecurity - Reverse me - Rev" - }, - { - "id": 2337, - "slug": "cybersecurity-stockholm-malware", - "name": " Cybersecurity - Stockholm - Malware" - }, - { - "id": 2338, - "slug": "fr-apprentissage-rncp-6-1-an", - "name": "FR Apprentissage RNCP 6 - 1 an" - }, - { - "id": 2339, - "slug": "fr-apprentissage-rncp-6-2-ans", - "name": "FR Apprentissage RNCP 6 - 2 ans" - }, - { - "id": 2340, - "slug": "fr-apprentissage-rncp-7-1-an", - "name": "FR Apprentissage RNCP 7 - 1 an" - }, - { - "id": 2341, - "slug": "fr-apprentissage-rncp-7-2-ans", - "name": "FR Apprentissage RNCP 7 - 2 ans" - }, - { - "id": 2343, - "slug": "cybersecurity-inquisitor-network", - "name": "Cybersecurity - Inquisitor - Network" - }, - { - "id": 2344, - "slug": "optional-cybersecurity-iron-dome-malware", - "name": "(Optional) Cybersecurity - Iron Dome - Malware" - }, - { - "id": 2345, - "slug": "cybersecurity-vaccine-web", - "name": "Cybersecurity - Vaccine - Web" - }, - { - "id": 2346, - "slug": "cybersecurity", - "name": "Cybersecurity" - }, - { - "id": 2347, - "slug": "42cursus-fwa", - "name": "fwa" - }, - { - "id": 2348, - "slug": "event-april_2023", - "name": "[Event]April_2023" - }, - { - "id": 2349, - "slug": "42cursus-cinema", - "name": "cinema" - }, - { - "id": 2350, - "slug": "spring-boot", - "name": "Spring Boot" - }, - { - "id": 2351, - "slug": "42cursus-restful", - "name": "restful" - }, - { - "id": 2353, - "slug": "42cursus-messagequeue", - "name": "messagequeue" - }, - { - "id": 2354, - "slug": "mobile-0-basic-of-the-mobile-application", - "name": "Mobile - 0 - Basic of the mobile application" - }, - { - "id": 2355, - "slug": "mobile", - "name": "Mobile" - }, - { - "id": 2356, - "slug": "mobile-1-structure-and-logic", - "name": "Mobile - 1 - Structure and logic" - }, - { - "id": 2357, - "slug": "mobile-2-api-and-data", - "name": "Mobile - 2 - API and data" - }, - { - "id": 2358, - "slug": "mobile-3-design", - "name": "Mobile - 3 - Design" - }, - { - "id": 2359, - "slug": "mobile-4-auth-and-database", - "name": "Mobile - 4 - Auth and dataBase" - }, - { - "id": 2360, - "slug": "mobile-5-manage-data-and-display", - "name": "Mobile - 5 - Manage data and display" - }, - { - "id": 2364, - "slug": "piscine-object", - "name": "Piscine Object" - }, - { - "id": 2365, - "slug": "piscine-object-module-00-encapsulation", - "name": "Piscine Object - Module 00 - Encapsulation" - }, - { - "id": 2366, - "slug": "piscine-object-module-01-relationship", - "name": "Piscine Object - Module 01 - Relationship" - }, - { - "id": 2367, - "slug": "piscine-object-module-02-uml", - "name": "Piscine Object - Module 02 - UML" - }, - { - "id": 2368, - "slug": "piscine-object-module-03-smart", - "name": "Piscine Object - Module 03 - SMART" - }, - { - "id": 2369, - "slug": "piscine-object-module-04-design-pattern", - "name": "Piscine Object - Module 04 - Design Pattern" - }, - { - "id": 2370, - "slug": "piscine-object-module-05-practical-work", - "name": "Piscine Object - Module 05 - Practical work" - }, - { - "id": 1995, - "slug": "deprecated-python-module-01", - "name": "[DEPRECATED] Python Module 01" - }, - { - "id": 2371, - "slug": "abstract_data", - "name": "Abstract_data" - }, - { - "id": 2372, - "slug": "leaffliction", - "name": "Leaffliction" - }, - { - "id": 2373, - "slug": "microshop", - "name": "Microshop" - }, - { - "id": 2374, - "slug": "exam_test_43", - "name": "exam_test_43" - }, - { - "id": 2376, - "slug": "cellule2-0-gecko", - "name": "Cellule2-0-gecko" - }, - { - "id": 2377, - "slug": "cellule2-1-gecko", - "name": "Cellule2-1-gecko " - }, - { - "id": 2378, - "slug": "cellule2-2-gecko", - "name": "Cellule2-2-gecko " - }, - { - "id": 2379, - "slug": "cellule2-3-gecko", - "name": "Cellule2-3-gecko " - }, - { - "id": 2384, - "slug": "cellule0-0-tyto", - "name": "Cellule0-0-Tyto" - }, - { - "id": 2385, - "slug": "cellule0-1-tyto", - "name": "Cellule0-1-Tyto" - }, - { - "id": 2387, - "slug": "cellule0-2-tyto", - "name": "Cellule0-2-Tyto" - }, - { - "id": 2388, - "slug": "cellule0-3-tyto", - "name": "Cellule0-3-Tyto" - }, - { - "id": 2389, - "slug": "cellule1-0-weasel", - "name": "Cellule1-0-weasel" - }, - { - "id": 2390, - "slug": "cellule1-1-weasel", - "name": "Cellule1-1-weasel" - }, - { - "id": 2391, - "slug": "cellule1-2-weasel", - "name": "Cellule1-2-weasel" - }, - { - "id": 2392, - "slug": "cellule1-3-weasel", - "name": "Cellule1-3-weasel" - }, - { - "id": 2393, - "slug": "unleashthebox", - "name": "UnleashTheBox" - }, - { - "id": 2394, - "slug": "ft_microservices", - "name": "ft_microservices" - }, - { - "id": 2396, - "slug": "eu-aceito-porto", - "name": "Eu Aceito Porto" - }, - { - "id": 2397, - "slug": "eu-aceito-lisboa", - "name": "Eu Aceito Lisboa" - }, - { - "id": 2411, - "slug": "leonardo-java-day-00", - "name": "Leonardo Java day-00" - }, - { - "id": 2412, - "slug": "leonardo-java-day-01", - "name": "Leonardo Java day-01" - }, - { - "id": 2413, - "slug": "leonardo-java-day-02", - "name": "Leonardo Java day-02" - }, - { - "id": 2414, - "slug": "leonardo-java-day-03", - "name": "Leonardo Java day-03" - }, - { - "id": 2415, - "slug": "leonardo-java-day-04", - "name": "Leonardo Java day-04" - }, - { - "id": 2416, - "slug": "leonardo-java-day-05", - "name": "Leonardo Java day-05" - }, - { - "id": 2417, - "slug": "leonardo-java-day-06", - "name": "Leonardo Java day-06" - }, - { - "id": 2418, - "slug": "leonardo-java-day-07", - "name": "Leonardo Java day-07" - }, - { - "id": 2419, - "slug": "leonardo-java-day-08", - "name": "Leonardo Java day-08" - }, - { - "id": 2420, - "slug": "leonardo-java-day-09", - "name": "Leonardo Java day-09" - }, - { - "id": 2421, - "slug": "leonardo-java-rush-00", - "name": "Leonardo Java Rush-00" - }, - { - "id": 2422, - "slug": "leonardo-java-rush-01", - "name": "Leonardo Java Rush-01" - }, - { - "id": 2471, - "slug": "angular-piscine-week-1-day-00", - "name": "Angular Piscine Week 1 | Day 00" - }, - { - "id": 2472, - "slug": "angular-piscine-week-1-day-02", - "name": "Angular Piscine Week 1 | Day 02" - }, - { - "id": 2473, - "slug": "angular-piscine-week-1-day-03", - "name": "Angular Piscine Week 1 | Day 03" - }, - { - "id": 2474, - "slug": "angular-piscine-week-1-day-04", - "name": "Angular Piscine Week 1 | Day 04" - }, - { - "id": 2475, - "slug": "angular-piscine-week-1-rush-00", - "name": "Angular Piscine Week 1 | Rush 00" - }, - { - "id": 2476, - "slug": "angular-piscine-week-2-day-00", - "name": "Angular Piscine Week 2 | Day 00" - }, - { - "id": 2477, - "slug": "angular-piscine-week-2-day-01", - "name": "Angular Piscine Week 2 | Day 01" - }, - { - "id": 2478, - "slug": "angular-piscine-week-2-day-02", - "name": "Angular Piscine Week 2 | Day 02" - }, - { - "id": 2479, - "slug": "angular-piscine-week-2-day-03", - "name": "Angular Piscine Week 2 | Day 03" - }, - { - "id": 2480, - "slug": "angular-piscine-week-2-day-04", - "name": "Angular Piscine Week 2 | Day 04" - }, - { - "id": 2481, - "slug": "angular-piscine-week-2-rush-01", - "name": "Angular Piscine Week 2 | Rush-01" - }, - { - "id": 2484, - "slug": "dirbato-00", - "name": "Dirbato 00" - }, - { - "id": 2485, - "slug": "tokenizer", - "name": "Tokenizer" - }, - { - "id": 2486, - "slug": "mini-piscine-mobile-day-00", - "name": "Mini-Piscine Mobile Day 00" - }, - { - "id": 2487, - "slug": "mini-piscine-mobile-day-01", - "name": " Mini-Piscine Mobile Day 01" - }, - { - "id": 2488, - "slug": "mini-piscine-mobile-day-02", - "name": "Mini-Piscine Mobile Day 02" - }, - { - "id": 2489, - "slug": "mini-piscine-mobile-day-03", - "name": "Mini-Piscine Mobile Day 03" - }, - { - "id": 2490, - "slug": "mini-piscine-mobile-day-04", - "name": " Mini-Piscine Mobile Day 04" - }, - { - "id": 2491, - "slug": "mini-piscine-mobile-day-05", - "name": " Mini-Piscine Mobile Day 05" - }, - { - "id": 2494, - "slug": "dirbato-01", - "name": "Dirbato 01" - }, - { - "id": 2503, - "slug": "codam-startup-internship", - "name": "Codam Startup Internship" - }, - { - "id": 80, - "slug": "abstract-vm", - "name": "Abstract VM" - }, - { - "id": 79, - "slug": "libftasm", - "name": "LibftASM" - }, - { - "id": 78, - "slug": "mod1", - "name": "mod1" - }, - { - "id": 62, - "slug": "piscine-cpp", - "name": "Piscine CPP" - }, - { - "id": 61, - "slug": "rushes", - "name": "Rushes" - }, - { - "id": 48, - "slug": "piscine-php", - "name": "Piscine PHP" - }, - { - "id": 43, - "slug": "zappy", - "name": "Zappy" - }, - { - "id": 42, - "slug": "lem-ipc", - "name": "Lem-ipc" - }, - { - "id": 41, - "slug": "irc", - "name": "IRC" - }, - { - "id": 40, - "slug": "ft_p", - "name": "ft_p" - }, - { - "id": 39, - "slug": "philosophers", - "name": "Philosophers" - }, - { - "id": 38, - "slug": "ft_script", - "name": "ft_script" - }, - { - "id": 37, - "slug": "nm-otool", - "name": "Nm-otool" - }, - { - "id": 36, - "slug": "malloc", - "name": "Malloc" - }, - { - "id": 35, - "slug": "42sh", - "name": "42sh" - }, - { - "id": 34, - "slug": "ft_sh3", - "name": "ft_sh3" - }, - { - "id": 33, - "slug": "ft_select", - "name": "ft_select" - }, - { - "id": 31, - "slug": "ft_sh2", - "name": "ft_sh2" - }, - { - "id": 29, - "slug": "lem_in", - "name": "Lem_in" - }, - { - "id": 27, - "slug": "push_swap", - "name": "Push_swap" - }, - { - "id": 26, - "slug": "filler", - "name": "Filler" - }, - { - "id": 24, - "slug": "rt", - "name": "RT" - }, - { - "id": 23, - "slug": "rtv1", - "name": "RTv1" - }, - { - "id": 22, - "slug": "corewar", - "name": "Corewar" - }, - { - "id": 15, - "slug": "fract-ol", - "name": "Fract'ol" - }, - { - "id": 11, - "slug": "c-exam-alone-in-the-dark-beginner", - "name": "C Exam Alone In The Dark - Beginner" - }, - { - "id": 8, - "slug": "wolf3d", - "name": "Wolf3d" - }, - { - "id": 7, - "slug": "minishell", - "name": "minishell" - }, - { - "id": 5, - "slug": "ft_printf", - "name": "ft_printf" - }, - { - "id": 4, - "slug": "fdf", - "name": "FdF" - }, - { - "id": 3, - "slug": "ft_ls", - "name": "ft_ls" - }, - { - "id": 2, - "slug": "get_next_line", - "name": "GET_Next_Line" - }, - { - "id": 1, - "slug": "libft", - "name": "Libft" - } -] \ No newline at end of file diff --git a/env/campusIDs.json b/env/campusIDs.json deleted file mode 100644 index c56a54d..0000000 --- a/env/campusIDs.json +++ /dev/null @@ -1,51 +0,0 @@ -{ - "Amsterdam": 14, - - "Paris": 1, - "Lyon": 9, - "Brussels": 12, - "Helsinki": 13, - "Khouribga": 16, - "Moscow": 17, - "São-Paulo": 20, - "Benguerir": 21, - "Madrid": 22, - "Kazan": 23, - "Quebec": 25, - "Tokyo": 26, - "Rio de Janeiro": 28, - "Seoul": 29, - "Rome": 30, - "Angouleme": 31, - "Yerevan": 32, - "Bangkok": 33, - "Kuala Lumpur": 34, - "Adelaide": 36, - "Malaga": 37, - "Lisboa": 38, - "Heilbronn": 39, - "Urduliz": 40, - "Nice": 41, - "42Network": 42, - "Abu Dhabi": 43, - "Wolfsburg": 44, - "Alicante": 45, - "Barcelona": 46, - "Lausanne": 47, - "Mulhouse": 48, - "Istanbul": 49, - "Kocaeli": 50, - "Berlin": 51, - "Florence": 52, - "Vienna": 53, - "Tétouan": 55, - "Prague": 56, - "London": 57, - "Porto": 58, - "Le Havre": 62, - "Singapore": 64, - "Antananarivo": 65, - "Warsaw": 67, - "Luanda":68, - "Gyeongsan":69 -} diff --git a/env/projectIDs.json b/env/projectIDs.json deleted file mode 100644 index 78e0c19..0000000 --- a/env/projectIDs.json +++ /dev/null @@ -1,174 +0,0 @@ -{ - "libft": 1314, - "Born2beroot": 1994, - "ft_printf": 1316, - "get_next_line": 1327, - "push_swap": 1471, - "minitalk": 2005, - "pipex": 2004, - "so_long": 2009, - "FdF": 2008, - "fract-ol": 1476, - "minishell": 1331, - "Philosophers": 1334, - "miniRT": 1315, - "cub3d": 1326, - "CPP module 00": 1338, - "CPP module 01": 1339, - "CPP module 02": 1340, - "CPP module 03": 1341, - "CPP module 04": 1342, - "CPP module 05": 1343, - "CPP module 06": 1344, - "CPP module 07": 1345, - "CPP module 08": 1346, - "CPP module 09": 2309, - - "NetPractice": 2007, - "Inception": 1983, - "ft_irc": 1336, - "webserv": 1332, - "ft_containers": 1335, - "ft_transcendence": 1337, - - "Internship I": 1638, - "Internship I - Contract Upload": 1640, - "Internship I - Duration": 1639, - "Internship I - Company Mid Evaluation": 1641, - "Internship I - Company Final Evaluation": 1642, - "Internship I - Peer Video": 1643, - - "startup internship": 1662, - "startup internship - Contract Upload": 1663, - "startup internship - Duration": 1664, - "startup internship - Company Mid Evaluation": 1665, - "startup internship - Company Final Evaluation": 1666, - "startup internship - Peer Video": 1667, - - "Piscine Python Django": 1483, - "camagru": 1396, - "darkly":1405 , - "Piscine Swift iOS":1486 , - "swifty-proteins": 1406, - "ft_hangouts": 1379, - "matcha": 1401, - "swifty-companion": 1395, - "swingy": 1436, - "red-tetris": 1428, - "music-room": 1427, - "hypertube": 1402, - - "rt": 1855, - "scop": 1390, - "zappy": 1463, - "doom_nukem": 1853, - "abstract-vm": 1461, - "humangl": 1394, - "guimp": 1455, - "nibbler": 1386, - "42run": 1387, - "Piscine Unity": 1485, - "gbmu": 1411, - "bomberman": 1389, - "particle-system": 1410, - "in-the-shadows": 1409, - "ft_newton": 1962, - "ft_vox": 1449, - "xv": 1408, - "shaderpixel": 1454, - - "ft_ping": 1397, - "libasm": 1330, - "malloc": 1468, - "nm": 1467, - "strace": 1388, - "ft_traceroute": 1399, - "dr-quine": 1418, - "ft_ssl_md5": 1451, - "snow-crash": 1404, - "ft_nmap": 1400, - "woody-woodpacker": 1419, - "ft_ssl_des": 1452, - "rainfall": 1417, - "ft_malcolm": 1840, - "famine": 1430, - "ft_ssl_rsa": 1450, - "boot2root": 1446, - "matt-daemon": 1420, - "pestilence": 1443, - "override": 1448, - "war": 1444, - "death": 1445, - - "lem_in": 1470, - "computorv1": 1382, - "Piscine OCaml": 1484, - "n-puzzle": 1385, - "ready set boole": 2076, - "computorv2": 1433, - "expert-system": 1384, - "rubik": 1393, - "ft_turing": 1403, - "h42n42": 1429, - "matrix": 2077, - "mod1": 1462, - "gomoku": 1383, - "ft_ality": 1407, - "krpsim": 1392, - "fix-me": 1437, - "ft_linear_regression": 1391, - "dslr": 1453, - "total-perspective-vortex": 1460, - "multilayer-perceptron": 1457, - "ft_kalman": 2098, - - "ft_ls": 1479, - "ft_select": 1469, - "ft_script": 1466, - "42sh": 1854, - "lem-ipc": 1464, - "corewar": 1475, - "taskmaster": 1381, - "ft_linux": 1415, - "little-penguin-1": 1416, - "drivers-and-interrupts": 1422, - "process-and-memory": 1421, - "userspace_digressions": 1456, - "filesystem": 1423, - "kfs-1": 1425, - "kfs-2": 1424, - "kfs-3": 1426, - "kfs-4": 1431, - "kfs-5": 1432, - "kfs-6": 1438, - "kfs-7": 1439, - "kfs-8": 1440, - "kfs-9": 1441, - "kfs-x": 1442, - - "Part_Time I": 1650, - "Open Project": 1635, - "Internship II": 1644, - "Inception-of-Things": 2064, - - "C Piscine Shell 00": 1255, - "C Piscine Shell 01": 1256, - "C Piscine C 00": 1257, - "C Piscine C 01": 1258, - "C Piscine C 02": 1259, - "C Piscine C 03": 1260, - "C Piscine C 04": 1261, - "C Piscine C 05": 1262, - "C Piscine C 06": 1263, - "C Piscine C 07": 1270, - "C Piscine C 08": 1264, - "C Piscine C 09": 1265, - "C Piscine C 10": 1266, - "C Piscine C 11": 1267, - "C Piscine C 12": 1268, - "C Piscine C 13": 1271, - "C Piscine Rush 00": 1308, - "C Piscine Rush 01": 1310, - "C Piscine Rush 02": 1309, - "C Piscine BSQ": 1305 -} diff --git a/package-lock.json b/package-lock.json index 71fc9d6..127d845 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,10 @@ "": { "name": "@codam-coding-college/find-peers", "dependencies": { + "@codam/fast42": "^2.1.6", + "@prisma/client": "^6.14.0", "@types/compression": "^1.7.2", + "@types/cookie-parser": "^1.4.9", "@types/ejs": "3.1.0", "@types/express": "^4.17.17", "@types/express-session": "1.17.4", @@ -22,10 +25,10 @@ "@typescript-eslint/parser": "^5.48.2", "42-connector": "github:codam-coding-college/42-connector#3.1.0", "compression": "^1.7.4", + "cookie-parser": "^1.4.7", "crypto": "^1.0.1", "dotenv": "^16.0.3", "ejs": "3.1.8", - "eslint": "^8.32.0", "eslint-config-prettier": "^8.6.0", "eslint-plugin-prettier": "^4.2.1", "express": "4.17.3", @@ -36,42 +39,77 @@ "passport-oauth": "1.0.0", "path": "0.12.7", "request": "^2.88.2", - "typescript": "4.9.5" + "typescript": "^5.9.2" + }, + "devDependencies": { + "@types/passport-oauth2": "^1.8.0", + "eslint": "^8.57.1", + "globals": "^16.4.0", + "jiti": "^2.5.1", + "ts-node-dev": "^2.0.0", + "typescript-eslint": "^8.44.0" }, "engines": { "node": ">=18.0.0" } }, + "node_modules/@codam/fast42": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@codam/fast42/-/fast42-2.1.6.tgz", + "integrity": "sha512-aMoGdlzr9FL9snws4gonbajBYQRvGFB3HIYg1hZv9pMeFf+5dDk5wgfTl1aMAYFMnTBNZJTGo33gaYpWjQpuAg==", + "license": "ISC", + "dependencies": { + "@sergiiivzhenko/bottleneck": "2.19.7", + "node-cache": "^5.1.2", + "node-fetch": "^2.6.2", + "redis": "^3.1.2" + } + }, + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" + }, + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "dependencies": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, + "funding": { + "url": "https://opencollective.com/eslint" + }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "node_modules/@eslint-community/regexpp": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.1.tgz", - "integrity": "sha512-BISJ6ZE4xQsuL/FmsyRaiffpq977bMlsKfGHTQrOGFErfByxIe6iZTxPf/00Zon9b9a7iUykfQwejN3s2ZW/Bw==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", "engines": { "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, "node_modules/@eslint/eslintrc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", - "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "dependencies": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.0", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -87,11 +125,11 @@ } }, "node_modules/@eslint/eslintrc/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -102,10 +140,24 @@ } } }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@eslint/eslintrc/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/@eslint/eslintrc/node_modules/strip-json-comments": { "version": "3.1.1", @@ -119,20 +171,21 @@ } }, "node_modules/@eslint/js": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", - "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, "node_modules/@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", "dependencies": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "engines": { @@ -140,11 +193,11 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "dependencies": { - "ms": "2.1.2" + "ms": "^2.1.3" }, "engines": { "node": ">=6.0" @@ -156,9 +209,9 @@ } }, "node_modules/@humanwhocodes/config-array/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/@humanwhocodes/module-importer": { "version": "1.0.1", @@ -173,9 +226,35 @@ } }, "node_modules/@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead" + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", @@ -209,6 +288,58 @@ "node": ">= 8" } }, + "node_modules/@prisma/client": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.14.0.tgz", + "integrity": "sha512-8E/Nk3eL5g7RQIg/LUj1ICyDmhD053STjxrPxUtCRybs2s/2sOEcx9NpITuAOPn07HEpWBfhAVe1T/HYWXUPOw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "peerDependencies": { + "prisma": "*", + "typescript": ">=5.1.0" + }, + "peerDependenciesMeta": { + "prisma": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, + "node_modules/@sergiiivzhenko/bottleneck": { + "version": "2.19.7", + "resolved": "https://registry.npmjs.org/@sergiiivzhenko/bottleneck/-/bottleneck-2.19.7.tgz", + "integrity": "sha512-Wm06FyMoTsbVTO1CPQ5CubsB9x4uTSpi0C3W9TMYKOwcIEuXEtR6lRxtmpQe42Eic9assvGt+Po5rXsDPjzAMA==", + "license": "MIT" + }, + "node_modules/@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "node_modules/@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "node_modules/@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "node_modules/@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, "node_modules/@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -239,6 +370,15 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.9.tgz", + "integrity": "sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==", + "license": "MIT", + "peerDependencies": { + "@types/express": "*" + } + }, "node_modules/@types/ejs": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.0.tgz", @@ -274,9 +414,9 @@ } }, "node_modules/@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "node_modules/@types/mime": { "version": "3.0.1", @@ -296,6 +436,16 @@ "@types/node": "*" } }, + "node_modules/@types/oauth": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.6.tgz", + "integrity": "sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/passport": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz", @@ -304,6 +454,18 @@ "@types/express": "*" } }, + "node_modules/@types/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-6//z+4orIOy/g3zx17HyQ71GSRK4bs7Sb+zFasRoc2xzlv7ZCJ+vkDBYFci8U6HY+or6Zy7ajf4mz4rK7nsWJQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*", + "@types/oauth": "*", + "@types/passport": "*" + } + }, "node_modules/@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -339,9 +501,9 @@ } }, "node_modules/@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==" }, "node_modules/@types/serve-static": { "version": "1.15.0", @@ -434,20 +596,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/parser": { "version": "5.56.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.56.0.tgz", @@ -495,6 +643,63 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.0.tgz", + "integrity": "sha512-ZeaGNraRsq10GuEohKTo4295Z/SuGcSq2LzfGlqiuEvfArzo/VRrT0ZaJsVPuKZ55lVbNk8U6FcL+ZMH8CoyVA==", + "dev": true, + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.44.0", + "@typescript-eslint/types": "^8.44.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service/node_modules/@typescript-eslint/types": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.0.tgz", + "integrity": "sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/project-service/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/project-service/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 + }, "node_modules/@typescript-eslint/scope-manager": { "version": "5.56.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", @@ -511,6 +716,22 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.0.tgz", + "integrity": "sha512-x5Y0+AuEPqAInc6yd0n5DAcvtoQ/vyaGwuX5HE9n6qAefk1GaedqrLQF8kQGylLUb9pnZyLf+iEiL9fr8APDtQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, "node_modules/@typescript-eslint/type-utils": { "version": "5.56.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz", @@ -617,20 +838,6 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/utils": { "version": "5.56.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.56.0.tgz", @@ -656,20 +863,6 @@ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@typescript-eslint/utils/node_modules/semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "dependencies": { - "lru-cache": "^6.0.0" - }, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/@typescript-eslint/visitor-keys": { "version": "5.56.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.56.0.tgz", @@ -686,6 +879,11 @@ "url": "https://opencollective.com/typescript-eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" + }, "node_modules/42-connector": { "version": "3.1.0", "resolved": "git+ssh://git@github.com/codam-coding-college/42-connector.git#048906628e3727bb2f70f30fab486019aee3aa09", @@ -697,6 +895,19 @@ "url-parameter-append": "^1.0.5" } }, + "node_modules/42-connector/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -710,9 +921,9 @@ } }, "node_modules/acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "bin": { "acorn": "bin/acorn" }, @@ -728,6 +939,18 @@ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, + "node_modules/acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "dependencies": { + "acorn": "^8.11.0" + }, + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -765,6 +988,25 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "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, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -843,6 +1085,18 @@ "tweetnacl": "^0.14.3" } }, + "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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -882,16 +1136,22 @@ } }, "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" } }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -928,6 +1188,39 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "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, + "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/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -1028,6 +1321,28 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "license": "MIT", + "dependencies": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "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", @@ -1038,10 +1353,16 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, + "node_modules/create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -1089,6 +1410,15 @@ "node": ">=0.4.0" } }, + "node_modules/denque": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", + "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==", + "license": "Apache-2.0", + "engines": { + "node": ">=0.10" + } + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -1102,6 +1432,15 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, "node_modules/dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -1140,6 +1479,15 @@ "node": ">=12" } }, + "node_modules/dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "dependencies": { + "xtend": "^4.0.0" + } + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -1193,26 +1541,28 @@ } }, "node_modules/eslint": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", - "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.1", - "@eslint/js": "8.36.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.5.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -1220,22 +1570,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "bin": { @@ -1292,11 +1639,14 @@ } }, "node_modules/eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/debug": { @@ -1316,15 +1666,18 @@ } }, "node_modules/eslint/node_modules/eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint/node_modules/estraverse": { @@ -1361,6 +1714,20 @@ "node": ">=10.13.0" } }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -1374,25 +1741,14 @@ "node": ">=8" } }, - "node_modules/eslint/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/espree": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", - "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "dependencies": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" }, "engines": { "node": "^12.22.0 || ^14.17.0 || >=16.0.0" @@ -1561,15 +1917,15 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==" }, "node_modules/fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" }, "engines": { "node": ">=8.6.0" @@ -1638,9 +1994,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dependencies": { "to-regex-range": "^5.0.1" }, @@ -1666,35 +2022,22 @@ } }, "node_modules/flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dependencies": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" }, "engines": { "node": "^10.12.0 || >=12.0.0" } }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" }, "node_modules/forever-agent": { "version": "0.6.1", @@ -1738,6 +2081,29 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "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, + "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==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -1750,6 +2116,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1777,14 +2144,12 @@ } }, "node_modules/globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "dependencies": { - "type-fest": "^0.20.2" - }, + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -1814,6 +2179,11 @@ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -1843,6 +2213,18 @@ "node": ">=8" } }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/hot-shots": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/hot-shots/-/hot-shots-10.0.0.tgz", @@ -1903,9 +2285,9 @@ } }, "node_modules/import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "dependencies": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -1929,6 +2311,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -1947,6 +2330,33 @@ "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, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -2014,13 +2424,13 @@ "node": ">=10" } }, - "node_modules/js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/js-sdsl" + "node_modules/jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "dev": true, + "bin": { + "jiti": "lib/jiti-cli.mjs" } }, "node_modules/js-yaml": { @@ -2039,6 +2449,11 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -2073,6 +2488,14 @@ "node": ">=0.6.0" } }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dependencies": { + "json-buffer": "3.0.1" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -2104,16 +2527,11 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, - "node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } + "node_modules/make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true }, "node_modules/media-typer": { "version": "0.3.0", @@ -2145,11 +2563,11 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -2197,6 +2615,27 @@ "node": "*" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2226,6 +2665,18 @@ "node": ">= 0.6" } }, + "node_modules/node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "license": "MIT", + "dependencies": { + "clone": "2.x" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -2245,6 +2696,15 @@ } } }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/oauth": { "version": "0.9.15", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", @@ -2286,16 +2746,16 @@ } }, "node_modules/optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" }, "engines": { "node": ">= 0.8.0" @@ -2446,6 +2906,12 @@ "node": ">=8" } }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, "node_modules/path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -2599,6 +3065,64 @@ "node": ">= 0.8" } }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/redis": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz", + "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==", + "license": "MIT", + "dependencies": { + "denque": "^1.5.0", + "redis-commands": "^1.7.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/node-redis" + } + }, + "node_modules/redis-commands": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", + "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==", + "license": "MIT" + }, + "node_modules/redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "license": "MIT", + "dependencies": { + "redis-errors": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -2638,6 +3162,26 @@ "node": ">=0.6" } }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -2655,6 +3199,21 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -2701,6 +3260,17 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/send": { "version": "0.17.2", "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", @@ -2775,6 +3345,25 @@ "node": ">=8" } }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "node_modules/sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -2818,6 +3407,15 @@ "node": ">=8" } }, + "node_modules/strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/strip-json-comments": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.0.tgz", @@ -2840,6 +3438,18 @@ "node": ">=8" } }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -2881,6 +3491,144 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "node_modules/tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true, + "bin": { + "tree-kill": "cli.js" + } + }, + "node_modules/ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } + } + }, + "node_modules/ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "dependencies": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "bin": { + "ts-node-dev": "lib/bin.js", + "tsnd": "lib/bin.js" + }, + "engines": { + "node": ">=0.8.0" + }, + "peerDependencies": { + "node-notifier": "*", + "typescript": "*" + }, + "peerDependenciesMeta": { + "node-notifier": { + "optional": true + } + } + }, + "node_modules/ts-node-dev/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "dependencies": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + } + }, + "node_modules/tsconfig/node_modules/@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, + "node_modules/tsconfig/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -2951,17 +3699,284 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.0.tgz", + "integrity": "sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==", + "dev": true, + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.44.0", + "@typescript-eslint/parser": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0", + "@typescript-eslint/utils": "8.44.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.0.tgz", + "integrity": "sha512-EGDAOGX+uwwekcS0iyxVDmRV9HX6FLSM5kzrAToLTsr9OWCIKG/y3lQheCq18yZ5Xh78rRKJiEpP0ZaCs4ryOQ==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.44.0", + "@typescript-eslint/type-utils": "8.44.0", + "@typescript-eslint/utils": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.44.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/parser": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.0.tgz", + "integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==", + "dev": true, + "dependencies": { + "@typescript-eslint/scope-manager": "8.44.0", + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/scope-manager": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.0.tgz", + "integrity": "sha512-87Jv3E+al8wpD+rIdVJm/ItDBe/Im09zXIjFoipOjr5gHUhJmTzfFLuTJ/nPTMc2Srsroy4IBXwcTCHyRR7KzA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/type-utils": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.0.tgz", + "integrity": "sha512-9cwsoSxJ8Sak67Be/hD2RNt/fsqmWnNE1iHohG8lxqLSNY8xNfyY7wloo5zpW3Nu9hxVgURevqfcH6vvKCt6yg==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0", + "@typescript-eslint/utils": "8.44.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/types": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.0.tgz", + "integrity": "sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.0.tgz", + "integrity": "sha512-lqNj6SgnGcQZwL4/SBJ3xdPEfcBuhCG8zdcwCPgYcmiPLgokiNDKlbPzCwEwu7m279J/lBYWtDYL+87OEfn8Jw==", + "dev": true, + "dependencies": { + "@typescript-eslint/project-service": "8.44.0", + "@typescript-eslint/tsconfig-utils": "8.44.0", + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/utils": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.0.tgz", + "integrity": "sha512-nktOlVcg3ALo0mYlV+L7sWUD58KG4CMj1rb2HUVOO4aL3K/6wcD+NERqd0rrA5Vg06b42YhF6cFxeixsp9Riqg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.44.0", + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/typescript-eslint/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.0.tgz", + "integrity": "sha512-zaz9u8EJ4GBmnehlrpoKvj/E3dNbuQ7q0ucyZImm3cLqJ8INTc970B1qEqDX/Rzq65r3TvVTN7kHWPBoyW7DWw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.44.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/typescript-eslint/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/typescript-eslint/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/typescript-eslint/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/typescript-eslint/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/typescript-eslint/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/typescript-eslint/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 + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -3046,6 +4061,12 @@ "uuid": "bin/uuid" } }, + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -3096,9 +4117,9 @@ } }, "node_modules/word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "engines": { "node": ">=0.10.0" } @@ -3108,10 +4129,23 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true, + "engines": { + "node": ">=0.4" + } + }, + "node_modules/yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true, + "engines": { + "node": ">=6" + } }, "node_modules/yocto-queue": { "version": "0.1.0", @@ -3126,27 +4160,47 @@ } }, "dependencies": { + "@codam/fast42": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/@codam/fast42/-/fast42-2.1.6.tgz", + "integrity": "sha512-aMoGdlzr9FL9snws4gonbajBYQRvGFB3HIYg1hZv9pMeFf+5dDk5wgfTl1aMAYFMnTBNZJTGo33gaYpWjQpuAg==", + "requires": { + "@sergiiivzhenko/bottleneck": "2.19.7", + "node-cache": "^5.1.2", + "node-fetch": "^2.6.2", + "redis": "^3.1.2" + } + }, + "@cspotcode/source-map-support": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", + "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "dev": true, + "requires": { + "@jridgewell/trace-mapping": "0.3.9" + } + }, "@eslint-community/eslint-utils": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", - "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "version": "4.9.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz", + "integrity": "sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==", "requires": { - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.3" } }, "@eslint-community/regexpp": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.4.1.tgz", - "integrity": "sha512-BISJ6ZE4xQsuL/FmsyRaiffpq977bMlsKfGHTQrOGFErfByxIe6iZTxPf/00Zon9b9a7iUykfQwejN3s2ZW/Bw==" + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==" }, "@eslint/eslintrc": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.0.1.tgz", - "integrity": "sha512-eFRmABvW2E5Ho6f5fHLqgena46rOj7r7OKHYfLElqcBfGFHHpjBhivyi5+jOEQuSpdc/1phIZJlbC2te+tZNIw==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", "requires": { "ajv": "^6.12.4", "debug": "^4.3.2", - "espree": "^9.5.0", + "espree": "^9.6.0", "globals": "^13.19.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", @@ -3156,17 +4210,25 @@ }, "dependencies": { "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" + } + }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "requires": { + "type-fest": "^0.20.2" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "strip-json-comments": { "version": "3.1.1", @@ -3176,32 +4238,32 @@ } }, "@eslint/js": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.36.0.tgz", - "integrity": "sha512-lxJ9R5ygVm8ZWgYdUweoq5ownDlJ4upvoWmO4eLxBYHdMo+vZ/Rx0EN6MbKWDJOSUGrqJy2Gt+Dyv/VKml0fjg==" + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==" }, "@humanwhocodes/config-array": { - "version": "0.11.8", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.8.tgz", - "integrity": "sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==", + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", "requires": { - "@humanwhocodes/object-schema": "^1.2.1", - "debug": "^4.1.1", + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", "minimatch": "^3.0.5" }, "dependencies": { "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" } }, "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" } } }, @@ -3211,9 +4273,31 @@ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" }, "@humanwhocodes/object-schema": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", - "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==" + }, + "@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true + }, + "@jridgewell/trace-mapping": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", + "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" + } }, "@nodelib/fs.scandir": { "version": "2.1.5", @@ -3238,6 +4322,41 @@ "fastq": "^1.6.0" } }, + "@prisma/client": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.14.0.tgz", + "integrity": "sha512-8E/Nk3eL5g7RQIg/LUj1ICyDmhD053STjxrPxUtCRybs2s/2sOEcx9NpITuAOPn07HEpWBfhAVe1T/HYWXUPOw==", + "requires": {} + }, + "@sergiiivzhenko/bottleneck": { + "version": "2.19.7", + "resolved": "https://registry.npmjs.org/@sergiiivzhenko/bottleneck/-/bottleneck-2.19.7.tgz", + "integrity": "sha512-Wm06FyMoTsbVTO1CPQ5CubsB9x4uTSpi0C3W9TMYKOwcIEuXEtR6lRxtmpQe42Eic9assvGt+Po5rXsDPjzAMA==" + }, + "@tsconfig/node10": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.11.tgz", + "integrity": "sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==", + "dev": true + }, + "@tsconfig/node12": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz", + "integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==", + "dev": true + }, + "@tsconfig/node14": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz", + "integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==", + "dev": true + }, + "@tsconfig/node16": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz", + "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", + "dev": true + }, "@types/body-parser": { "version": "1.19.2", "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", @@ -3268,6 +4387,12 @@ "@types/node": "*" } }, + "@types/cookie-parser": { + "version": "1.4.9", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.9.tgz", + "integrity": "sha512-tGZiZ2Gtc4m3wIdLkZ8mkj1T6CEHb35+VApbL2T14Dew8HA7c+04dmKqsKRNC+8RJPm16JEK0tFSwdZqubfc4g==", + "requires": {} + }, "@types/ejs": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@types/ejs/-/ejs-3.1.0.tgz", @@ -3303,9 +4428,9 @@ } }, "@types/json-schema": { - "version": "7.0.11", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", - "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==" + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==" }, "@types/mime": { "version": "3.0.1", @@ -3325,6 +4450,15 @@ "@types/node": "*" } }, + "@types/oauth": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/@types/oauth/-/oauth-0.9.6.tgz", + "integrity": "sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/passport": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/@types/passport/-/passport-1.0.7.tgz", @@ -3333,6 +4467,17 @@ "@types/express": "*" } }, + "@types/passport-oauth2": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@types/passport-oauth2/-/passport-oauth2-1.8.0.tgz", + "integrity": "sha512-6//z+4orIOy/g3zx17HyQ71GSRK4bs7Sb+zFasRoc2xzlv7ZCJ+vkDBYFci8U6HY+or6Zy7ajf4mz4rK7nsWJQ==", + "dev": true, + "requires": { + "@types/express": "*", + "@types/oauth": "*", + "@types/passport": "*" + } + }, "@types/qs": { "version": "6.9.7", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", @@ -3367,9 +4512,9 @@ } }, "@types/semver": { - "version": "7.3.13", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz", - "integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==" + "version": "7.7.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", + "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==" }, "@types/serve-static": { "version": "1.15.0", @@ -3436,14 +4581,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "requires": { - "lru-cache": "^6.0.0" - } } } }, @@ -3473,6 +4610,40 @@ } } }, + "@typescript-eslint/project-service": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.44.0.tgz", + "integrity": "sha512-ZeaGNraRsq10GuEohKTo4295Z/SuGcSq2LzfGlqiuEvfArzo/VRrT0ZaJsVPuKZ55lVbNk8U6FcL+ZMH8CoyVA==", + "dev": true, + "requires": { + "@typescript-eslint/tsconfig-utils": "^8.44.0", + "@typescript-eslint/types": "^8.44.0", + "debug": "^4.3.4" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.0.tgz", + "integrity": "sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA==", + "dev": true + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + } + } + }, "@typescript-eslint/scope-manager": { "version": "5.56.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.56.0.tgz", @@ -3482,6 +4653,13 @@ "@typescript-eslint/visitor-keys": "5.56.0" } }, + "@typescript-eslint/tsconfig-utils": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.44.0.tgz", + "integrity": "sha512-x5Y0+AuEPqAInc6yd0n5DAcvtoQ/vyaGwuX5HE9n6qAefk1GaedqrLQF8kQGylLUb9pnZyLf+iEiL9fr8APDtQ==", + "dev": true, + "requires": {} + }, "@typescript-eslint/type-utils": { "version": "5.56.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.56.0.tgz", @@ -3539,14 +4717,6 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "requires": { - "lru-cache": "^6.0.0" - } } } }, @@ -3563,16 +4733,6 @@ "@typescript-eslint/typescript-estree": "5.56.0", "eslint-scope": "^5.1.1", "semver": "^7.3.7" - }, - "dependencies": { - "semver": { - "version": "7.3.8", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", - "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", - "requires": { - "lru-cache": "^6.0.0" - } - } } }, "@typescript-eslint/visitor-keys": { @@ -3584,6 +4744,11 @@ "eslint-visitor-keys": "^3.3.0" } }, + "@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==" + }, "42-connector": { "version": "git+ssh://git@github.com/codam-coding-college/42-connector.git#048906628e3727bb2f70f30fab486019aee3aa09", "from": "42-connector@github:codam-coding-college/42-connector#3.1.0", @@ -3591,6 +4756,13 @@ "node-fetch": "^2.1.0", "typescript": "^4.2.4", "url-parameter-append": "^1.0.5" + }, + "dependencies": { + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" + } } }, "accepts": { @@ -3603,9 +4775,9 @@ } }, "acorn": { - "version": "8.8.2", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", - "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==" + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" }, "acorn-jsx": { "version": "5.3.2", @@ -3613,6 +4785,15 @@ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "requires": {} }, + "acorn-walk": { + "version": "8.3.4", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", + "integrity": "sha512-ueEepnujpqee2o5aIYnvHU6C0A42MNdsIDeqy5BydrkuC5R1ZuUFnm27EeFJGoEHJQgn3uleRvmTXaJgfXbt4g==", + "dev": true, + "requires": { + "acorn": "^8.11.0" + } + }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -3637,6 +4818,22 @@ "color-convert": "^2.0.1" } }, + "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, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -3703,6 +4900,12 @@ "tweetnacl": "^0.14.3" } }, + "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 + }, "bindings": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -3739,13 +4942,19 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, + "buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, "bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -3770,6 +4979,27 @@ "supports-color": "^7.1.0" } }, + "chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.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" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==" + }, "color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3848,6 +5078,22 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" }, + "cookie-parser": { + "version": "1.4.7", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.7.tgz", + "integrity": "sha512-nGUvgXnotP3BsjiLX2ypbQnWoGUPIIfHQNZkkC668ntrzGWEZVW70HDEB1qnNGMicPje6EttlIgzo51YSwNQGw==", + "requires": { + "cookie": "0.7.2", + "cookie-signature": "1.0.6" + }, + "dependencies": { + "cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==" + } + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", @@ -3858,10 +5104,16 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==" }, + "create-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", + "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", + "dev": true + }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "requires": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", @@ -3899,6 +5151,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" }, + "denque": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.5.1.tgz", + "integrity": "sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==" + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -3909,6 +5166,12 @@ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, "dir-glob": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", @@ -3937,6 +5200,15 @@ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" }, + "dynamic-dedupe": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", + "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "dev": true, + "requires": { + "xtend": "^4.0.0" + } + }, "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -3975,26 +5247,27 @@ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" }, "eslint": { - "version": "8.36.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.36.0.tgz", - "integrity": "sha512-Y956lmS7vDqomxlaaQAHVmeb4tNMp2FWIvU/RnU5BD3IKMD/MJPr76xdyr68P8tV1iNMvN2mRK0yy3c+UjL+bw==", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", "requires": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.4.0", - "@eslint/eslintrc": "^2.0.1", - "@eslint/js": "8.36.0", - "@humanwhocodes/config-array": "^0.11.8", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", "@nodelib/fs.walk": "^1.2.8", - "ajv": "^6.10.0", + "@ungap/structured-clone": "^1.2.0", + "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.1.1", - "eslint-visitor-keys": "^3.3.0", - "espree": "^9.5.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", @@ -4002,22 +5275,19 @@ "find-up": "^5.0.0", "glob-parent": "^6.0.2", "globals": "^13.19.0", - "grapheme-splitter": "^1.0.4", + "graphemer": "^1.4.0", "ignore": "^5.2.0", - "import-fresh": "^3.0.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", - "js-sdsl": "^4.1.4", "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", "minimatch": "^3.1.2", "natural-compare": "^1.4.0", - "optionator": "^0.9.1", + "optionator": "^0.9.3", "strip-ansi": "^6.0.1", - "strip-json-comments": "^3.1.0", "text-table": "^0.2.0" }, "dependencies": { @@ -4030,9 +5300,9 @@ } }, "eslint-scope": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", - "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "requires": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" @@ -4060,6 +5330,14 @@ "is-glob": "^4.0.3" } }, + "globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "requires": { + "type-fest": "^0.20.2" + } + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -4069,11 +5347,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" } } }, @@ -4101,18 +5374,18 @@ } }, "eslint-visitor-keys": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", - "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==" }, "espree": { - "version": "9.5.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.5.0.tgz", - "integrity": "sha512-JPbJGhKc47++oo4JkEoTe2wjy4fmMwvFpgJT9cQzmfXKp22Dr6Hf1tdCteLz1h0P3t+mGvWZ+4Uankvh8+c6zw==", + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", "requires": { - "acorn": "^8.8.0", + "acorn": "^8.9.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.3.0" + "eslint-visitor-keys": "^3.4.1" } }, "esquery": { @@ -4245,15 +5518,15 @@ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==" }, "fast-glob": { - "version": "3.2.12", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", - "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "requires": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", - "micromatch": "^4.0.4" + "micromatch": "^4.0.8" } }, "fast-json-stable-stringify": { @@ -4315,9 +5588,9 @@ } }, "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "requires": { "to-regex-range": "^5.0.1" } @@ -4337,28 +5610,19 @@ } }, "flat-cache": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", - "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "requires": { - "flatted": "^3.1.0", + "flatted": "^3.2.9", + "keyv": "^4.5.3", "rimraf": "^3.0.2" - }, - "dependencies": { - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - } } }, "flatted": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", - "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==" }, "forever-agent": { "version": "0.6.1", @@ -4390,6 +5654,19 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, + "fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", @@ -4420,12 +5697,10 @@ } }, "globals": { - "version": "13.20.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", - "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", - "requires": { - "type-fest": "^0.20.2" - } + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true }, "globby": { "version": "11.1.0", @@ -4445,6 +5720,11 @@ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + }, "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -4464,6 +5744,15 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dev": true, + "requires": { + "function-bind": "^1.1.2" + } + }, "hot-shots": { "version": "10.0.0", "resolved": "https://registry.npmjs.org/hot-shots/-/hot-shots-10.0.0.tgz", @@ -4508,9 +5797,9 @@ "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==" }, "import-fresh": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", - "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", "requires": { "parent-module": "^1.0.0", "resolve-from": "^4.0.0" @@ -4540,6 +5829,24 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" }, + "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, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "dev": true, + "requires": { + "hasown": "^2.0.2" + } + }, "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -4589,10 +5896,11 @@ "minimatch": "^3.0.4" } }, - "js-sdsl": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.4.0.tgz", - "integrity": "sha512-FfVSdx6pJ41Oa+CF7RDaFmTnCaFhua+SNYQX74riGOpl96x+2jQCqEfQ2bnXu/5DPCqlRuiqyvTJM0Qjz26IVg==" + "jiti": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.5.1.tgz", + "integrity": "sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==", + "dev": true }, "js-yaml": { "version": "4.1.0", @@ -4607,6 +5915,11 @@ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" }, + "json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + }, "json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -4638,6 +5951,14 @@ "verror": "1.10.0" } }, + "keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "requires": { + "json-buffer": "3.0.1" + } + }, "levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4660,13 +5981,11 @@ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true }, "media-typer": { "version": "0.3.0", @@ -4689,11 +6008,11 @@ "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, @@ -4723,6 +6042,18 @@ "brace-expansion": "^1.1.7" } }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -4749,6 +6080,14 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" }, + "node-cache": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "requires": { + "clone": "2.x" + } + }, "node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -4757,6 +6096,12 @@ "whatwg-url": "^5.0.0" } }, + "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 + }, "oauth": { "version": "0.9.15", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", @@ -4789,16 +6134,16 @@ } }, "optionator": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", - "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "requires": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", - "word-wrap": "^1.2.3" + "word-wrap": "^1.2.5" } }, "p-limit": { @@ -4895,6 +6240,12 @@ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", @@ -4989,6 +6340,44 @@ "unpipe": "1.0.0" } }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "redis": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/redis/-/redis-3.1.2.tgz", + "integrity": "sha512-grn5KoZLr/qrRQVwoSkmzdbw6pwF+/rwODtrOr6vuBRiR/f3rjSTGupbF90Zpqm2oenix8Do6RV7pYEkGwlKkw==", + "requires": { + "denque": "^1.5.0", + "redis-commands": "^1.7.0", + "redis-errors": "^1.2.0", + "redis-parser": "^3.0.0" + } + }, + "redis-commands": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.7.0.tgz", + "integrity": "sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==" + }, + "redis-errors": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/redis-errors/-/redis-errors-1.2.0.tgz", + "integrity": "sha512-1qny3OExCf0UvUV/5wpYKf2YwPcOqXzkwKKSmKHiE6ZMQs5heeE/c8eXK+PNllPvmjgAbfnsbpkGZWy8cBpn9w==" + }, + "redis-parser": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-3.0.0.tgz", + "integrity": "sha512-DJnGAeenTdpMEH6uAJRK/uiyEIH9WVsUmoLwzudwGJUwZPp80PDBWPHXSAGNPwNvIXAbe7MSUB1zQFugFml66A==", + "requires": { + "redis-errors": "^1.0.0" + } + }, "request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -5023,6 +6412,17 @@ } } }, + "resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "dev": true, + "requires": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, "resolve-from": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", @@ -5033,6 +6433,14 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, "run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -5051,6 +6459,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==" + }, "send": { "version": "0.17.2", "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", @@ -5112,6 +6525,22 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==" }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, "sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -5141,6 +6570,12 @@ "ansi-regex": "^5.0.1" } }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", + "dev": true + }, "strip-json-comments": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.0.tgz", @@ -5154,6 +6589,12 @@ "has-flag": "^4.0.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -5186,6 +6627,95 @@ "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" }, + "tree-kill": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", + "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", + "dev": true + }, + "ts-api-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz", + "integrity": "sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==", + "dev": true, + "requires": {} + }, + "ts-node": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", + "dev": true, + "requires": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + } + }, + "ts-node-dev": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ts-node-dev/-/ts-node-dev-2.0.0.tgz", + "integrity": "sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==", + "dev": true, + "requires": { + "chokidar": "^3.5.1", + "dynamic-dedupe": "^0.3.0", + "minimist": "^1.2.6", + "mkdirp": "^1.0.4", + "resolve": "^1.0.0", + "rimraf": "^2.6.1", + "source-map-support": "^0.5.12", + "tree-kill": "^1.2.2", + "ts-node": "^10.4.0", + "tsconfig": "^7.0.0" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + } + } + }, + "tsconfig": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/tsconfig/-/tsconfig-7.0.0.tgz", + "integrity": "sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw==", + "dev": true, + "requires": { + "@types/strip-bom": "^3.0.0", + "@types/strip-json-comments": "0.0.30", + "strip-bom": "^3.0.0", + "strip-json-comments": "^2.0.0" + }, + "dependencies": { + "@types/strip-json-comments": { + "version": "0.0.30", + "resolved": "https://registry.npmjs.org/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz", + "integrity": "sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ==", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true + } + } + }, "tslib": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", @@ -5235,9 +6765,167 @@ } }, "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==" + }, + "typescript-eslint": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.44.0.tgz", + "integrity": "sha512-ib7mCkYuIzYonCq9XWF5XNw+fkj2zg629PSa9KNIQ47RXFF763S5BIX4wqz1+FLPogTZoiw8KmCiRPRa8bL3qw==", + "dev": true, + "requires": { + "@typescript-eslint/eslint-plugin": "8.44.0", + "@typescript-eslint/parser": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0", + "@typescript-eslint/utils": "8.44.0" + }, + "dependencies": { + "@typescript-eslint/eslint-plugin": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.44.0.tgz", + "integrity": "sha512-EGDAOGX+uwwekcS0iyxVDmRV9HX6FLSM5kzrAToLTsr9OWCIKG/y3lQheCq18yZ5Xh78rRKJiEpP0ZaCs4ryOQ==", + "dev": true, + "requires": { + "@eslint-community/regexpp": "^4.10.0", + "@typescript-eslint/scope-manager": "8.44.0", + "@typescript-eslint/type-utils": "8.44.0", + "@typescript-eslint/utils": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0", + "graphemer": "^1.4.0", + "ignore": "^7.0.0", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.1.0" + } + }, + "@typescript-eslint/parser": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.44.0.tgz", + "integrity": "sha512-VGMpFQGUQWYT9LfnPcX8ouFojyrZ/2w3K5BucvxL/spdNehccKhB4jUyB1yBCXpr2XFm0jkECxgrpXBW2ipoAw==", + "dev": true, + "requires": { + "@typescript-eslint/scope-manager": "8.44.0", + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0", + "debug": "^4.3.4" + } + }, + "@typescript-eslint/scope-manager": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.44.0.tgz", + "integrity": "sha512-87Jv3E+al8wpD+rIdVJm/ItDBe/Im09zXIjFoipOjr5gHUhJmTzfFLuTJ/nPTMc2Srsroy4IBXwcTCHyRR7KzA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0" + } + }, + "@typescript-eslint/type-utils": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.44.0.tgz", + "integrity": "sha512-9cwsoSxJ8Sak67Be/hD2RNt/fsqmWnNE1iHohG8lxqLSNY8xNfyY7wloo5zpW3Nu9hxVgURevqfcH6vvKCt6yg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0", + "@typescript-eslint/utils": "8.44.0", + "debug": "^4.3.4", + "ts-api-utils": "^2.1.0" + } + }, + "@typescript-eslint/types": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.44.0.tgz", + "integrity": "sha512-ZSl2efn44VsYM0MfDQe68RKzBz75NPgLQXuGypmym6QVOWL5kegTZuZ02xRAT9T+onqvM6T8CdQk0OwYMB6ZvA==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.44.0.tgz", + "integrity": "sha512-lqNj6SgnGcQZwL4/SBJ3xdPEfcBuhCG8zdcwCPgYcmiPLgokiNDKlbPzCwEwu7m279J/lBYWtDYL+87OEfn8Jw==", + "dev": true, + "requires": { + "@typescript-eslint/project-service": "8.44.0", + "@typescript-eslint/tsconfig-utils": "8.44.0", + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/visitor-keys": "8.44.0", + "debug": "^4.3.4", + "fast-glob": "^3.3.2", + "is-glob": "^4.0.3", + "minimatch": "^9.0.4", + "semver": "^7.6.0", + "ts-api-utils": "^2.1.0" + } + }, + "@typescript-eslint/utils": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.44.0.tgz", + "integrity": "sha512-nktOlVcg3ALo0mYlV+L7sWUD58KG4CMj1rb2HUVOO4aL3K/6wcD+NERqd0rrA5Vg06b42YhF6cFxeixsp9Riqg==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.7.0", + "@typescript-eslint/scope-manager": "8.44.0", + "@typescript-eslint/types": "8.44.0", + "@typescript-eslint/typescript-estree": "8.44.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.44.0.tgz", + "integrity": "sha512-zaz9u8EJ4GBmnehlrpoKvj/E3dNbuQ7q0ucyZImm3cLqJ8INTc970B1qEqDX/Rzq65r3TvVTN7kHWPBoyW7DWw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "8.44.0", + "eslint-visitor-keys": "^4.2.1" + } + }, + "brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "requires": { + "ms": "^2.1.3" + } + }, + "eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true + }, + "ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true + }, + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + }, + "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 + } + } }, "uid-safe": { "version": "2.1.5", @@ -5305,6 +6993,12 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, + "v8-compile-cache-lib": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", + "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", + "dev": true + }, "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -5343,19 +7037,26 @@ } }, "word-wrap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", - "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==" }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true }, "yocto-queue": { "version": "0.1.0", diff --git a/package.json b/package.json index 4780b54..e20ebbb 100644 --- a/package.json +++ b/package.json @@ -10,7 +10,10 @@ "lint:fix": "eslint --fix src; prettier --write src" }, "dependencies": { + "@codam/fast42": "^2.1.6", + "@prisma/client": "^6.14.0", "@types/compression": "^1.7.2", + "@types/cookie-parser": "^1.4.9", "@types/ejs": "3.1.0", "@types/express": "^4.17.17", "@types/express-session": "1.17.4", @@ -25,10 +28,10 @@ "@typescript-eslint/parser": "^5.48.2", "42-connector": "github:codam-coding-college/42-connector#3.1.0", "compression": "^1.7.4", + "cookie-parser": "^1.4.7", "crypto": "^1.0.1", "dotenv": "^16.0.3", "ejs": "3.1.8", - "eslint": "^8.32.0", "eslint-config-prettier": "^8.6.0", "eslint-plugin-prettier": "^4.2.1", "express": "4.17.3", @@ -39,9 +42,17 @@ "passport-oauth": "1.0.0", "path": "0.12.7", "request": "^2.88.2", - "typescript": "4.9.5" + "typescript": "^5.9.2" }, "engines": { "node": ">=18.0.0" + }, + "devDependencies": { + "@types/passport-oauth2": "^1.8.0", + "eslint": "^8.57.1", + "globals": "^16.4.0", + "jiti": "^2.5.1", + "ts-node-dev": "^2.0.0", + "typescript-eslint": "^8.44.0" } } diff --git a/prisma/schema.prisma b/prisma/schema.prisma new file mode 100644 index 0000000..082ff03 --- /dev/null +++ b/prisma/schema.prisma @@ -0,0 +1,59 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions? +// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "sqlite" + url = "file:./myfindpeers.db" +} + +model Campus { + id Int @id + name String + users User[] +} + +model Project { + id Int @id + name String + slug String + difficulty Int? + users ProjectUser[] +} + +model User { + id Int @id + login String @unique + primary_campus_id Int? + image_url String? + pool String + anonymize_date String? + + campus Campus? @relation(fields: [primary_campus_id], references: [id]) + projects ProjectUser[] +} + +model ProjectUser { + project_id Int + user_id Int + created_at String + updated_at String + validated_at String? + status String + + project Project @relation(fields: [project_id], references: [id]) + user User @relation(fields: [user_id], references: [id]) + + @@id([project_id, user_id]) +} + +model Sync { + id Int @id @default(autoincrement()) + last_pull String? +} diff --git a/src/app.ts b/src/app.ts index 29d84d5..c365245 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,30 +1,44 @@ // eslint-disable-next-line require('dotenv').config({ path: __dirname + '/../env/.env' }) -import { syncCampuses, campusDBs } from './db' import { startWebserver } from './express' import { env } from './env' +import { DatabaseService } from './services'; +import { syncWithIntra } from './sync' import util from 'util' // set depth of object expansion in terminal as printed by console.*() -util.inspect.defaultOptions.depth = 10 +util.inspect.defaultOptions.depth = 10; -function msUntilNextPull(): number { - let nextPull = env.pullTimeout - for (const campus of Object.values(env.campuses)) { - const lastPullAgo = Date.now() - campusDBs[campus.name].lastPull - const msUntilNexPull = Math.max(0, env.pullTimeout - lastPullAgo) - nextPull = Math.min(nextPull, msUntilNexPull) +/** + * Calculates the time in milliseconds until the next pull request should be made. + * @returns How many milliseconds until the next pull request should be made. + */ +async function msUntilNextPull(): Promise { + const lastPullAgo = await DatabaseService.getLastSyncTimestamp().then(date => { + if (!date) { + console.warn('No last sync timestamp found, assuming first pull.'); + return env.pullTimeout; + } + return Date.now() - date.getTime(); + }); + const msUntilNextPull = Math.max(0, env.pullTimeout - lastPullAgo); + if (msUntilNextPull === 0) { + console.warn(`Last pull was more than ${env.pullTimeout / 1000 / 60 / 60} hours ago, pulling immediately.`); + return (0); } - return nextPull + console.info(`Next pull will be in ${(msUntilNextPull / 1000 / 60 / 60).toFixed(2)} hours.`); + return msUntilNextPull; } +// Main Program Execution ;(async () => { - const port = parseInt(process.env['PORT'] || '8080') - await startWebserver(port) + const port = parseInt(process.env['PORT'] || '8080'); + await startWebserver(port); while (true) { - await syncCampuses() - await new Promise(resolve => setTimeout(resolve, msUntilNextPull() + 1000)) + await syncWithIntra(); + await DatabaseService.anonymizeOldEntries(); + await new Promise(async resolve => setTimeout(resolve, await msUntilNextPull() + 1000)); } })() diff --git a/src/authentication.ts b/src/authentication.ts index 8508e5d..140ad67 100644 --- a/src/authentication.ts +++ b/src/authentication.ts @@ -1,100 +1,99 @@ import passport from 'passport' -// eslint-disable-next-line @typescript-eslint/no-var-requires -const { OAuth2Strategy } = require('passport-oauth') +import { Strategy as OAuth2Strategy } from 'passport-oauth2' import fetch from 'node-fetch' -import fs from 'fs' import { env } from './env' -import { UserProfile } from './types' import { Request, Response, NextFunction } from 'express' +import { log } from './logger' +import { DatabaseService } from './services' +import { transformApiUserToDb, transformApiCampusToDb } from './transform' +/** + * Middleware to ensure user is authenticated. + * @param req Request object containing user information + * @param res Response object to redirect if not authenticated + * @param next Function to call after authentication check + * @returns Redirects to OAuth login if user is not authenticated + */ export function authenticate(req: Request, res: Response, next: NextFunction) { if (!req.user) { - res.redirect(`/auth/${env.provider}`) + res.redirect(`${env.authPath}`); } else { - next() + next(); } } -const usersDB: UserProfile[] = [] -const emptyUsersDB: string = JSON.stringify(usersDB) -if (!fs.existsSync(env.userDBpath) || fs.statSync(env.userDBpath).size < emptyUsersDB.length) { - fs.writeFileSync(env.userDBpath, emptyUsersDB) -} - -const users: UserProfile[] = JSON.parse(fs.readFileSync(env.userDBpath).toString()) - -passport.serializeUser((user, done) => { - //@ts-ignore - done(null, user.id) +// Store (only) access token in session +passport.serializeUser((user: any, done) => { + done(null, { accessToken: user.accessToken, login: user.login }); }) -passport.deserializeUser((id, done) => { - const user = users.find(user => user.id === id) - done(null, user) -}) - -async function getProfile(accessToken: string, refreshToken: string): Promise { +// On every request, validate access token +passport.deserializeUser(async (sessionUser: { accessToken: string, login: string }, done) => { try { - const response = await fetch('https://api.intra.42.fr/v2/me', { - headers: { - Authorization: `Bearer ${accessToken}`, - }, - }) - const json = await response.json() - const profile: UserProfile = { - id: json.id, - login: json.login, - first_name: json.first_name, - displayname: json.displayname, - campusID: json.campus.length > 0 ? json.campus[0].id : 42, // set user's campus to first one listed in API call - campusName: json.campus.length > 0 ? json.campus[0].name : 'Paris', - timeZone: json.campus.length > 0 ? json.campus[0].time_zone : 'Europe/Paris', - accessToken, - refreshToken, - } - for (const i in json.campus_users) { - // get user's primary campus - if (json.campus_users[i].is_primary) { - for (const j in json.campus) { - // get primary campus name and store it in UserProfile (overwriting the one assigned above, which might not be primary) - if (json.campus[j].id === json.campus_users[i].campus_id) { - profile.campusName = json.campus[j].name - profile.timeZone = json.campus[j].time_zone - profile.campusID = json.campus_users[i].campus_id - break - } - } - break - } + const user = await DatabaseService.findUserByLogin(sessionUser.login); + + if (!user) { + log(2, 'Access token is no longer valid'); + done(null, false); } - return profile - } catch (err) { - return null + + done(null, { accessToken: sessionUser.accessToken, isAuthenticated: true }); + + } catch (error) { + log(2, 'Cannot verify token'); + done(null, false); } -} +}) + +// OAuth2 strategy for initial authentication const opt = { authorizationURL: env.authorizationURL, tokenURL: env.tokenURL, clientID: env.tokens.userAuth.UID, clientSecret: env.tokens.userAuth.secret, callbackURL: env.tokens.userAuth.callbackURL, - // passReqToCallback: true } -const client = new OAuth2Strategy(opt, async (accessToken: string, refreshToken: string, _profile: string, done: (err: string | null, user: UserProfile | null) => void) => { - // fires when user clicked allow - const newUser = await getProfile(accessToken, refreshToken) - if (!newUser) { - return done('cannot get user info', null) - } - const userIndex = users.findIndex(user => user.id === newUser.id) - if (userIndex < 0) { - users.push(newUser) - } else { - users[userIndex] = newUser + +const client = new OAuth2Strategy(opt, async (accessToken: string, refreshToken: string, _profile: string, done: (err: string | null, user: any) => void) => { + try { + const userDB = await fetchandInsertUserData(accessToken); + done(null, { accessToken, refreshToken, login: userDB.login }); + } catch (error) { + done('Authentication failed', null); } - await fs.promises.writeFile(env.userDBpath, JSON.stringify(users)) - done(null, newUser) }) -passport.use(env.provider, client) + +async function fetchandInsertUserData(accessToken: string) { + const response = await fetch('https://api.intra.42.fr/v2/me', { + headers: { Authorization: `Bearer ${accessToken}` }, + }) + const json = await response.json(); + const userDB = transformApiUserToDb(json); + if (await DatabaseService.findUserByLogin(userDB.login) === null + && await DatabaseService.findUserByLogin('3c3' + userDB.login) === null) { + const missingCampusId = await DatabaseService.getMissingCampusId(json); + if (missingCampusId !== null) { + log(2, `Found missing campus ID ${missingCampusId}, syncing...`); + try { + const campusResponse = await fetch(`https://api.intra.42.fr/v2/campus/${missingCampusId}`, { + headers: { Authorization: `Bearer ${accessToken}` }, + }); + const campusJson = await campusResponse.json(); + await DatabaseService.insertCampus({ ...transformApiCampusToDb(campusJson) }); + userDB.primary_campus_id = missingCampusId; + log(1, `Successfully synced and assigned campus ID ${missingCampusId} to user ${userDB.login}`); + + } catch (error) { + console.error(`Assigning to non-existent campus; failed to fetch ${missingCampusId}:`, error); + DatabaseService.insertCampus({ id: 42, name: `Ghost Campus` }); + userDB.primary_campus_id = 42; // Assign to Ghost Campus + } + } + await DatabaseService.insertUser(userDB); + } + return userDB; +} + +passport.use(env.provider, client); export { passport } diff --git a/src/db.ts b/src/db.ts deleted file mode 100644 index 33ac30e..0000000 --- a/src/db.ts +++ /dev/null @@ -1,172 +0,0 @@ -import fs from 'fs' -import { API } from '42-connector' -import { ApiProject, Project, ProjectSubscriber } from './types' -import { env, Campus, ProjectStatus, CampusName } from './env' -import { logCampus, log, msToHuman, nowISO } from './logger' -import * as StatsD from './statsd' - -const Api: API = new API(env.tokens.sync.UID, env.tokens.sync.secret, { - maxRequestPerSecond: env.tokens.sync.maxRequestPerSecond, - logging: env.logLevel >= 3, -}) - -export interface CampusDB { - name: CampusName - projects: Project[] - lastPull: number -} - -export const campusDBs: Record = {} as Record - -fs.mkdirSync(env.databaseRoot, { recursive: true }) -function setupCampusDB(campus: Campus) { - const campusDB: CampusDB = { - name: campus.name, - projects: [], - lastPull: 0, - } - - fs.mkdirSync(campus.databasePath, { recursive: true }) - if (!fs.existsSync(campus.projectUsersPath)) { - fs.writeFileSync(campus.projectUsersPath, '[]') - } - campusDB.projects = JSON.parse(fs.readFileSync(campus.projectUsersPath).toString()) - if (!fs.existsSync(campus.lastPullPath)) { - fs.writeFileSync(campus.lastPullPath, '0') - } - campusDB.lastPull = parseInt(fs.readFileSync(campus.lastPullPath).toString()) - campusDBs[campus.name] = campusDB -} - -for (const campus of Object.values(env.campuses)) { - setupCampusDB(campus) -} - -// Next time we use SQL -function findProjectUserByLogin(login: string, projectName: string): ProjectSubscriber | undefined { - for (const campus of Object.values(env.campuses)) { - const projects = campusDBs[campus.name].projects as Project[] - for (const project of projects) { - if (project.name !== projectName) { - continue - } - const user = project.users.find(x => x.login === login) - if (user) { - return user - } - } - } - return undefined -} - -function getUpdate(status: ProjectStatus, existingUser?: ProjectSubscriber): { new: boolean; lastChangeD: Date } { - if (!existingUser) { - return { new: true, lastChangeD: new Date() } - } - - if (status !== existingUser.status) { - return { new: true, lastChangeD: new Date() } - } - - const lastChangeD = new Date(existingUser.lastChangeD) - const isNew = Date.now() - lastChangeD.getTime() < env.userNewStatusThresholdDays * 24 * 60 * 60 * 1000 - return { new: isNew, lastChangeD: lastChangeD } -} - -// Intra's 'validated' key is sometimes wrong, therefore we use our own logic -function getStatus(x: Readonly): ProjectStatus { - let status: ProjectStatus = x['validated?'] ? 'finished' : x.status - - if (!env.projectStatuses.includes(x.status)) { - console.error(`Invalid status: ${x.status} on user ${x.user}`) - status = 'finished' - } - return status -} - -function toProjectSubscriber(x: Readonly, projectName: string): ProjectSubscriber | undefined { - try { - const status = getStatus(x) - const existing = findProjectUserByLogin(x.user.login, projectName) - const valid: ProjectSubscriber = { - login: x.user.login, - status, - staff: !!x.user['staff?'], - image_url: x.user.image.versions.medium, - ...getUpdate(status, existing), - } - return valid - } catch (e) { - console.error(e) - return undefined - } -} - -export async function getProjectSubscribers(campus: Campus, projectID: number, projectName: string): Promise { - const url = `/v2/projects/${projectID}/projects_users?filter[campus]=${campus.id}&page[size]=100` - const onPage = () => StatsD.increment('dbfetch', StatsD.strToTag('campus', campus.name)) - - const { ok, json: users }: { ok: boolean; json?: ApiProject[] } = await Api.getPaged(url, onPage) - if (!ok || !users) { - throw new Error('Could not get project subscribers') - } - return users.map(u => toProjectSubscriber(u, projectName)).filter(x => !!x) as ProjectSubscriber[] -} - -export async function writeAllProjectIds() { - const a = (await Api.getPaged(`/v2/projects`)) as { - ok: true - status: 200 - json: ({ id: string; slug: string; name: string } & Record)[] - } - const path = 'env/allProjectIDs.json' - const summary = a.json.map(x => ({ id: x.id, slug: x.slug, name: x.name })) - fs.writeFileSync(path, JSON.stringify(summary, null, 4)) - console.log('Project IDs written to', path) -} -// writeAllProjectIds() - -// @return number of users pulled -export async function saveAllProjectSubscribers(campus: Campus): Promise { - let usersPulled = 0 - const startPull = Date.now() - const newProjects: Project[] = [] - for (const [name, id] of Object.entries(env.projectIDs)) { - let item: Project - try { - item = { - name, - users: await getProjectSubscribers(campus, id, name), - } - } catch (e) { - return 0 - } - usersPulled += item.users.length - logCampus(2, campus.name, name, `total users: ${item.users.length}`) - newProjects.push(item) - } - campusDBs[campus.name].projects = newProjects - log(2, `Pull took ${msToHuman(Date.now() - startPull)}`) - - await fs.promises.writeFile(campus.projectUsersPath, JSON.stringify(newProjects)) - await fs.promises.writeFile(campus.lastPullPath, String(Date.now())) - campusDBs[campus.name].lastPull = parseInt((await fs.promises.readFile(campus.lastPullPath)).toString()) - return usersPulled -} - -// Sync all user statuses form all campuses if the env.pullTimeout for that campus has not been reached -export async function syncCampuses(): Promise { - const startPull = Date.now() - - log(1, 'starting pull') - for (const campus of Object.values(campusDBs)) { - const lastPullAgo = Date.now() - campus.lastPull - logCampus(2, campus.name, '', `last pull was on ${nowISO(campus.lastPull)}, ${(lastPullAgo / 1000 / 60).toFixed(0)} minutes ago`) - if (lastPullAgo < env.pullTimeout) { - logCampus(2, campus.name, '', `not pulling, timeout of ${env.pullTimeout / 1000 / 60} minutes not reached`) - continue - } - await saveAllProjectSubscribers(env.campuses[campus.name]) - } - log(1, `complete pull took ${msToHuman(Date.now() - startPull)}`) -} diff --git a/src/env.ts b/src/env.ts index 42ecdc4..0b0c2a6 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,30 +1,12 @@ -import { log } from './logger' -import path from 'path' -import campusIDs from '../env/campusIDs.json' -import projectIDs from '../env/projectIDs.json' -import { assertEnvInt, assertEnvStr, mapObject } from './util' +import { assertEnvInt, assertEnvStr } from './util' -export type CampusID = (typeof campusIDs)[keyof typeof campusIDs] -export type CampusName = keyof typeof campusIDs - -export interface Campus { - name: CampusName - id: number - databasePath: string // path to the database subfolder for this campus - projectUsersPath: string // users that are subscribed to a project - lastPullPath: string // timestamp for when the server did a last pull -} +export const DEV_DAYS_LIMIT: number = process.env['DEV_DAYS_LIMIT'] ? parseInt(process.env['DEV_DAYS_LIMIT'] as string) : 365; +export const NODE_ENV = process.env['NODE_ENV'] || 'development'; export interface Env { logLevel: 0 | 1 | 2 | 3 pullTimeout: number - projectIDs: typeof projectIDs - campusIDs: typeof campusIDs - databaseRoot: string - campuses: Record projectStatuses: typeof projectStatuses - sessionStorePath: string // session key data - userDBpath: string // users associated with sessions scope: string[] authorizationURL: string tokenURL: string @@ -46,31 +28,16 @@ export interface Env { userNewStatusThresholdDays: number } -const databaseRoot = 'database' -const campuses: Record = mapObject(campusIDs, (name, id) => ({ - name, - id, - databasePath: path.join(databaseRoot, name), - projectUsersPath: path.join(databaseRoot, name, 'projectUsers.json'), - lastPullPath: path.join(databaseRoot, name, 'lastpull.txt'), -})) - // known statuses, in the order we want them displayed on the website -const projectStatuses = ['creating_group', 'searching_a_group', 'in_progress', 'waiting_for_correction', 'finished', 'parent'] as const -export type ProjectStatus = (typeof projectStatuses)[number] +const projectStatuses = ['creating_group', 'searching_a_group', 'in_progress', 'waiting_for_correction', 'finished', 'parent'] as const; +export type ProjectStatus = (typeof projectStatuses)[number]; export const env: Readonly = { logLevel: process.env['NODE_ENV'] === 'production' ? 3 : 1, // 0 being no logging - pullTimeout: 24 * 60 * 60 * 1000, // how often to pull the project users statuses form the intra api (in Ms) - projectIDs, - campusIDs, - databaseRoot, - campuses, + pullTimeout: 24 * 60 * 60 * 1000, // how often to sync with the 42 API (in ms) projectStatuses, - sessionStorePath: path.join(databaseRoot, 'sessions'), authorizationURL: 'https://api.intra.42.fr/oauth/authorize', tokenURL: 'https://api.intra.42.fr/oauth/token', - userDBpath: path.join(databaseRoot, 'users.json'), provider: '42', authPath: '/auth/42', scope: ['public'], @@ -89,6 +56,3 @@ export const env: Readonly = { }, userNewStatusThresholdDays: 7, } - -log(1, `Watching ${Object.keys(campusIDs).length} campuses`) -log(1, `Watching ${Object.keys(projectIDs).length} projects`) diff --git a/src/express.ts b/src/express.ts index e56b32d..88c151d 100644 --- a/src/express.ts +++ b/src/express.ts @@ -1,65 +1,76 @@ import path from 'path' import express, { Response } from 'express' import { passport, authenticate } from './authentication' -import { CampusName, env, ProjectStatus } from './env' +import { env, ProjectStatus } from './env' import session from 'express-session' -import { campusDBs, CampusDB } from './db' -import { Project, ProjectSubscriber, UserProfile } from './types' import { log } from './logger' -import { MetricsStorage } from './metrics' import compression from 'compression' -import request from 'request' -import { isLinguisticallySimilar } from './util' - -function errorPage(res: Response, error: string): void { +import cookieParser from 'cookie-parser' +import { DatabaseService } from './services' +import { DisplayProject } from './types' + +/** + * Render the error page. + * @param res The response object + * @param error The error message to display + */ +async function errorPage(res: Response, error: string): Promise { const settings = { - campuses: Object.values(env.campuses).sort((a, b) => (a.name < b.name ? -1 : 1)), + // Hide the Ghost Campus (id 42) from the selectable list of campuses in the dropdown (website header) + campuses: (await DatabaseService.getAllCampuses()).filter(c => c.id !== 42), error, } - res.render('error.ejs', settings) + res.render('error.ejs', settings); } -const cachingProxy = '/proxy' - -function filterUsers(users: ProjectSubscriber[], requestedStatus: string | undefined): ProjectSubscriber[] { - const newUsers = users - .filter(user => { - if (user.staff) { - return false - } - if (user.login.match(/^3b3/)) { - // accounts who's login start with 3b3 are deactivated - return false - } - if ((requestedStatus === 'finished' || user.status !== 'finished') && (!requestedStatus || user.status === requestedStatus)) { - return true - } - return false - }) - .map(user => ({ ...user, image_url: `${cachingProxy}?q=${user.image_url}` })) - .sort((a, b) => { +/** + * Get the projects for a specific campus and status. + * @param campusId The ID of the campus + * @param requestedStatus The status of the projects to retrieve + * @returns A list of projects for the specified campus and status, sorted on status. + */ +async function getProjects(campusId: number, requestedStatus: string | undefined, showEmptyProjects: boolean): Promise { + const projectList = await DatabaseService.getAllProjects(); + if (!projectList.length) { + return []; + } + const projectsWithUsers: DisplayProject[] = await Promise.all(projectList.map(async project => ({ + name: project.name, + slug: project.slug, + users: (await DatabaseService.getProjectUserInfo(project.id, campusId, requestedStatus)).map(projUser => ({ + login: projUser.user.login, + image_url: projUser.user.image_url, + status: projUser.status, + pool: projUser.user.pool ? projUser.user.pool : 'N/A', + new: (Date.now() - new Date(projUser.created_at).getTime()) < env.userNewStatusThresholdDays * 24 * 60 * 60 * 1000, + })).sort((a, b) => { if (a.status !== b.status) { - const preferredOrder = env.projectStatuses - const indexA = preferredOrder.findIndex(x => x === a.status) - const indexB = preferredOrder.findIndex(x => x === b.status) - return indexA < indexB ? -1 : 1 + const preferredOrder = env.projectStatuses; + const indexA = preferredOrder.findIndex(x => x === a.status); + const indexB = preferredOrder.findIndex(x => x === b.status); + return indexA < indexB ? -1 : 1; } - return a.login < b.login ? -1 : 1 + return a.login < b.login ? -1 : 1; }) - return newUsers + }))); + const filteredProjects = projectsWithUsers.map(project => ({ + ...project, + users: project.users.filter(user => !user.login.match(/^3b3/) && !user.login.match(/^3c3/)) + })); + return showEmptyProjects + ? filteredProjects + : filteredProjects.filter(project => project.users.length > 0); } -function filterProjects(projects: Project[], requestedStatus: string | undefined): Project[] { - return projects.map(project => ({ - name: project.name, - users: filterUsers(project.users, requestedStatus), - })) -} - -const metrics = new MetricsStorage() - +/** + * Start the web server. + * @param port The port to listen on + */ export async function startWebserver(port: number) { - const app = express() + const app = express(); + + // Add cookie parser middleware + app.use(cookieParser()); app.use( session({ @@ -67,110 +78,105 @@ export async function startWebserver(port: number) { resave: false, saveUninitialized: true, }) - ) - app.use(passport.initialize()) - app.use(passport.session()) - - app.use(cachingProxy, (req, res) => { - const url = req.query['q'] - if (!url || typeof url !== 'string' || !url.startsWith('http')) { - res.status(404).send('No URL provided') - return - } - - // inject cache header for images - res.setHeader('Cache-Control', `public, max-age=${100 * 24 * 60 * 60}`) - req.pipe(request(url)).pipe(res) - }) + ); + app.use(passport.initialize()); + app.use(passport.session()); + // Compression middleware app.use((req, res, next) => { try { - compression()(req, res, next) - return + compression()(req, res, next); + return; } catch (e) { - console.error('Compression error', e) + console.error('Compression error', e); } - next() + next(); }) + // Robots.txt app.get('/robots.txt', (_, res) => { - res.type('text/plain') - res.send('User-agent: *\nAllow: /') - }) + res.type('text/plain'); + res.send('User-agent: *\nAllow: /'); + }); - app.get(`/auth/${env.provider}/`, passport.authenticate(env.provider, { scope: env.scope })) + // Authentication routes + app.get(`${env.authPath}/`, passport.authenticate(env.provider, { scope: env.scope })); app.get( - `/auth/${env.provider}/callback`, + `${env.authPath}/callback`, passport.authenticate(env.provider, { successRedirect: '/', - failureRedirect: `/auth/${env.provider}`, + failureRedirect: `${env.authPath}`, }) - ) + ); - app.get('/', authenticate, (req, res) => { - const user: UserProfile = req.user as UserProfile - res.redirect(`/${user.campusName}`) - }) + // Main route + app.get('/', authenticate, async (req, res) => { + const user = req.user as any; + const accessToken = user?.accessToken; - app.get('/:campus', authenticate, (req, res) => { - const user: UserProfile = req.user as UserProfile - const requestedStatus: string | undefined = req.query['status']?.toString() + if (!accessToken) { + return errorPage(res, 'Access token not found for user'); + } - const campus = req.params['campus'] as string - const campusName: CampusName | undefined = Object.keys(campusDBs).find(k => isLinguisticallySimilar(k, campus)) as CampusName | undefined - if (!campusName || !campusDBs[campusName]) { - return errorPage(res, `Campus ${campus} is not supported by Find Peers (yet)`) + const campus = await DatabaseService.getCampusByUser(user.login); + if (!campus) { + return errorPage(res, 'User campus not found in database'); + } + res.redirect(`/${campus.name}`); + }); + + // Campus-specific route + app.get('/:campus', authenticate, async (req, res) => { + const user = req.user as any; + const accessToken = user?.accessToken; + if (!accessToken) { + return errorPage(res, 'Access token not found for user'); } - // saving anonymized metrics - metrics.addVisitor(user) + // Campus to use if none is provided. (User's primary campus) + const campus = await DatabaseService.getCampusByUser(user.login); + if (!campus) { + return errorPage(res, 'User campus not found in database'); + } - const campusDB: CampusDB = campusDBs[campusName] - if (!campusDB.projects.length) { - return errorPage(res, 'Empty database (please try again later)') + // If a campus is explicitly provided, use that one + if (req.params['campus'] !== undefined) { + campus.name = req.params['campus']; + campus.id = await DatabaseService.getCampusIdByName(campus.name); + if (campus.id === -1) { + return errorPage(res, `Unknown campus ${campus.name}`); + } } + const requestedStatus: string | undefined = req.query['status']?.toString(); if (requestedStatus && !env.projectStatuses.includes(requestedStatus as ProjectStatus)) { - return errorPage(res, `Unknown status ${req.query['status']}`) + return errorPage(res, `Unknown status ${req.query['status']}`); } - const { uniqVisitorsTotal: v, uniqVisitorsCampus } = metrics.generateMetrics() - const campuses = uniqVisitorsCampus.reduce((acc, visitors) => { - acc += visitors.month > 0 ? 1 : 0 - return acc - }, 0) + const showEmptyProjects: boolean = req.query['showEmptyProjects'] === '1'; + + // Get all necessary data to be displayed to the user + const userTimeZone = req.cookies.timezone || 'Europe/Amsterdam'; const settings = { - projects: filterProjects(campusDB.projects, requestedStatus), - lastUpdate: new Date(campusDB.lastPull).toLocaleString('en-NL', { timeZone: user.timeZone }).slice(0, -3), - hoursAgo: ((Date.now() - campusDB.lastPull) / 1000 / 60 / 60).toFixed(2), + projects: await getProjects(campus.id, requestedStatus, showEmptyProjects), + lastUpdate: await DatabaseService.getLastSyncTimestamp().then(date => date ? date.toLocaleString('en-NL', { timeZone: userTimeZone }).slice(0, -3) : 'N/A'), + hoursAgo: (((Date.now()) - await DatabaseService.getLastSyncTimestamp().then(date => date ? date.getTime() : 0)) / (1000 * 60 * 60)).toFixed(2), // hours ago requestedStatus, projectStatuses: env.projectStatuses, - campusName, - campuses: Object.values(env.campuses).sort((a, b) => (a.name < b.name ? -1 : 1)), + campusName: campus.name, + // Hide the Ghost Campus (id 42) from the selectable list of campuses in the dropdown (website header) + campuses: (await DatabaseService.getAllCampuses()).filter(c => c.id !== 42), updateEveryHours: (env.pullTimeout / 1000 / 60 / 60).toFixed(0), - usage: `${v.day} unique visitors today, ${v.month} this month, from ${campuses} different campuses`, userNewStatusThresholdDays: env.userNewStatusThresholdDays, - } - res.render('index.ejs', settings) - }) - - app.get('/status/pull', (_, res) => { - const obj = Object.values(campusDBs).map(campus => ({ - name: campus.name, - lastPull: new Date(campus.lastPull), - hoursAgo: (Date.now() - campus.lastPull) / 1000 / 60 / 60, - })) - res.json(obj) - }) - - app.get('/status/metrics', authenticate, (_, res) => { - res.json(metrics.generateMetrics()) - }) + showEmptyProjects, + }; + res.render('index.ejs', settings); + }); - app.set('views', path.join(__dirname, '../views')) - app.set('viewengine', 'ejs') - app.use('/public', express.static('public/')) + app.set('views', path.join(__dirname, '../views')); + app.set('view engine', 'ejs'); + app.use('/public', express.static('public/')); - await app.listen(port) - log(1, `${process.env['NODE_ENV'] ?? 'development'} app ready on http://localhost:${port}`) + await app.listen(port); + log(1, `${process.env['NODE_ENV'] ?? 'development'} app ready on http://localhost:${port}`); } diff --git a/src/logger.ts b/src/logger.ts index 96dc194..571667c 100644 --- a/src/logger.ts +++ b/src/logger.ts @@ -1,43 +1,12 @@ import { env, Env } from './env' -import { campusDBs } from './db' - -// eg. 24 -export function msToHuman(milliseconds: number): string { - const hours = milliseconds / (1000 * 60 * 60) - const h = Math.floor(hours) - - const minutes = (hours - h) * 60 - const m = Math.floor(minutes) - - const seconds = (minutes - m) * 60 - const s = Math.floor(seconds) - - return `${String(h).padStart(2, '0')}h ${String(m).padStart(2, '0')}m ${String(s).padStart(2, '0')}s` -} - -function longestKeyLength(obj: Record): number { - let longestCampusNameLength = 0 - for (const key in obj) { - if (key.length > longestCampusNameLength) { - longestCampusNameLength = key.length - } - } - return longestCampusNameLength -} export function nowISO(d: Date | number = new Date()): string { - d = new Date(d) - return `${d.toISOString().slice(0, -5)}Z` -} - -export function logCampus(level: Env['logLevel'], campus: string, project: string, message: string) { - if (level <= env.logLevel) { - console.log(`${nowISO()} | ${campus.padEnd(longestKeyLength(campusDBs))} ${project.padEnd(longestKeyLength(env.projectIDs))} | ${message}`) - } + d = new Date(d); + return `${d.toISOString().slice(0, -5)}Z`; } export function log(level: Env['logLevel'], message: string) { if (level <= env.logLevel) { - console.log(`${nowISO()} | ${message}`) + console.log(`${nowISO()} | ${message}`); } } diff --git a/src/metrics.ts b/src/metrics.ts deleted file mode 100644 index ad1999c..0000000 --- a/src/metrics.ts +++ /dev/null @@ -1,90 +0,0 @@ -import fs from 'fs' -import { env } from './env' -import crypto from 'crypto' -import { UserProfile } from './types' -import * as StatsD from './statsd' -import { findLast, unique } from './util' - -interface Visitor { - id: string - campus: string - date: Date -} - -interface Metric { - hour: number - day: number - month: number -} - -interface Metrics { - uniqVisitorsTotal: Metric - uniqVisitorsCampus: ({ name: string } & Metric)[] - nVisitors: number -} - -export class MetricsStorage { - constructor() { - if (!fs.existsSync(this.dbPath)) { - fs.writeFileSync(this.dbPath, '[]') - } - try { - this.visitors = (JSON.parse(fs.readFileSync(this.dbPath, 'utf8')) as Visitor[]).map(x => ({ ...x, date: new Date(x.date) })) // in JSON Date is stored as a string, so now we convert it back to Date - } catch (err) { - console.error('Error while reading visitors database, resetting it...', err) - this.visitors = [] - } - } - - public async addVisitor(user: UserProfile): Promise { - // create a hash instead of storing the user id directly, for privacy - const rawID = user.id.toString() + user.login + env.tokens.metricsSalt - const id = crypto.createHash('sha256').update(rawID).digest('hex') - - // if the user has visited the page in the last n minutes, do not count it as a new visitor - const lastVisit = findLast(this.visitors, x => x.id === id) - if (lastVisit && Date.now() - lastVisit.date.getTime() < 1000 * 60 * 30) { - return - } - - this.visitors.push({ id, campus: user.campusName, date: new Date() }) - StatsD.increment('visits', StatsD.strToTag('origin', user.campusName)) - await fs.promises.writeFile(this.dbPath, JSON.stringify(this.visitors)) - } - - uniqueVisitorsInLast(timeMs: number): Visitor[] { - const now = Date.now() - let visitors = this.visitors.filter(x => now - x.date.getTime() < timeMs) - visitors = unique(visitors, (a, b) => a.id === b.id) - visitors = visitors.map(x => ({ ...x, id: x.id.substring(5, -5) })) // cut a little of the id to keep it private - return visitors - } - - public generateMetrics(): Metrics { - const hour = this.uniqueVisitorsInLast(3600 * 1000) - const day = this.uniqueVisitorsInLast(24 * 3600 * 1000) - const month = this.uniqueVisitorsInLast(30 * 24 * 3600 * 1000) - - const uniqVisitorsCampus = Object.values(env.campuses) - .map(campus => ({ - name: campus.name, - hour: hour.filter(x => x.campus === campus.name).length, - day: day.filter(x => x.campus === campus.name).length, - month: month.filter(x => x.campus === campus.name).length, - })) - .sort((a, b) => b.day - a.day) - - return { - uniqVisitorsTotal: { - hour: hour.length, - day: day.length, - month: month.length, - }, - uniqVisitorsCampus, - nVisitors: this.visitors.length, - } - } - - private readonly dbPath: string = `${env.databaseRoot}/visitors.json` - private visitors: Visitor[] = [] -} diff --git a/src/services.ts b/src/services.ts new file mode 100644 index 0000000..d6cb5b3 --- /dev/null +++ b/src/services.ts @@ -0,0 +1,273 @@ +import { PrismaClient, User, Project, Campus, ProjectUser } from '@prisma/client' +import { log } from 'console'; + +const prisma = new PrismaClient(); + +/** + * Get a user-friendly error message from an error object. + * @param error The error object to extract the message from. + * @returns A user-friendly error message. + */ +function getErrorMessage(error: unknown): string { + if (error instanceof Error) + return error.message; + return String(error); +} + +export class DatabaseService { + + /*************************************************************************\ + * Query Methods * + \*************************************************************************/ + + + /** + * @returns The last synchronization timestamp. + */ + static getLastSyncTimestamp = async function(): Promise { + const sync = await prisma.sync.findUnique({ + where: { id: 1 }, + select: { last_pull: true } + }); + if (sync?.last_pull === null || sync?.last_pull === undefined) { + log(2, `No last sync timestamp found, returning null.`); + return null; + } + return new Date(sync.last_pull); + } + + /** + * Get project users by project, status, and campus. + * @param project The project to filter by. + * @param requestedStatus The status to filter by. + * @param campus The campus to filter by. + * @returns Project; name. Users; login, image_url. Project Status. + */ + static async getProjectUserInfo( + project_id: any, campus_id: any, requestedStatus: string | undefined): Promise { + const whereClause: any = { + project_id: project_id, + user: { primary_campus_id: campus_id } + }; + if (typeof requestedStatus === 'string' && requestedStatus.length > 0) { + whereClause.status = requestedStatus; + } + + const projusers = await prisma.projectUser.findMany({ + where: whereClause, + select: { + user: { select: { login: true, image_url: true, pool: true } }, + status: true, + created_at: true + } + }); + if (requestedStatus == 'finished') { + return projusers; + } + return projusers.filter(pu => pu.status !== 'finished'); + } + + /** + * Get the ID of a campus by its name. + * @param campus_name The campus name to look for. + * @returns The ID of the campus. + */ + static async getCampusIdByName(campus_name: string | null): Promise { + if (campus_name === null) return -1; + + const campus = await prisma.campus.findFirst({ + where: { name: campus_name }, + select: { id: true } + }); + return campus?.id ?? -1; + } + + /** + * @returns The list of all campuses in ascending (name) order. + */ + static async getAllCampuses(): Promise { + return prisma.campus.findMany({ + orderBy: { name: 'asc' } + }); + } + + /** + * @returns The list of all projects in ascending (difficulty) order. + */ + static async getAllProjects(): Promise { + return prisma.project.findMany({ + orderBy: { difficulty: 'asc'} + }); + } + + /** + * Retrieve the missing campus ID for a user. + * @param user The user object from the API. + * @returns The missing campus ID or null if not found. + */ + static async getMissingCampusId(user: any): Promise { + const campusId = user.campus_users.find((cu: any) => cu.is_primary)?.campus_id; + if (campusId === null || campusId === undefined) { + return null; + } + const existingCampus = await prisma.campus.findUnique({ + where: { id: campusId }, + select: { id: true } + }); + if (!existingCampus) { + return campusId; + } + return null; + } + + /** + * Retrieve the campus associated with a user. + * @param userLogin The login of the user. + * @returns The campus information or null if not found. + */ + static async getCampusByUser(userLogin: string): Promise<{name: string, id: number} | null> { + const campus = await prisma.campus.findFirst({ + where: { users: { some: { login: userLogin } } }, + select: { name: true, id: true } + }); + if (!campus) { + return null; + } + return {name: campus.name, id: campus.id}; + } + + /** + * Retrieve a user by their login. + * @param login The login of the user to find. + * @returns The user object or null if not found. + */ + static async findUserByLogin(login: string): Promise { + return prisma.user.findFirst({ + where: { login: login } + }); + } + + /*************************************************************************\ + * Insert Methods * + \*************************************************************************/ + + /** + * Save the synchronization timestamp. + * @param timestamp The timestamp to save + */ + static saveSyncTimestamp = async function(timestamp: Date): Promise { + await prisma.sync.upsert({ + where: { id: 1 }, + update: { last_pull: timestamp.toISOString() }, + create: { id: 1, last_pull: timestamp.toISOString() } + }); + } + + /** + * Inserts multiple project users into the database. + * @param projectUsers - The list of project user data to insert. + * @returns {Promise} - Resolves when all project users are inserted. + */ + static async insertManyProjectUsers(projectUsers: ProjectUser[]): Promise { + try { + const insert = projectUsers.map(projectUser => + prisma.projectUser.upsert({ + where: { + project_id_user_id: { + user_id: projectUser.user_id, + project_id: projectUser.project_id + } + }, + update: projectUser, + create: projectUser + }) + ); + await prisma.$transaction(insert); + } catch (error) { + throw new Error(`Failed to insert project users: ${getErrorMessage(error)}`); + } + } + + /** + * Inserts a user into the database. + * @param {User} user - The user data to insert. + * @returns {Promise} - Resolves when the user is inserted. + */ + static async insertUser(user: User): Promise { + try { + return prisma.user.upsert({ + where: { id: user.id }, + update: user, + create: user + }); + } catch (error) { + throw new Error(`Failed to insert user ${user.id}: ${getErrorMessage(error)}`); + } + } + + /** + * Inserts a campus into the database. + * @param campus - The campus data to insert. + * @returns {Promise} - The ID of the inserted campus. + */ + static async insertCampus(campus: Campus): Promise { + try { + log(2, `-------------Inserted campus`); + return prisma.campus.upsert({ + where: { id: campus.id }, + update: campus, + create: campus + }); + } catch (error) { + throw new Error(`-------Failed to insert campus ${campus.id}: ${getErrorMessage(error)}`); + } + } + + /** + * Inserts multiple projects into the database. + * @param projects - The list of project data to insert. + * @returns {Promise} - Resolves when all projects are inserted. + */ + static async insertManyProjects(projects: Project[]): Promise { + try { + const insert = projects.map(project => + prisma.project.upsert({ + where: { id: project.id }, + update: project, + create: project + }) + ); + await prisma.$transaction(insert); + } catch (error) { + throw new Error(`Failed to insert projects: ${getErrorMessage(error)}`); + } + } + + /*************************************************************************\ + * Miscellaneous Methods * + \*************************************************************************/ + + /** + * Delete users who should be anonymized from the database. + * @returns {Promise} - Resolves when the operation is complete. + */ + static async anonymizeOldEntries(): Promise { + // Delete project users and users whose anonymization date has passed or login starts with '3b3' + await prisma.projectUser.deleteMany({ + where: { + OR: [ + { user: { anonymize_date: { lt: new Date().toISOString() } } }, + { user: { login: { startsWith: '3b3' } } } + ] + } + }); + await prisma.user.deleteMany({ + where: { + OR: [ + { anonymize_date: { lt: new Date().toISOString() } }, + { login: { startsWith: '3b3' } } + ] + } + }); + } +} diff --git a/src/statsd.ts b/src/statsd.ts deleted file mode 100644 index d675faf..0000000 --- a/src/statsd.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { StatsD as StatsDObj } from 'hot-shots' - -const client = new StatsDObj({ - port: 8125, - host: 'datadog-agent', // TODO use env - errorHandler: console.error, -}) - -export function increment(stat: string, tag?: string): void { - if (!isValidDataDogStr(stat)) { - return console.error(`Invalid stat ${stat}`) - } - if (tag && !isValidDataDogStr(tag)) { - return console.error(`Invalid tag ${tag} for stat ${stat}`) - } - client.increment(stat, tag ? [tag] : []) -} - -export function strToTag(prefix: string, str: string): string { - const normalized = str - .toLowerCase() - .normalize('NFD') - .replace(/[\u0300-\u036f]/g, '') - .replace(/[^a-z0-9]/g, '_') - return `${prefix}:${normalized}` -} - -function isValidDataDogStr(tag: string): boolean { - return /^[a-z0-9_:]+$/.test(tag) -} diff --git a/src/sync.ts b/src/sync.ts new file mode 100644 index 0000000..3de480f --- /dev/null +++ b/src/sync.ts @@ -0,0 +1,314 @@ +import Fast42 from "@codam/fast42"; +import { NODE_ENV, DEV_DAYS_LIMIT, env } from "./env"; +import { transformApiCampusToDb, transformApiUserToDb, transformApiProjectToDb, transformApiProjectUserToDb } from './transform'; +import { DatabaseService } from './services'; +import { log } from "./logger"; + +const fast42Api = new Fast42( + [ + { + client_id: env.tokens.sync.UID, + client_secret: env.tokens.sync.secret, + }, + ], + undefined, // concurrentOffset (keep default) + 60000 // jobExpiration: 60 seconds +); + +/** + * Initialize Fast42 API. + */ +let fast42Initialized = false; +async function initializeFast42() { + if (fast42Initialized) { + return; + } + try { + await fast42Api.init(); + fast42Initialized = true; + console.log('Fast42 initialized successfully'); + } catch (error) { + console.error('Failed to initialize Fast42:', error); + } +} + +/** + * Synchronize with 42API. + * This function fetches project users, users, campuses, and projects from the Fast42 API and saves them to the database. + * It also saves the last synchronization timestamp to a file. + * @returns A promise that resolves when the synchronization is complete + * @throws Will throw an error if the synchronization fails + */ +export const syncWithIntra = async function(): Promise { + await initializeFast42(); + if (!fast42Initialized) { + console.log('Failed to initialize fast42...'); + return; + } + const now = new Date(); + + try { + let lastSyncRaw = await DatabaseService.getLastSyncTimestamp(); + let lastSync: Date | undefined = lastSyncRaw === null ? undefined : lastSyncRaw; + + await syncCursusProjects(fast42Api, lastSync, 21) // 42 cursus + await syncCursusProjects(fast42Api, lastSync, 9) // Piscine cursus + await DatabaseService.saveSyncTimestamp(now); + + console.info(`Intra synchronization completed at ${new Date().toISOString()}.`); + } + catch (err) { + console.error('Failed to synchronize with Intra:', err); + console.log('Future synchronization attempts will start from the last successful sync timestamp, so no data should be missing.'); + } +} + +/** + * Sync cursus projects with the Fast42 API. + * @param fast42Api The Fast42 API instance to use for fetching projects + * @param lastPullDate The date of the last synchronization + * @returns A promise that resolves when the synchronization is complete + */ +async function syncCursusProjects(fast42Api: Fast42, lastPullDate: Date | undefined, cursusId: number): Promise { + let pageIndex = 0; + let hasMorePages = true; + let params: { [key: string]: string } = { 'page[size]': '100' }; + + while (hasMorePages) { + pageIndex++; + params['page[number]'] = pageIndex.toString(); + log(2, `Fetching page ${pageIndex} of projects...`); + + // Does /projects update when the users working on them update? (Check this later) + const projectsData = await fetchSingle42ApiPage(fast42Api, `/cursus/${cursusId}/projects`, params); + if (!projectsData || projectsData.length === 0) { + log(2, `No more projects found on page ${pageIndex}. Stopping.`); + hasMorePages = false; + break; + } + + log(2, `Processing page ${pageIndex} with ${projectsData.length} projects...`); + const dbProjects = projectsData.map(transformApiProjectToDb); + await DatabaseService.insertManyProjects(dbProjects); + + await syncProjectUsers(fast42Api, lastPullDate, projectsData); + } +} + +/** + * Sync project users with the Fast42 API. + * @param fast42Api The Fast42 API instance to use for fetching project users + * @param lastPullDate The date of the last synchronization + * @param projectsData The projects to sync with + * @returns A promise that resolves when the synchronization is complete + */ +async function syncProjectUsers(fast42Api: Fast42, lastPullDate: Date | undefined, projectsData: any[]): Promise { + let params: { [key: string]: string } = { 'page[size]': '100' }; + if (lastPullDate) { + let syncDate = new Date(); + params['range[updated_at]'] = `${lastPullDate.toISOString()},${syncDate.toISOString()}`; + } + + for (const project of projectsData) { + let pageIndex = 0; + let hasMorePages = true; + + while (hasMorePages) { + pageIndex++; + params['page[number]'] = pageIndex.toString(); + log(2, `Fetching page ${pageIndex} of projectUsers...`); + + let projectUsersData; + try { + projectUsersData = await fetchSingle42ApiPage(fast42Api, `/projects/${project.id}/projects_users`, params); + } catch (error) { + console.error(`Failed to fetch project users for project ${project.id} on page ${pageIndex}:`, error); + break; // Skip to the next project + } + if (!projectUsersData || projectUsersData.length === 0) { + log(2, `No more users found for project ${project.id} on page ${pageIndex}. Stopping.`); + hasMorePages = false; + break; + } + + await syncUsers(fast42Api, lastPullDate, projectUsersData); + + log(2, `Processing page ${pageIndex} with ${projectUsersData.length} users...`); + const dbProjectUsers = projectUsersData.map(transformApiProjectUserToDb); + await DatabaseService.insertManyProjectUsers(dbProjectUsers); + } + } +} + +/** + * Sync users with the Fast42 API. + * @param fast42Api The Fast42 API instance to use for fetching project users + * @param lastPullDate The date of the last synchronization + * @returns A promise that resolves when the synchronization is complete + */ +async function syncUsers(fast42Api: Fast42, lastPullDate: Date | undefined, projectUsersData: any[]): Promise { + for (const projectUser of projectUsersData) { + log(2, `Processing missing user ${projectUser.user.id}...`); + const userApi = await syncData(fast42Api, new Date(), lastPullDate, `/users/${projectUser.user.id}`, {}); + const user = userApi[0]; + + if (!user) { + log(2, `No user data found for ID: ${projectUser.user.id}, skipping...`); + continue; // Skip to next user + } + + const dbUser = transformApiUserToDb(user); + + // What if the campus is in our database, but the campus info gets updated? (Check this later) + const missingCampusId = await DatabaseService.getMissingCampusId(user); + if (missingCampusId !== null) { + log(2, `Found missing campus ID ${missingCampusId}, syncing...`); + + try { + await syncCampus(fast42Api, lastPullDate, missingCampusId); + } catch (error) { + console.error(`Assigning to non-existent campus; failed to fetch ${missingCampusId}:`, error); + DatabaseService.insertCampus({ id: 42, name: `Ghost Campus` }); + dbUser.primary_campus_id = 42; // Assign to Ghost Campus + } + } + await DatabaseService.insertUser(dbUser); + } + log(2, 'Syncing Users completed.'); +} + +/** + * Sync campuses with the Fast42 API. + * @param fast42Api The Fast42 API instance to use for fetching campuses + * @param lastPullDate The date of the last synchronization + * @param campusIds The IDs of the campuses to sync + * @returns A promise that resolves when the synchronization is complete + */ +async function syncCampus(fast42Api: Fast42, lastPullDate: Date | undefined, campusId: number): Promise { + let campusApi; + try { + campusApi = await syncData(fast42Api, new Date(), lastPullDate, `/campus/${campusId}`, {}); + } catch (error) { + console.error(`Campus ${campusId} doesn't exist`, error); + throw error; + } + try { + const campus = campusApi[0]; + log(2, `Syncing campus ${campus}...`); + const dbCampus = transformApiCampusToDb(campus); + await DatabaseService.insertCampus(dbCampus); + log(2, `Finished syncing campus ${dbCampus.id} - ${dbCampus.name}`); + } catch (error) { + console.error(`Failed to sync campus ${campusId}:`, error); + throw error; + } +} + +/** + * Fetch all items from all pages of a Fast42 API endpoint. + * @usage const codamStudents = await fetchMultiple42ApiPages(api, '/v2/campus/14/users'); + * @param api A Fast42 instance + * @param path The API path to fetch + * @param params Optional query parameters for the API request + * @returns A promise that resolves to an array containing all items from all pages of the API responses + */ +export const fetchMultiple42ApiPages = async function(api: Fast42, path: string, params: { [key: string]: string } = {}): Promise { + return new Promise(async (resolve, reject) => { + try { + const pages = await api.getAllPages(path, params); + + let i = 0; + const pageItems = await Promise.all(pages.map(async (page) => { + let p = null; + while (!p) { + p = await page; + if (p.status == 429) { + console.error('Intra API rate limit exceeded, let\'s wait a bit...'); + const waitFor = parseInt(p.headers.get('Retry-After') ?? '1'); + console.log(`Waiting ${waitFor} seconds...`); + await new Promise((resolve) => setTimeout(resolve, waitFor * 1000 + Math.random() * 1000)); + p = null; + continue; + } + if (!p.ok) { + throw new Error(`Intra API error: ${p.status} ${p.statusText} on ${p.url}`); + } + } + if (p.ok) { + const data = await p.json(); + console.debug(`Fetched page ${++i} of ${pages.length} on ${path}...`); + return data; + } + })); + return resolve(pageItems.flat()); + } + catch (err) { + return reject(err); + } + }); +}; + +/** + * Fetch a single page of a Fast42 API endpoint. + * @param api A Fast42 instance + * @param path The API path to fetch + * @param params Optional query parameters for the API request + * @returns A promise that resolves to the JSON data from the API response + */ +export const fetchSingle42ApiPage = async function(api: Fast42, path: string, params: { [key: string]: string } = {}): Promise { + return new Promise(async (resolve, reject) => { + try { + while (true) { + const page = await api.get(path, params); + + if (page.status == 429) { + console.error('Intra API rate limit exceeded, let\'s wait a bit...'); + const waitFor = parseInt(page.headers.get('Retry-After') ?? '1'); + console.log(`Waiting ${waitFor} seconds...`); + await new Promise((resolve) => setTimeout(resolve, waitFor * 1000 + Math.random() * 1000)); + continue; + } + if (page.ok) { + const data = await page.json(); + return resolve(data); + } + else { + reject(`Intra API error: ${page.status} ${page.statusText} on ${page.url}`); + break; + } + } + } + catch (err) { + return reject(err); + } + }); +}; + +/** + * Synchronize data with the Intra API. + * @param api A Fast42 instance + * @param syncDate The current date + * @param lastSyncDate The date of the last synchronization + * @param path The API path to fetch + * @param params The query parameters for the API request + */ +export const syncData = async function(api: Fast42, syncDate: Date, lastSyncDate: Date | undefined, path: string, params: any): Promise { + // In development mode we do not want to be stuck fetching too much data, + // so we impose a limit based on the DEV_DAYS_LIMIT environment variable. + // + // The only case in which we do not want to do this is the users endpoint, + // for which we always fetch all data + if (lastSyncDate === undefined && NODE_ENV == "development" && !path.includes('/users')) { + lastSyncDate = new Date(syncDate.getTime() - DEV_DAYS_LIMIT * 24 * 60 * 60 * 1000); + } + + if (lastSyncDate !== undefined) { + params['range[updated_at]'] = `${lastSyncDate.toISOString()},${syncDate.toISOString()}`; + console.log(`Fetching data from Intra API updated on path ${path} since ${lastSyncDate.toISOString()}...`); + } + else { + console.log(`Fetching all data from Intra API on path ${path}...`); + } + + return fetchMultiple42ApiPages(api, path, params); +}; diff --git a/src/transform.ts b/src/transform.ts new file mode 100644 index 0000000..4a9ccc4 --- /dev/null +++ b/src/transform.ts @@ -0,0 +1,64 @@ +import { User, Project, Campus, ProjectUser } from '@prisma/client' + +/** + * Transforms 42Api /v2/projects_users data to Database data. + * @param apiProjectUser Fetched data from the 42Api + * @returns ProjectUser object for the database + */ +export function transformApiProjectUserToDb(apiProjectUser: any): ProjectUser { + return { + project_id: apiProjectUser.project.id, + user_id: apiProjectUser.user.id, + created_at: apiProjectUser.created_at, + updated_at: apiProjectUser.updated_at, + validated_at: apiProjectUser.marked_at || null, + status: apiProjectUser.status + }; +} + +/** + * Transforms 42Api /v2/users data to Database data. + * @param apiUser Fetched data from the 42Api + * @returns User object for the database + */ +export function transformApiUserToDb(apiUser: any): User { + const primaryCampus = apiUser.campus_users.find((cu: any) => cu.is_primary); + if (apiUser.staff) { + // Add prefix to staff logins to make filtering them easier, without storing "staff" boolean in the database + apiUser.login = '3c3' + apiUser.login; + } + return { + id: apiUser.id, + login: apiUser.login, + primary_campus_id: primaryCampus ? primaryCampus.campus_id : 1, + image_url: apiUser.image?.versions?.medium || null, + pool: apiUser.pool_month + ' ' + apiUser.pool_year, + anonymize_date: apiUser.anonymize_date || null + }; +} + +/** + * Transforms 42Api /v2/campus data to Database data. + * @param apiCampus Fetched data from the 42Api + * @returns Campus object for the database + */ +export function transformApiCampusToDb(apiCampus: any): Campus { + return { + id: apiCampus.id, + name: apiCampus.name + }; +} + +/** + * Transforms 42Api /v2/projects_users data to Database data. + * @param apiProjectUser Fetched data from the 42Api + * @returns Project object for the database + */ +export function transformApiProjectToDb(apiProject: any): Project { + return { + id: apiProject.id, + name: apiProject.name || '', + slug: apiProject.slug || '', + difficulty: apiProject.difficulty || undefined + }; +} diff --git a/src/types.ts b/src/types.ts index cdba216..285ba08 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,114 +1,11 @@ -import { ProjectStatus } from './env' - -export interface ApiProject { - id: number - occurrence: number - final_mark: number - status: ProjectStatus - 'validated?': boolean - current_team_id: number - project: { - id: number - name: string - slug: string - parent_id?: unknown - } - cursus_ids: number[] - marked_at: Date - marked: boolean - retriable_at: Date - created_at: Date - updated_at: Date - user: { - id: number - email: string - login: string - first_name: string - last_name: string - usual_full_name: string - 'usual_first_name?': unknown - url: string - phone: string - displayname: string - kind: string - image_url: string - image: { - link: string - versions: { - large: string - medium: string - small: string - micro: string - } - } - new_image_url: string - 'staff?': boolean - correction_point: number - pool_month: string - pool_year: string - 'location?': unknown - wallet: number - anonymize_date: Date - data_erasure_date: Date - created_at: Date - updated_at: Date - 'alumnized_at?': unknown - 'alumni?': boolean - 'active?': boolean - } - teams: { - id: number - name: string - url: string - final_mark: number - project_id: number - created_at: Date - updated_at: Date - status: string - 'terminating_at?': unknown - users: { - id: number - login: string - url: string - leader: boolean - occurrence: number - validated: boolean - projects_user_id: number - }[] - 'locked?': boolean - 'validated?': boolean - 'closed?': boolean - repo_url: string - repo_uuid: string - locked_at: Date - closed_at: Date - project_session_id: number - project_gitlab_path: string - }[] -} - -export interface ProjectSubscriber { - login: string - status: ProjectStatus - staff: boolean - image_url: string - lastChangeD: Date | string - new: boolean -} - -export interface Project { - name: string - users: ProjectSubscriber[] -} - -export interface UserProfile { - id: number - login: string - first_name: string - displayname: string - accessToken: string - refreshToken: string - campusID: number - campusName: string - timeZone: string +export interface DisplayProject { + name: string, + slug: string, + users: { + login: string, + image_url: string, + status: string, + pool: string, + new: boolean, + }[]; } diff --git a/src/util.ts b/src/util.ts index 211a900..f7b4f20 100644 --- a/src/util.ts +++ b/src/util.ts @@ -1,62 +1,24 @@ -export function findLast(arr: T[], predicate: (x: T) => boolean): T | undefined { - for (let i = arr.length - 1; i >= 0; i--) { - if (predicate(arr[i] as T)) { - return arr[i] - } - } - return undefined -} - -// get unique elements in array based on equalFn() -export function unique(arr: T[], equalFn: (a: T, b: T) => boolean): T[] { - return arr.filter((current, pos) => arr.findIndex(x => equalFn(x, current)) === pos) -} - -// ignoring case, whitespace, -, _, non ascii chars -export function isLinguisticallySimilar(a: string, b: string): boolean { - a = a - .toLowerCase() - .replace(/\s|-|_/g, '') - .normalize('NFKD') - .replace(/[\u0300-\u036F]/g, '') - b = b - .toLowerCase() - .replace(/\s|-|_/g, '') - .normalize('NFKD') - .replace(/[\u0300-\u036F]/g, '') - return a === b -} - function assertEnv(env: string): string { - const value = process.env[env] + const value = process.env[env]; if (value === undefined) { - throw new Error(`Environment variable "${env}" is not set`) + throw new Error(`Environment variable "${env}" is not set`); } - return value + return value; } export function assertEnvStr(env: string): string { - const value = assertEnv(env) + const value = assertEnv(env); if (typeof value !== 'string' || value.length === 0) { - throw new Error(`Environment variable "${value}" is not a non-empty string`) + throw new Error(`Environment variable "${value}" is not a non-empty string`); } - return value + return value; } export function assertEnvInt(env: string): number { - const value = assertEnv(env) - const num = parseInt(value) + const value = assertEnv(env); + const num = parseInt(value); if (isNaN(num)) { - throw new Error(`Environment variable "${value}" is not a number`) - } - return num -} - -export function mapObject(object: Record, mapFn: (key: Key, value: Value) => NewValue): Record { - const newObj: Record = {} as Record - - for (const key in object) { - newObj[key] = mapFn(key, object[key]) + throw new Error(`Environment variable "${value}" is not a number`); } - return newObj + return num; } diff --git a/tsconfig.json b/tsconfig.json index 1c51e3a..755bec7 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,7 @@ { "compilerOptions": { "target": "ES2020", - "module": "NodeNext", - "moduleResolution": "node", + "module": "CommonJS", "esModuleInterop": true, "outDir": "./build", "rootDirs": ["./src", "./env"], diff --git a/views/index.ejs b/views/index.ejs index 381acb6..4b0dc5d 100644 --- a/views/index.ejs +++ b/views/index.ejs @@ -13,6 +13,8 @@ font-family: monospace; padding: 0; + --accent: #ffffff; + --search-height: 50px; --root-padding: 32px; @@ -25,19 +27,63 @@ } body { - margin: 0 auto; + background: #1a1a1a; + background-image: + linear-gradient(to right, #00babc11 1px, transparent 1px), + linear-gradient(to bottom, #00babc11 1px, transparent 1px); + background-size: 20px 20px; + min-height: 100vh; padding: var(--search-height) var(--root-padding) 0px; } #title { font-size: var(--font-size-1); font-weight: bold; + text-align: center; + } + + #title-small { + font-size: var(--font-size-2-5); + font-weight: normal; + color: #ffffff; + text-align: center; } #description { font-size: var(--font-size-3); line-height: 1.2em; font-weight: bold; + color: #ffffff; + display: flex; + gap: 32px; + } + + .description-left { + flex: 1.5; + padding: 16px; + } + + .description-right { + flex: 1; + padding: 16px; + } + + .description { + margin: 40px 0; + padding: 32px; + background: #2c7385; + border-radius: 16px; + box-shadow: 0 4px 16px rgba(0,0,0,0.2); + border: 1px solid #222; + } + + .banner { + position: relative; + margin: calc(-1 * var(--search-height)) calc(-1 * var(--root-padding)) 0; + padding: calc(var(--search-height) + 64px) var(--root-padding) 64px; + background: #78a9bd; + width: 100vw; + margin-left: calc(-50vw + 50%); } #projects { @@ -46,19 +92,24 @@ } .project { - margin: 80px 0px; + margin: 40px 0; + padding: 32px; + background: #1c566d; + border-radius: 16px; + box-shadow: 0 4px 16px rgba(0,0,0,0.2); + border: 1px solid #222; } .project-name { + margin-bottom: 16px; font-size: var(--font-size-2); - display: flex; - flex-direction: row; - align-items: flex-end; + font-weight: bold; + color: #4a98c5; } - .project-title:hover { - cursor: pointer; - text-decoration: underline; + .project-title { + color: var(--accent); + transition: color 0.2s; } .users { @@ -85,6 +136,10 @@ font-size: var(--font-size-4); } + .pool { + font-size: var(--font-size-4); + } + .user-image { display: block; object-fit: cover; @@ -103,12 +158,13 @@ .user:hover, .user:focus { - box-shadow: 0 0 3px white; + box-shadow: 0 0 7px white; } .n-users { + color: #aaa; font-size: var(--font-size-4); - padding: 5px 20px; + margin-left: 16px; } #search-bar { @@ -121,7 +177,7 @@ right: 0; padding: 0 var(--root-padding); height: var(--search-height); - background: black; + background: rgb(26, 32, 126); box-shadow: 0 0 3px 0px black; font-size: var(--font-size-3); z-index: 2; @@ -151,21 +207,44 @@ + function setTimezoneCookie() { + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + document.cookie = `timezone=${timezone}; path=/; max-age=31536000`; // (1 year) + } + setTimezoneCookie(); + - - -
- Find Peers + + +
- Built by Joppe Koers/@jkoers - Source
- This page is updated every <%= updateEveryHours %> hours, last update was on <%= lastUpdate %>: <%= hoursAgo %> hours ago
- indicates that the user's project status was changed in the last <%= userNewStatusThresholdDays %> days
- <%= usage %>
+
+ This page displays what projects 42 students are working on, allowing them to find peers to collaborate with.
+ Clicking on a user will take you to their intra profile page.

+ indicates that the user's project status was changed in the last <%= userNewStatusThresholdDays %> days

+ Built by Joppe Koers/@jkoers
+ Maintained by Noah Mattos Oudejans/@nmattos-
+ Source
+
+
+ This page is updated every <%= updateEveryHours %> hours.

+ Last update was on <%= lastUpdate %> + <% if (lastUpdate != 'N/A') { %> + : <%= hoursAgo %> hours ago
+ <% } %> +
+ + +
+ <% projects.forEach(project=>{ %>
-
- <%= project.name %> +
+ <%= project.name.toLowerCase().replaceAll(' ', '-') %>
<%= project.users.length %> users
+
<% if (project.users.length == 0 && !requestedStatus) { %>

No users are subscribed to this project

@@ -220,6 +319,9 @@
<%= user.status %>
+
+ <%= user.pool %> +
<% }) %> <% } %> @@ -228,9 +330,5 @@ <% }) %>
- -