diff --git a/challenge_3/README.md b/challenge_3/README.md new file mode 100644 index 0000000..b31a87d --- /dev/null +++ b/challenge_3/README.md @@ -0,0 +1,23 @@ +# Challenge 1 + +## Instructions + +A Web application has been reported by a customer as broken and this has been escalated to the team. You're job is to investigate, troubleshoot and resolve the problem, returning the web app to normal service. + +You will be provided with an individual url for the site. + +[source code](./trubble/trubble.js), [documentation](./trubble/README.md) and [deployment code](./trubble/ansible/deploy.yml) is in the [trubble directory](./trubble/) + +1. in advance, please generate a new, unique SSH keypair and send us the **public** key. You may follow [these](https://docs.github.com/en/authentication/connecting-to-github-with-ssh/generating-a-new-ssh-key-and-adding-it-to-the-ssh-agent#generating-a-new-ssh-key) instructions. + +2. You will recieve a url from us. + +3. Using SSH log in to the instance the web app is hosted on (the address is the same as the url the username is `ubuntu`). + +4. Document your troubleshooting process while you restore normal operation. + +5. Provide a permanent solution or work-around to the issue. + +## Solution Submission + +Please host your solution as a **private** repository and invite your interviewer as a collaborator. \ No newline at end of file diff --git a/challenge_3/trubble/.gitignore b/challenge_3/trubble/.gitignore new file mode 100644 index 0000000..82ccb66 --- /dev/null +++ b/challenge_3/trubble/.gitignore @@ -0,0 +1,170 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Snowpack dependency directory (https://snowpack.dev/) +web_modules/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional stylelint cache +.stylelintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variable files +.env +.env.development.local +.env.test.local +.env.production.local +.env.local + +# parcel-bundler cache (https://parceljs.org/) +.cache +.parcel-cache + +# Next.js build output +.next +out + +# Nuxt.js build / generate output +.nuxt +dist + +# Gatsby files +.cache/ +# Comment in the public line in if your project uses Gatsby and not Next.js +# https://nextjs.org/blog/next-9-1#public-directory-support +# public + +# vuepress build output +.vuepress/dist + +# vuepress v2.x temp and cache directory +.temp +.cache + +# Docusaurus cache and generated files +.docusaurus + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + +# TernJS port file +.tern-port + +# Stores VSCode versions used for testing VSCode extensions +.vscode-test + +# yarn v2 +.yarn/cache +.yarn/unplugged +.yarn/build-state.yml +.yarn/install-state.gz +.pnp.* + +# Local .terraform directories +**/.terraform/* + +# .tfstate files +*.tfstate +*.tfstate.* + +# Crash log files +crash.log +crash.*.log + +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars +*.tfvars.json + +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json + +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf + +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* + +# Ignore CLI configuration files +.terraformrc +terraform.rc + +# Ansible retry files +*.retry + +dist \ No newline at end of file diff --git a/challenge_3/trubble/.node-version b/challenge_3/trubble/.node-version new file mode 100644 index 0000000..7b768e0 --- /dev/null +++ b/challenge_3/trubble/.node-version @@ -0,0 +1 @@ +14.17.6 \ No newline at end of file diff --git a/challenge_3/trubble/README.md b/challenge_3/trubble/README.md new file mode 100644 index 0000000..07861b0 --- /dev/null +++ b/challenge_3/trubble/README.md @@ -0,0 +1,88 @@ +# Trubble + +A web application built using Node.js and Express. It serves a simple web page at the root URL. There is only a single route `/` which searches for a random entity from [pokeapi](https://pokeapi.co/) and presents the encounter as plain text. + +## Prerequisites + +Before running the app, make sure you have the following installed: + +- Node.js version 14 (https://nodejs.org/en/download) +- NPM (Node Package Manager) + +## Configuration + +The web app can be configured using environment variables. Create a `.env` file in the root directory of the project with the following variables: +- `NODE_ENV` (optional): The environment the app will run in. +- `PORT` (optional): The port number the app will listen on. Defaults to `3000`. +- `IMPORTANT_VALUE` (required): A boolean value that must be set to `true` for the app to run. If this value is not set or is set to anything other than `true`, the app will log an error message and exit. + +## Logging + +The application will log to an `app.log` file in `logs` directory. +In production logging will also be handled by systemd and will log to `/var/logs/syslog`. check the below systemd file for details. + +## Running the App in Development + +To run the app, follow these steps: + +1. Install dependencies by running `npm install`. +2. Start the app by running `npm start`. +3. Access the app by navigating to `http://localhost:3000` (or whichever port you specified in the `PORT` environment variable). + +## Testing + +Open a web browser and navigate to `http://localhost:3000/`. + +## Building the app for production + +1. Install dependencies by running `npm install`. +2. Build the application with `npm run build` + +## Installing the app in Production + +Creation of the instance, install and managment of the application is by ansible. the playbook is in the [ansible directory](./ansible/deploy.yml). +Instructions on how to use the ansible playbook can be found in the [ansible directory](./ansible/README.md). +Ansible should be used to ensure the application is installed and configured correctly. + +The [playbook](./ansible/deploy.yml) reads as the documentation however the install steps can be summarised as: + +1. Ensure the App is built in the `dist` directory. +1. Create a new Linux instance. +2. Ensure nodejs is installed on the system. +3. Ensure nginx is installed and configured correctly. +4. Copy the contents of the local `dist` directory to the remote machine at the `/opt/` directory +5. Use systemd (example below) to keep the app running + + +## Systemd Unit File + +To run the app as a systemd service on Linux, create a file as a root user `/etc/systemd/system/trubble.service` with the following content: + +``` +[Unit] +Description=trubble +After=network.target + +[Service] +Environment="NODE_ENV=production" +User=root +WorkingDirectory=/opt +ExecStart=/usr/bin/node index.js +Restart=always +StandardOutput=syslog +StandardError=syslog +SyslogIdentifier=trubble + +[Install] +WantedBy=multi-user.target +``` + +Then run the following commands: + +``` +sudo systemctl daemon-reload +sudo systemctl enable trubble +sudo systemctl start trubble +``` + +The app will now start automatically on boot and can be managed using the `systemctl` command. \ No newline at end of file diff --git a/challenge_3/trubble/ansible/README.md b/challenge_3/trubble/ansible/README.md new file mode 100644 index 0000000..468276e --- /dev/null +++ b/challenge_3/trubble/ansible/README.md @@ -0,0 +1,38 @@ +# Deploy Trubble + +A playbook to create an AWS EC2 instance and install The trubble nodejs application. + +## Prerequisites + +- python 3.9+ +- python pip + +## Setup + +Install dependencies + +``` +python3 -m pip install -r requirements.txt +ansible-galaxy install -f -r requirements.yml +ansible-galaxy collection install -f -r requirements.yml +``` + +Setup AWS credentials + +``` +export AWS_ACCESS_KEY_ID="xxxxx" +export AWS_SECRET_ACCESS_KEY="xxxxx" +export AWS_SESSION_TOKEN="xxxxx" +``` + +To only manage existing running application instances run: + +ansible-playbook deploy.yml --extra-vars='\{"users": \[user1,user2,user3\]\}' --skip-tags='create' + +where `users` var is an array of users to create instances for. + +Run the full playbook and create a new instance: + +``` +ansible-playbook deploy.yml --extra-vars='\{"users": \[user1,user2,user3\]\}' +``` \ No newline at end of file diff --git a/challenge_3/trubble/ansible/ansible.cfg b/challenge_3/trubble/ansible/ansible.cfg new file mode 100644 index 0000000..56a757d --- /dev/null +++ b/challenge_3/trubble/ansible/ansible.cfg @@ -0,0 +1,7 @@ + +[defaults] +ansible_python_interpreter=/usr/bin/python3 +inventory=aws_ec2.yaml + +[inventory] +enable_plugins = aws_ec2 \ No newline at end of file diff --git a/challenge_3/trubble/ansible/aws_ec2.yaml b/challenge_3/trubble/ansible/aws_ec2.yaml new file mode 100644 index 0000000..6731e4c --- /dev/null +++ b/challenge_3/trubble/ansible/aws_ec2.yaml @@ -0,0 +1,9 @@ +--- +plugin: aws_ec2 +filters: + tag:Env: dev +keyed_groups: + - key: tags + prefix: tag +regions: + - us-east-1 \ No newline at end of file diff --git a/challenge_3/trubble/ansible/deploy.yml b/challenge_3/trubble/ansible/deploy.yml new file mode 100644 index 0000000..40d32f1 --- /dev/null +++ b/challenge_3/trubble/ansible/deploy.yml @@ -0,0 +1,122 @@ +--- +- name: Deploy web app + hosts: localhost + become: false + + vars: + users: + - user1 + + tasks: + + - name: Lookup existing AWS subnet id + ec2_vpc_subnet_info: + filters: + "tag:Name": "devops-challenge-public-us-east-1a" + register: subnet_info + + # ubuntu 16.04 (Xenial Xerus) + - name: Create the EC2 instance + ec2_instance: + key_name: "{{ item }}" + instance_type: t3.micro + image_id: ami-0b0ea68c435eb488d + wait: true + vpc_subnet_id: "{{ subnet_info.subnets[0].id }}" + security_group: devops-challenge-allow-http + network: + assign_public_ip: true + tags: + Env: "dev" + Application: "devops-challenge" + Name: "devops-challenge-{{ item }}" + register: ec2_instance + loop: "{{ users }}" + + - meta: refresh_inventory + +- name: Deploy web app + hosts: tag_Application_devops_challenge + become: true + gather_facts: true + user: ubuntu + tags: + - config + + pre_tasks: + - name: Wait 600 seconds for target connection to become reachable/usable + ansible.builtin.wait_for_connection: + timeout: 600 + + - name: Wait for cloud-init to complete + shell: cloud-init status + register: cloud_init_install + retries: 60 + delay: 5 + until: 'cloud_init_install.stdout == "status: done"' + tags: + - molecule-notest + + - name: Update the apt cache + apt: + update_cache: true + force_apt_get: true + cache_valid_time: 3600 + become: true + + + roles: + - role: geerlingguy.nodejs + vars: + nodejs_version: "14.x" + + post_tasks: + + - name: Install an NGINX web server + yum: + name: nginx + state: present + + - name: Configure NGINX to front the web app + copy: + src: nginx.conf + dest: /etc/nginx/nginx.conf + + - name: copy NGINX error page + copy: + src: error.html + dest: /usr/share/nginx/html/error.html + + - name: Enable nginx and reload + systemd: + name: nginx + enabled: true + daemon_reload: true + state: restarted + + - name: Copy web app files + copy: + src: "{{ item }}" + dest: /opt/ + loop: + - ../dist/index.js + - ../dist/package.json + + # TODO: web app Configuration management not implemented + + - name: Add systemd unit file for web app + copy: + src: trubble.service + dest: /etc/systemd/system/ + tags: + - systemd + + - name: Enable and start the web app + systemd: + name: trubble + enabled: true + daemon_reload: true + state: started + tags: + - start_app + - systemd diff --git a/challenge_3/trubble/ansible/files/error.html b/challenge_3/trubble/ansible/files/error.html new file mode 100644 index 0000000..13e27b0 --- /dev/null +++ b/challenge_3/trubble/ansible/files/error.html @@ -0,0 +1,10 @@ + + + + I am very broken + + +

I am very broken

+ Sad Computer + + \ No newline at end of file diff --git a/challenge_3/trubble/ansible/files/nginx.conf b/challenge_3/trubble/ansible/files/nginx.conf new file mode 100644 index 0000000..3d46e4f --- /dev/null +++ b/challenge_3/trubble/ansible/files/nginx.conf @@ -0,0 +1,30 @@ +user www-data; +worker_processes auto; +pid /run/nginx.pid; +events { + worker_connections 1024; +} +http { +server { + listen 80; + server_name _; + + location / { + proxy_pass http://localhost:3000; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + + # Set the maximum time to wait for a response from the upstream server + proxy_connect_timeout 5s; + proxy_read_timeout 10s; + } + + # Serve the custom error page if the upstream server is unavailable + error_page 502 503 /error.html; + location = /error.html { + root /usr/share/nginx/html; + internal; + } +} +} \ No newline at end of file diff --git a/challenge_3/trubble/ansible/files/trubble.service b/challenge_3/trubble/ansible/files/trubble.service new file mode 100644 index 0000000..02def39 --- /dev/null +++ b/challenge_3/trubble/ansible/files/trubble.service @@ -0,0 +1,16 @@ +[Unit] +Description=trubble +After=network.target + +[Service] +Environment="NODE_ENV=production" +User=root +WorkingDirectory=/opt +ExecStart=/usr/bin/node index.js +Restart=always +StandardOutput=syslog +StandardError=syslog +SyslogIdentifier=trubble + +[Install] +WantedBy=multi-user.target \ No newline at end of file diff --git a/challenge_3/trubble/ansible/requirements.txt b/challenge_3/trubble/ansible/requirements.txt new file mode 100644 index 0000000..6f62328 --- /dev/null +++ b/challenge_3/trubble/ansible/requirements.txt @@ -0,0 +1,4 @@ +molecule[docker] +ansible-lint>=5,<6 +ansible==2.9.26 +boto3==1.18.46 \ No newline at end of file diff --git a/challenge_3/trubble/ansible/requirements.yml b/challenge_3/trubble/ansible/requirements.yml new file mode 100644 index 0000000..777a27c --- /dev/null +++ b/challenge_3/trubble/ansible/requirements.yml @@ -0,0 +1,5 @@ +roles: + - name: geerlingguy.nodejs +collections: + - name: amazon.aws + - name: community.general \ No newline at end of file diff --git a/challenge_3/trubble/bootstrap/infrastructure/terraform/.terraform.lock.hcl b/challenge_3/trubble/bootstrap/infrastructure/terraform/.terraform.lock.hcl new file mode 100644 index 0000000..cb5f1be --- /dev/null +++ b/challenge_3/trubble/bootstrap/infrastructure/terraform/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "4.65.0" + constraints = ">= 4.35.0" + hashes = [ + "h1:ZEdurVGkjkOZzhJijFTF+3djXZ9N4Js8Ss6tM43n3HA=", + "zh:0461b8dfc14e94971bfd12783cbd5a5574b9fcfc3694b6afaa8836f90b61c1f9", + "zh:24a27e7b1f6eb33e9da6f2ffaaa6bc48e933a24224c6572d6e588994e5c7130b", + "zh:2ca189d04573414bef4876c17ccb2b76f6e721e0450f6ab3700d94d7c04bec64", + "zh:3fb0654a527677231dab2140e9a55df3b90dba478b3db50001e21a045437a47a", + "zh:4918173d9c7d2735908622c17efd01746a046f0a571690afa7dd0866f22045f7", + "zh:491d259b15166f751076d2bdc443928ca63f6c0a83b02ea75fff8b4224662207", + "zh:4ff8e178f0656f04f88558c295a1d246b1bdcf5ad81d8b3b9ccceaeca2eb7fa8", + "zh:5e4eaf2855a740124f4bbe34ac4bd22c7f320aa3e91d9cef64396ad0a1571544", + "zh:65762c60c4bac2e0d55ed8c2877e455e84465cb12f0c885363a1b561cd4f5f07", + "zh:7c5e4f85eb5f70e6da2d64701dd5551f2bc334dbb9add76bfc6a2bea6acf4483", + "zh:90d32b238113528319d7a5fade97bd8ac9a8b654482fc9056478a43d2e297886", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:e6ed3299516a8fb2292af7e7e123d09817dfd8e039aaf35ad5a276f739668e88", + "zh:eb84fa96c63d836b3b4689835cb7c4487808dfd1ba7ddacf4d8c4c6ff65cdbef", + "zh:ff97d1498193c99c9c35afd9bfcdce011abf460ec041721727d6e542f7a3bedd", + ] +} diff --git a/challenge_3/trubble/bootstrap/infrastructure/terraform/README.md b/challenge_3/trubble/bootstrap/infrastructure/terraform/README.md new file mode 100644 index 0000000..24c7e65 --- /dev/null +++ b/challenge_3/trubble/bootstrap/infrastructure/terraform/README.md @@ -0,0 +1,22 @@ +# Trubble infra bootstrap + +## internal use only + +Creates the base infra needed for the trubble instances: + +- A vpc with a public subnet and routes +- an open security group for HTTP and SSH +- ssh public keys for each user + + +## Setup + +Setup AWS credentials + +``` +export AWS_ACCESS_KEY_ID="xxxxx" +export AWS_SECRET_ACCESS_KEY="xxxxx" +export AWS_SESSION_TOKEN="xxxxx" +``` + +add username, ssh public keys to the local variable `allowed_keys` in the [locals.tf file](./locals.tf) diff --git a/challenge_3/trubble/bootstrap/infrastructure/terraform/config.tf b/challenge_3/trubble/bootstrap/infrastructure/terraform/config.tf new file mode 100644 index 0000000..bf40ea2 --- /dev/null +++ b/challenge_3/trubble/bootstrap/infrastructure/terraform/config.tf @@ -0,0 +1,28 @@ +terraform { + backend "s3" { + region = "us-east-1" + bucket = "zestia-dev-terraform-state" + key = "terraform.tfstate" + dynamodb_table = "zestia-dev-terraform-state-lock" + encrypt = "true" + workspace_key_prefix = "devops-challenge" + } + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 4.0" + } + } +} + + +provider "aws" { + region = "us-east-1" + + default_tags { + tags = { + Env = "dev" + Application = "devops-challenge" + } + } +} \ No newline at end of file diff --git a/challenge_3/trubble/bootstrap/infrastructure/terraform/locals.tf b/challenge_3/trubble/bootstrap/infrastructure/terraform/locals.tf new file mode 100644 index 0000000..0bafe67 --- /dev/null +++ b/challenge_3/trubble/bootstrap/infrastructure/terraform/locals.tf @@ -0,0 +1,5 @@ +locals { + allowed_keys = { + user1 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGWcVfpqphsL3D3kujDSBmx48zVhPFd8mwAN1K9do0P1 user1@example.com" + } +} \ No newline at end of file diff --git a/challenge_3/trubble/bootstrap/infrastructure/terraform/main.tf b/challenge_3/trubble/bootstrap/infrastructure/terraform/main.tf new file mode 100644 index 0000000..5e40feb --- /dev/null +++ b/challenge_3/trubble/bootstrap/infrastructure/terraform/main.tf @@ -0,0 +1,71 @@ +data "aws_availability_zones" "available" {} + +resource "aws_key_pair" "deployer" { + for_each = local.allowed_keys + key_name = each.key + public_key = each.value +} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + name = "devops-challenge" + + enable_nat_gateway = false + create_database_subnet_group = false + enable_vpn_gateway = false + manage_default_vpc = false + manage_default_security_group = false + manage_default_network_acl = false + manage_default_route_table = false + + public_subnets = ["10.0.101.0/24"] + + azs = [data.aws_availability_zones.available.names[0]] +} + + +resource "aws_security_group" "allow_http" { + name = "devops-challenge-allow-http" + description = "Allow HTTP traffic" + vpc_id = module.vpc.vpc_id + + ingress { + description = "HTTP" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "SSH" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + ipv6_cidr_blocks = ["::/0"] + } + + tags = { + Name = "devops-challenge-allow-http" + } +} + +output "vpc_id" { + value = module.vpc.vpc_id +} + +output "subnet_id" { + value = module.vpc.public_subnets[0] +} + +output "security_group_id" { + value = aws_security_group.allow_http.id +} \ No newline at end of file diff --git a/challenge_3/trubble/dev.env b/challenge_3/trubble/dev.env new file mode 100644 index 0000000..9e6ac67 --- /dev/null +++ b/challenge_3/trubble/dev.env @@ -0,0 +1,2 @@ +#PORT="3000" +IMPORTANT_VALUE="true" \ No newline at end of file diff --git a/challenge_3/trubble/package-lock.json b/challenge_3/trubble/package-lock.json new file mode 100644 index 0000000..f87026e --- /dev/null +++ b/challenge_3/trubble/package-lock.json @@ -0,0 +1,670 @@ +{ + "name": "trubble", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@colors/colors": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", + "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==" + }, + "@dabh/diagnostics": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@dabh/diagnostics/-/diagnostics-2.0.3.tgz", + "integrity": "sha512-hrlQOIi7hAfzsMqlGSFyVucrx38O+j6wiGOf//H2ecvIEqYN4ADBSS2iLMh5UFyDunCNniUIPk/q3riFv45xRA==", + "requires": { + "colorspace": "1.1.x", + "enabled": "2.0.x", + "kuler": "^2.0.0" + } + }, + "@types/triple-beam": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@types/triple-beam/-/triple-beam-1.3.2.tgz", + "integrity": "sha512-txGIh+0eDFzKGC25zORnswy+br1Ha7hj5cMVwKIU7+s0U2AxxJru/jZSMU6OC9MJWP6+pc/hc6ZjyZShpsyY2g==" + }, + "@vercel/ncc": { + "version": "0.36.1", + "resolved": "https://registry.npmjs.org/@vercel/ncc/-/ncc-0.36.1.tgz", + "integrity": "sha512-S4cL7Taa9yb5qbv+6wLgiKVZ03Qfkc4jGRuiUQMQ8HGBD5pcNRnHeYM33zBvJE4/zJGjJJ8GScB+WmTsn9mORw==", + "dev": true + }, + "accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "requires": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + } + }, + "aggregate-error": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-4.0.1.tgz", + "integrity": "sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==", + "requires": { + "clean-stack": "^4.0.0", + "indent-string": "^5.0.0" + } + }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "async": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz", + "integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==" + }, + "axios": { + "version": "0.24.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", + "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "requires": { + "follow-redirects": "^1.14.4" + } + }, + "body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "requires": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + } + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "clean-stack": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-4.2.0.tgz", + "integrity": "sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==", + "requires": { + "escape-string-regexp": "5.0.0" + } + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==" + }, + "color": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/color/-/color-3.2.1.tgz", + "integrity": "sha512-aBl7dZI9ENN6fUGC7mWpMTPNHmWUSNan9tuWN6ahh5ZLNk9baLJOnSMlrQkHcrfFgz2/RigjUVAjdx36VcemKA==", + "requires": { + "color-convert": "^1.9.3", + "color-string": "^1.6.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "requires": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "colorspace": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/colorspace/-/colorspace-1.1.4.tgz", + "integrity": "sha512-BgvKJiuVu1igBUF2kEjRCZXol6wiiGbY5ipL/oVPwm0BL9sIpMIzM8IK7vwuxIIzOXMV3Ey5w+vxhm0rR/TN8w==", + "requires": { + "color": "^3.1.3", + "text-hex": "1.0.x" + } + }, + "content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "requires": { + "safe-buffer": "5.1.2" + } + }, + "content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==" + }, + "cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==" + }, + "dotenv": { + "version": "16.0.3", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz", + "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "enabled": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", + "integrity": "sha512-AKrN98kuwOzMIdAizXGI86UFBoo26CL21UM763y1h/GMSJ4/OHU9k2YlsmBpyScFo/wbLzWQJBMCW4+IO3/+OQ==" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "requires": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + } + }, + "express-rate-limit": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-6.7.0.tgz", + "integrity": "sha512-vhwIdRoqcYB/72TK3tRZI+0ttS8Ytrk24GfmsxDXK9o9IhHNO5bXRiXQSExPQ4GbaE5tvIS7j1SGrxsuWs+sGA==" + }, + "fecha": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/fecha/-/fecha-4.2.3.tgz", + "integrity": "sha512-OP2IUU6HeYKJi3i0z4A19kHMQoLVs4Hc+DPqqxI2h/DPZHTm/vjsfC6P0b4jCMy14XizLBqvndQ+UilD7707Jw==" + }, + "finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + } + }, + "fn.name": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/fn.name/-/fn.name-1.1.0.tgz", + "integrity": "sha512-GRnmB5gPyJpAhTQdSZTSp9uaPSvl09KoYcMQtsB9rQoOmzs9dH6ffeccH+Z+cv6P68Hu5bC6JjRh4Ah/mHSNRw==" + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + }, + "http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + } + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "indent-string": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-5.0.0.tgz", + "integrity": "sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==" + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" + }, + "is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==" + }, + "kuler": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/kuler/-/kuler-2.0.0.tgz", + "integrity": "sha512-Xq9nH7KlWZmXAtodXDDRE7vs6DU1gTU8zYDHDiWLSip45Egwq3plLHzPn27NgvzL2r1LMPC1vdqh98sQxtqj4A==" + }, + "logform": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/logform/-/logform-2.5.1.tgz", + "integrity": "sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==", + "requires": { + "@colors/colors": "1.5.0", + "@types/triple-beam": "^1.3.2", + "fecha": "^4.2.0", + "ms": "^2.1.1", + "safe-stable-stringify": "^2.3.1", + "triple-beam": "^1.3.0" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "negotiator": { + "version": "0.6.3", + "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" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "requires": { + "ee-first": "1.1.1" + } + }, + "one-time": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-1.0.0.tgz", + "integrity": "sha512-5DXOiRKwuSEcQ/l0kGCF6Q3jcADFv5tSmRaJck/OqkVFcOzutB134KRSfF0xDrL39MNnqxbHBbUUcjZIhTgb2g==", + "requires": { + "fn.name": "1.x.x" + } + }, + "p-map": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-5.5.0.tgz", + "integrity": "sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==", + "requires": { + "aggregate-error": "^4.0.0" + } + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, + "pokedex-promise-v2": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/pokedex-promise-v2/-/pokedex-promise-v2-4.1.1.tgz", + "integrity": "sha512-3v22kBwgJWzEcyyK3nOd+s3WzaGFcdkZ/PtxjJRklVF4OBOIl1evkQHkAGvN5r40lLlBn3/yVo8dlZvJmvIsew==", + "requires": { + "axios": "^0.24.0", + "node-cache": "^5.1.2", + "p-map": "^5.5.0" + } + }, + "proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "requires": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + } + }, + "qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==" + }, + "range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" + }, + "raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "safe-stable-stringify": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.4.3.tgz", + "integrity": "sha512-e2bDA2WJT0wxseVd4lsDP4+3ONX6HpMXQa1ZhFQ7SU+GjvORCmShbCMltrtIDfkYhVHrOcPtj+KhmDBdPdZD1g==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "dependencies": { + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + } + } + }, + "serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + } + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "requires": { + "is-arrayish": "^0.3.1" + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha512-KGzahc7puUKkzyMt+IqAep+TVNbKP+k2Lmwhub39m1AsTSkaDutx56aDCo+HLDzf/D26BIHTJWNiTG1KAJiQCg==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==" + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + } + } + }, + "text-hex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/text-hex/-/text-hex-1.0.0.tgz", + "integrity": "sha512-uuVGNWzgJ4yhRaNSiubPY7OjISw4sw4E5Uv0wbjp+OzcbmVU/rsT8ujgcXJhn9ypzsgr5vlzpPqP+MBBKcGvbg==" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "triple-beam": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.3.0.tgz", + "integrity": "sha512-XrHUvV5HpdLmIj4uVMxHggLbFSZYIn7HEWsqePZcI50pco+MPqJ50wMGY794X7AOOhxOBAjbkqfAbEe/QMp2Lw==" + }, + "type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" + }, + "winston": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/winston/-/winston-3.8.2.tgz", + "integrity": "sha512-MsE1gRx1m5jdTTO9Ld/vND4krP2To+lgDoMEHGGa4HIlAUyXJtfc7CxQcGXVyz2IBpw5hbFkj2b/AtUdQwyRew==", + "requires": { + "@colors/colors": "1.5.0", + "@dabh/diagnostics": "^2.0.2", + "async": "^3.2.3", + "is-stream": "^2.0.0", + "logform": "^2.4.0", + "one-time": "^1.0.0", + "readable-stream": "^3.4.0", + "safe-stable-stringify": "^2.3.1", + "stack-trace": "0.0.x", + "triple-beam": "^1.3.0", + "winston-transport": "^4.5.0" + } + }, + "winston-transport": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/winston-transport/-/winston-transport-4.5.0.tgz", + "integrity": "sha512-YpZzcUzBedhlTAfJg6vJDlyEai/IFMIVcaEZZyl3UXIl4gmqRpU7AE89AHLkbzLUsv0NVmw7ts+iztqKxxPW1Q==", + "requires": { + "logform": "^2.3.2", + "readable-stream": "^3.6.0", + "triple-beam": "^1.3.0" + } + } + } +} diff --git a/challenge_3/trubble/package.json b/challenge_3/trubble/package.json new file mode 100644 index 0000000..9865808 --- /dev/null +++ b/challenge_3/trubble/package.json @@ -0,0 +1,24 @@ +{ + "name": "trubble", + "type": "module", + "version": "1.0.0", + "description": "", + "main": "trubble.js", + "scripts": { + "test": "echo \"Warning: no tests implemented\" && exit 1", + "build": "ncc build trubble.js -o dist", + "start": "node trubble.js" + }, + "author": "zestia ltd", + "license": "MIT", + "dependencies": { + "dotenv": "^16.0.3", + "express": "^4.17.1", + "express-rate-limit": "^6.7.0", + "pokedex-promise-v2": "^4.1.1", + "winston": "^3.8.2" + }, + "devDependencies": { + "@vercel/ncc": "^0.36.1" + } +} diff --git a/challenge_3/trubble/trubble.js b/challenge_3/trubble/trubble.js new file mode 100644 index 0000000..d397715 --- /dev/null +++ b/challenge_3/trubble/trubble.js @@ -0,0 +1,96 @@ +import rateLimit from 'express-rate-limit' +import express from 'express' +import dotenv from 'dotenv' +import * as winston from 'winston'; +import * as fs from 'fs'; +import * as path from 'path'; +import Pokedex from 'pokedex-promise-v2'; + +// Load configuration variables from .env file +const env = process.env.NODE_ENV || 'development' +if (env === 'development') { + dotenv.config({ path: 'dev.env' }); +} else { + dotenv.config(); +} + +// setup the port + +const port = process.env.PORT || 3000 + +// Create log directory if it doesn't exist +const logDirectory = './logs'; +if (!fs.existsSync(logDirectory)) { + fs.mkdirSync(logDirectory); +} + +// Create Winston logger +const logger = winston.createLogger({ + format: winston.format.combine( + winston.format.timestamp(), + winston.format.json() + ), + transports: [ + new winston.transports.Console({ + colorize: true + }), + new winston.transports.File({ + filename: `${logDirectory}/app.log`, + level: 'info' + }) + ], + exceptionHandlers: [ + new winston.transports.File({ + filename: `${logDirectory}/exceptions.log` + }) + ], + exitOnError: false +}); + + +const app = express(); + +// Set up rate limiter +const limiter = rateLimit({ + windowMs: 1000, // 1 second + max: 10, // limit each IP to 10 requests per windowMs + handler: function (req, res, /*next*/) { + // Log an error when rate limit exceeded + logger.error('Rate limit exceeded for IP ' + req.ip); + res.status(429).send('Too many requests'); + } +}); + +// Apply the rate limiter to all requests +app.use(limiter); + +// Validate IMPORTANT_VALUE configuration variable +if (!process.env.IMPORTANT_VALUE) { + logger.error('Config Error. The key IMPORTANT_VALUE is not set to true'); + logger.end(); + // give for logger to log + setTimeout(function () { + process.exit(1); + }, 1000) +} + +// Define route for homepage +app.get('/', (req, res) => { + const P = new Pokedex(); + P.getPokemonsList() + .then((response) => { + const randomElement = response.results[Math.floor(Math.random() * response.results.length)]; + logger.info('Searching...') + res.send(`Wild ${randomElement.name.toUpperCase()} appeared!`); + logger.info(`${randomElement.name.toUpperCase()} seen`) + }) + .catch((error) => { + logger.error('There was an ERROR: ', error); + }); +}); + +// Start server +app.listen(port, () => { + logger.info(`Running in Environment ${env}`); + logger.info(`Server listening on port ${port}`); +});