diff --git a/.github/workflows/maven-publish.yaml b/.github/workflows/maven-publish.yaml index 60628b97..00402167 100644 --- a/.github/workflows/maven-publish.yaml +++ b/.github/workflows/maven-publish.yaml @@ -62,4 +62,14 @@ jobs: run: | docker build ./webserver --tag ahmad45123/workup:webserver docker push ahmad45123/workup:webserver - \ No newline at end of file + + - name: Build and push Autoscaler Image + run: | + docker build ./autoscaler/docker-swarm-autoscaler --tag ahmad45123/workup:autoscaler + docker push ahmad45123/workup:autoscaler + + - name: Build and push Mediaserver Image + run: | + docker build ./mediaserver --tag ahmad45123/workup:mediaserver + docker push ahmad45123/workup:mediaserver + diff --git a/.idea/compiler.xml b/.idea/compiler.xml index f20e8c93..a2d709a9 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -22,11 +22,11 @@ \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 97e8c0e5..05cc42ad 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -8,6 +8,5 @@ - \ No newline at end of file diff --git a/Makefile b/Makefile index 844a16ee..239fcf5c 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ build: docker build ./services/users --tag workup:service_users docker build ./services/contracts --tag workup:service_contracts docker build ./webserver --tag workup:webserver + docker build ./mediaserver --tag workup:mediaserver up: docker stack deploy -c compose.yaml -c compose.override.yaml workup diff --git a/autoscaler/README.md b/autoscaler/README.md new file mode 100644 index 00000000..b75dad7f --- /dev/null +++ b/autoscaler/README.md @@ -0,0 +1,43 @@ +# docker-swarm-autoscaler + +## Current Release: 0.1.0 + +This project is intended to bring auto service staling to Docker Swarm. This script uses prometheus paired with cadvisor metrics to determine cpu usage. It then uses a manager node to determine if a service wants to be autoscaled and uses a manager node to scale the service. + +Currently the project only uses cpu to autoscale. If cpu usage reaches 85% the service will scale up, if it reaches 25% it will scale down. + +## Usage +1. You can deploy prometheus, cadvisor, and docker-swarm-autoscaler by running `docker stack deploy -c swarm-autoscaler-stack.yml autoscaler` from the root of this repo. + * You can also utilize an already deploy prometheus and cadvisor by specifying the `PROMETHEUS_URL` in docker-swarm-autoscaler environment. `swarm-autoscaler-stack.yml` shows an example of this. + * docker-swarm-autoscale needs a placement contstraint to deploy to a manager. `swarm-autoscaler-stack.yml` shows an example of this. +2. For services you want to autoscale you will need a deploy label `swarm.autoscaler=true`. + +``` +deploy: + labels: + - "swarm.autoscaler=true" +``` + +This is best paired with resource constraints limits. This is also under the deploy key. + +``` +deploy: + resources: + reservations: + cpus: '0.25' + memory: 512M + limits: + cpus: '0.50' +``` + +## Configuration +| Setting | Value | Description | +| --- | --- | --- | +| `swarm.autoscaler` | `true` | Required. This enables autoscaling for a service. Anything other than `true` will not enable it | +| `swarm.autoscaler.minimum` | Integer | Optional. This is the minimum number of replicas wanted for a service. The autoscaler will not downscale below this number | +| `swarm.autoscaler.maximum` | Integer | Optional. This is the maximum number of replicas wanted for a service. The autoscaler will not scale up past this number | + +## Test +You can deploy a test app with the following commands below. Helloworld is initially only 1 replica. The autoscaler will scale to the minimum 3 replicas. +1. `docker stack deploy -c swarm-autoscaler-stack.yml autoscaler` +2. `docker stack deploy -c helloworld.yml hello` \ No newline at end of file diff --git a/autoscaler/docker-swarm-autoscaler/Dockerfile b/autoscaler/docker-swarm-autoscaler/Dockerfile new file mode 100644 index 00000000..e9812056 --- /dev/null +++ b/autoscaler/docker-swarm-autoscaler/Dockerfile @@ -0,0 +1,26 @@ +FROM ubuntu:xenial + +RUN apt-get update -qq \ + && apt-get install -y -qq \ + jq \ + apt-transport-https \ + ca-certificates \ + curl \ + software-properties-common \ + dnsutils \ + && curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add - \ + && add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu xenial stable" \ + && apt-get update -qq \ + && apt-get install -y -qq \ + docker-ce=5:19.03.5* \ + && apt-get -qq clean \ + && apt-get autoremove -y \ + && rm -rf \ + /var/lib/apt/lists/* \ + /tmp/* \ + /var/tmp/* + +COPY auto-scale.sh /auto-scale.sh +RUN chmod a+x /auto-scale.sh + +CMD ["/auto-scale.sh"] \ No newline at end of file diff --git a/autoscaler/docker-swarm-autoscaler/auto-scale.sh b/autoscaler/docker-swarm-autoscaler/auto-scale.sh new file mode 100644 index 00000000..6f93d647 --- /dev/null +++ b/autoscaler/docker-swarm-autoscaler/auto-scale.sh @@ -0,0 +1,114 @@ +#!/bin/bash + +LOOP=${LOOP:='yes'} +CPU_PERCENTAGE_UPPER_LIMIT=60 +CPU_PERCENTAGE_LOWER_LIMIT=25 +PROMETHEUS_API="api/v1/query?query=" +PROMETHEUS_QUERY="sum(rate(container_cpu_usage_seconds_total%7Bcontainer_label_com_docker_swarm_task_name%3D~%27.%2B%27%7D%5B5m%5D))BY(container_label_com_docker_swarm_service_name%2Cinstance)*100" + +get_high_cpu_services () { + local prometheus_results="${1}" + local services="" + for service in $(printf "%s$prometheus_results" | jq ".data.result[] | select( all(.value[1]|tonumber; . > $CPU_PERCENTAGE_UPPER_LIMIT) ) | .metric.container_label_com_docker_swarm_service_name" | sed 's/"//g' | sort | uniq); do + services="$services $service" + done + echo $services +} + +get_all_services () { + local prometheus_results="${1}" + local services="" + for service in $(printf "%s$prometheus_results" | jq ".data.result[].metric.container_label_com_docker_swarm_service_name" | sed 's/"//g' | sort | uniq); do + services="$services $service" + done +} + +get_low_cpu_services () { + local prometheus_results="${1}" + local services="" + for service in $(printf "%s$prometheus_results" | jq ".data.result[] | select( all(.value[1]|tonumber; . < $CPU_PERCENTAGE_LOWER_LIMIT) ) | .metric.container_label_com_docker_swarm_service_name" | sed 's/"//g' | sort | uniq); do + services="$services $service" + done + + echo $services +} + +default_scale () { + service_name=$1 + auto_scale_label=$(docker service inspect $service_name | jq '.[].Spec.Labels["swarm.autoscaler"]') + replica_minimum=$(docker service inspect $service_name | jq '.[].Spec.Labels["swarm.autoscaler.minimum"]' | sed 's/\"//g') + replica_maximum=$(docker service inspect $service_name | jq '.[].Spec.Labels["swarm.autoscaler.maximum"]' | sed 's/\"//g') + if [[ "${auto_scale_label}" == "\"true\"" ]]; then + echo Service $service has an autoscale label. + current_replicas=$(docker service inspect $service_name | jq ".[].Spec.Mode.Replicated | .Replicas") + if [[ $replica_minimum -gt $current_replicas ]]; then + echo Service $service_name is below the minimum. Scaling to the minimum of $replica_minimum + docker service scale $service_name=$replica_minimum + elif [[ $current_replicas -gt $replica_maximum ]]; then + echo Service $service_name is above the maximum. Scaling to the maximum of $replica_maximum + docker service scale $service_name=$replica_maximum + fi + else + echo Service $service does not have an autoscale label. + fi + +} + +scale_down () { + service_name=$1 + auto_scale_label=$(docker service inspect $service_name | jq '.[].Spec.Labels["swarm.autoscaler"]') + replica_minimum=$(docker service inspect $service_name | jq '.[].Spec.Labels["swarm.autoscaler.minimum"]' | sed 's/\"//g') + if [[ "${auto_scale_label}" == "\"true\"" ]]; then + current_replicas=$(docker service inspect $service_name | jq ".[].Spec.Mode.Replicated | .Replicas") + new_replicas=$(expr $current_replicas - 1) + if [[ $replica_minimum -le $new_replicas ]]; then + echo Scaling down the service $service_name to $new_replicas + docker service scale $service_name=$new_replicas + elif [[ $current_replicas -eq $replica_minimum ]]; then + echo Service $service_name has the minumum number of replicas. + fi + fi + +} + +scale_up () { + service_name=$1 + auto_scale_label=$(docker service inspect $service_name | jq '.[].Spec.Labels["swarm.autoscaler"]') + replica_maximum=$(docker service inspect $service_name | jq '.[].Spec.Labels["swarm.autoscaler.maximum"]' | sed 's/\"//g') + if [[ "${auto_scale_label}" == "\"true\"" ]]; then + current_replicas=$(docker service inspect $service_name | jq ".[].Spec.Mode.Replicated | .Replicas") + new_replicas=$(expr $current_replicas + 1) + if [[ $current_replicas -eq $replica_maximum ]]; then + echo Service $service already has the maximum of $replica_maximum replicas + elif [[ $replica_maximum -ge $new_replicas ]]; then + echo Scaling up the service $service_name to $new_replicas + docker service scale $service_name=$new_replicas + fi + fi +} + +main () { + prometheus_initial_results=$(curl --silent "${PROMETHEUS_URL}/${PROMETHEUS_API}${PROMETHEUS_QUERY}" | jq .) + echo Prometheus results + echo $prometheus_initial_results + for service in $(get_all_services "${prometheus_initial_results}"); do + default_scale $service + done + echo Checking for high cpu services + for service in $(get_high_cpu_services "${prometheus_initial_results}"); do + echo Service $service is above $CPU_PERCENTAGE_UPPER_LIMIT percent cpu usage. + scale_up $service + done + echo Checking for low cpu services + for service in $(get_low_cpu_services "${prometheus_initial_results}"); do + echo Service $service is below $CPU_PERCENTAGE_LOWER_LIMIT percent cpu usage. + scale_down $service + done +} + +main +while [[ $LOOP == 'yes' ]]; do + echo Waiting 5 seconds for the next test + sleep 5s + main +done diff --git a/autoscaler/prometheus.yml b/autoscaler/prometheus.yml new file mode 100644 index 00000000..1d8988f0 --- /dev/null +++ b/autoscaler/prometheus.yml @@ -0,0 +1,18 @@ +global: + scrape_interval: 5s + evaluation_interval: 5s + +scrape_configs: + - job_name: 'prometheus' + dns_sd_configs: + - names: + - 'tasks.prometheus' + type: 'A' + port: 9090 + + - job_name: 'cadvisor' + dns_sd_configs: + - names: + - 'tasks.cadvisor' + type: 'A' + port: 8080 diff --git a/autoscaler/swarm-autoscaler-stack.yml b/autoscaler/swarm-autoscaler-stack.yml new file mode 100755 index 00000000..0c1010ab --- /dev/null +++ b/autoscaler/swarm-autoscaler-stack.yml @@ -0,0 +1,73 @@ +version: "3.7" + +networks: + autoscale: + +configs: + prometheus_config: + file: ./prometheus.yml + +services: + docker-swarm-autoscaler: + image: ahmad45123/workup:autoscaler + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + environment: + - PROMETHEUS_URL=http://prometheus:9090 + networks: + - autoscale + deploy: + mode: replicated + replicas: 1 + placement: + constraints: + - node.role == manager + resources: + limits: + cpus: '0.10' + memory: 128M + reservations: + cpus: '0.10' + memory: 64M + cadvisor: + image: gcr.io/cadvisor/cadvisor + networks: + - autoscale + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - /:/rootfs:ro + - /var/run:/var/run:ro + - /sys:/sys:ro + - /var/lib/docker/:/var/lib/docker:ro + - /dev/disk/:/dev/disk:ro + deploy: + mode: global + resources: + limits: + cpus: '0.10' + memory: 128M + reservations: + cpus: '0.10' + memory: 64M + + prometheus: + image: prom/prometheus:v2.12.0 + networks: + - autoscale + command: ["--storage.tsdb.retention.size=1GB", "--config.file=/etc/prometheus/prometheus.yml", "--web.console.libraries=/etc/prometheus/console_libraries", "--web.console.templates=/etc/prometheus/consoles", "--web.enable-lifecycle"] + configs: + - source: prometheus_config + target: /etc/prometheus/prometheus.yml + deploy: + mode: replicated + replicas: 1 + placement: + constraints: + - node.role == manager + resources: + limits: + cpus: '0.50' + memory: 1024M + reservations: + cpus: '0.50' + memory: 128M diff --git a/compose.override.yaml b/compose.override.yaml index 4d005919..b70f434b 100644 --- a/compose.override.yaml +++ b/compose.override.yaml @@ -1,4 +1,3 @@ - version: '3.7' services: service_jobs: @@ -15,3 +14,6 @@ services: service_webserver: image: workup:webserver + + service_mediaserver: + image: workup:mediaserver diff --git a/compose.yaml b/compose.yaml index 25310f90..0700244d 100644 --- a/compose.yaml +++ b/compose.yaml @@ -8,15 +8,30 @@ services: image: ahmad45123/workup:webserver depends_on: - service_mq + - service_mediaserver ports: - - "8080:8080" + - "80:8080" networks: - frontend env_file: - ./webserver/.env + deploy: + labels: + swarm.autoscaler: 'true' + swarm.autoscaler.minimum: '1' + swarm.autoscaler.maximum: '4' + + service_mediaserver: + image: ahmad45123/workup:mediaserver + ports: + - "8080:8080" + env_file: + - ./mediaserver/.env service_mq: image: rabbitmq:3.13-management + ports: + - "5672:5672" # hacky method.. dont ever do this :( healthcheck: test: rabbitmq-diagnostics -q ping interval: 30s @@ -25,7 +40,7 @@ services: networks: - default - frontend - + service_redis: image: redis:7.2.4 healthcheck: @@ -47,11 +62,14 @@ services: - jobs env_file: - ./services/jobs/.env + deploy: + labels: + swarm.autoscaler: 'true' + swarm.autoscaler.minimum: '1' + swarm.autoscaler.maximum: '3' jobs_db: - image: cassandra:4.0.7 - volumes: - - ./services/jobs/cassandra-config/cassandra.yaml:/etc/cassandra/cassandra.yaml + image: ahmad45123/workup:sasicassandra healthcheck: test: [ "CMD", "cqlsh", "-e", "describe keyspaces" ] interval: 20s @@ -72,6 +90,11 @@ services: - payments env_file: - ./services/payments/.env + deploy: + labels: + swarm.autoscaler: 'true' + swarm.autoscaler.minimum: '1' + swarm.autoscaler.maximum: '3' payments_db: image: postgres:12.18 @@ -80,7 +103,7 @@ services: POSTGRES_USER: payments_user POSTGRES_DB: payments_database healthcheck: - test: ["CMD", "pg_isready"] + test: [ "CMD", "pg_isready" ] interval: 20s timeout: 10s retries: 10 @@ -98,6 +121,11 @@ services: - contracts env_file: - ./services/contracts/.env + deploy: + labels: + swarm.autoscaler: 'true' + swarm.autoscaler.minimum: '1' + swarm.autoscaler.maximum: '3' contracts_db: image: cassandra:4.0.7 @@ -120,6 +148,11 @@ services: - users env_file: - ./services/users/.env + deploy: + labels: + swarm.autoscaler: 'true' + swarm.autoscaler.minimum: '1' + swarm.autoscaler.maximum: '3' users_db: image: mongo:7.0 diff --git a/controller/.env b/controller/.env new file mode 100644 index 00000000..3fedf8df --- /dev/null +++ b/controller/.env @@ -0,0 +1,4 @@ +JOBS_MQ_URL=service_mq +JOBS_MQ_USER=guest +JOBS_MQ_PASSWORD=guest +JOBS_MQ_PORT=guest \ No newline at end of file diff --git a/controller/Dockerfile b/controller/Dockerfile new file mode 100644 index 00000000..6730ed34 --- /dev/null +++ b/controller/Dockerfile @@ -0,0 +1,5 @@ +FROM eclipse-temurin:21-jre-alpine +VOLUME /tmp +ARG JAR_FILE=target/*.jar +COPY ${JAR_FILE} controller.jar +ENTRYPOINT ["tail", "-f", "/dev/null"] \ No newline at end of file diff --git a/controller/pom.xml b/controller/pom.xml index c2035f0c..f5ccdbdc 100644 --- a/controller/pom.xml +++ b/controller/pom.xml @@ -20,16 +20,57 @@ org.springframework.boot spring-boot-starter + + + org.springframework.boot + spring-boot-starter-logging + + + + + com.workup + jobs + ${project.version} + + + org.testcontainers + cassandra + + + org.springframework.boot + spring-boot-starter-data-cassandra + + + org.springframework.boot + spring-boot-starter-web + + - org.springframework.boot spring-boot-starter-test + + + org.springframework.boot + spring-boot-starter-logging + + test org.springframework.boot spring-boot-starter-amqp + + + org.springframework.boot + spring-boot-starter-logging + + + + + javassist + javassist + 3.12.1.GA com.workup @@ -41,6 +82,10 @@ cliche 110413 + + org.springframework.boot + spring-boot-starter-log4j2 + diff --git a/controller/src/main/java/com/workup/controller/CLIHandler.java b/controller/src/main/java/com/workup/controller/CLIHandler.java index 69be21e8..9e0200fe 100644 --- a/controller/src/main/java/com/workup/controller/CLIHandler.java +++ b/controller/src/main/java/com/workup/controller/CLIHandler.java @@ -2,10 +2,15 @@ import asg.cliche.CLIException; import asg.cliche.Command; -import com.workup.shared.commands.controller.SetMaxThreadsRequest; +import com.workup.shared.commands.controller.*; +import com.workup.shared.commands.jobs.requests.CreateJobRequest; import com.workup.shared.enums.ControllerQueueNames; +import com.workup.shared.enums.ServiceQueueNames; +import java.io.IOException; import java.util.HashMap; import java.util.Map; +import javassist.*; +import org.apache.logging.log4j.Level; import org.springframework.amqp.core.AmqpTemplate; import org.springframework.beans.factory.annotation.Autowired; @@ -21,7 +26,7 @@ public class CLIHandler { } @Command(description = "Set the maximum number of threads for a specific app") - public String maxthreads(String app, int maxThreads) throws CLIException { + public String maxThreads(String app, int maxThreads) throws CLIException { app = app.toLowerCase(); if (!appQueueMap.containsKey(app)) { return "Error: app can only be jobs, users, contracts or payments!"; @@ -33,22 +38,44 @@ public String maxthreads(String app, int maxThreads) throws CLIException { appQueueMap.get(app), "", SetMaxThreadsRequest.builder().withMaxThreads(maxThreads).build()); - return "MaxThreads"; + return "Command sent"; } @Command(description = "Set the maximum number of DB connections for a specific app") - public String maxdb(String app, int appNum, String maxDBConn) { - return "maxdb"; + public String maxdb(String app, int maxDBConn) { + app = app.toLowerCase(); + if (!appQueueMap.containsKey(app)) { + return "Error: app can only be jobs, users, contracts or payments!"; + } + if (maxDBConn > 100 || maxDBConn < 1) { + return "Error: Max threads must have a value between 1 and 100"; + } + rabbitTemplate.convertAndSend( + appQueueMap.get(app), + "", + SetMaxDBConnectionsRequest.builder().withMaxDBConnections(maxDBConn).build()); + return "Command Sent!"; } @Command(description = "starts a specific app") - public String start(String app, int appNum) { - return "start"; + public String start(String app) { + app = app.toLowerCase(); + if (!appQueueMap.containsKey(app)) { + return "Error: app can only be jobs, users, contracts or payments!"; + } + + rabbitTemplate.convertAndSend(appQueueMap.get(app), "", ContinueRequest.builder().build()); + return "Command sent"; } @Command(description = "stops a specific app") - public String freeze(String app, int appNum) { - return "freeze"; + public String freeze(String app) { + app = app.toLowerCase(); + if (!appQueueMap.containsKey(app)) { + return "Error: app can only be jobs, users, contracts or payments!"; + } + rabbitTemplate.convertAndSend(appQueueMap.get(app), "", FreezeRequest.builder().build()); + return "Command sent"; } @Command(description = "stops a specific app") @@ -57,8 +84,22 @@ public String setmq(String app, int appNum) { } @Command(description = "stops a specific app") - public String setErrorReportingLevel(String app, int appNum) { - return "error level"; + public String setLoggingLevel(String app, String level) { + app = app.toLowerCase(); + if (!appQueueMap.containsKey(app)) { + return "Error: app can only be jobs, users, contracts or payments!"; + } + // To throw an error in case an invalid level is provided :) + Level.valueOf(level); + rabbitTemplate.convertAndSend( + appQueueMap.get(app), "", SetLoggingLevelRequest.builder().withLevel(level).build()); + return "Command sent!!"; + } + + @Command(description = "test") + public void test() { + CreateJobRequest request = CreateJobRequest.builder().withTitle("Ziko").build(); + rabbitTemplate.convertSendAndReceive(ServiceQueueNames.JOBS, request); } @Command(description = "Creates a new command") @@ -67,8 +108,37 @@ public String addcommand(String app, String commandName, String className) { } @Command(description = "Updates an existing command") - public String updatecommand(String app, String commandName, String className) { - return "Update Command"; + public String updateCommand(String app, String commandName, String className) throws Exception { + app = app.toLowerCase(); + if (!appQueueMap.containsKey(app)) { + return "Error: app can only be jobs, users, contracts or payments!"; + } + try { + byte[] byteArray = getByteCode(commandName, className); + rabbitTemplate.convertAndSend( + appQueueMap.get(app), + "", + UpdateCommandRequest.builder() + .withCommandName(commandName) + .withByteCode(byteArray) + .build()); + } catch (Exception ex) { + ex.printStackTrace(); + } + return "Command sent!!"; + } + + private byte[] getByteCode(String commandName, String className) + throws InstantiationException, + IllegalAccessException, + NotFoundException, + IOException, + CannotCompileException { + ClassPool pool = ClassPool.getDefault(); + pool.insertClassPath(new ClassClassPath(ControllerApplication.class)); + CtClass ctClass = pool.get(className); + // That's the compiled class byte code + return ctClass.toBytecode(); } @Command(description = "Deletes an existing command") diff --git a/mediaserver/.dockerignore b/mediaserver/.dockerignore new file mode 100644 index 00000000..3ee47320 --- /dev/null +++ b/mediaserver/.dockerignore @@ -0,0 +1,3 @@ +Dockerfile +node_modules +npm-debug.log \ No newline at end of file diff --git a/mediaserver/.env b/mediaserver/.env new file mode 100644 index 00000000..1724b3dc --- /dev/null +++ b/mediaserver/.env @@ -0,0 +1,3 @@ +UPLOAD_USER=user +UPLOAD_PASSWORD=password +DISCOGS_API_KEY=123 diff --git a/mediaserver/.gitignore b/mediaserver/.gitignore new file mode 100644 index 00000000..da7520d7 --- /dev/null +++ b/mediaserver/.gitignore @@ -0,0 +1,4 @@ +/node_modules +.env +/media/static/icons/* +/media/static/resume/* \ No newline at end of file diff --git a/mediaserver/Dockerfile b/mediaserver/Dockerfile new file mode 100644 index 00000000..51501359 --- /dev/null +++ b/mediaserver/Dockerfile @@ -0,0 +1,26 @@ +# Use latest slim node +FROM node:current-slim + +# Set the working directory +WORKDIR /www + +# Copy package.json and package-lock.json to the working directory +COPY package*.json ./ + +# Install modules for production +RUN npm install --production + +# Copy entire bundle to the working directory +COPY . . + +# Create a mount point marked as containing externally mounted volumes. +# Prevents the container's size from increasing with the addition of more static media assets. +# Can be overridden by binding a host directory at runtime. +VOLUME /www/media/static + +# Override and expose port 8080 +ENV PORT 8080 +EXPOSE 8080 + +# Start the server +CMD ["npm", "run", "start"] \ No newline at end of file diff --git a/mediaserver/README.md b/mediaserver/README.md new file mode 100644 index 00000000..2fefae44 --- /dev/null +++ b/mediaserver/README.md @@ -0,0 +1,112 @@ +# `media-server` + +A media server that serves static resources. + +## ENV + +- `UPLOAD_USER` +- `UPLOAD_PASSWORD` +- `DISCOGS_API_KEY` + + +## Static Endpoints + +For all static resources, this server will attempt to return a relevant resource, or else if the resource does not exist, it will return a default 'placeholder' resource. This prevents clients from having no resource to display at all; clients can make use of this media server's 'describe' endpoint to learn about what resources are available. + +### `GET` /static/icons/:icon.png + +Returns an icon based on the filename. + +`:icon` can be any icon filename. + +Example: return an icon with filename 'accept.png'. + +```bash +curl --request GET \ + --url http://path_to_server/static/icons/accept.png +``` + +### `GET` /static/resume/:resume.pdf + +Returns a resume based on the filename. + +`:resume` can be any resume filename. + +Example: return a resume with filename 'resume.pdf'. + +```bash +curl --request GET \ + --url http://path_to_server/static/resume/resume.pdf +``` + + +## Describe + +### `GET` /describe + +Returns a JSON representation of the media groups. + +Example: return JSON containing of all groups present. + +```bash +curl --request GET \ + --url http://localhost:8910/describe +``` + +```json +{ + "success": true, + "path": "/static/", + "groups": ["icons", "resume"] +} +``` + +### `GET` /describe/:group + +Returns a JSON representation of all the files current present for a given group. + +`:group` can be any valid group. + +Example: return JSON containing all the media resources for a the exec resource group. + +```bash +curl --request GET \ + --url http://localhost:8910/describe/exec +``` + +```json +{ + "success": true, + "path": "/static/exec/", + "mimeType": "image/jpeg", + "files": [] +} +``` + +## Upload + +Upload and convert media to any of the given static resource group. + +All upload routes are protected by basic HTTP auth. The credentials are defined by ENV variables `UPLOAD_USER` and `UPLOAD_PASSWORD`. + +### `POST` /upload/:group + +POST a resource to a given group, assigning that resource a given filename. + +`:group` can be any valid group. + +```bash +curl --location 'http://path-to-server/upload/resume/' \ +--header 'Authorization: Basic dXNlcjpwYXNz' \ +--form 'resource=@"/C:/Users/ibrah/Downloads/Ibrahim_Abou_Elenein_Resume.pdf"' \ +--form 'filename="aboueleyes-reume-2"' +``` + +```json +{ + "success": true, + "path": "/static/resume/aboueleyes-reume-2.pdf" +} +``` + +A resource at `http://path_to_server/static/resume/aboueleyes-reume-2.pdf` will now be available. diff --git a/mediaserver/index.js b/mediaserver/index.js new file mode 100644 index 00000000..58767da9 --- /dev/null +++ b/mediaserver/index.js @@ -0,0 +1,20 @@ +/** + * Load env variables. + */ +require("dotenv").config(); + +/** + * Load the server config constants. + */ +const config = require("./src/config"); + +/** + * Initiate the app. + */ +const app = require("./src/app"); + +/** + * Define port, and listen... + */ +const port = config.PORT; +app.listen(port, () => console.log("listening on port " + port)); diff --git a/mediaserver/media/defaults/raw-banner.gif b/mediaserver/media/defaults/raw-banner.gif new file mode 100644 index 00000000..c7d18b48 Binary files /dev/null and b/mediaserver/media/defaults/raw-banner.gif differ diff --git a/mediaserver/media/defaults/raw-banner.jpg b/mediaserver/media/defaults/raw-banner.jpg new file mode 100644 index 00000000..cdffccdd Binary files /dev/null and b/mediaserver/media/defaults/raw-banner.jpg differ diff --git a/mediaserver/media/defaults/raw-banner.png b/mediaserver/media/defaults/raw-banner.png new file mode 100644 index 00000000..6d3de496 Binary files /dev/null and b/mediaserver/media/defaults/raw-banner.png differ diff --git a/mediaserver/media/defaults/raw-icon.gif b/mediaserver/media/defaults/raw-icon.gif new file mode 100644 index 00000000..b4194b77 Binary files /dev/null and b/mediaserver/media/defaults/raw-icon.gif differ diff --git a/mediaserver/media/defaults/raw-icon.jpg b/mediaserver/media/defaults/raw-icon.jpg new file mode 100644 index 00000000..1d8f6b4e Binary files /dev/null and b/mediaserver/media/defaults/raw-icon.jpg differ diff --git a/mediaserver/media/defaults/raw-icon.png b/mediaserver/media/defaults/raw-icon.png new file mode 100644 index 00000000..da1cb022 Binary files /dev/null and b/mediaserver/media/defaults/raw-icon.png differ diff --git a/mediaserver/media/defaults/raw-logo-only.gif b/mediaserver/media/defaults/raw-logo-only.gif new file mode 100644 index 00000000..ae7f356e Binary files /dev/null and b/mediaserver/media/defaults/raw-logo-only.gif differ diff --git a/mediaserver/media/defaults/raw-logo-only.jpg b/mediaserver/media/defaults/raw-logo-only.jpg new file mode 100644 index 00000000..77aa47d0 Binary files /dev/null and b/mediaserver/media/defaults/raw-logo-only.jpg differ diff --git a/mediaserver/media/defaults/raw-logo-only.png b/mediaserver/media/defaults/raw-logo-only.png new file mode 100644 index 00000000..a210f7b1 Binary files /dev/null and b/mediaserver/media/defaults/raw-logo-only.png differ diff --git a/mediaserver/media/defaults/raw-logo.gif b/mediaserver/media/defaults/raw-logo.gif new file mode 100644 index 00000000..1aafd6dc Binary files /dev/null and b/mediaserver/media/defaults/raw-logo.gif differ diff --git a/mediaserver/media/defaults/raw-logo.jpg b/mediaserver/media/defaults/raw-logo.jpg new file mode 100644 index 00000000..56fa6fb5 Binary files /dev/null and b/mediaserver/media/defaults/raw-logo.jpg differ diff --git a/mediaserver/media/defaults/raw-logo.png b/mediaserver/media/defaults/raw-logo.png new file mode 100644 index 00000000..1bc1c934 Binary files /dev/null and b/mediaserver/media/defaults/raw-logo.png differ diff --git a/mediaserver/media/static/icons/aboueleyes.png b/mediaserver/media/static/icons/aboueleyes.png new file mode 100644 index 00000000..7a6022ad Binary files /dev/null and b/mediaserver/media/static/icons/aboueleyes.png differ diff --git a/mediaserver/media/static/resume/aboueleyes-reume-2.pdf b/mediaserver/media/static/resume/aboueleyes-reume-2.pdf new file mode 100644 index 00000000..52faa0e7 Binary files /dev/null and b/mediaserver/media/static/resume/aboueleyes-reume-2.pdf differ diff --git a/mediaserver/media/static/resume/aboueleyes-reume.pdf b/mediaserver/media/static/resume/aboueleyes-reume.pdf new file mode 100644 index 00000000..32e5d7f5 Binary files /dev/null and b/mediaserver/media/static/resume/aboueleyes-reume.pdf differ diff --git a/mediaserver/package-lock.json b/mediaserver/package-lock.json new file mode 100644 index 00000000..288f7d7b --- /dev/null +++ b/mediaserver/package-lock.json @@ -0,0 +1,2765 @@ +{ + "name": "media-server", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "media-server", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "axios": "^0.18.1", + "cron": "^1.7.0", + "dotenv": "^7.0.0", + "fs-extra": "^7.0.1", + "jimp": "^0.6.4", + "koa": "^2.7.0", + "koa-basic-auth": "^4.0.0", + "koa-body": "^4.1.1", + "koa-compress": "^3.0.0", + "koa-mount": "^4.0.0", + "koa-router": "^7.4.0", + "koa-sendfile": "^3.0.0", + "koa-static": "^5.0.0" + } + }, + "node_modules/@jimp/bmp": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.6.8.tgz", + "integrity": "sha512-uxVgSkI62uAzk5ZazYHEHBehow590WAkLKmDXLzkr/XP/Hv2Fx1T4DKwJ/15IY5ktq5VAhAUWGXTyd8KWFsx7w==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "bmp-js": "^0.1.0", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/core": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.6.8.tgz", + "integrity": "sha512-JOFqBBcSNiDiMZJFr6OJqC6viXj5NVBQISua0eacoYvo4YJtTajOIxC4MqWyUmGrDpRMZBR8QhSsIOwsFrdROA==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "core-js": "^2.5.7", + "exif-parser": "^0.1.12", + "file-type": "^9.0.0", + "load-bmfont": "^1.3.1", + "mkdirp": "0.5.1", + "phin": "^2.9.1", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.4.1" + } + }, + "node_modules/@jimp/core/node_modules/file-type": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", + "integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@jimp/custom": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.6.8.tgz", + "integrity": "sha512-FrYlzZRVXP2vuVwd7Nc2dlK+iZk4g6IaT1Ib8Z6vU5Kkwlt83FJIPJ2UUFABf3bF5big0wkk8ZUihWxE4Nzdng==", + "dependencies": { + "@jimp/core": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "node_modules/@jimp/gif": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.6.8.tgz", + "integrity": "sha512-yyOlujjQcgz9zkjM5ihZDEppn9d1brJ7jQHP5rAKmqep0G7FU1D0AKcV+Ql18RhuI/CgWs10wAVcrQpmLnu4Yw==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7", + "omggif": "^1.0.9" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/jpeg": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.6.8.tgz", + "integrity": "sha512-rGtXbYpFXAn471qLpTGvhbBMNHJo5KiufN+vC5AWyufntmkt5f0Ox2Cx4ijuBMDtirZchxbMLtrfGjznS4L/ew==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7", + "jpeg-js": "^0.3.4" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.6.8.tgz", + "integrity": "sha512-7Tl6YpKTSpvwQbnGNhsfX2zyl3jRVVopd276Y2hF2zpDz9Bycow7NdfNU/4Nx1jaf96X6uWOtSVINcQ7rGd47w==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.6.8.tgz", + "integrity": "sha512-NpZCMKxXHLDQsX9zPlWtpMA660DQStY6/z8ZetyxCDbqrLe9YCXpeR4MNhdJdABIiwTm1W5FyFF4kp81PHJx3Q==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.6.8.tgz", + "integrity": "sha512-jjFyU0zNmGOH2rjzHuOMU4kaia0oo82s/7UYfn5h7OUkmUZTd6Do3ZSK1PiXA7KR+s4B76/Omm6Doh/0SGb7BQ==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7", + "tinycolor2": "^1.4.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.6.8.tgz", + "integrity": "sha512-p/P2wCXhAzbmEgXvGsvmxLmbz45feF6VpR4m9suPSOr8PC/i/XvTklTqYEUidYYAft4vHgsYJdS74HKSMnH8lw==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.6.8.tgz", + "integrity": "sha512-2PvWgk+PJfRsfWDI1G8Fpjrsu0ZlpNyZxO2+fqWlVo6y/y2gP4v08FqvbkcqSjNlOu2IDWIFXpgyU0sTINWZLg==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.6.8.tgz", + "integrity": "sha512-CbrcpWE2xxPK1n/JoTXzhRUhP4mO07mTWaSavenCg664oQl/9XCtL+A0FekuNHzIvn4myEqvkiTwN7FsbunS/Q==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.6.8.tgz", + "integrity": "sha512-RmV2bPxoPE6mrPxtYSPtHxm2cGwBQr5a2p+9gH6SPy+eUMrbGjbvjwKNfXWUYD0leML+Pt5XOmAS9pIROmuruQ==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.6.8.tgz", + "integrity": "sha512-x6V/qjxe+xypjpQm7GbiMNqci1EW5UizrcebOhHr8AHijOEqHd2hjXh5f6QIGfrkTFelc4/jzq1UyCsYntqz9Q==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.6.8.tgz", + "integrity": "sha512-4il6Da6G39s9MyWBEee4jztEOUGJ40E6OlPjkMrdpDNvge6hYEAB31BczTYBP/CEY74j4LDSoY5LbcU4kv06yA==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-rotate": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-gaussian": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.6.8.tgz", + "integrity": "sha512-pVOblmjv7stZjsqloi4YzHVwAPXKGdNaHPhp4KP4vj41qtc6Hxd9z/+VWGYRTunMFac84gUToe0UKIXd6GhoKw==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-invert": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.6.8.tgz", + "integrity": "sha512-11zuLiXDHr6tFv4U8aieXqNXQEKbDbSBG/h+X62gGTNFpyn8EVPpncHhOqrAFtZUaPibBqMFlNJ15SzwC7ExsQ==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.6.8.tgz", + "integrity": "sha512-hZJ0OiKGJyv7hDSATwJDkunB1Ie80xJnONMgpUuUseteK45YeYNBOiZVUe8vum8QI1UwavgBzcvQ9u4fcgXc9g==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-normalize": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.6.8.tgz", + "integrity": "sha512-Q4oYhU+sSyTJI7pMZlg9/mYh68ujLfOxXzQGEXuw0sHGoGQs3B0Jw7jmzGa6pIS06Hup5hD2Zuh1ppvMdjJBfQ==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.6.8.tgz", + "integrity": "sha512-2aokejGn4Drv1FesnZGqh5KEq0FQtR0drlmtyZrBH+r9cx7hh0Qgf4D1BOTDEgXkfSSngjGRjKKRW/fwOrVXYw==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7", + "load-bmfont": "^1.4.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.6.8.tgz", + "integrity": "sha512-27nPh8L1YWsxtfmV/+Ub5dOTpXyC0HMF2cu52RQSCYxr+Lm1+23dJF70AF1poUbUe+FWXphwuUxQzjBJza9UoA==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.6.8.tgz", + "integrity": "sha512-GbjETvL05BDoLdszNUV4Y0yLkHf177MnqGqilA113LIvx9aD0FtUopGXYfRGVvmtTOTouoaGJUc+K6qngvKxww==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-scale": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.6.8.tgz", + "integrity": "sha512-GzIYWR/oCUK2jAwku23zt19V1ssaEU4pL0x2XsLNKuuJEU6DvEytJyTMXCE7OLG/MpDBQcQclJKHgiyQm5gIOQ==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugins": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.6.8.tgz", + "integrity": "sha512-fMcTI72Vn/Lz6JftezTURmyP5ml/xGMe0Ljx2KRJ85IWyP33vDmGIUuutFiBEbh2+y7lRT+aTSmjs0QGa/xTmQ==", + "dependencies": { + "@jimp/plugin-blit": "^0.6.8", + "@jimp/plugin-blur": "^0.6.8", + "@jimp/plugin-color": "^0.6.8", + "@jimp/plugin-contain": "^0.6.8", + "@jimp/plugin-cover": "^0.6.8", + "@jimp/plugin-crop": "^0.6.8", + "@jimp/plugin-displace": "^0.6.8", + "@jimp/plugin-dither": "^0.6.8", + "@jimp/plugin-flip": "^0.6.8", + "@jimp/plugin-gaussian": "^0.6.8", + "@jimp/plugin-invert": "^0.6.8", + "@jimp/plugin-mask": "^0.6.8", + "@jimp/plugin-normalize": "^0.6.8", + "@jimp/plugin-print": "^0.6.8", + "@jimp/plugin-resize": "^0.6.8", + "@jimp/plugin-rotate": "^0.6.8", + "@jimp/plugin-scale": "^0.6.8", + "core-js": "^2.5.7", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/png": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.6.8.tgz", + "integrity": "sha512-JHHg/BZ7KDtHQrcG+a7fztw45rdf7okL/YwkN4qU5FH7Fcrp41nX5QnRviDtD9hN+GaNC7kvjvcqRAxW25qjew==", + "dependencies": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7", + "pngjs": "^3.3.3" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/tiff": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.6.8.tgz", + "integrity": "sha512-iWHbxd+0IKWdJyJ0HhoJCGYmtjPBOusz1z1HT/DnpePs/Lo3TO4d9ALXqYfUkyG74ZK5jULZ69KLtwuhuJz1bg==", + "dependencies": { + "core-js": "^2.5.7", + "utif": "^2.0.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/types": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.6.8.tgz", + "integrity": "sha512-vCZ/Cp2osy69VP21XOBACfHI5HeR60Rfd4Jidj4W73UL+HrFWOtyQiJ7hlToyu1vI5mR/NsUQpzyQvz56ADm5A==", + "dependencies": { + "@jimp/bmp": "^0.6.8", + "@jimp/gif": "^0.6.8", + "@jimp/jpeg": "^0.6.8", + "@jimp/png": "^0.6.8", + "@jimp/tiff": "^0.6.8", + "core-js": "^2.5.7", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/utils": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.6.8.tgz", + "integrity": "sha512-7RDfxQ2C/rarNG9iso5vmnKQbcvlQjBIlF/p7/uYj72WeZgVCB+5t1fFBKJSU4WhniHX4jUMijK+wYGE3Y3bGw==", + "dependencies": { + "core-js": "^2.5.7" + } + }, + "node_modules/@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + }, + "node_modules/@types/formidable": { + "version": "1.0.31", + "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-1.0.31.tgz", + "integrity": "sha512-dIhM5t8lRP0oWe2HF8MuPvdd1TpPTjhDMAqemcq6oIZQCBQTovhBAdTQ5L5veJB4pdQChadmHuxtB0YzqvfU3Q==", + "dependencies": { + "@types/events": "*", + "@types/node": "*" + } + }, + "node_modules/@types/node": { + "version": "13.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.5.tgz", + "integrity": "sha512-hkzMMD3xu6BrJpGVLeQ3htQQNAcOrJjX7WFmtK8zWQpz2UJf13LCFF2ALA7c9OVdvc2vQJeDdjfR35M0sBCxvw==" + }, + "node_modules/accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "dependencies": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "node_modules/axios": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", + "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", + "deprecated": "Critical security vulnerability fixed in v0.21.1. For more information, see https://github.com/axios/axios/pull/3410", + "dependencies": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + } + }, + "node_modules/base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/basic-auth/node_modules/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==" + }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha1-4Fpj95amwf8l9Hcex62twUjAcjM=" + }, + "node_modules/buffer": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz", + "integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==", + "dependencies": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "node_modules/buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "dependencies": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "engines": { + "iojs": ">= 1.0.0", + "node": ">= 0.12.0" + } + }, + "node_modules/co-body": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-5.2.0.tgz", + "integrity": "sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ==", + "dependencies": { + "inflation": "^2.0.0", + "qs": "^6.4.0", + "raw-body": "^2.2.0", + "type-is": "^1.6.14" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", + "integrity": "sha512-ExO0774ikEObIAEV9kDo50o+79VCUdEB6n6lzKgGwupcVeRlhrj3qGAfwq8G6uBJjkqLrhT0qEYFcWng8z1z0g==", + "dependencies": { + "safe-buffer": "5.1.2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-disposition/node_modules/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==" + }, + "node_modules/content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "dependencies": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cookies/node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==", + "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, + "node_modules/cron": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", + "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", + "dependencies": { + "moment-timezone": "^0.5.x" + } + }, + "node_modules/debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "node_modules/dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" + }, + "node_modules/dotenv": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz", + "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/error-inject": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", + "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=" + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha1-WKnS1ywCwfbwKg70qRZicrd2CSI=" + }, + "node_modules/follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "dependencies": { + "debug": "=3.1.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==", + "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "dependencies": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + }, + "engines": { + "node": ">=6 <7 || >=8" + } + }, + "node_modules/global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "dependencies": { + "min-document": "^2.19.0", + "process": "~0.5.1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" + }, + "node_modules/http-assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", + "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "dependencies": { + "deep-equal": "~1.0.1", + "http-errors": "~1.7.2" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "node_modules/inflation": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", + "integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", + "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" + }, + "node_modules/is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/jimp": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.6.8.tgz", + "integrity": "sha512-F7emeG7Hp61IM8VFbNvWENLTuHe0ghizWPuP4JS9ujx2r5mCVYEd/zdaz6M2M42ZdN41blxPajLWl9FXo7Mr2Q==", + "dependencies": { + "@jimp/custom": "^0.6.8", + "@jimp/plugins": "^0.6.8", + "@jimp/types": "^0.6.8", + "core-js": "^2.5.7", + "regenerator-runtime": "^0.13.3" + } + }, + "node_modules/jpeg-js": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.7.tgz", + "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==" + }, + "node_modules/jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "dependencies": { + "tsscmp": "1.0.6" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/koa": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.11.0.tgz", + "integrity": "sha512-EpR9dElBTDlaDgyhDMiLkXrPwp6ZqgAIBvhhmxQ9XN4TFgW+gEz6tkcsNI6BnUbUftrKDjVFj4lW2/J2aNBMMA==", + "dependencies": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.8.0", + "debug": "~3.1.0", + "delegates": "^1.0.0", + "depd": "^1.1.2", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "error-inject": "^1.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^1.2.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + }, + "engines": { + "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" + } + }, + "node_modules/koa-basic-auth": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/koa-basic-auth/-/koa-basic-auth-4.0.0.tgz", + "integrity": "sha512-eV1sGVAizDuFWNpY43VF3Z1ND4PotQZB/igxHNrcJXzXw+Flmj8Uv+4hP9LyNXyvqLJz/X5bmXeMu84AAGD9Jw==", + "dependencies": { + "basic-auth": "^2.0.0", + "tsscmp": "^1.0.6" + } + }, + "node_modules/koa-body": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/koa-body/-/koa-body-4.1.1.tgz", + "integrity": "sha512-rLb/KVD8qplEcK8Qsu6F4Xw+uHkmx3MWogDVmMX07DpjXizhw3pOEp1ja1MqqAcl0ei75AsrbGVDlySmsUrreA==", + "dependencies": { + "@types/formidable": "^1.0.31", + "co-body": "^5.1.1", + "formidable": "^1.1.1" + } + }, + "node_modules/koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" + }, + "node_modules/koa-compress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-3.0.0.tgz", + "integrity": "sha512-xol+LkNB1mozKJkB5Kj6nYXbJXhkLkZlXl9BsGBPjujVfZ8MsIXwU4GHRTT7TlSfUcl2DU3JtC+j6wOWcovfuQ==", + "dependencies": { + "bytes": "^3.0.0", + "compressible": "^2.0.0", + "koa-is-json": "^1.0.0", + "statuses": "^1.0.0" + } + }, + "node_modules/koa-convert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", + "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "dependencies": { + "co": "^4.6.0", + "koa-compose": "^3.0.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/koa-convert/node_modules/koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "dependencies": { + "any-promise": "^1.1.0" + } + }, + "node_modules/koa-is-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", + "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=" + }, + "node_modules/koa-mount": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/koa-mount/-/koa-mount-4.0.0.tgz", + "integrity": "sha512-rm71jaA/P+6HeCpoRhmCv8KVBIi0tfGuO/dMKicbQnQW/YJntJ6MnnspkodoA4QstMVEZArsCphmd0bJEtoMjQ==", + "dependencies": { + "debug": "^4.0.1", + "koa-compose": "^4.1.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/koa-mount/node_modules/debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/koa-mount/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==" + }, + "node_modules/koa-router": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/koa-router/-/koa-router-7.4.0.tgz", + "integrity": "sha512-IWhaDXeAnfDBEpWS6hkGdZ1ablgr6Q6pGdXCyK38RbzuH4LkUOpPqPw+3f8l8aTDrQmBQ7xJc0bs2yV4dzcO+g==", + "deprecated": "**IMPORTANT 10x+ PERFORMANCE UPGRADE**: Please upgrade to v12.0.1+ as we have fixed an issue with debuglog causing 10x slower router benchmark performance, see https://github.com/koajs/router/pull/173", + "dependencies": { + "debug": "^3.1.0", + "http-errors": "^1.3.1", + "koa-compose": "^3.0.0", + "methods": "^1.0.1", + "path-to-regexp": "^1.1.1", + "urijs": "^1.19.0" + }, + "engines": { + "node": ">= 4" + } + }, + "node_modules/koa-router/node_modules/koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "dependencies": { + "any-promise": "^1.1.0" + } + }, + "node_modules/koa-send": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.0.tgz", + "integrity": "sha512-90ZotV7t0p3uN9sRwW2D484rAaKIsD8tAVtypw/aBU+ryfV+fR2xrcAwhI8Wl6WRkojLUs/cB9SBSCuIb+IanQ==", + "dependencies": { + "debug": "^3.1.0", + "http-errors": "^1.6.3", + "mz": "^2.7.0", + "resolve-path": "^1.4.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/koa-sendfile": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/koa-sendfile/-/koa-sendfile-3.0.0.tgz", + "integrity": "sha512-wvvvDQIh7eNFRm/0AZNYtL+gl7uSG7qC4r0b7yJAQUgOh40yRW6hLjUWJPD09aa8t1c077X5G+pzOK9+WuF4Eg==", + "dependencies": { + "debug": "^4.2.0", + "etag": "^1.8.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/koa-sendfile/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/koa-sendfile/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==" + }, + "node_modules/koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "dependencies": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + }, + "engines": { + "node": ">= 7.6.0" + } + }, + "node_modules/load-bmfont": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.0.tgz", + "integrity": "sha512-kT63aTAlNhZARowaNYcY29Fn/QYkc52M3l6V1ifRcPewg2lvUZDAj7R6dXjOL9D0sict76op3T5+odumDSF81g==", + "dependencies": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^2.9.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "dependencies": { + "mime-db": "1.43.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "node_modules/mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dependencies": { + "minimist": "0.0.8" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-timezone": { + "version": "0.5.28", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", + "integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", + "dependencies": { + "moment": ">= 2.9.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" + }, + "node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=" + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha1-Eaw8P/WPfCAgqyJ2kHkQjU36AoU=" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha1-0Di0dtPp3Z2x4RoLDlOiJ5K2kAY=" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.4.5" + } + }, + "node_modules/parse-headers": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.3.tgz", + "integrity": "sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA==" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dependencies": { + "isarray": "0.0.1" + } + }, + "node_modules/path-to-regexp/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + }, + "node_modules/phin": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info." + }, + "node_modules/pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=", + "dependencies": { + "pngjs": "^3.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, + "node_modules/resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", + "dependencies": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/resolve-path/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/resolve-path/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/resolve-path/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/timm": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.6.2.tgz", + "integrity": "sha512-IH3DYDL1wMUwmIlVmMrmesw5lZD6N+ZOAFWEyLrtpoL9Bcrs9u7M/vyOnHzDD2SMs4irLkVjqxZbHrXStS/Nmw==" + }, + "node_modules/tinycolor2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", + "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=", + "engines": { + "node": "*" + } + }, + "node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", + "engines": { + "node": ">=0.6.x" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", + "engines": { + "node": ">= 4.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/urijs": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.2.tgz", + "integrity": "sha512-s/UIq9ap4JPZ7H1EB5ULo/aOUbWqfDi7FKzMC2Nz+0Si8GiT1rIEaprt8hy3Vy2Ex2aJPpOQv4P4DuOZ+K1c6w==" + }, + "node_modules/utif": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz", + "integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==", + "dependencies": { + "pako": "^1.0.5" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/xhr": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.5.0.tgz", + "integrity": "sha512-4nlO/14t3BNUZRXIXfXe+3N6w3s1KoxcJUUURctd64BLRe67E4gRwp4PjywtDY72fXpZ1y6Ch0VZQRY/gMPzzQ==", + "dependencies": { + "global": "~4.3.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha1-qQKekp09vN7RafPG4oI42VpdWig=" + }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/ylru": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", + "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==", + "engines": { + "node": ">= 4.0.0" + } + } + }, + "dependencies": { + "@jimp/bmp": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.6.8.tgz", + "integrity": "sha512-uxVgSkI62uAzk5ZazYHEHBehow590WAkLKmDXLzkr/XP/Hv2Fx1T4DKwJ/15IY5ktq5VAhAUWGXTyd8KWFsx7w==", + "requires": { + "@jimp/utils": "^0.6.8", + "bmp-js": "^0.1.0", + "core-js": "^2.5.7" + } + }, + "@jimp/core": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.6.8.tgz", + "integrity": "sha512-JOFqBBcSNiDiMZJFr6OJqC6viXj5NVBQISua0eacoYvo4YJtTajOIxC4MqWyUmGrDpRMZBR8QhSsIOwsFrdROA==", + "requires": { + "@jimp/utils": "^0.6.8", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "core-js": "^2.5.7", + "exif-parser": "^0.1.12", + "file-type": "^9.0.0", + "load-bmfont": "^1.3.1", + "mkdirp": "0.5.1", + "phin": "^2.9.1", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.4.1" + }, + "dependencies": { + "file-type": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-9.0.0.tgz", + "integrity": "sha512-Qe/5NJrgIOlwijpq3B7BEpzPFcgzggOTagZmkXQY4LA6bsXKTUstK7Wp12lEJ/mLKTpvIZxmIuRcLYWT6ov9lw==" + } + } + }, + "@jimp/custom": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.6.8.tgz", + "integrity": "sha512-FrYlzZRVXP2vuVwd7Nc2dlK+iZk4g6IaT1Ib8Z6vU5Kkwlt83FJIPJ2UUFABf3bF5big0wkk8ZUihWxE4Nzdng==", + "requires": { + "@jimp/core": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/gif": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.6.8.tgz", + "integrity": "sha512-yyOlujjQcgz9zkjM5ihZDEppn9d1brJ7jQHP5rAKmqep0G7FU1D0AKcV+Ql18RhuI/CgWs10wAVcrQpmLnu4Yw==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7", + "omggif": "^1.0.9" + } + }, + "@jimp/jpeg": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.6.8.tgz", + "integrity": "sha512-rGtXbYpFXAn471qLpTGvhbBMNHJo5KiufN+vC5AWyufntmkt5f0Ox2Cx4ijuBMDtirZchxbMLtrfGjznS4L/ew==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7", + "jpeg-js": "^0.3.4" + } + }, + "@jimp/plugin-blit": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.6.8.tgz", + "integrity": "sha512-7Tl6YpKTSpvwQbnGNhsfX2zyl3jRVVopd276Y2hF2zpDz9Bycow7NdfNU/4Nx1jaf96X6uWOtSVINcQ7rGd47w==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-blur": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.6.8.tgz", + "integrity": "sha512-NpZCMKxXHLDQsX9zPlWtpMA660DQStY6/z8ZetyxCDbqrLe9YCXpeR4MNhdJdABIiwTm1W5FyFF4kp81PHJx3Q==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-color": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.6.8.tgz", + "integrity": "sha512-jjFyU0zNmGOH2rjzHuOMU4kaia0oo82s/7UYfn5h7OUkmUZTd6Do3ZSK1PiXA7KR+s4B76/Omm6Doh/0SGb7BQ==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7", + "tinycolor2": "^1.4.1" + } + }, + "@jimp/plugin-contain": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.6.8.tgz", + "integrity": "sha512-p/P2wCXhAzbmEgXvGsvmxLmbz45feF6VpR4m9suPSOr8PC/i/XvTklTqYEUidYYAft4vHgsYJdS74HKSMnH8lw==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-cover": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.6.8.tgz", + "integrity": "sha512-2PvWgk+PJfRsfWDI1G8Fpjrsu0ZlpNyZxO2+fqWlVo6y/y2gP4v08FqvbkcqSjNlOu2IDWIFXpgyU0sTINWZLg==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-crop": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.6.8.tgz", + "integrity": "sha512-CbrcpWE2xxPK1n/JoTXzhRUhP4mO07mTWaSavenCg664oQl/9XCtL+A0FekuNHzIvn4myEqvkiTwN7FsbunS/Q==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-displace": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.6.8.tgz", + "integrity": "sha512-RmV2bPxoPE6mrPxtYSPtHxm2cGwBQr5a2p+9gH6SPy+eUMrbGjbvjwKNfXWUYD0leML+Pt5XOmAS9pIROmuruQ==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-dither": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.6.8.tgz", + "integrity": "sha512-x6V/qjxe+xypjpQm7GbiMNqci1EW5UizrcebOhHr8AHijOEqHd2hjXh5f6QIGfrkTFelc4/jzq1UyCsYntqz9Q==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-flip": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.6.8.tgz", + "integrity": "sha512-4il6Da6G39s9MyWBEee4jztEOUGJ40E6OlPjkMrdpDNvge6hYEAB31BczTYBP/CEY74j4LDSoY5LbcU4kv06yA==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-gaussian": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.6.8.tgz", + "integrity": "sha512-pVOblmjv7stZjsqloi4YzHVwAPXKGdNaHPhp4KP4vj41qtc6Hxd9z/+VWGYRTunMFac84gUToe0UKIXd6GhoKw==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-invert": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.6.8.tgz", + "integrity": "sha512-11zuLiXDHr6tFv4U8aieXqNXQEKbDbSBG/h+X62gGTNFpyn8EVPpncHhOqrAFtZUaPibBqMFlNJ15SzwC7ExsQ==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-mask": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.6.8.tgz", + "integrity": "sha512-hZJ0OiKGJyv7hDSATwJDkunB1Ie80xJnONMgpUuUseteK45YeYNBOiZVUe8vum8QI1UwavgBzcvQ9u4fcgXc9g==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-normalize": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.6.8.tgz", + "integrity": "sha512-Q4oYhU+sSyTJI7pMZlg9/mYh68ujLfOxXzQGEXuw0sHGoGQs3B0Jw7jmzGa6pIS06Hup5hD2Zuh1ppvMdjJBfQ==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-print": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.6.8.tgz", + "integrity": "sha512-2aokejGn4Drv1FesnZGqh5KEq0FQtR0drlmtyZrBH+r9cx7hh0Qgf4D1BOTDEgXkfSSngjGRjKKRW/fwOrVXYw==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7", + "load-bmfont": "^1.4.0" + } + }, + "@jimp/plugin-resize": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.6.8.tgz", + "integrity": "sha512-27nPh8L1YWsxtfmV/+Ub5dOTpXyC0HMF2cu52RQSCYxr+Lm1+23dJF70AF1poUbUe+FWXphwuUxQzjBJza9UoA==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-rotate": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.6.8.tgz", + "integrity": "sha512-GbjETvL05BDoLdszNUV4Y0yLkHf177MnqGqilA113LIvx9aD0FtUopGXYfRGVvmtTOTouoaGJUc+K6qngvKxww==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugin-scale": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.6.8.tgz", + "integrity": "sha512-GzIYWR/oCUK2jAwku23zt19V1ssaEU4pL0x2XsLNKuuJEU6DvEytJyTMXCE7OLG/MpDBQcQclJKHgiyQm5gIOQ==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7" + } + }, + "@jimp/plugins": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.6.8.tgz", + "integrity": "sha512-fMcTI72Vn/Lz6JftezTURmyP5ml/xGMe0Ljx2KRJ85IWyP33vDmGIUuutFiBEbh2+y7lRT+aTSmjs0QGa/xTmQ==", + "requires": { + "@jimp/plugin-blit": "^0.6.8", + "@jimp/plugin-blur": "^0.6.8", + "@jimp/plugin-color": "^0.6.8", + "@jimp/plugin-contain": "^0.6.8", + "@jimp/plugin-cover": "^0.6.8", + "@jimp/plugin-crop": "^0.6.8", + "@jimp/plugin-displace": "^0.6.8", + "@jimp/plugin-dither": "^0.6.8", + "@jimp/plugin-flip": "^0.6.8", + "@jimp/plugin-gaussian": "^0.6.8", + "@jimp/plugin-invert": "^0.6.8", + "@jimp/plugin-mask": "^0.6.8", + "@jimp/plugin-normalize": "^0.6.8", + "@jimp/plugin-print": "^0.6.8", + "@jimp/plugin-resize": "^0.6.8", + "@jimp/plugin-rotate": "^0.6.8", + "@jimp/plugin-scale": "^0.6.8", + "core-js": "^2.5.7", + "timm": "^1.6.1" + } + }, + "@jimp/png": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.6.8.tgz", + "integrity": "sha512-JHHg/BZ7KDtHQrcG+a7fztw45rdf7okL/YwkN4qU5FH7Fcrp41nX5QnRviDtD9hN+GaNC7kvjvcqRAxW25qjew==", + "requires": { + "@jimp/utils": "^0.6.8", + "core-js": "^2.5.7", + "pngjs": "^3.3.3" + } + }, + "@jimp/tiff": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.6.8.tgz", + "integrity": "sha512-iWHbxd+0IKWdJyJ0HhoJCGYmtjPBOusz1z1HT/DnpePs/Lo3TO4d9ALXqYfUkyG74ZK5jULZ69KLtwuhuJz1bg==", + "requires": { + "core-js": "^2.5.7", + "utif": "^2.0.1" + } + }, + "@jimp/types": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.6.8.tgz", + "integrity": "sha512-vCZ/Cp2osy69VP21XOBACfHI5HeR60Rfd4Jidj4W73UL+HrFWOtyQiJ7hlToyu1vI5mR/NsUQpzyQvz56ADm5A==", + "requires": { + "@jimp/bmp": "^0.6.8", + "@jimp/gif": "^0.6.8", + "@jimp/jpeg": "^0.6.8", + "@jimp/png": "^0.6.8", + "@jimp/tiff": "^0.6.8", + "core-js": "^2.5.7", + "timm": "^1.6.1" + } + }, + "@jimp/utils": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.6.8.tgz", + "integrity": "sha512-7RDfxQ2C/rarNG9iso5vmnKQbcvlQjBIlF/p7/uYj72WeZgVCB+5t1fFBKJSU4WhniHX4jUMijK+wYGE3Y3bGw==", + "requires": { + "core-js": "^2.5.7" + } + }, + "@types/events": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", + "integrity": "sha512-EaObqwIvayI5a8dCzhFrjKzVwKLxjoG9T6Ppd5CEo07LRKfQ8Yokw54r5+Wq7FaBQ+yXRvQAYPrHwya1/UFt9g==" + }, + "@types/formidable": { + "version": "1.0.31", + "resolved": "https://registry.npmjs.org/@types/formidable/-/formidable-1.0.31.tgz", + "integrity": "sha512-dIhM5t8lRP0oWe2HF8MuPvdd1TpPTjhDMAqemcq6oIZQCBQTovhBAdTQ5L5veJB4pdQChadmHuxtB0YzqvfU3Q==", + "requires": { + "@types/events": "*", + "@types/node": "*" + } + }, + "@types/node": { + "version": "13.9.5", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.5.tgz", + "integrity": "sha512-hkzMMD3xu6BrJpGVLeQ3htQQNAcOrJjX7WFmtK8zWQpz2UJf13LCFF2ALA7c9OVdvc2vQJeDdjfR35M0sBCxvw==" + }, + "accepts": { + "version": "1.3.7", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", + "integrity": "sha512-Il80Qs2WjYlJIBNzNkK6KYqlVMTbZLXgHx2oT0pU/fjRHyEp+PEfEPY0R3WCwAGVOtauxh1hOxNgIf5bv7dQpA==", + "requires": { + "mime-types": "~2.1.24", + "negotiator": "0.6.2" + } + }, + "any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" + }, + "axios": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", + "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" + } + }, + "base64-js": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz", + "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==" + }, + "basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", + "requires": { + "safe-buffer": "5.1.2" + }, + "dependencies": { + "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==" + } + } + }, + "bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha1-4Fpj95amwf8l9Hcex62twUjAcjM=" + }, + "buffer": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.5.0.tgz", + "integrity": "sha512-9FTEDjLjwoAkEwyMGDjYJQN2gfRgOKBKRfiglhvibGbpeeU/pQn1bJxQqm32OD/AIeEuHxU9roxXxg34Byp/Ww==", + "requires": { + "base64-js": "^1.0.2", + "ieee754": "^1.1.4" + } + }, + "buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha1-kbx0sR6kBbyRa8aqkI+q+ltKrEs=" + }, + "bytes": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.0.tgz", + "integrity": "sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg==" + }, + "cache-content-type": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", + "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", + "requires": { + "mime-types": "^2.1.18", + "ylru": "^1.2.0" + } + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" + }, + "co-body": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/co-body/-/co-body-5.2.0.tgz", + "integrity": "sha512-sX/LQ7LqUhgyaxzbe7IqwPeTr2yfpfUIQ/dgpKo6ZI4y4lpQA0YxAomWIY+7I7rHWcG02PG+OuPREzMW/5tszQ==", + "requires": { + "inflation": "^2.0.0", + "qs": "^6.4.0", + "raw-body": "^2.2.0", + "type-is": "^1.6.14" + } + }, + "compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "requires": { + "mime-db": ">= 1.43.0 < 2" + } + }, + "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" + }, + "dependencies": { + "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==" + } + } + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookies": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.8.0.tgz", + "integrity": "sha512-8aPsApQfebXnuI+537McwYsDtjVxGm8gTIzQI3FDW6t5t/DAhERxtnbEPN/8RX+uZthoz4eCOgloXaE5cYyNow==", + "requires": { + "depd": "~2.0.0", + "keygrip": "~1.1.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==" + } + } + }, + "core-js": { + "version": "2.6.11", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.11.tgz", + "integrity": "sha512-5wjnpaT/3dV+XB4borEsnAYQchn00XSgTAWKDkEqv+K8KevjbzmofK6hfJ9TZIlpj2N0xQpazy7PiRQiWHqzWg==" + }, + "cron": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/cron/-/cron-1.8.2.tgz", + "integrity": "sha512-Gk2c4y6xKEO8FSAUTklqtfSr7oTq0CiPQeLBG5Fl0qoXpZyMcj1SG59YL+hqq04bu6/IuEA7lMkYDAplQNKkyg==", + "requires": { + "moment-timezone": "^0.5.x" + } + }, + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "requires": { + "ms": "2.0.0" + } + }, + "deep-equal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", + "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=" + }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" + }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, + "dom-walk": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.1.tgz", + "integrity": "sha1-ZyIm3HTI95mtNTB9+TaroRrNYBg=" + }, + "dotenv": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-7.0.0.tgz", + "integrity": "sha512-M3NhsLbV1i6HuGzBUH8vXrtxOk+tWmzWKDMbAVSUp3Zsjm7ywFeuwrUXhmhQyRK1q5B5GGy7hcXPbj3bnfZg2g==" + }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "error-inject": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/error-inject/-/error-inject-1.0.0.tgz", + "integrity": "sha1-4rPZG1Su1nLzCdlQ0VSFD6EdTzc=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + }, + "exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha1-WKnS1ywCwfbwKg70qRZicrd2CSI=" + }, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "requires": { + "debug": "=3.1.0" + } + }, + "formidable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.2.tgz", + "integrity": "sha512-V8gLm+41I/8kguQ4/o1D3RIHRmhYFG4pnNyonvua+40rqcEmT4+V71yaZ3B457xbbgCsCfjSPi65u/W6vK1U5Q==" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, + "fs-extra": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", + "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", + "requires": { + "graceful-fs": "^4.1.2", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, + "global": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/global/-/global-4.3.2.tgz", + "integrity": "sha1-52mJJopsdMOJCLEwWxD8DjlOnQ8=", + "requires": { + "min-document": "^2.19.0", + "process": "~0.5.1" + } + }, + "graceful-fs": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", + "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" + }, + "http-assert": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.4.1.tgz", + "integrity": "sha512-rdw7q6GTlibqVVbXr0CKelfV5iY8G2HqEUkhSk297BMbSpSL8crXC+9rjKoMcZZEsksX30le6f/4ul4E28gegw==", + "requires": { + "deep-equal": "~1.0.1", + "http-errors": "~1.7.2" + } + }, + "http-errors": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.3.tgz", + "integrity": "sha512-ZTTX0MWrsQ2ZAhA1cejAwDLycFsd7I7nVtnkT3Ol0aqodaKW+0CTZDQ1uBv5whptCnc8e8HeRRJxRs0kmm/Qfw==", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.4", + "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" + } + }, + "ieee754": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz", + "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg==" + }, + "inflation": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/inflation/-/inflation-2.0.0.tgz", + "integrity": "sha1-i0F+R8KPklpFEz2RTKH9OJEH8w8=" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-buffer": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz", + "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==" + }, + "is-function": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.1.tgz", + "integrity": "sha1-Es+5i2W1fdPRk6MSH19uL0N2ArU=" + }, + "is-generator-function": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.7.tgz", + "integrity": "sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw==" + }, + "jimp": { + "version": "0.6.8", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.6.8.tgz", + "integrity": "sha512-F7emeG7Hp61IM8VFbNvWENLTuHe0ghizWPuP4JS9ujx2r5mCVYEd/zdaz6M2M42ZdN41blxPajLWl9FXo7Mr2Q==", + "requires": { + "@jimp/custom": "^0.6.8", + "@jimp/plugins": "^0.6.8", + "@jimp/types": "^0.6.8", + "core-js": "^2.5.7", + "regenerator-runtime": "^0.13.3" + } + }, + "jpeg-js": { + "version": "0.3.7", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.3.7.tgz", + "integrity": "sha512-9IXdWudL61npZjvLuVe/ktHiA41iE8qFyLB+4VDTblEsWBzeg8WQTlktdUK4CdncUqtUgUg0bbOmTE2bKBKaBQ==" + }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "requires": { + "graceful-fs": "^4.1.6" + } + }, + "keygrip": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", + "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", + "requires": { + "tsscmp": "1.0.6" + } + }, + "koa": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/koa/-/koa-2.11.0.tgz", + "integrity": "sha512-EpR9dElBTDlaDgyhDMiLkXrPwp6ZqgAIBvhhmxQ9XN4TFgW+gEz6tkcsNI6BnUbUftrKDjVFj4lW2/J2aNBMMA==", + "requires": { + "accepts": "^1.3.5", + "cache-content-type": "^1.0.0", + "content-disposition": "~0.5.2", + "content-type": "^1.0.4", + "cookies": "~0.8.0", + "debug": "~3.1.0", + "delegates": "^1.0.0", + "depd": "^1.1.2", + "destroy": "^1.0.4", + "encodeurl": "^1.0.2", + "error-inject": "^1.0.0", + "escape-html": "^1.0.3", + "fresh": "~0.5.2", + "http-assert": "^1.3.0", + "http-errors": "^1.6.3", + "is-generator-function": "^1.0.7", + "koa-compose": "^4.1.0", + "koa-convert": "^1.2.0", + "on-finished": "^2.3.0", + "only": "~0.0.2", + "parseurl": "^1.3.2", + "statuses": "^1.5.0", + "type-is": "^1.6.16", + "vary": "^1.1.2" + } + }, + "koa-basic-auth": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/koa-basic-auth/-/koa-basic-auth-4.0.0.tgz", + "integrity": "sha512-eV1sGVAizDuFWNpY43VF3Z1ND4PotQZB/igxHNrcJXzXw+Flmj8Uv+4hP9LyNXyvqLJz/X5bmXeMu84AAGD9Jw==", + "requires": { + "basic-auth": "^2.0.0", + "tsscmp": "^1.0.6" + } + }, + "koa-body": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/koa-body/-/koa-body-4.1.1.tgz", + "integrity": "sha512-rLb/KVD8qplEcK8Qsu6F4Xw+uHkmx3MWogDVmMX07DpjXizhw3pOEp1ja1MqqAcl0ei75AsrbGVDlySmsUrreA==", + "requires": { + "@types/formidable": "^1.0.31", + "co-body": "^5.1.1", + "formidable": "^1.1.1" + } + }, + "koa-compose": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", + "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==" + }, + "koa-compress": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-3.0.0.tgz", + "integrity": "sha512-xol+LkNB1mozKJkB5Kj6nYXbJXhkLkZlXl9BsGBPjujVfZ8MsIXwU4GHRTT7TlSfUcl2DU3JtC+j6wOWcovfuQ==", + "requires": { + "bytes": "^3.0.0", + "compressible": "^2.0.0", + "koa-is-json": "^1.0.0", + "statuses": "^1.0.0" + } + }, + "koa-convert": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-1.2.0.tgz", + "integrity": "sha1-2kCHXfSd4FOQmNFwC1CCDOvNIdA=", + "requires": { + "co": "^4.6.0", + "koa-compose": "^3.0.0" + }, + "dependencies": { + "koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "requires": { + "any-promise": "^1.1.0" + } + } + } + }, + "koa-is-json": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", + "integrity": "sha1-JzwH7c3Ljfaiwat9We52SRRR7BQ=" + }, + "koa-mount": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/koa-mount/-/koa-mount-4.0.0.tgz", + "integrity": "sha512-rm71jaA/P+6HeCpoRhmCv8KVBIi0tfGuO/dMKicbQnQW/YJntJ6MnnspkodoA4QstMVEZArsCphmd0bJEtoMjQ==", + "requires": { + "debug": "^4.0.1", + "koa-compose": "^4.1.0" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "koa-router": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/koa-router/-/koa-router-7.4.0.tgz", + "integrity": "sha512-IWhaDXeAnfDBEpWS6hkGdZ1ablgr6Q6pGdXCyK38RbzuH4LkUOpPqPw+3f8l8aTDrQmBQ7xJc0bs2yV4dzcO+g==", + "requires": { + "debug": "^3.1.0", + "http-errors": "^1.3.1", + "koa-compose": "^3.0.0", + "methods": "^1.0.1", + "path-to-regexp": "^1.1.1", + "urijs": "^1.19.0" + }, + "dependencies": { + "koa-compose": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-3.2.1.tgz", + "integrity": "sha1-qFzLQLfZhtjlo0Wzoazo6rz1Tec=", + "requires": { + "any-promise": "^1.1.0" + } + } + } + }, + "koa-send": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.0.tgz", + "integrity": "sha512-90ZotV7t0p3uN9sRwW2D484rAaKIsD8tAVtypw/aBU+ryfV+fR2xrcAwhI8Wl6WRkojLUs/cB9SBSCuIb+IanQ==", + "requires": { + "debug": "^3.1.0", + "http-errors": "^1.6.3", + "mz": "^2.7.0", + "resolve-path": "^1.4.0" + } + }, + "koa-sendfile": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/koa-sendfile/-/koa-sendfile-3.0.0.tgz", + "integrity": "sha512-wvvvDQIh7eNFRm/0AZNYtL+gl7uSG7qC4r0b7yJAQUgOh40yRW6hLjUWJPD09aa8t1c077X5G+pzOK9+WuF4Eg==", + "requires": { + "debug": "^4.2.0", + "etag": "^1.8.1" + }, + "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + } + } + }, + "koa-static": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", + "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", + "requires": { + "debug": "^3.1.0", + "koa-send": "^5.0.0" + } + }, + "load-bmfont": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.0.tgz", + "integrity": "sha512-kT63aTAlNhZARowaNYcY29Fn/QYkc52M3l6V1ifRcPewg2lvUZDAj7R6dXjOL9D0sict76op3T5+odumDSF81g==", + "requires": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^2.9.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + } + }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "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.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "requires": { + "mime-db": "1.43.0" + } + }, + "min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha1-e9KC4/WELtKVu3SM3Z8f+iyCRoU=", + "requires": { + "dom-walk": "^0.1.0" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "requires": { + "minimist": "0.0.8" + } + }, + "moment": { + "version": "2.24.0", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.24.0.tgz", + "integrity": "sha512-bV7f+6l2QigeBBZSM/6yTNq4P2fNpSWj/0e7jQcy87A8e7o2nAfP/34/2ky5Vw4B9S446EtIhodAzkFCcR4dQg==" + }, + "moment-timezone": { + "version": "0.5.28", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.28.tgz", + "integrity": "sha512-TDJkZvAyKIVWg5EtVqRzU97w0Rb0YVbfpqyjgu6GwXCAohVRqwZjf4fOzDE6p1Ch98Sro/8hQQi65WDXW5STPw==", + "requires": { + "moment": ">= 2.9.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "negotiator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", + "integrity": "sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "only": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", + "integrity": "sha1-Kv3oTQPlC5qO3EROMGEKcCle37Q=" + }, + "pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha1-Eaw8P/WPfCAgqyJ2kHkQjU36AoU=" + }, + "parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha1-0Di0dtPp3Z2x4RoLDlOiJ5K2kAY=" + }, + "parse-bmfont-xml": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", + "requires": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.4.5" + } + }, + "parse-headers": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.3.tgz", + "integrity": "sha512-QhhZ+DCCit2Coi2vmAKbq5RGTRcQUOE2+REgv8vdyu7MnYx2eZztegqtTx99TZ86GTIwqiy3+4nQTWZ2tgmdCA==" + }, + "parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" + } + } + }, + "phin": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" + }, + "pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha1-j0fc7FARtHe2fbA8JDvB8wheiFQ=", + "requires": { + "pngjs": "^3.0.0" + } + }, + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==" + }, + "process": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/process/-/process-0.5.2.tgz", + "integrity": "sha1-FjjYqONML0QKkduVq5rrZ3/Bhc8=" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "raw-body": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.1.tgz", + "integrity": "sha512-9WmIKF6mkvA0SLmA2Knm9+qj89e+j1zqgyn8aXGd7+nAduPoqgI9lO57SAZNn/Byzo5P7JhXTyg9PzaJbH73bA==", + "requires": { + "bytes": "3.1.0", + "http-errors": "1.7.3", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + } + }, + "regenerator-runtime": { + "version": "0.13.5", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.5.tgz", + "integrity": "sha512-ZS5w8CpKFinUzOwW3c83oPeVXoNsrLsaCoLtJvAClH135j/R77RuymhiSErhm2lKcwSCIpmvIWSbDkIfAqKQlA==" + }, + "resolve-path": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", + "integrity": "sha1-xL2p9e+y/OZSR4c6s2u02DT+Fvc=", + "requires": { + "http-errors": "~1.6.2", + "path-is-absolute": "1.0.1" + }, + "dependencies": { + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + } + } + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, + "setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, + "thenify": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.0.tgz", + "integrity": "sha1-5p44obq+lpsBCCB5eLn2K4hgSDk=", + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY=", + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "timm": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.6.2.tgz", + "integrity": "sha512-IH3DYDL1wMUwmIlVmMrmesw5lZD6N+ZOAFWEyLrtpoL9Bcrs9u7M/vyOnHzDD2SMs4irLkVjqxZbHrXStS/Nmw==" + }, + "tinycolor2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.1.tgz", + "integrity": "sha1-9PrTM0R7wLB9TcjpIJ2POaisd+g=" + }, + "toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==" + }, + "tsscmp": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", + "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==" + }, + "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" + } + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, + "urijs": { + "version": "1.19.2", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.2.tgz", + "integrity": "sha512-s/UIq9ap4JPZ7H1EB5ULo/aOUbWqfDi7FKzMC2Nz+0Si8GiT1rIEaprt8hy3Vy2Ex2aJPpOQv4P4DuOZ+K1c6w==" + }, + "utif": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/utif/-/utif-2.0.1.tgz", + "integrity": "sha512-Z/S1fNKCicQTf375lIP9G8Sa1H/phcysstNrrSdZKj1f9g58J4NMgb5IgiEZN9/nLMPDwF0W7hdOe9Qq2IYoLg==", + "requires": { + "pako": "^1.0.5" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, + "xhr": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.5.0.tgz", + "integrity": "sha512-4nlO/14t3BNUZRXIXfXe+3N6w3s1KoxcJUUURctd64BLRe67E4gRwp4PjywtDY72fXpZ1y6Ch0VZQRY/gMPzzQ==", + "requires": { + "global": "~4.3.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha1-qQKekp09vN7RafPG4oI42VpdWig=" + }, + "xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "requires": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + } + }, + "xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" + }, + "ylru": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.2.1.tgz", + "integrity": "sha512-faQrqNMzcPCHGVC2aaOINk13K+aaBDUPjGWl0teOXywElLjyVAB6Oe2jj62jHYtwsU49jXhScYbvPENK+6zAvQ==" + } + } +} diff --git a/mediaserver/package.json b/mediaserver/package.json new file mode 100644 index 00000000..2d084a0f --- /dev/null +++ b/mediaserver/package.json @@ -0,0 +1,31 @@ +{ + "name": "media-server", + "version": "1.0.0", + "description": "Media server for workup", + "main": "index.js", + "scripts": { + "start": "node index.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/ahmed54123/workup.git" + }, + "author": "aboueleyes", + "license": "ISC", + "dependencies": { + "axios": "^0.18.1", + "cron": "^1.7.0", + "dotenv": "^7.0.0", + "fs-extra": "^7.0.1", + "jimp": "^0.6.4", + "koa": "^2.7.0", + "koa-basic-auth": "^4.0.0", + "koa-body": "^4.1.1", + "koa-compress": "^3.0.0", + "koa-mount": "^4.0.0", + "koa-router": "^7.4.0", + "koa-sendfile": "^3.0.0", + "koa-static": "^5.0.0" + } +} \ No newline at end of file diff --git a/mediaserver/src/app.js b/mediaserver/src/app.js new file mode 100644 index 00000000..3a9b308e --- /dev/null +++ b/mediaserver/src/app.js @@ -0,0 +1,82 @@ +const Koa = require("koa"); +const mount = require("koa-mount"); +const compress = require("koa-compress"); +const body = require("koa-body"); +const { SUPPORTED_MIME_TYPES } = require("./enums"); + +/** + * Constructs an array of all supported MIME types. + */ +const mimeTypes = [] + .concat(SUPPORTED_MIME_TYPES.DOC) + .concat(SUPPORTED_MIME_TYPES.VIDEO) + .concat(SUPPORTED_MIME_TYPES.IMAGE); + +/** + * Instantiate a new KOA app. + */ +const app = new Koa(); + +/** + * Import all the routes. + */ +const static = require("./routes/static"); +const describe = require("./routes/describe"); +const upload = require("./routes/upload"); + +/** + * For each async piece of middleware, try it, and report on any errors. + * + * Koa middleware cascades, so putting this at the top of the file ensures all errors are captured. + */ +app.use(async (ctx, next) => { + try { + await next(); + } catch (err) { + ctx.status = err.status || 500; + ctx.body = { + success: false, + message: err.message + }; + ctx.app.emit("error", err, ctx); + } +}); + +/** + * Compress all compressible responses over 2KB. + * This is includes compression of all mime types listed in the config file. + */ +app.use( + compress({ + filter: type => mimeTypes.find(mimeType => mimeType === type), + threshold: 2048, + flush: require("zlib").Z_SYNC_FLUSH + }) +); + +/** + * Allow KOA to parse the body of a request, including support for multipart forms. + * Limit the maximum file size to 300MB. + */ +app.use( + body({ + multipart: true, + formidable: { + maxFileSize: 300 * 1024 * 1024 + } + }) +); + +/** + * Define routes + * - Music uses a cache-able router. + * - Static is, well, static. + * - Describe tells us about the available media. + * - Upload is a secure media upload endpoint. + */ +app.use(mount("/static", static.router.routes())); +app.use(mount("/describe", describe.router.routes())); +app.use(mount("/upload", upload.router.routes())); + + +module.exports = app; diff --git a/mediaserver/src/config.js b/mediaserver/src/config.js new file mode 100644 index 00000000..02fffb58 --- /dev/null +++ b/mediaserver/src/config.js @@ -0,0 +1,84 @@ +/** + * config.js - Defines the configuration of the media server through a series of constant definitions. + */ + +/** + * Define what environment variables must be set. + */ +const expected_env_vars = ["DISCOGS_API_KEY", "UPLOAD_USER", "UPLOAD_PASSWORD"]; + +/** + * For each expected environment variable, check if it is set. + * If unset, throw an error reporting which environment variable must be set. + */ +expected_env_vars.forEach(env_var => { + if (!(env_var in process.env)) { + throw new Error("Environment variable " + env_var + " must be set."); + } +}); + +module.exports = { + /** + * Define some global constants which define required values for the server's function. + * This includes API keys and resource upload user credentials. + */ + DISCOGS_API_KEY: process.env.DISCOGS_API_KEY, + PORT: process.env.PORT || 3000, + UPLOAD: { + USER: process.env.UPLOAD_USER, + PASSWORD: process.env.UPLOAD_PASSWORD + }, + /** + * Define the parameters of all static resource groups. To define a new static group, simply + * include a new resource group object in this array. The resource group will be instantiated when the + * server is restarted. + * + * All resource group objects must follow a defined shape: + * + *``` + *{ + * NAME: string, + * MIME_TYPE: string, + * FILE_EXTENSION: string, + * DEFAULT_RESOURCE: string, + * QUALITY: float, + * PIXEL_DIMENSIONS: { + * WIDTH: integer, + * HEIGHT: integer + * } + * } + *``` + + * `NAME` can be any string. + * `MIME_TYPE` can be any valid IANA MIME type. Ensure the MIME type is listed in the supported MIME types at "./enums.js". + * `FILE_EXTENSION` can be any valid file extension which matches the MIME type. + * `DEFAULT_RESOURCE` can be any valid filename and extension of any default resource listed in the './media/defaults' folder. + * `QUALITY` can be any valid float between 1 and 100. + * `PIXEL_DIMENSIONS` can be an object with keys WIDTH and HEIGHT with any integer values. + * + * Some common sense must be used to match the MIME type, file extension, and default resource filename extension. + */ + STATIC: [ + { + NAME: "icons", + MIME_TYPE: "image/png", + FILE_EXTENSION: "png", + DEFAULT_RESOURCE: "raw-icon.png", + QUALITY: 100.0, + PIXEL_DIMENSIONS: { + WIDTH: 16, + HEIGHT: 16 + } + }, + { + NAME: "resume", + MIME_TYPE: "application", + FILE_EXTENSION: "pdf", + }, + { + NAME: "attachments", + MIME_TYPE: "application", + FILE_EXTENSION: "pdf", + } + ] +}; diff --git a/mediaserver/src/enums.js b/mediaserver/src/enums.js new file mode 100644 index 00000000..1649af18 --- /dev/null +++ b/mediaserver/src/enums.js @@ -0,0 +1,11 @@ +module.exports = { + /** + * Defines which media types are supported by this server. + * Supported MIME types can be any valid IANA MIME type. + */ + SUPPORTED_MIME_TYPES: { + IMAGE: ["image/jpeg", "image/png", "image/gif", "image/bmp", "image/tiff"], + VIDEO: ["video/mp4", "video/avi", "video/x-msvideo", "video/quicktime"], + DOC: ["application/pdf"] + } +}; diff --git a/mediaserver/src/routes/describe.js b/mediaserver/src/routes/describe.js new file mode 100644 index 00000000..b68740bf --- /dev/null +++ b/mediaserver/src/routes/describe.js @@ -0,0 +1,58 @@ +/** + * describe.js - Provides routes which describe the resource endpoints. + */ + +const koaRouter = require("koa-router"); +const fs = require("fs-extra"); +const { STATIC } = require("../config"); + +const router = new koaRouter(); + +const groups = STATIC.map(group => group.NAME); + +/** + * Return all the static resource groups defined by the config file. + * + * Describes which groups are present, and at what path. + */ +router.get("/", async ctx => { + ctx.body = { + success: true, + path: "/static/", + groups: groups + }; +}); + +/** + * For a specific group, return the files within it. + */ +router.get("/:group", async ctx => { + /** + * Get and escape the group from the request. + */ + const group = ctx.params.group.toLowerCase(); + + const index = groups.indexOf(group); + + if (index !== -1) { + const filenames = await fs.readdir("./media/static/" + group); + + ctx.body = { + success: true, + path: "/static/" + group + "/", + mimeType: STATIC[index].MIME_TYPE, + files: filenames + }; + } else { + ctx.body = { + success: false, + path: null, + fileExtension: null, + files: null + }; + } +}); + +module.exports = { + router: router +}; diff --git a/mediaserver/src/routes/static.js b/mediaserver/src/routes/static.js new file mode 100644 index 00000000..adf8fbf7 --- /dev/null +++ b/mediaserver/src/routes/static.js @@ -0,0 +1,56 @@ +const koaRouter = require("koa-router"); +const fs = require("fs-extra"); +const { STATIC } = require("../config"); + +const router = new koaRouter(); + +/** + * Ensure that the static media directory exists locally on the server. + */ +fs.ensureDir("./media/static"); + +/** + * For each static resource group, define a route which serves that resource group. Each route is constructed + * by reading the configuration for each static resource group from the config file. The route will return + * either a meaningful resource if it is found on disk, or else a default 'placeholder' image. + */ +STATIC.forEach(async group => { + const route = "/" + group.NAME + "/:filename." + group.FILE_EXTENSION; + + /** + * Ensure that the directory for this group exists locally on the server. + */ + await fs.ensureDir("./media/static/" + group.NAME); + + router.get(route, async ctx => { + ctx.set("Content-Type", group.MIME_TYPE); + + filename = ctx.params.filename.toLowerCase(); + + const path = + "./media/static/" + + group.NAME + + "/" + + filename + + "." + + group.FILE_EXTENSION; + + const exists = await fs.pathExists(path); + + /** + * If exists, return the file. + * Else, return default of a given file type. + */ + if (exists) { + ctx.body = await fs.readFile(path); + } else { + ctx.body = await fs.readFile( + "./media/defaults/" + group.DEFAULT_RESOURCE + ); + } + }); +}); + +module.exports = { + router: router +}; diff --git a/mediaserver/src/routes/upload.js b/mediaserver/src/routes/upload.js new file mode 100644 index 00000000..8e2df14b --- /dev/null +++ b/mediaserver/src/routes/upload.js @@ -0,0 +1,149 @@ +const koaRouter = require("koa-router"); +const basicAuth = require("koa-basic-auth"); +const jimp = require("jimp"); +const { STATIC, UPLOAD } = require("../config"); +const { SUPPORTED_MIME_TYPES } = require("../enums"); +const fs = require("fs"); + + +const router = new koaRouter(); + +/** + * Authorise this route using basic HTTP authorisation. + * Credentials are defined in the config file. + */ +router.use(basicAuth({ name: UPLOAD.USER, pass: UPLOAD.PASSWORD })); + +/** + * Handles the POSTing of a new resource to the media server. + */ +router.post("/:group", async ctx => { + const group = ctx.params.group.toLowerCase(); + + /** + * Return the target group definition if the group exists within the configuration file's static definition. + */ + const targetGroup = STATIC.find(targetGroup => targetGroup.NAME === group); + + /** + * Assert that the requested target upload resource group is a valid resource group. + */ + ctx.assert(targetGroup, 400, "invalid resource group: " + group); + + const {resource} = ctx.request.files; + + ctx.assert(resource, 400, "must include 'resource' in POST body"); + + const {filename} = ctx.request.body; + + ctx.assert(filename, 400, "must include 'filename' in POST body"); + + /** + * Deconstruct targetGroup object into individual keys for improved readability. + */ + const { NAME, FILE_EXTENSION, MIME_TYPE } = targetGroup; + + const targetPath = + "/static/" + NAME + "/" + escapeFilename(filename) + "." + FILE_EXTENSION; + + ctx.assert( + extractMIMEClass(resource.type) === extractMIMEClass(MIME_TYPE), + 400, + "cannot upload MIME class: " + + extractMIMEClass(resource.type) + + ", to resource group of class: " + + extractMIMEClass(MIME_TYPE) + ); + + await save(resource, targetPath, targetGroup); + + ctx.body = { success: true, path: targetPath }; +}); + +/** + * Escapes a filename string: + * - removes leading and trailing whitespace. + * - All chars converted to lower case. + * - Replace all spaces with '_'. + * + * @param {string} filename - The string filename to be escaped. + */ +const escapeFilename = filename => + filename + .trim() + .toLowerCase() + .replace(/ /g, "_"); + +/** + * Returns the high level 'class' of the MIME type from a MIME type string. + * + * @param {string} mimeType - The MIME type from which to extract the MIME class. + */ +const extractMIMEClass = mimeType => mimeType.split("/")[0]; + +/** + * An async function which tries to save a given resource at a given path based on given parameters. + * + * Will handle conversion and compression of all resources. + * + * @param {*} resource - the resource object of a new resource to be saved. + * @param {*} targetPath - the target local path at which the resource should be saved. + * @param {*} targetGroup - the parameters of the target group. + */ +const save = async (resource, targetPath, { QUALITY, PIXEL_DIMENSIONS }) => { + return new Promise(async (resolve, reject) => { + /** + * If the resource type is an image, apply image processing with jimp. + * Else, if the resource is a video, reject the promise. + * Else, if the resource is a document, pipe the file to the target path. + * Else, the resource has an unsupported MIME type. + */ + if (SUPPORTED_MIME_TYPES.IMAGE.find(type => type === resource.type)) { + /** + * Read the resource into the jimp buffer. + */ + const image = await jimp.read(resource.path); + + /** + * Executing methods on the returned jimp object carries out image manipulation. + * Here we: + * - Resize the image to the dimensions defined by the target group parameters. + * - Compress the image based on the target group parameters. + * - Write the file to the target path. + */ + image + .cover(PIXEL_DIMENSIONS.WIDTH, PIXEL_DIMENSIONS.HEIGHT) + .quality(QUALITY) + .write("./media" + targetPath); + + /** + * Resolve the promise. + */ + resolve(); + } else if ( + SUPPORTED_MIME_TYPES.VIDEO.find(type => type === resource.type) + ) { + reject(new Error("video processing not supported")); + } else if (SUPPORTED_MIME_TYPES.DOC.find(type => type === resource.type)) { + + const src = fs.createReadStream(resource.path) + const dest = fs.createWriteStream("./media" + targetPath) + + src.pipe(dest) + src.on("close", () => { + resolve() + }) + src.on("error", (err) => { + reject(new Error(err.message)) + }) + } + else { + reject(new Error("unsupported MIME type: " + resource.type)); + } + } + ) +} + +module.exports = { + router: router +}; diff --git a/pom.xml b/pom.xml index 750662ae..c85af379 100644 --- a/pom.xml +++ b/pom.xml @@ -16,7 +16,6 @@ shared - controller services/jobs services/payments services/users @@ -61,18 +60,18 @@ git-build-hook-maven-plugin 3.5.0 - - true - + + true + - - - configure - - + + + configure + + - + diff --git a/services/jobs/cassandra-config/cassandra.yaml b/services/jobs/cassandra-config/cassandra.yaml deleted file mode 100644 index 4a5239b4..00000000 --- a/services/jobs/cassandra-config/cassandra.yaml +++ /dev/null @@ -1,1418 +0,0 @@ -# Cassandra storage config YAML - -# NOTE: -# See https://cassandra.apache.org/doc/latest/configuration/ for -# full explanations of configuration directives -# /NOTE - -# The name of the cluster. This is mainly used to prevent machines in -# one logical cluster from joining another. -cluster_name: 'Test Cluster' - -# This defines the number of tokens randomly assigned to this node on the ring -# The more tokens, relative to other nodes, the larger the proportion of data -# that this node will store. You probably want all nodes to have the same number -# of tokens assuming they have equal hardware capability. -# -# If you leave this unspecified, Cassandra will use the default of 1 token for legacy compatibility, -# and will use the initial_token as described below. -# -# Specifying initial_token will override this setting on the node's initial start, -# on subsequent starts, this setting will apply even if initial token is set. -# -# See https://cassandra.apache.org/doc/latest/getting_started/production.html#tokens for -# best practice information about num_tokens. -# -num_tokens: 16 - -# Triggers automatic allocation of num_tokens tokens for this node. The allocation -# algorithm attempts to choose tokens in a way that optimizes replicated load over -# the nodes in the datacenter for the replica factor. -# -# The load assigned to each node will be close to proportional to its number of -# vnodes. -# -# Only supported with the Murmur3Partitioner. - -# Replica factor is determined via the replication strategy used by the specified -# keyspace. -# allocate_tokens_for_keyspace: KEYSPACE - -# Replica factor is explicitly set, regardless of keyspace or datacenter. -# This is the replica factor within the datacenter, like NTS. -allocate_tokens_for_local_replication_factor: 3 - -# initial_token allows you to specify tokens manually. While you can use it with -# vnodes (num_tokens > 1, above) -- in which case you should provide a -# comma-separated list -- it's primarily used when adding nodes to legacy clusters -# that do not have vnodes enabled. -# initial_token: - -# May either be "true" or "false" to enable globally -hinted_handoff_enabled: true - -# When hinted_handoff_enabled is true, a black list of data centers that will not -# perform hinted handoff -# hinted_handoff_disabled_datacenters: -# - DC1 -# - DC2 - -# this defines the maximum amount of time a dead host will have hints -# generated. After it has been dead this long, new hints for it will not be -# created until it has been seen alive and gone down again. -max_hint_window_in_ms: 10800000 # 3 hours - -# Maximum throttle in KBs per second, per delivery thread. This will be -# reduced proportionally to the number of nodes in the cluster. (If there -# are two nodes in the cluster, each delivery thread will use the maximum -# rate; if there are three, each will throttle to half of the maximum, -# since we expect two nodes to be delivering hints simultaneously.) -hinted_handoff_throttle_in_kb: 1024 - -# Number of threads with which to deliver hints; -# Consider increasing this number when you have multi-dc deployments, since -# cross-dc handoff tends to be slower -max_hints_delivery_threads: 2 - -# Directory where Cassandra should store hints. -# If not set, the default directory is $CASSANDRA_HOME/data/hints. -# hints_directory: /var/lib/cassandra/hints - -# How often hints should be flushed from the internal buffers to disk. -# Will *not* trigger fsync. -hints_flush_period_in_ms: 10000 - -# Maximum size for a single hints file, in megabytes. -max_hints_file_size_in_mb: 128 - -# Compression to apply to the hint files. If omitted, hints files -# will be written uncompressed. LZ4, Snappy, and Deflate compressors -# are supported. -#hints_compression: -# - class_name: LZ4Compressor -# parameters: -# - - -# Maximum throttle in KBs per second, total. This will be -# reduced proportionally to the number of nodes in the cluster. -batchlog_replay_throttle_in_kb: 1024 - -# Authentication backend, implementing IAuthenticator; used to identify users -# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthenticator, -# PasswordAuthenticator}. -# -# - AllowAllAuthenticator performs no checks - set it to disable authentication. -# - PasswordAuthenticator relies on username/password pairs to authenticate -# users. It keeps usernames and hashed passwords in system_auth.roles table. -# Please increase system_auth keyspace replication factor if you use this authenticator. -# If using PasswordAuthenticator, CassandraRoleManager must also be used (see below) -authenticator: AllowAllAuthenticator - -# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions -# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllAuthorizer, -# CassandraAuthorizer}. -# -# - AllowAllAuthorizer allows any action to any user - set it to disable authorization. -# - CassandraAuthorizer stores permissions in system_auth.role_permissions table. Please -# increase system_auth keyspace replication factor if you use this authorizer. -authorizer: AllowAllAuthorizer - -# Part of the Authentication & Authorization backend, implementing IRoleManager; used -# to maintain grants and memberships between roles. -# Out of the box, Cassandra provides org.apache.cassandra.auth.CassandraRoleManager, -# which stores role information in the system_auth keyspace. Most functions of the -# IRoleManager require an authenticated login, so unless the configured IAuthenticator -# actually implements authentication, most of this functionality will be unavailable. -# -# - CassandraRoleManager stores role data in the system_auth keyspace. Please -# increase system_auth keyspace replication factor if you use this role manager. -role_manager: CassandraRoleManager - -# Network authorization backend, implementing INetworkAuthorizer; used to restrict user -# access to certain DCs -# Out of the box, Cassandra provides org.apache.cassandra.auth.{AllowAllNetworkAuthorizer, -# CassandraNetworkAuthorizer}. -# -# - AllowAllNetworkAuthorizer allows access to any DC to any user - set it to disable authorization. -# - CassandraNetworkAuthorizer stores permissions in system_auth.network_permissions table. Please -# increase system_auth keyspace replication factor if you use this authorizer. -network_authorizer: AllowAllNetworkAuthorizer - -# Validity period for roles cache (fetching granted roles can be an expensive -# operation depending on the role manager, CassandraRoleManager is one example) -# Granted roles are cached for authenticated sessions in AuthenticatedUser and -# after the period specified here, become eligible for (async) reload. -# Defaults to 2000, set to 0 to disable caching entirely. -# Will be disabled automatically for AllowAllAuthenticator. -roles_validity_in_ms: 2000 - -# Refresh interval for roles cache (if enabled). -# After this interval, cache entries become eligible for refresh. Upon next -# access, an async reload is scheduled and the old value returned until it -# completes. If roles_validity_in_ms is non-zero, then this must be -# also. -# Defaults to the same value as roles_validity_in_ms. -# roles_update_interval_in_ms: 2000 - -# Validity period for permissions cache (fetching permissions can be an -# expensive operation depending on the authorizer, CassandraAuthorizer is -# one example). Defaults to 2000, set to 0 to disable. -# Will be disabled automatically for AllowAllAuthorizer. -permissions_validity_in_ms: 2000 - -# Refresh interval for permissions cache (if enabled). -# After this interval, cache entries become eligible for refresh. Upon next -# access, an async reload is scheduled and the old value returned until it -# completes. If permissions_validity_in_ms is non-zero, then this must be -# also. -# Defaults to the same value as permissions_validity_in_ms. -# permissions_update_interval_in_ms: 2000 - -# Validity period for credentials cache. This cache is tightly coupled to -# the provided PasswordAuthenticator implementation of IAuthenticator. If -# another IAuthenticator implementation is configured, this cache will not -# be automatically used and so the following settings will have no effect. -# Please note, credentials are cached in their encrypted form, so while -# activating this cache may reduce the number of queries made to the -# underlying table, it may not bring a significant reduction in the -# latency of individual authentication attempts. -# Defaults to 2000, set to 0 to disable credentials caching. -credentials_validity_in_ms: 2000 - -# Refresh interval for credentials cache (if enabled). -# After this interval, cache entries become eligible for refresh. Upon next -# access, an async reload is scheduled and the old value returned until it -# completes. If credentials_validity_in_ms is non-zero, then this must be -# also. -# Defaults to the same value as credentials_validity_in_ms. -# credentials_update_interval_in_ms: 2000 - -# The partitioner is responsible for distributing groups of rows (by -# partition key) across nodes in the cluster. The partitioner can NOT be -# changed without reloading all data. If you are adding nodes or upgrading, -# you should set this to the same partitioner that you are currently using. -# -# The default partitioner is the Murmur3Partitioner. Older partitioners -# such as the RandomPartitioner, ByteOrderedPartitioner, and -# OrderPreservingPartitioner have been included for backward compatibility only. -# For new clusters, you should NOT change this value. -# -partitioner: org.apache.cassandra.dht.Murmur3Partitioner - -# Directories where Cassandra should store data on disk. If multiple -# directories are specified, Cassandra will spread data evenly across -# them by partitioning the token ranges. -# If not set, the default directory is $CASSANDRA_HOME/data/data. -# data_file_directories: -# - /var/lib/cassandra/data - -# Directory were Cassandra should store the data of the local system keyspaces. -# By default Cassandra will store the data of the local system keyspaces in the first of the data directories specified -# by data_file_directories. -# This approach ensures that if one of the other disks is lost Cassandra can continue to operate. For extra security -# this setting allows to store those data on a different directory that provides redundancy. -# local_system_data_file_directory: - -# commit log. when running on magnetic HDD, this should be a -# separate spindle than the data directories. -# If not set, the default directory is $CASSANDRA_HOME/data/commitlog. -# commitlog_directory: /var/lib/cassandra/commitlog - -# Enable / disable CDC functionality on a per-node basis. This modifies the logic used -# for write path allocation rejection (standard: never reject. cdc: reject Mutation -# containing a CDC-enabled table if at space limit in cdc_raw_directory). -cdc_enabled: false - -# CommitLogSegments are moved to this directory on flush if cdc_enabled: true and the -# segment contains mutations for a CDC-enabled table. This should be placed on a -# separate spindle than the data directories. If not set, the default directory is -# $CASSANDRA_HOME/data/cdc_raw. -# cdc_raw_directory: /var/lib/cassandra/cdc_raw - -# Policy for data disk failures: -# -# die -# shut down gossip and client transports and kill the JVM for any fs errors or -# single-sstable errors, so the node can be replaced. -# -# stop_paranoid -# shut down gossip and client transports even for single-sstable errors, -# kill the JVM for errors during startup. -# -# stop -# shut down gossip and client transports, leaving the node effectively dead, but -# can still be inspected via JMX, kill the JVM for errors during startup. -# -# best_effort -# stop using the failed disk and respond to requests based on -# remaining available sstables. This means you WILL see obsolete -# data at CL.ONE! -# -# ignore -# ignore fatal errors and let requests fail, as in pre-1.2 Cassandra -disk_failure_policy: stop - -# Policy for commit disk failures: -# -# die -# shut down the node and kill the JVM, so the node can be replaced. -# -# stop -# shut down the node, leaving the node effectively dead, but -# can still be inspected via JMX. -# -# stop_commit -# shutdown the commit log, letting writes collect but -# continuing to service reads, as in pre-2.0.5 Cassandra -# -# ignore -# ignore fatal errors and let the batches fail -commit_failure_policy: stop - -# Maximum size of the native protocol prepared statement cache -# -# Valid values are either "auto" (omitting the value) or a value greater 0. -# -# Note that specifying a too large value will result in long running GCs and possbily -# out-of-memory errors. Keep the value at a small fraction of the heap. -# -# If you constantly see "prepared statements discarded in the last minute because -# cache limit reached" messages, the first step is to investigate the root cause -# of these messages and check whether prepared statements are used correctly - -# i.e. use bind markers for variable parts. -# -# Do only change the default value, if you really have more prepared statements than -# fit in the cache. In most cases it is not neccessary to change this value. -# Constantly re-preparing statements is a performance penalty. -# -# Default value ("auto") is 1/256th of the heap or 10MB, whichever is greater -prepared_statements_cache_size_mb: - -# Maximum size of the key cache in memory. -# -# Each key cache hit saves 1 seek and each row cache hit saves 2 seeks at the -# minimum, sometimes more. The key cache is fairly tiny for the amount of -# time it saves, so it's worthwhile to use it at large numbers. -# The row cache saves even more time, but must contain the entire row, -# so it is extremely space-intensive. It's best to only use the -# row cache if you have hot rows or static rows. -# -# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. -# -# Default value is empty to make it "auto" (min(5% of Heap (in MB), 100MB)). Set to 0 to disable key cache. -key_cache_size_in_mb: - -# Duration in seconds after which Cassandra should -# save the key cache. Caches are saved to saved_caches_directory as -# specified in this configuration file. -# -# Saved caches greatly improve cold-start speeds, and is relatively cheap in -# terms of I/O for the key cache. Row cache saving is much more expensive and -# has limited use. -# -# Default is 14400 or 4 hours. -key_cache_save_period: 14400 - -# Number of keys from the key cache to save -# Disabled by default, meaning all keys are going to be saved -# key_cache_keys_to_save: 100 - -# Row cache implementation class name. Available implementations: -# -# org.apache.cassandra.cache.OHCProvider -# Fully off-heap row cache implementation (default). -# -# org.apache.cassandra.cache.SerializingCacheProvider -# This is the row cache implementation availabile -# in previous releases of Cassandra. -# row_cache_class_name: org.apache.cassandra.cache.OHCProvider - -# Maximum size of the row cache in memory. -# Please note that OHC cache implementation requires some additional off-heap memory to manage -# the map structures and some in-flight memory during operations before/after cache entries can be -# accounted against the cache capacity. This overhead is usually small compared to the whole capacity. -# Do not specify more memory that the system can afford in the worst usual situation and leave some -# headroom for OS block level cache. Do never allow your system to swap. -# -# Default value is 0, to disable row caching. -row_cache_size_in_mb: 0 - -# Duration in seconds after which Cassandra should save the row cache. -# Caches are saved to saved_caches_directory as specified in this configuration file. -# -# Saved caches greatly improve cold-start speeds, and is relatively cheap in -# terms of I/O for the key cache. Row cache saving is much more expensive and -# has limited use. -# -# Default is 0 to disable saving the row cache. -row_cache_save_period: 0 - -# Number of keys from the row cache to save. -# Specify 0 (which is the default), meaning all keys are going to be saved -# row_cache_keys_to_save: 100 - -# Maximum size of the counter cache in memory. -# -# Counter cache helps to reduce counter locks' contention for hot counter cells. -# In case of RF = 1 a counter cache hit will cause Cassandra to skip the read before -# write entirely. With RF > 1 a counter cache hit will still help to reduce the duration -# of the lock hold, helping with hot counter cell updates, but will not allow skipping -# the read entirely. Only the local (clock, count) tuple of a counter cell is kept -# in memory, not the whole counter, so it's relatively cheap. -# -# NOTE: if you reduce the size, you may not get you hottest keys loaded on startup. -# -# Default value is empty to make it "auto" (min(2.5% of Heap (in MB), 50MB)). Set to 0 to disable counter cache. -# NOTE: if you perform counter deletes and rely on low gcgs, you should disable the counter cache. -counter_cache_size_in_mb: - -# Duration in seconds after which Cassandra should -# save the counter cache (keys only). Caches are saved to saved_caches_directory as -# specified in this configuration file. -# -# Default is 7200 or 2 hours. -counter_cache_save_period: 7200 - -# Number of keys from the counter cache to save -# Disabled by default, meaning all keys are going to be saved -# counter_cache_keys_to_save: 100 - -# saved caches -# If not set, the default directory is $CASSANDRA_HOME/data/saved_caches. -# saved_caches_directory: /var/lib/cassandra/saved_caches - -# Number of seconds the server will wait for each cache (row, key, etc ...) to load while starting -# the Cassandra process. Setting this to a negative value is equivalent to disabling all cache loading on startup -# while still having the cache during runtime. -# cache_load_timeout_seconds: 30 - -# commitlog_sync may be either "periodic", "group", or "batch." -# -# When in batch mode, Cassandra won't ack writes until the commit log -# has been flushed to disk. Each incoming write will trigger the flush task. -# commitlog_sync_batch_window_in_ms is a deprecated value. Previously it had -# almost no value, and is being removed. -# -# commitlog_sync_batch_window_in_ms: 2 -# -# group mode is similar to batch mode, where Cassandra will not ack writes -# until the commit log has been flushed to disk. The difference is group -# mode will wait up to commitlog_sync_group_window_in_ms between flushes. -# -# commitlog_sync_group_window_in_ms: 1000 -# -# the default option is "periodic" where writes may be acked immediately -# and the CommitLog is simply synced every commitlog_sync_period_in_ms -# milliseconds. -commitlog_sync: periodic -commitlog_sync_period_in_ms: 10000 - -# When in periodic commitlog mode, the number of milliseconds to block writes -# while waiting for a slow disk flush to complete. -# periodic_commitlog_sync_lag_block_in_ms: - -# The size of the individual commitlog file segments. A commitlog -# segment may be archived, deleted, or recycled once all the data -# in it (potentially from each columnfamily in the system) has been -# flushed to sstables. -# -# The default size is 32, which is almost always fine, but if you are -# archiving commitlog segments (see commitlog_archiving.properties), -# then you probably want a finer granularity of archiving; 8 or 16 MB -# is reasonable. -# Max mutation size is also configurable via max_mutation_size_in_kb setting in -# cassandra.yaml. The default is half the size commitlog_segment_size_in_mb * 1024. -# This should be positive and less than 2048. -# -# NOTE: If max_mutation_size_in_kb is set explicitly then commitlog_segment_size_in_mb must -# be set to at least twice the size of max_mutation_size_in_kb / 1024 -# -commitlog_segment_size_in_mb: 32 - -# Compression to apply to the commit log. If omitted, the commit log -# will be written uncompressed. LZ4, Snappy, and Deflate compressors -# are supported. -# commitlog_compression: -# - class_name: LZ4Compressor -# parameters: -# - - -# Compression to apply to SSTables as they flush for compressed tables. -# Note that tables without compression enabled do not respect this flag. -# -# As high ratio compressors like LZ4HC, Zstd, and Deflate can potentially -# block flushes for too long, the default is to flush with a known fast -# compressor in those cases. Options are: -# -# none : Flush without compressing blocks but while still doing checksums. -# fast : Flush with a fast compressor. If the table is already using a -# fast compressor that compressor is used. -# table: Always flush with the same compressor that the table uses. This -# was the pre 4.0 behavior. -# -# flush_compression: fast - -# any class that implements the SeedProvider interface and has a -# constructor that takes a Map of parameters will do. -seed_provider: - # Addresses of hosts that are deemed contact points. - # Cassandra nodes use this list of hosts to find each other and learn - # the topology of the ring. You must change this if you are running - # multiple nodes! - - class_name: org.apache.cassandra.locator.SimpleSeedProvider - parameters: - # seeds is actually a comma-delimited list of addresses. - # Ex: ",," - - seeds: "10.0.24.3" - -# For workloads with more data than can fit in memory, Cassandra's -# bottleneck will be reads that need to fetch data from -# disk. "concurrent_reads" should be set to (16 * number_of_drives) in -# order to allow the operations to enqueue low enough in the stack -# that the OS and drives can reorder them. Same applies to -# "concurrent_counter_writes", since counter writes read the current -# values before incrementing and writing them back. -# -# On the other hand, since writes are almost never IO bound, the ideal -# number of "concurrent_writes" is dependent on the number of cores in -# your system; (8 * number_of_cores) is a good rule of thumb. -concurrent_reads: 32 -concurrent_writes: 32 -concurrent_counter_writes: 32 - -# For materialized view writes, as there is a read involved, so this should -# be limited by the less of concurrent reads or concurrent writes. -concurrent_materialized_view_writes: 32 - -# Maximum memory to use for inter-node and client-server networking buffers. -# -# Defaults to the smaller of 1/16 of heap or 128MB. This pool is allocated off-heap, -# so is in addition to the memory allocated for heap. The cache also has on-heap -# overhead which is roughly 128 bytes per chunk (i.e. 0.2% of the reserved size -# if the default 64k chunk size is used). -# Memory is only allocated when needed. -# networking_cache_size_in_mb: 128 - -# Enable the sstable chunk cache. The chunk cache will store recently accessed -# sections of the sstable in-memory as uncompressed buffers. -# file_cache_enabled: false - -# Maximum memory to use for sstable chunk cache and buffer pooling. -# 32MB of this are reserved for pooling buffers, the rest is used for chunk cache -# that holds uncompressed sstable chunks. -# Defaults to the smaller of 1/4 of heap or 512MB. This pool is allocated off-heap, -# so is in addition to the memory allocated for heap. The cache also has on-heap -# overhead which is roughly 128 bytes per chunk (i.e. 0.2% of the reserved size -# if the default 64k chunk size is used). -# Memory is only allocated when needed. -# file_cache_size_in_mb: 512 - -# Flag indicating whether to allocate on or off heap when the sstable buffer -# pool is exhausted, that is when it has exceeded the maximum memory -# file_cache_size_in_mb, beyond which it will not cache buffers but allocate on request. - -# buffer_pool_use_heap_if_exhausted: true - -# The strategy for optimizing disk read -# Possible values are: -# ssd (for solid state disks, the default) -# spinning (for spinning disks) -# disk_optimization_strategy: ssd - -# Total permitted memory to use for memtables. Cassandra will stop -# accepting writes when the limit is exceeded until a flush completes, -# and will trigger a flush based on memtable_cleanup_threshold -# If omitted, Cassandra will set both to 1/4 the size of the heap. -# memtable_heap_space_in_mb: 2048 -# memtable_offheap_space_in_mb: 2048 - -# memtable_cleanup_threshold is deprecated. The default calculation -# is the only reasonable choice. See the comments on memtable_flush_writers -# for more information. -# -# Ratio of occupied non-flushing memtable size to total permitted size -# that will trigger a flush of the largest memtable. Larger mct will -# mean larger flushes and hence less compaction, but also less concurrent -# flush activity which can make it difficult to keep your disks fed -# under heavy write load. -# -# memtable_cleanup_threshold defaults to 1 / (memtable_flush_writers + 1) -# memtable_cleanup_threshold: 0.11 - -# Specify the way Cassandra allocates and manages memtable memory. -# Options are: -# -# heap_buffers -# on heap nio buffers -# -# offheap_buffers -# off heap (direct) nio buffers -# -# offheap_objects -# off heap objects -memtable_allocation_type: heap_buffers - -# Limit memory usage for Merkle tree calculations during repairs. The default -# is 1/16th of the available heap. The main tradeoff is that smaller trees -# have less resolution, which can lead to over-streaming data. If you see heap -# pressure during repairs, consider lowering this, but you cannot go below -# one megabyte. If you see lots of over-streaming, consider raising -# this or using subrange repair. -# -# For more details see https://issues.apache.org/jira/browse/CASSANDRA-14096. -# -# repair_session_space_in_mb: - -# Total space to use for commit logs on disk. -# -# If space gets above this value, Cassandra will flush every dirty CF -# in the oldest segment and remove it. So a small total commitlog space -# will tend to cause more flush activity on less-active columnfamilies. -# -# The default value is the smaller of 8192, and 1/4 of the total space -# of the commitlog volume. -# -# commitlog_total_space_in_mb: 8192 - -# This sets the number of memtable flush writer threads per disk -# as well as the total number of memtables that can be flushed concurrently. -# These are generally a combination of compute and IO bound. -# -# Memtable flushing is more CPU efficient than memtable ingest and a single thread -# can keep up with the ingest rate of a whole server on a single fast disk -# until it temporarily becomes IO bound under contention typically with compaction. -# At that point you need multiple flush threads. At some point in the future -# it may become CPU bound all the time. -# -# You can tell if flushing is falling behind using the MemtablePool.BlockedOnAllocation -# metric which should be 0, but will be non-zero if threads are blocked waiting on flushing -# to free memory. -# -# memtable_flush_writers defaults to two for a single data directory. -# This means that two memtables can be flushed concurrently to the single data directory. -# If you have multiple data directories the default is one memtable flushing at a time -# but the flush will use a thread per data directory so you will get two or more writers. -# -# Two is generally enough to flush on a fast disk [array] mounted as a single data directory. -# Adding more flush writers will result in smaller more frequent flushes that introduce more -# compaction overhead. -# -# There is a direct tradeoff between number of memtables that can be flushed concurrently -# and flush size and frequency. More is not better you just need enough flush writers -# to never stall waiting for flushing to free memory. -# -#memtable_flush_writers: 2 - -# Total space to use for change-data-capture logs on disk. -# -# If space gets above this value, Cassandra will throw WriteTimeoutException -# on Mutations including tables with CDC enabled. A CDCCompactor is responsible -# for parsing the raw CDC logs and deleting them when parsing is completed. -# -# The default value is the min of 4096 mb and 1/8th of the total space -# of the drive where cdc_raw_directory resides. -# cdc_total_space_in_mb: 4096 - -# When we hit our cdc_raw limit and the CDCCompactor is either running behind -# or experiencing backpressure, we check at the following interval to see if any -# new space for cdc-tracked tables has been made available. Default to 250ms -# cdc_free_space_check_interval_ms: 250 - -# A fixed memory pool size in MB for for SSTable index summaries. If left -# empty, this will default to 5% of the heap size. If the memory usage of -# all index summaries exceeds this limit, SSTables with low read rates will -# shrink their index summaries in order to meet this limit. However, this -# is a best-effort process. In extreme conditions Cassandra may need to use -# more than this amount of memory. -index_summary_capacity_in_mb: - -# How frequently index summaries should be resampled. This is done -# periodically to redistribute memory from the fixed-size pool to sstables -# proportional their recent read rates. Setting to -1 will disable this -# process, leaving existing index summaries at their current sampling level. -index_summary_resize_interval_in_minutes: 60 - -# Whether to, when doing sequential writing, fsync() at intervals in -# order to force the operating system to flush the dirty -# buffers. Enable this to avoid sudden dirty buffer flushing from -# impacting read latencies. Almost always a good idea on SSDs; not -# necessarily on platters. -trickle_fsync: false -trickle_fsync_interval_in_kb: 10240 - -# TCP port, for commands and data -# For security reasons, you should not expose this port to the internet. Firewall it if needed. -storage_port: 7000 - -# SSL port, for legacy encrypted communication. This property is unused unless enabled in -# server_encryption_options (see below). As of cassandra 4.0, this property is deprecated -# as a single port can be used for either/both secure and insecure connections. -# For security reasons, you should not expose this port to the internet. Firewall it if needed. -ssl_storage_port: 7001 - -# Address or interface to bind to and tell other Cassandra nodes to connect to. -# You _must_ change this if you want multiple nodes to be able to communicate! -# -# Set listen_address OR listen_interface, not both. -# -# Leaving it blank leaves it up to InetAddress.getLocalHost(). This -# will always do the Right Thing _if_ the node is properly configured -# (hostname, name resolution, etc), and the Right Thing is to use the -# address associated with the hostname (it might not be). If unresolvable -# it will fall back to InetAddress.getLoopbackAddress(), which is wrong for production systems. -# -# Setting listen_address to 0.0.0.0 is always wrong. -# -listen_address: 10.0.24.3 - -# Set listen_address OR listen_interface, not both. Interfaces must correspond -# to a single address, IP aliasing is not supported. -# listen_interface: eth0 - -# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address -# you can specify which should be chosen using listen_interface_prefer_ipv6. If false the first ipv4 -# address will be used. If true the first ipv6 address will be used. Defaults to false preferring -# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6. -# listen_interface_prefer_ipv6: false - -# Address to broadcast to other Cassandra nodes -# Leaving this blank will set it to the same value as listen_address -broadcast_address: 10.0.24.3 - -# When using multiple physical network interfaces, set this -# to true to listen on broadcast_address in addition to -# the listen_address, allowing nodes to communicate in both -# interfaces. -# Ignore this property if the network configuration automatically -# routes between the public and private networks such as EC2. -# listen_on_broadcast_address: false - -# Internode authentication backend, implementing IInternodeAuthenticator; -# used to allow/disallow connections from peer nodes. -# internode_authenticator: org.apache.cassandra.auth.AllowAllInternodeAuthenticator - -# Whether to start the native transport server. -# The address on which the native transport is bound is defined by rpc_address. -start_native_transport: true -# port for the CQL native transport to listen for clients on -# For security reasons, you should not expose this port to the internet. Firewall it if needed. -native_transport_port: 9042 -# Enabling native transport encryption in client_encryption_options allows you to either use -# encryption for the standard port or to use a dedicated, additional port along with the unencrypted -# standard native_transport_port. -# Enabling client encryption and keeping native_transport_port_ssl disabled will use encryption -# for native_transport_port. Setting native_transport_port_ssl to a different value -# from native_transport_port will use encryption for native_transport_port_ssl while -# keeping native_transport_port unencrypted. -# native_transport_port_ssl: 9142 -# The maximum threads for handling requests (note that idle threads are stopped -# after 30 seconds so there is not corresponding minimum setting). -# native_transport_max_threads: 128 -# -# The maximum size of allowed frame. Frame (requests) larger than this will -# be rejected as invalid. The default is 256MB. If you're changing this parameter, -# you may want to adjust max_value_size_in_mb accordingly. This should be positive and less than 2048. -# native_transport_max_frame_size_in_mb: 256 - -# The maximum number of concurrent client connections. -# The default is -1, which means unlimited. -# native_transport_max_concurrent_connections: -1 - -# The maximum number of concurrent client connections per source ip. -# The default is -1, which means unlimited. -# native_transport_max_concurrent_connections_per_ip: -1 - -# Controls whether Cassandra honors older, yet currently supported, protocol versions. -# The default is true, which means all supported protocols will be honored. -native_transport_allow_older_protocols: true - -# Controls when idle client connections are closed. Idle connections are ones that had neither reads -# nor writes for a time period. -# -# Clients may implement heartbeats by sending OPTIONS native protocol message after a timeout, which -# will reset idle timeout timer on the server side. To close idle client connections, corresponding -# values for heartbeat intervals have to be set on the client side. -# -# Idle connection timeouts are disabled by default. -# native_transport_idle_timeout_in_ms: 60000 - -# The address or interface to bind the native transport server to. -# -# Set rpc_address OR rpc_interface, not both. -# -# Leaving rpc_address blank has the same effect as on listen_address -# (i.e. it will be based on the configured hostname of the node). -# -# Note that unlike listen_address, you can specify 0.0.0.0, but you must also -# set broadcast_rpc_address to a value other than 0.0.0.0. -# -# For security reasons, you should not expose this port to the internet. Firewall it if needed. -rpc_address: 0.0.0.0 - -# Set rpc_address OR rpc_interface, not both. Interfaces must correspond -# to a single address, IP aliasing is not supported. -# rpc_interface: eth1 - -# If you choose to specify the interface by name and the interface has an ipv4 and an ipv6 address -# you can specify which should be chosen using rpc_interface_prefer_ipv6. If false the first ipv4 -# address will be used. If true the first ipv6 address will be used. Defaults to false preferring -# ipv4. If there is only one address it will be selected regardless of ipv4/ipv6. -# rpc_interface_prefer_ipv6: false - -# RPC address to broadcast to drivers and other Cassandra nodes. This cannot -# be set to 0.0.0.0. If left blank, this will be set to the value of -# rpc_address. If rpc_address is set to 0.0.0.0, broadcast_rpc_address must -# be set. -broadcast_rpc_address: 10.0.24.3 - -# enable or disable keepalive on rpc/native connections -rpc_keepalive: true - -# Uncomment to set socket buffer size for internode communication -# Note that when setting this, the buffer size is limited by net.core.wmem_max -# and when not setting it it is defined by net.ipv4.tcp_wmem -# See also: -# /proc/sys/net/core/wmem_max -# /proc/sys/net/core/rmem_max -# /proc/sys/net/ipv4/tcp_wmem -# /proc/sys/net/ipv4/tcp_wmem -# and 'man tcp' -# internode_socket_send_buffer_size_in_bytes: - -# Uncomment to set socket buffer size for internode communication -# Note that when setting this, the buffer size is limited by net.core.wmem_max -# and when not setting it it is defined by net.ipv4.tcp_wmem -# internode_socket_receive_buffer_size_in_bytes: - -# Set to true to have Cassandra create a hard link to each sstable -# flushed or streamed locally in a backups/ subdirectory of the -# keyspace data. Removing these links is the operator's -# responsibility. -incremental_backups: false - -# Whether or not to take a snapshot before each compaction. Be -# careful using this option, since Cassandra won't clean up the -# snapshots for you. Mostly useful if you're paranoid when there -# is a data format change. -snapshot_before_compaction: false - -# Whether or not a snapshot is taken of the data before keyspace truncation -# or dropping of column families. The STRONGLY advised default of true -# should be used to provide data safety. If you set this flag to false, you will -# lose data on truncation or drop. -auto_snapshot: true - -# The act of creating or clearing a snapshot involves creating or removing -# potentially tens of thousands of links, which can cause significant performance -# impact, especially on consumer grade SSDs. A non-zero value here can -# be used to throttle these links to avoid negative performance impact of -# taking and clearing snapshots -snapshot_links_per_second: 0 - -# Granularity of the collation index of rows within a partition. -# Increase if your rows are large, or if you have a very large -# number of rows per partition. The competing goals are these: -# -# - a smaller granularity means more index entries are generated -# and looking up rows withing the partition by collation column -# is faster -# - but, Cassandra will keep the collation index in memory for hot -# rows (as part of the key cache), so a larger granularity means -# you can cache more hot rows -column_index_size_in_kb: 64 - -# Per sstable indexed key cache entries (the collation index in memory -# mentioned above) exceeding this size will not be held on heap. -# This means that only partition information is held on heap and the -# index entries are read from disk. -# -# Note that this size refers to the size of the -# serialized index information and not the size of the partition. -column_index_cache_size_in_kb: 2 - -# Number of simultaneous compactions to allow, NOT including -# validation "compactions" for anti-entropy repair. Simultaneous -# compactions can help preserve read performance in a mixed read/write -# workload, by mitigating the tendency of small sstables to accumulate -# during a single long running compactions. The default is usually -# fine and if you experience problems with compaction running too -# slowly or too fast, you should look at -# compaction_throughput_mb_per_sec first. -# -# concurrent_compactors defaults to the smaller of (number of disks, -# number of cores), with a minimum of 2 and a maximum of 8. -# -# If your data directories are backed by SSD, you should increase this -# to the number of cores. -#concurrent_compactors: 1 - -# Number of simultaneous repair validations to allow. If not set or set to -# a value less than 1, it defaults to the value of concurrent_compactors. -# To set a value greeater than concurrent_compactors at startup, the system -# property cassandra.allow_unlimited_concurrent_validations must be set to -# true. To dynamically resize to a value > concurrent_compactors on a running -# node, first call the bypassConcurrentValidatorsLimit method on the -# org.apache.cassandra.db:type=StorageService mbean -# concurrent_validations: 0 - -# Number of simultaneous materialized view builder tasks to allow. -concurrent_materialized_view_builders: 1 - -# Throttles compaction to the given total throughput across the entire -# system. The faster you insert data, the faster you need to compact in -# order to keep the sstable count down, but in general, setting this to -# 16 to 32 times the rate you are inserting data is more than sufficient. -# Setting this to 0 disables throttling. Note that this accounts for all types -# of compaction, including validation compaction (building Merkle trees -# for repairs). -compaction_throughput_mb_per_sec: 64 - -# When compacting, the replacement sstable(s) can be opened before they -# are completely written, and used in place of the prior sstables for -# any range that has been written. This helps to smoothly transfer reads -# between the sstables, reducing page cache churn and keeping hot rows hot -sstable_preemptive_open_interval_in_mb: 50 - -# When enabled, permits Cassandra to zero-copy stream entire eligible -# SSTables between nodes, including every component. -# This speeds up the network transfer significantly subject to -# throttling specified by stream_throughput_outbound_megabits_per_sec. -# Enabling this will reduce the GC pressure on sending and receiving node. -# When unset, the default is enabled. While this feature tries to keep the -# disks balanced, it cannot guarantee it. This feature will be automatically -# disabled if internode encryption is enabled. -# stream_entire_sstables: true - -# Throttles all outbound streaming file transfers on this node to the -# given total throughput in Mbps. This is necessary because Cassandra does -# mostly sequential IO when streaming data during bootstrap or repair, which -# can lead to saturating the network connection and degrading rpc performance. -# When unset, the default is 200 Mbps or 25 MB/s. -# stream_throughput_outbound_megabits_per_sec: 200 - -# Throttles all streaming file transfer between the datacenters, -# this setting allows users to throttle inter dc stream throughput in addition -# to throttling all network stream traffic as configured with -# stream_throughput_outbound_megabits_per_sec -# When unset, the default is 200 Mbps or 25 MB/s -# inter_dc_stream_throughput_outbound_megabits_per_sec: 200 - -# Server side timeouts for requests. The server will return a timeout exception -# to the client if it can't complete an operation within the corresponding -# timeout. Those settings are a protection against: -# 1) having client wait on an operation that might never terminate due to some -# failures. -# 2) operations that use too much CPU/read too much data (leading to memory build -# up) by putting a limit to how long an operation will execute. -# For this reason, you should avoid putting these settings too high. In other words, -# if you are timing out requests because of underlying resource constraints then -# increasing the timeout will just cause more problems. Of course putting them too -# low is equally ill-advised since clients could get timeouts even for successful -# operations just because the timeout setting is too tight. - -# How long the coordinator should wait for read operations to complete. -# Lowest acceptable value is 10 ms. -read_request_timeout_in_ms: 5000 -# How long the coordinator should wait for seq or index scans to complete. -# Lowest acceptable value is 10 ms. -range_request_timeout_in_ms: 10000 -# How long the coordinator should wait for writes to complete. -# Lowest acceptable value is 10 ms. -write_request_timeout_in_ms: 2000 -# How long the coordinator should wait for counter writes to complete. -# Lowest acceptable value is 10 ms. -counter_write_request_timeout_in_ms: 5000 -# How long a coordinator should continue to retry a CAS operation -# that contends with other proposals for the same row. -# Lowest acceptable value is 10 ms. -cas_contention_timeout_in_ms: 1000 -# How long the coordinator should wait for truncates to complete -# (This can be much longer, because unless auto_snapshot is disabled -# we need to flush first so we can snapshot before removing the data.) -# Lowest acceptable value is 10 ms. -truncate_request_timeout_in_ms: 60000 -# The default timeout for other, miscellaneous operations. -# Lowest acceptable value is 10 ms. -request_timeout_in_ms: 10000 - -# Defensive settings for protecting Cassandra from true network partitions. -# See (CASSANDRA-14358) for details. -# -# The amount of time to wait for internode tcp connections to establish. -# internode_tcp_connect_timeout_in_ms: 2000 -# -# The amount of time unacknowledged data is allowed on a connection before we throw out the connection -# Note this is only supported on Linux + epoll, and it appears to behave oddly above a setting of 30000 -# (it takes much longer than 30s) as of Linux 4.12. If you want something that high set this to 0 -# which picks up the OS default and configure the net.ipv4.tcp_retries2 sysctl to be ~8. -# internode_tcp_user_timeout_in_ms: 30000 - -# The amount of time unacknowledged data is allowed on a streaming connection. -# The default is 5 minutes. Increase it or set it to 0 in order to increase the timeout. -# internode_streaming_tcp_user_timeout_in_ms: 300000 - -# Global, per-endpoint and per-connection limits imposed on messages queued for delivery to other nodes -# and waiting to be processed on arrival from other nodes in the cluster. These limits are applied to the on-wire -# size of the message being sent or received. -# -# The basic per-link limit is consumed in isolation before any endpoint or global limit is imposed. -# Each node-pair has three links: urgent, small and large. So any given node may have a maximum of -# N*3*(internode_application_send_queue_capacity_in_bytes+internode_application_receive_queue_capacity_in_bytes) -# messages queued without any coordination between them although in practice, with token-aware routing, only RF*tokens -# nodes should need to communicate with significant bandwidth. -# -# The per-endpoint limit is imposed on all messages exceeding the per-link limit, simultaneously with the global limit, -# on all links to or from a single node in the cluster. -# The global limit is imposed on all messages exceeding the per-link limit, simultaneously with the per-endpoint limit, -# on all links to or from any node in the cluster. -# -# internode_application_send_queue_capacity_in_bytes: 4194304 #4MiB -# internode_application_send_queue_reserve_endpoint_capacity_in_bytes: 134217728 #128MiB -# internode_application_send_queue_reserve_global_capacity_in_bytes: 536870912 #512MiB -# internode_application_receive_queue_capacity_in_bytes: 4194304 #4MiB -# internode_application_receive_queue_reserve_endpoint_capacity_in_bytes: 134217728 #128MiB -# internode_application_receive_queue_reserve_global_capacity_in_bytes: 536870912 #512MiB - - -# How long before a node logs slow queries. Select queries that take longer than -# this timeout to execute, will generate an aggregated log message, so that slow queries -# can be identified. Set this value to zero to disable slow query logging. -slow_query_log_timeout_in_ms: 500 - -# Enable operation timeout information exchange between nodes to accurately -# measure request timeouts. If disabled, replicas will assume that requests -# were forwarded to them instantly by the coordinator, which means that -# under overload conditions we will waste that much extra time processing -# already-timed-out requests. -# -# Warning: It is generally assumed that users have setup NTP on their clusters, and that clocks are modestly in sync, -# since this is a requirement for general correctness of last write wins. -#cross_node_timeout: true - -# Set keep-alive period for streaming -# This node will send a keep-alive message periodically with this period. -# If the node does not receive a keep-alive message from the peer for -# 2 keep-alive cycles the stream session times out and fail -# Default value is 300s (5 minutes), which means stalled stream -# times out in 10 minutes by default -# streaming_keep_alive_period_in_secs: 300 - -# Limit number of connections per host for streaming -# Increase this when you notice that joins are CPU-bound rather that network -# bound (for example a few nodes with big files). -# streaming_connections_per_host: 1 - - -# phi value that must be reached for a host to be marked down. -# most users should never need to adjust this. -# phi_convict_threshold: 8 - -# endpoint_snitch -- Set this to a class that implements -# IEndpointSnitch. The snitch has two functions: -# -# - it teaches Cassandra enough about your network topology to route -# requests efficiently -# - it allows Cassandra to spread replicas around your cluster to avoid -# correlated failures. It does this by grouping machines into -# "datacenters" and "racks." Cassandra will do its best not to have -# more than one replica on the same "rack" (which may not actually -# be a physical location) -# -# CASSANDRA WILL NOT ALLOW YOU TO SWITCH TO AN INCOMPATIBLE SNITCH -# ONCE DATA IS INSERTED INTO THE CLUSTER. This would cause data loss. -# This means that if you start with the default SimpleSnitch, which -# locates every node on "rack1" in "datacenter1", your only options -# if you need to add another datacenter are GossipingPropertyFileSnitch -# (and the older PFS). From there, if you want to migrate to an -# incompatible snitch like Ec2Snitch you can do it by adding new nodes -# under Ec2Snitch (which will locate them in a new "datacenter") and -# decommissioning the old ones. -# -# Out of the box, Cassandra provides: -# -# SimpleSnitch: -# Treats Strategy order as proximity. This can improve cache -# locality when disabling read repair. Only appropriate for -# single-datacenter deployments. -# -# GossipingPropertyFileSnitch -# This should be your go-to snitch for production use. The rack -# and datacenter for the local node are defined in -# cassandra-rackdc.properties and propagated to other nodes via -# gossip. If cassandra-topology.properties exists, it is used as a -# fallback, allowing migration from the PropertyFileSnitch. -# -# PropertyFileSnitch: -# Proximity is determined by rack and data center, which are -# explicitly configured in cassandra-topology.properties. -# -# Ec2Snitch: -# Appropriate for EC2 deployments in a single Region. Loads Region -# and Availability Zone information from the EC2 API. The Region is -# treated as the datacenter, and the Availability Zone as the rack. -# Only private IPs are used, so this will not work across multiple -# Regions. -# -# Ec2MultiRegionSnitch: -# Uses public IPs as broadcast_address to allow cross-region -# connectivity. (Thus, you should set seed addresses to the public -# IP as well.) You will need to open the storage_port or -# ssl_storage_port on the public IP firewall. (For intra-Region -# traffic, Cassandra will switch to the private IP after -# establishing a connection.) -# -# RackInferringSnitch: -# Proximity is determined by rack and data center, which are -# assumed to correspond to the 3rd and 2nd octet of each node's IP -# address, respectively. Unless this happens to match your -# deployment conventions, this is best used as an example of -# writing a custom Snitch class and is provided in that spirit. -# -# You can use a custom Snitch by setting this to the full class name -# of the snitch, which will be assumed to be on your classpath. -endpoint_snitch: SimpleSnitch - -# controls how often to perform the more expensive part of host score -# calculation -dynamic_snitch_update_interval_in_ms: 100 -# controls how often to reset all host scores, allowing a bad host to -# possibly recover -dynamic_snitch_reset_interval_in_ms: 600000 -# if set greater than zero, this will allow -# 'pinning' of replicas to hosts in order to increase cache capacity. -# The badness threshold will control how much worse the pinned host has to be -# before the dynamic snitch will prefer other replicas over it. This is -# expressed as a double which represents a percentage. Thus, a value of -# 0.2 means Cassandra would continue to prefer the static snitch values -# until the pinned host was 20% worse than the fastest. -dynamic_snitch_badness_threshold: 1.0 - -# Configure server-to-server internode encryption -# -# JVM and netty defaults for supported SSL socket protocols and cipher suites can -# be replaced using custom encryption options. This is not recommended -# unless you have policies in place that dictate certain settings, or -# need to disable vulnerable ciphers or protocols in case the JVM cannot -# be updated. -# -# FIPS compliant settings can be configured at JVM level and should not -# involve changing encryption settings here: -# https://docs.oracle.com/javase/8/docs/technotes/guides/security/jsse/FIPS.html -# -# **NOTE** this default configuration is an insecure configuration. If you need to -# enable server-to-server encryption generate server keystores (and truststores for mutual -# authentication) per: -# http://download.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore -# Then perform the following configuration changes: -# -# Step 1: Set internode_encryption= and explicitly set optional=true. Restart all nodes -# -# Step 2: Set optional=false (or remove it) and if you generated truststores and want to use mutual -# auth set require_client_auth=true. Restart all nodes -server_encryption_options: - # On outbound connections, determine which type of peers to securely connect to. - # The available options are : - # none : Do not encrypt outgoing connections - # dc : Encrypt connections to peers in other datacenters but not within datacenters - # rack : Encrypt connections to peers in other racks but not within racks - # all : Always use encrypted connections - internode_encryption: none - # When set to true, encrypted and unencrypted connections are allowed on the storage_port - # This should _only be true_ while in unencrypted or transitional operation - # optional defaults to true if internode_encryption is none - # optional: true - # If enabled, will open up an encrypted listening socket on ssl_storage_port. Should only be used - # during upgrade to 4.0; otherwise, set to false. - enable_legacy_ssl_storage_port: false - # Set to a valid keystore if internode_encryption is dc, rack or all - keystore: conf/.keystore - keystore_password: cassandra - # Verify peer server certificates - require_client_auth: false - # Set to a valid trustore if require_client_auth is true - truststore: conf/.truststore - truststore_password: cassandra - # Verify that the host name in the certificate matches the connected host - require_endpoint_verification: false - # More advanced defaults: - # protocol: TLS - # store_type: JKS - # cipher_suites: [ - # TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - # TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - # TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, - # TLS_RSA_WITH_AES_256_CBC_SHA - # ] - -# Configure client-to-server encryption. -# -# **NOTE** this default configuration is an insecure configuration. If you need to -# enable client-to-server encryption generate server keystores (and truststores for mutual -# authentication) per: -# http://download.oracle.com/javase/8/docs/technotes/guides/security/jsse/JSSERefGuide.html#CreateKeystore -# Then perform the following configuration changes: -# -# Step 1: Set enabled=true and explicitly set optional=true. Restart all nodes -# -# Step 2: Set optional=false (or remove it) and if you generated truststores and want to use mutual -# auth set require_client_auth=true. Restart all nodes -client_encryption_options: - # Enable client-to-server encryption - enabled: false - # When set to true, encrypted and unencrypted connections are allowed on the native_transport_port - # This should _only be true_ while in unencrypted or transitional operation - # optional defaults to true when enabled is false, and false when enabled is true. - # optional: true - # Set keystore and keystore_password to valid keystores if enabled is true - keystore: conf/.keystore - keystore_password: cassandra - # Verify client certificates - require_client_auth: false - # Set trustore and truststore_password if require_client_auth is true - # truststore: conf/.truststore - # truststore_password: cassandra - # More advanced defaults: - # protocol: TLS - # store_type: JKS - # cipher_suites: [ - # TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384, TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256, - # TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256, TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA, - # TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA, TLS_RSA_WITH_AES_128_GCM_SHA256, TLS_RSA_WITH_AES_128_CBC_SHA, - # TLS_RSA_WITH_AES_256_CBC_SHA - # ] - -# internode_compression controls whether traffic between nodes is -# compressed. -# Can be: -# -# all -# all traffic is compressed -# -# dc -# traffic between different datacenters is compressed -# -# none -# nothing is compressed. -internode_compression: dc - -# Enable or disable tcp_nodelay for inter-dc communication. -# Disabling it will result in larger (but fewer) network packets being sent, -# reducing overhead from the TCP protocol itself, at the cost of increasing -# latency if you block for cross-datacenter responses. -inter_dc_tcp_nodelay: false - -# TTL for different trace types used during logging of the repair process. -tracetype_query_ttl: 86400 -tracetype_repair_ttl: 604800 - -# If unset, all GC Pauses greater than gc_log_threshold_in_ms will log at -# INFO level -# UDFs (user defined functions) are disabled by default. -# As of Cassandra 3.0 there is a sandbox in place that should prevent execution of evil code. -enable_user_defined_functions: false - -# Enables scripted UDFs (JavaScript UDFs). -# Java UDFs are always enabled, if enable_user_defined_functions is true. -# Enable this option to be able to use UDFs with "language javascript" or any custom JSR-223 provider. -# This option has no effect, if enable_user_defined_functions is false. -enable_scripted_user_defined_functions: false - -# The default Windows kernel timer and scheduling resolution is 15.6ms for power conservation. -# Lowering this value on Windows can provide much tighter latency and better throughput, however -# some virtualized environments may see a negative performance impact from changing this setting -# below their system default. The sysinternals 'clockres' tool can confirm your system's default -# setting. -windows_timer_interval: 1 - - -# Enables encrypting data at-rest (on disk). Different key providers can be plugged in, but the default reads from -# a JCE-style keystore. A single keystore can hold multiple keys, but the one referenced by -# the "key_alias" is the only key that will be used for encrypt opertaions; previously used keys -# can still (and should!) be in the keystore and will be used on decrypt operations -# (to handle the case of key rotation). -# -# It is strongly recommended to download and install Java Cryptography Extension (JCE) -# Unlimited Strength Jurisdiction Policy Files for your version of the JDK. -# (current link: http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html) -# -# Currently, only the following file types are supported for transparent data encryption, although -# more are coming in future cassandra releases: commitlog, hints -transparent_data_encryption_options: - enabled: false - chunk_length_kb: 64 - cipher: AES/CBC/PKCS5Padding - key_alias: testing:1 - # CBC IV length for AES needs to be 16 bytes (which is also the default size) - # iv_length: 16 - key_provider: - - class_name: org.apache.cassandra.security.JKSKeyProvider - parameters: - - keystore: conf/.keystore - keystore_password: cassandra - store_type: JCEKS - key_password: cassandra - - -##################### -# SAFETY THRESHOLDS # -##################### - -# When executing a scan, within or across a partition, we need to keep the -# tombstones seen in memory so we can return them to the coordinator, which -# will use them to make sure other replicas also know about the deleted rows. -# With workloads that generate a lot of tombstones, this can cause performance -# problems and even exaust the server heap. -# (http://www.datastax.com/dev/blog/cassandra-anti-patterns-queues-and-queue-like-datasets) -# Adjust the thresholds here if you understand the dangers and want to -# scan more tombstones anyway. These thresholds may also be adjusted at runtime -# using the StorageService mbean. -tombstone_warn_threshold: 1000 -tombstone_failure_threshold: 100000 - -# Filtering and secondary index queries at read consistency levels above ONE/LOCAL_ONE use a -# mechanism called replica filtering protection to ensure that results from stale replicas do -# not violate consistency. (See CASSANDRA-8272 and CASSANDRA-15907 for more details.) This -# mechanism materializes replica results by partition on-heap at the coordinator. The more possibly -# stale results returned by the replicas, the more rows materialized during the query. -replica_filtering_protection: - # These thresholds exist to limit the damage severely out-of-date replicas can cause during these - # queries. They limit the number of rows from all replicas individual index and filtering queries - # can materialize on-heap to return correct results at the desired read consistency level. - # - # "cached_replica_rows_warn_threshold" is the per-query threshold at which a warning will be logged. - # "cached_replica_rows_fail_threshold" is the per-query threshold at which the query will fail. - # - # These thresholds may also be adjusted at runtime using the StorageService mbean. - # - # If the failure threshold is breached, it is likely that either the current page/fetch size - # is too large or one or more replicas is severely out-of-sync and in need of repair. - cached_rows_warn_threshold: 2000 - cached_rows_fail_threshold: 32000 - -# Log WARN on any multiple-partition batch size exceeding this value. 5kb per batch by default. -# Caution should be taken on increasing the size of this threshold as it can lead to node instability. -batch_size_warn_threshold_in_kb: 5 - -# Fail any multiple-partition batch exceeding this value. 50kb (10x warn threshold) by default. -batch_size_fail_threshold_in_kb: 50 - -# Log WARN on any batches not of type LOGGED than span across more partitions than this limit -unlogged_batch_across_partitions_warn_threshold: 10 - -# Log a warning when compacting partitions larger than this value -compaction_large_partition_warning_threshold_mb: 100 - -# GC Pauses greater than 200 ms will be logged at INFO level -# This threshold can be adjusted to minimize logging if necessary -# gc_log_threshold_in_ms: 200 - -# GC Pauses greater than gc_warn_threshold_in_ms will be logged at WARN level -# Adjust the threshold based on your application throughput requirement. Setting to 0 -# will deactivate the feature. -# gc_warn_threshold_in_ms: 1000 - -# Maximum size of any value in SSTables. Safety measure to detect SSTable corruption -# early. Any value size larger than this threshold will result into marking an SSTable -# as corrupted. This should be positive and less than 2048. -# max_value_size_in_mb: 256 - -# Track a metric per keyspace indicating whether replication achieved the ideal consistency -# level for writes without timing out. This is different from the consistency level requested by -# each write which may be lower in order to facilitate availability. -# ideal_consistency_level: EACH_QUORUM - -# Automatically upgrade sstables after upgrade - if there is no ordinary compaction to do, the -# oldest non-upgraded sstable will get upgraded to the latest version -# automatic_sstable_upgrade: false -# Limit the number of concurrent sstable upgrades -# max_concurrent_automatic_sstable_upgrades: 1 - -# Audit logging - Logs every incoming CQL command request, authentication to a node. See the docs -# on audit_logging for full details about the various configuration options. -audit_logging_options: - enabled: false - logger: - - class_name: BinAuditLogger - # audit_logs_dir: - # included_keyspaces: - # excluded_keyspaces: system, system_schema, system_virtual_schema - # included_categories: - # excluded_categories: - # included_users: - # excluded_users: - # roll_cycle: HOURLY - # block: true - # max_queue_weight: 268435456 # 256 MiB - # max_log_size: 17179869184 # 16 GiB - ## archive command is "/path/to/script.sh %path" where %path is replaced with the file being rolled: - # archive_command: - # max_archive_retries: 10 - - - # default options for full query logging - these can be overridden from command line when executing - # nodetool enablefullquerylog - #full_query_logging_options: - # log_dir: - # roll_cycle: HOURLY - # block: true - # max_queue_weight: 268435456 # 256 MiB - # max_log_size: 17179869184 # 16 GiB - ## archive command is "/path/to/script.sh %path" where %path is replaced with the file being rolled: - # archive_command: - # max_archive_retries: 10 - -# validate tombstones on reads and compaction -# can be either "disabled", "warn" or "exception" -# corrupted_tombstone_strategy: disabled - -# Diagnostic Events # -# If enabled, diagnostic events can be helpful for troubleshooting operational issues. Emitted events contain details -# on internal state and temporal relationships across events, accessible by clients via JMX. -diagnostic_events_enabled: false - -# Use native transport TCP message coalescing. If on upgrade to 4.0 you found your throughput decreasing, and in -# particular you run an old kernel or have very fewer client connections, this option might be worth evaluating. -#native_transport_flush_in_batches_legacy: false - -# Enable tracking of repaired state of data during reads and comparison between replicas -# Mismatches between the repaired sets of replicas can be characterized as either confirmed -# or unconfirmed. In this context, unconfirmed indicates that the presence of pending repair -# sessions, unrepaired partition tombstones, or some other condition means that the disparity -# cannot be considered conclusive. Confirmed mismatches should be a trigger for investigation -# as they may be indicative of corruption or data loss. -# There are separate flags for range vs partition reads as single partition reads are only tracked -# when CL > 1 and a digest mismatch occurs. Currently, range queries don't use digests so if -# enabled for range reads, all range reads will include repaired data tracking. As this adds -# some overhead, operators may wish to disable it whilst still enabling it for partition reads -repaired_data_tracking_for_range_reads_enabled: false -repaired_data_tracking_for_partition_reads_enabled: false -# If false, only confirmed mismatches will be reported. If true, a separate metric for unconfirmed -# mismatches will also be recorded. This is to avoid potential signal:noise issues are unconfirmed -# mismatches are less actionable than confirmed ones. -report_unconfirmed_repaired_data_mismatches: false - -# Having many tables and/or keyspaces negatively affects performance of many operations in the -# cluster. When the number of tables/keyspaces in the cluster exceeds the following thresholds -# a client warning will be sent back to the user when creating a table or keyspace. -# table_count_warn_threshold: 150 -# keyspace_count_warn_threshold: 40 - -######################### -# EXPERIMENTAL FEATURES # -######################### - -# Enables materialized view creation on this node. -# Materialized views are considered experimental and are not recommended for production use. -enable_materialized_views: false - -# Enables SASI index creation on this node. -# SASI indexes are considered experimental and are not recommended for production use. -enable_sasi_indexes: true - -# Enables creation of transiently replicated keyspaces on this node. -# Transient replication is experimental and is not recommended for production use. -enable_transient_replication: false - -# Enables the used of 'ALTER ... DROP COMPACT STORAGE' statements on this node. -# 'ALTER ... DROP COMPACT STORAGE' is considered experimental and is not recommended for production use. -enable_drop_compact_storage: false \ No newline at end of file diff --git a/services/jobs/pom.xml b/services/jobs/pom.xml index 53281e1e..d6d51d0f 100644 --- a/services/jobs/pom.xml +++ b/services/jobs/pom.xml @@ -48,19 +48,26 @@ shared ${project.version} - - javassist - javassist - 3.12.1.GA - org.springframework.boot spring-boot-starter-test + + + org.springframework.boot + spring-boot-starter-logging + + test org.springframework.boot spring-boot-testcontainers + + + org.springframework.boot + spring-boot-starter-logging + + test @@ -94,6 +101,12 @@ org.springframework.boot spring-boot-starter-data-cassandra + + + org.springframework.boot + spring-boot-starter-logging + + org.projectlombok diff --git a/services/jobs/src/main/java/com/workup/jobs/ControllerMQListener.java b/services/jobs/src/main/java/com/workup/jobs/ControllerMQListener.java index 1a63839a..8ef28ae6 100644 --- a/services/jobs/src/main/java/com/workup/jobs/ControllerMQListener.java +++ b/services/jobs/src/main/java/com/workup/jobs/ControllerMQListener.java @@ -1,38 +1,123 @@ package com.workup.jobs; +import com.workup.jobs.commands.JobCommand; import com.workup.jobs.commands.JobCommandMap; -import com.workup.shared.commands.controller.SetMaxThreadsRequest; +import com.workup.shared.commands.Command; +import com.workup.shared.commands.CommandRequest; +import com.workup.shared.commands.CommandResponse; +import com.workup.shared.commands.controller.*; +import com.workup.shared.enums.ServiceQueueNames; +import com.workup.shared.enums.ThreadPoolSize; import java.lang.reflect.Field; -import javassist.*; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.Configurator; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationContext; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import org.springframework.stereotype.Service; @Service -@RabbitListener(queues = "#{controllerQueue.name}") +@RabbitListener(queues = "#{controllerQueue.name}", id = "#{controllerQueue.name}") public class ControllerMQListener { - @Autowired public JobCommandMap commandMap; @Autowired public ThreadPoolTaskExecutor taskExecutor; - @Autowired private ApplicationContext context; + @Autowired private RabbitListenerEndpointRegistry registry; @RabbitHandler public void receive(SetMaxThreadsRequest in) throws Exception { try { - ThreadPoolTaskExecutor myBean = context.getBean(ThreadPoolTaskExecutor.class); - Field maxPoolSize = ThreadPoolTaskExecutor.class.getDeclaredField("maxPoolSize"); - maxPoolSize.setAccessible(true); - maxPoolSize.set(myBean, in.getMaxThreads()); - Field corePoolSize = ThreadPoolTaskExecutor.class.getDeclaredField("corePoolSize"); - corePoolSize.setAccessible(true); - corePoolSize.set(myBean, in.getMaxThreads()); + System.out.println("Max threads is: " + taskExecutor.getMaxPoolSize()); + setThreads(in.getMaxThreads()); + ThreadPoolSize.POOL_SIZE = taskExecutor.getMaxPoolSize(); + System.out.println("Max threads set to: " + taskExecutor.getMaxPoolSize()); + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } + + @RabbitHandler + public void receive(SetLoggingLevelRequest in) throws Exception { + try { + Logger logger = LogManager.getRootLogger(); + Configurator.setAllLevels(logger.getName(), Level.valueOf(in.getLevel())); + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } + + @RabbitHandler + public void receive(FreezeRequest in) throws Exception { + try { + registry.getListenerContainer(ServiceQueueNames.JOBS).stop(); + taskExecutor.shutdown(); + setThreads(0); + System.out.println("Stopped all threads."); } catch (Exception e) { System.out.println(e.getMessage()); e.printStackTrace(); } } + + @RabbitHandler + public void receive(ContinueRequest in) throws Exception { + try { + taskExecutor.start(); + setThreads(ThreadPoolSize.POOL_SIZE); + registry.getListenerContainer(ServiceQueueNames.JOBS).start(); + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } + + @SuppressWarnings("unchecked") + @RabbitHandler + public void receive(UpdateCommandRequest in) throws Exception { + try { + String className = commandMap.getCommand(in.getCommandName()).getClass().getName(); + byte[] byteArray = in.getByteCode(); + Class clazz = + (Class) + (new MyClassLoader(this.getClass().getClassLoader()).loadClass(byteArray, className)); + + commandMap.replaceCommand( + in.getCommandName(), + (Class>) + ((Command) clazz.newInstance()).getClass()); + + System.out.println("Updated command: " + in.getCommandName()); + // clazz.newInstance().Run(null); + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } + + static class MyClassLoader extends ClassLoader { + public MyClassLoader(ClassLoader classLoader) { + super(classLoader); + } + + public Class loadClass(byte[] byteCode, String className) { + return defineClass(className, byteCode, 0, byteCode.length); + } + } + + private void setThreads(int threads) throws NoSuchFieldException, IllegalAccessException { + ThreadPoolTaskExecutor myBean = context.getBean(ThreadPoolTaskExecutor.class); + Field maxPoolSize = ThreadPoolTaskExecutor.class.getDeclaredField("maxPoolSize"); + maxPoolSize.setAccessible(true); + maxPoolSize.set(myBean, threads); + Field corePoolSize = ThreadPoolTaskExecutor.class.getDeclaredField("corePoolSize"); + corePoolSize.setAccessible(true); + corePoolSize.set(myBean, threads); + } } diff --git a/services/jobs/src/main/java/com/workup/jobs/JobsApplication.java b/services/jobs/src/main/java/com/workup/jobs/JobsApplication.java index 01eb15b9..4ae506ef 100644 --- a/services/jobs/src/main/java/com/workup/jobs/JobsApplication.java +++ b/services/jobs/src/main/java/com/workup/jobs/JobsApplication.java @@ -2,6 +2,7 @@ import com.workup.shared.enums.ControllerQueueNames; import com.workup.shared.enums.ServiceQueueNames; +import com.workup.shared.enums.ThreadPoolSize; import org.springframework.amqp.core.*; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; @@ -56,8 +57,9 @@ public MessageConverter messageConverter() { @Bean public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(50); - executor.setMaxPoolSize(50); + executor.setCorePoolSize(ThreadPoolSize.POOL_SIZE); + executor.setMaxPoolSize(ThreadPoolSize.POOL_SIZE); + executor.setWaitForTasksToCompleteOnShutdown(true); executor.setQueueCapacity(500); executor.setThreadNamePrefix("jobs-"); executor.initialize(); diff --git a/services/jobs/src/main/java/com/workup/jobs/RabbitMQListener.java b/services/jobs/src/main/java/com/workup/jobs/RabbitMQListener.java index 847f20c3..76e5a1b7 100644 --- a/services/jobs/src/main/java/com/workup/jobs/RabbitMQListener.java +++ b/services/jobs/src/main/java/com/workup/jobs/RabbitMQListener.java @@ -1,7 +1,6 @@ package com.workup.jobs; import com.workup.jobs.commands.AcceptProposalCommand; -import com.workup.jobs.commands.CreateJobCommand; import com.workup.jobs.commands.CreateProposalCommand; import com.workup.jobs.commands.GetJobByIdCommand; import com.workup.jobs.commands.GetMyJobsCommand; @@ -9,6 +8,8 @@ import com.workup.jobs.commands.GetProposalsByJobIdCommand; import com.workup.jobs.commands.JobCommandMap; import com.workup.jobs.commands.SearchJobsCommand; +import com.workup.shared.commands.Command; +import com.workup.shared.commands.CommandRequest; import com.workup.shared.commands.jobs.proposals.requests.AcceptProposalRequest; import com.workup.shared.commands.jobs.proposals.requests.CreateProposalRequest; import com.workup.shared.commands.jobs.proposals.requests.GetMyProposalsRequest; @@ -34,7 +35,7 @@ import org.springframework.stereotype.Service; @Service -@RabbitListener(queues = ServiceQueueNames.JOBS) +@RabbitListener(queues = ServiceQueueNames.JOBS, id = ServiceQueueNames.JOBS) public class RabbitMQListener { @Autowired public JobCommandMap commandMap; @@ -42,8 +43,10 @@ public class RabbitMQListener { @RabbitHandler @Async public CompletableFuture receive(CreateJobRequest in) throws Exception { - CreateJobResponse response = ((CreateJobCommand) commandMap.getCommand("CreateJob")).Run(in); - return CompletableFuture.completedFuture(response); + CreateJobResponse resp = + (CreateJobResponse) + ((Command) commandMap.getCommand("CreateJob")).Run(in); + return CompletableFuture.completedFuture(resp); } @RabbitHandler diff --git a/services/jobs/src/main/java/com/workup/jobs/commands/JobCommand.java b/services/jobs/src/main/java/com/workup/jobs/commands/JobCommand.java index de96129e..1b46a9f7 100644 --- a/services/jobs/src/main/java/com/workup/jobs/commands/JobCommand.java +++ b/services/jobs/src/main/java/com/workup/jobs/commands/JobCommand.java @@ -11,9 +11,9 @@ public abstract class JobCommand implements Command { - @Setter JobRepository jobRepository; + @Setter public JobRepository jobRepository; - @Setter ProposalRepository proposalRepository; + @Setter public ProposalRepository proposalRepository; - @Setter AmqpTemplate rabbitTemplate; + @Setter public AmqpTemplate rabbitTemplate; } diff --git a/services/jobs/src/main/java/com/workup/jobs/commands/JobCommandMap.java b/services/jobs/src/main/java/com/workup/jobs/commands/JobCommandMap.java index 8bc9456a..3de55692 100644 --- a/services/jobs/src/main/java/com/workup/jobs/commands/JobCommandMap.java +++ b/services/jobs/src/main/java/com/workup/jobs/commands/JobCommandMap.java @@ -13,11 +13,11 @@ public class JobCommandMap extends CommandMap> { - @Autowired JobRepository jobRepository; + @Autowired public JobRepository jobRepository; - @Autowired ProposalRepository proposalRepository; + @Autowired public ProposalRepository proposalRepository; - @Autowired AmqpTemplate rabbitTemplate; + @Autowired public AmqpTemplate rabbitTemplate; public void registerCommands() { commands.put("CreateJob", CreateJobCommand.class); diff --git a/services/jobs/src/main/resources/application.properties b/services/jobs/src/main/resources/application.properties index 61221848..ce5d1578 100644 --- a/services/jobs/src/main/resources/application.properties +++ b/services/jobs/src/main/resources/application.properties @@ -5,4 +5,4 @@ spring.rabbitmq.password=guest spring.cassandra.local-datacenter=datacenter1 spring.cassandra.keyspace-name=jobs_data spring.cassandra.contact-points=${JOBS_DB_URL} -spring.cassandra.schema-action=CREATE_IF_NOT_EXISTS \ No newline at end of file +spring.cassandra.schema-action=CREATE_IF_NOT_EXISTS diff --git a/services/payments/src/main/java/com/workup/payments/ControllerMQListener.java b/services/payments/src/main/java/com/workup/payments/ControllerMQListener.java index 706bf74d..17526e5f 100644 --- a/services/payments/src/main/java/com/workup/payments/ControllerMQListener.java +++ b/services/payments/src/main/java/com/workup/payments/ControllerMQListener.java @@ -1,38 +1,125 @@ -package com.workup.payments; - -import com.workup.shared.commands.controller.SetMaxThreadsRequest; -import java.lang.reflect.Field; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.springframework.amqp.rabbit.annotation.RabbitHandler; -import org.springframework.amqp.rabbit.annotation.RabbitListener; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.ApplicationContext; -import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; -import org.springframework.stereotype.Service; - -@Service -@RabbitListener(queues = "#{controllerQueue.name}") -public class ControllerMQListener { - - private static final Logger logger = LogManager.getLogger(ControllerMQListener.class); - - @Autowired public ThreadPoolTaskExecutor taskExecutor; - - @Autowired private ApplicationContext context; - - @RabbitHandler - public void receive(SetMaxThreadsRequest in) throws Exception { - try { - ThreadPoolTaskExecutor myBean = context.getBean(ThreadPoolTaskExecutor.class); - Field maxPoolSize = ThreadPoolTaskExecutor.class.getDeclaredField("maxPoolSize"); - maxPoolSize.setAccessible(true); - maxPoolSize.set(myBean, in.getMaxThreads()); - Field corePoolSize = ThreadPoolTaskExecutor.class.getDeclaredField("corePoolSize"); - corePoolSize.setAccessible(true); - corePoolSize.set(myBean, in.getMaxThreads()); - } catch (Exception e) { - logger.error("Error setting max threads", e.getMessage()); - } - } -} +package com.workup.payments; + +import com.workup.payments.commands.PaymentCommandMap; +import com.workup.shared.commands.controller.*; +import com.workup.shared.enums.ServiceQueueNames; +import com.workup.shared.enums.ThreadPoolSize; +import com.zaxxer.hikari.HikariDataSource; +import java.lang.reflect.Field; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.core.config.Configurator; +import org.springframework.amqp.rabbit.annotation.RabbitHandler; +import org.springframework.amqp.rabbit.annotation.RabbitListener; +import org.springframework.amqp.rabbit.listener.RabbitListenerEndpointRegistry; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.ApplicationContext; +import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; +import org.springframework.stereotype.Service; + +@Service +@RabbitListener(queues = "#{controllerQueue.name}", id = "#{controllerQueue.name}") +public class ControllerMQListener { + @Autowired public PaymentCommandMap commandMap; + @Autowired public ThreadPoolTaskExecutor taskExecutor; + @Autowired private ApplicationContext context; + @Autowired private RabbitListenerEndpointRegistry registry; + @Autowired private HikariDataSource hikariDataSource; + + @RabbitHandler + public void receive(SetMaxThreadsRequest in) throws Exception { + try { + System.out.println("Max threads is: " + taskExecutor.getMaxPoolSize()); + setThreads(in.getMaxThreads()); + ThreadPoolSize.POOL_SIZE = taskExecutor.getMaxPoolSize(); + System.out.println("Max threads set to: " + taskExecutor.getMaxPoolSize()); + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } + + @RabbitHandler + public void receive(SetLoggingLevelRequest in) throws Exception { + try { + Logger logger = LogManager.getRootLogger(); + Configurator.setAllLevels(logger.getName(), Level.valueOf(in.getLevel())); + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } + + @RabbitHandler + public void receive(FreezeRequest in) throws Exception { + try { + registry.getListenerContainer(ServiceQueueNames.JOBS).stop(); + taskExecutor.shutdown(); + setThreads(0); + System.out.println("Stopped all threads."); + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } + + @RabbitHandler + public void receive(ContinueRequest in) throws Exception { + try { + taskExecutor.start(); + setThreads(ThreadPoolSize.POOL_SIZE); + registry.getListenerContainer(ServiceQueueNames.JOBS).start(); + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } + + @RabbitHandler + public void receive(UpdateCommandRequest in) throws Exception { + // try { + // String className = commandMap.getCommand(in.getName()).getClass().getName(); + // System.out.println("Updating command: " + in.getName()); + // System.out.println("Class: " + className); + // Class newClass = new MyClassLoader().loadClass(in.getByteCode(), className); + // commandMap.replaceCommand(in.getName(), newClass); + // } catch (Exception e) { + // System.out.println(e.getMessage()); + // e.printStackTrace(); + // } + } + + @RabbitHandler + private void SetMaxDBConnections(SetMaxDBConnectionsRequest in) { + try { + if (hikariDataSource == null) { + System.out.println("HikariDataSource is null"); + return; + } + System.out.println("Max DB connections is: " + hikariDataSource.getMaximumPoolSize()); + hikariDataSource.setMaximumPoolSize(in.getMaxDBConnections()); + System.out.println("Max DB connections set to: " + hikariDataSource.getMaximumPoolSize()); + } catch (Exception e) { + System.out.println(e.getMessage()); + e.printStackTrace(); + } + } + + private void setThreads(int threads) throws NoSuchFieldException, IllegalAccessException { + ThreadPoolTaskExecutor myBean = context.getBean(ThreadPoolTaskExecutor.class); + Field maxPoolSize = ThreadPoolTaskExecutor.class.getDeclaredField("maxPoolSize"); + maxPoolSize.setAccessible(true); + maxPoolSize.set(myBean, threads); + Field corePoolSize = ThreadPoolTaskExecutor.class.getDeclaredField("corePoolSize"); + corePoolSize.setAccessible(true); + corePoolSize.set(myBean, threads); + } +} + +class MyClassLoader extends ClassLoader { + public Class loadClass(byte[] byteCode, String className) { + System.out.println("Loading class: " + className); + return defineClass(className, byteCode, 0, byteCode.length); + } +} diff --git a/services/payments/src/main/java/com/workup/payments/PaymentsApplication.java b/services/payments/src/main/java/com/workup/payments/PaymentsApplication.java index fee10a68..dfd16f9b 100644 --- a/services/payments/src/main/java/com/workup/payments/PaymentsApplication.java +++ b/services/payments/src/main/java/com/workup/payments/PaymentsApplication.java @@ -2,6 +2,7 @@ import com.workup.shared.enums.ControllerQueueNames; import com.workup.shared.enums.ServiceQueueNames; +import com.workup.shared.enums.ThreadPoolSize; import org.springframework.amqp.core.AnonymousQueue; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; @@ -32,11 +33,6 @@ public Queue myQueue() { return new Queue(ServiceQueueNames.PAYMENTS); } - @Bean - public MessageConverter messageConverter() { - return new Jackson2JsonMessageConverter(); - } - @Bean public Queue controllerQueue() { return new AnonymousQueue(); @@ -52,11 +48,17 @@ public Binding fanoutBinding(FanoutExchange fanout, Queue controllerQueue) { return BindingBuilder.bind(controllerQueue).to(fanout); } + @Bean + public MessageConverter messageConverter() { + return new Jackson2JsonMessageConverter(); + } + @Bean public ThreadPoolTaskExecutor taskExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); - executor.setCorePoolSize(50); - executor.setMaxPoolSize(50); + executor.setCorePoolSize(ThreadPoolSize.POOL_SIZE); + executor.setMaxPoolSize(ThreadPoolSize.POOL_SIZE); + executor.setWaitForTasksToCompleteOnShutdown(true); executor.setQueueCapacity(500); executor.setThreadNamePrefix("payments-"); executor.initialize(); diff --git a/services/users/.env b/services/users/.env index 64db5d17..9b4de138 100644 --- a/services/users/.env +++ b/services/users/.env @@ -5,4 +5,6 @@ USERS_SECRET_KEY=j4#BbFGfoc^2k*Bz ADMIN_EMAIL=admin@workup.com ADMIN_PASSWORD=admin -ADMIN_ID=admin \ No newline at end of file +ADMIN_ID=admin + +REDIS_URL=service_redis \ No newline at end of file diff --git a/services/users/pom.xml b/services/users/pom.xml index 281a218b..fade9f78 100644 --- a/services/users/pom.xml +++ b/services/users/pom.xml @@ -18,9 +18,21 @@ 21 - + org.springframework.boot - spring-boot-starter + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-logging + + + + + + com.lmax + disruptor + 3.4.2 @@ -55,6 +67,17 @@ 3.1.2 + + org.springframework.boot + spring-boot-starter-amqp + + + org.springframework.boot + spring-boot-starter-logging + + + + org.junit.jupiter junit-jupiter-params @@ -124,6 +147,11 @@ 0.11.2 runtime + + + org.springframework.boot + spring-boot-starter-log4j2 + diff --git a/services/users/src/main/java/com/workup/users/RedisConfig.java b/services/users/src/main/java/com/workup/users/RedisConfig.java new file mode 100644 index 00000000..2d1939f7 --- /dev/null +++ b/services/users/src/main/java/com/workup/users/RedisConfig.java @@ -0,0 +1,8 @@ +package com.workup.users; + +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; + +@Configuration +@Import(com.workup.shared.redis.RedisConfig.class) +public class RedisConfig {} diff --git a/services/users/src/main/java/com/workup/users/UsersApplication.java b/services/users/src/main/java/com/workup/users/UsersApplication.java index c9cfee2e..b88fac3a 100644 --- a/services/users/src/main/java/com/workup/users/UsersApplication.java +++ b/services/users/src/main/java/com/workup/users/UsersApplication.java @@ -11,12 +11,14 @@ import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cache.annotation.EnableCaching; import org.springframework.context.annotation.Bean; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; @SpringBootApplication @EnableAsync +@EnableCaching // @EnableMongoRepositories(basePackageClasses = ClientRepository.class) public class UsersApplication { diff --git a/services/users/src/main/java/com/workup/users/commands/AddFreelancerAchievementCommand.java b/services/users/src/main/java/com/workup/users/commands/AddFreelancerAchievementCommand.java index 5f8bfd16..7166e427 100644 --- a/services/users/src/main/java/com/workup/users/commands/AddFreelancerAchievementCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/AddFreelancerAchievementCommand.java @@ -6,17 +6,24 @@ import com.workup.users.db.Achievement; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class AddFreelancerAchievementCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(AddFreelancerAchievementCommand.class); + @Override public AddFreelancerAchievementResponse Run(AddFreelancerAchievementRequest request) { + logger.info("Add Freelancer Achievement - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return AddFreelancerAchievementResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); Achievement newAchievement = Achievement.builder() diff --git a/services/users/src/main/java/com/workup/users/commands/AddFreelancerEducationCommand.java b/services/users/src/main/java/com/workup/users/commands/AddFreelancerEducationCommand.java index 93263bfa..b0f34aec 100644 --- a/services/users/src/main/java/com/workup/users/commands/AddFreelancerEducationCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/AddFreelancerEducationCommand.java @@ -6,18 +6,24 @@ import com.workup.users.db.Education; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class AddFreelancerEducationCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(AddFreelancerEducationCommand.class); @Override public AddFreelancerEducationResponse Run(AddFreelancerEducationRequest request) { + logger.info("Add Freelancer Education - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return AddFreelancerEducationResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); Education newEducation = Education.builder() diff --git a/services/users/src/main/java/com/workup/users/commands/AddFreelancerExperienceCommand.java b/services/users/src/main/java/com/workup/users/commands/AddFreelancerExperienceCommand.java index 5e6a81ed..e1c09ecb 100644 --- a/services/users/src/main/java/com/workup/users/commands/AddFreelancerExperienceCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/AddFreelancerExperienceCommand.java @@ -6,17 +6,24 @@ import com.workup.users.db.Experience; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class AddFreelancerExperienceCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(AddFreelancerExperienceCommand.class); + @Override public AddFreelancerExperienceResponse Run(AddFreelancerExperienceRequest request) { + logger.info("Add Freelancer Experience - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return AddFreelancerExperienceResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); Experience newExperience = Experience.builder() diff --git a/services/users/src/main/java/com/workup/users/commands/AddFreelancerLanguageCommand.java b/services/users/src/main/java/com/workup/users/commands/AddFreelancerLanguageCommand.java index 09322f18..a847be78 100644 --- a/services/users/src/main/java/com/workup/users/commands/AddFreelancerLanguageCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/AddFreelancerLanguageCommand.java @@ -6,18 +6,24 @@ import com.workup.users.db.Freelancer; import java.util.ArrayList; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class AddFreelancerLanguageCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(AddFreelancerLanguageCommand.class); @Override public AddFreelancerLanguageResponse Run(AddFreelancerLanguageRequest request) { + logger.info("Add Freelancer Language - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return AddFreelancerLanguageResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); if (freelancer.getLanguages() == null) freelancer.setLanguages(new ArrayList<>()); freelancer.getLanguages().add(request.getNewLanguage()); diff --git a/services/users/src/main/java/com/workup/users/commands/AddFreelancerSkillCommand.java b/services/users/src/main/java/com/workup/users/commands/AddFreelancerSkillCommand.java index d30ca841..896bc312 100644 --- a/services/users/src/main/java/com/workup/users/commands/AddFreelancerSkillCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/AddFreelancerSkillCommand.java @@ -7,18 +7,24 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class AddFreelancerSkillCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(AddFreelancerSkillCommand.class); @Override public AddFreelancerSkillResponse Run(AddFreelancerSkillRequest request) { + logger.info("Add Freelancer Skill - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return AddFreelancerSkillResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } String newSkill = request.getNewSkill(); Freelancer freelancer = freelancerOptional.get(); if (freelancer.getSkills() == null) freelancer.setSkills(new ArrayList<>()); diff --git a/services/users/src/main/java/com/workup/users/commands/ClientGetPhotoCommand.java b/services/users/src/main/java/com/workup/users/commands/ClientGetPhotoCommand.java index 43c1fd7b..eb95d91a 100644 --- a/services/users/src/main/java/com/workup/users/commands/ClientGetPhotoCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/ClientGetPhotoCommand.java @@ -5,15 +5,20 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Client; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class ClientGetPhotoCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(ClientGetPhotoCommand.class); @Override public ClientGetPhotoResponse Run(ClientGetPhotoRequest request) { + logger.info("[i] Getting Photo for Client with id: " + request.getUserId()); Optional clientOptional = clientRepository.findById(request.getUserId()); if (!clientOptional.isPresent()) { + logger.error("[x] Client Doesn't Exist"); return ClientGetPhotoResponse.builder() .withStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR) .build(); diff --git a/services/users/src/main/java/com/workup/users/commands/ClientGetProfileCommand.java b/services/users/src/main/java/com/workup/users/commands/ClientGetProfileCommand.java index 882e2017..c862e5d3 100644 --- a/services/users/src/main/java/com/workup/users/commands/ClientGetProfileCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/ClientGetProfileCommand.java @@ -5,15 +5,20 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Client; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class ClientGetProfileCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(ClientGetProfileCommand.class); @Override public ClientGetProfileResponse Run(ClientGetProfileRequest request) { + logger.info("[i] Getting Profile for Client with id: " + request.getUserId()); Optional clientOptional = clientRepository.findById(request.getUserId()); if (!clientOptional.isPresent()) { + logger.error("[x] Client Doesn't Exist"); return ClientGetProfileResponse.builder() .withStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR) .build(); diff --git a/services/users/src/main/java/com/workup/users/commands/ClientRegisterCommand.java b/services/users/src/main/java/com/workup/users/commands/ClientRegisterCommand.java index 016a2b1e..053941ad 100644 --- a/services/users/src/main/java/com/workup/users/commands/ClientRegisterCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/ClientRegisterCommand.java @@ -8,20 +8,35 @@ import com.workup.shared.enums.users.UserType; import com.workup.users.db.Client; import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class ClientRegisterCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(ClientRegisterCommand.class); @Override public SignUpAndInResponse Run(ClientRegisterRequest request) { + logger.info("[i] Registering Client with Email: " + request.getEmail()); if (Objects.isNull(request.getEmail()) || Objects.isNull(request.getPassword()) || Objects.isNull(request.getClientName())) { + logger.error("[x] Missing Required Fields"); return SignUpAndInResponse.builder() .withStatusCode(HttpStatusCode.BAD_REQUEST) .withSuccess(false) .build(); } try { + // check if registered already as freelancer + if (freelancerRepository.findByEmail(request.getEmail()).isPresent()) { + logger.error( + "[x] User with email" + request.getEmail() + " already registered as freelancer"); + return SignUpAndInResponse.builder() + .withStatusCode(HttpStatusCode.BAD_REQUEST) + .withSuccess(false) + .withErrorMessage("User already registered as freelancer") + .build(); + } Client client = Client.builder() .withEmail(request.getEmail()) @@ -42,6 +57,7 @@ public SignUpAndInResponse Run(ClientRegisterRequest request) { .withStatusCode(HttpStatusCode.OK) .build(); } catch (Exception e) { + logger.error("[x] Error Registering Client: " + e.getMessage()); return SignUpAndInResponse.builder() .withStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR) .withSuccess(false) diff --git a/services/users/src/main/java/com/workup/users/commands/ClientSetPhotoCommand.java b/services/users/src/main/java/com/workup/users/commands/ClientSetPhotoCommand.java index e1b71cd8..d9bd217b 100644 --- a/services/users/src/main/java/com/workup/users/commands/ClientSetPhotoCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/ClientSetPhotoCommand.java @@ -5,16 +5,20 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Client; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class ClientSetPhotoCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(ClientSetPhotoCommand.class); @Override public ClientSetPhotoResponse Run(ClientSetPhotoRequest request) { - + logger.info("[i] Setting Photo for Client with id: " + request.getUserId()); Optional clientOption = clientRepository.findById(request.getUserId()); if (!clientOption.isPresent()) { + logger.error("[x] Client Doesn't Exist"); throw new RuntimeException("User not found"); } diff --git a/services/users/src/main/java/com/workup/users/commands/ClientSetProfileCommand.java b/services/users/src/main/java/com/workup/users/commands/ClientSetProfileCommand.java index 3341bcc1..64b2f5a7 100644 --- a/services/users/src/main/java/com/workup/users/commands/ClientSetProfileCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/ClientSetProfileCommand.java @@ -5,13 +5,16 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Client; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class ClientSetProfileCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(ClientSetProfileCommand.class); @Override public ClientSetProfileResponse Run(ClientSetProfileRequest request) { - + logger.info("[i] Setting Profile for Client with id: " + request.getUserId()); Client client; if (request.getUserId() == null) { @@ -19,6 +22,7 @@ public ClientSetProfileResponse Run(ClientSetProfileRequest request) { } else { Optional clientOption = clientRepository.findById(request.getUserId()); if (!clientOption.isPresent()) { + logger.error("[x] Client Doesn't Exist"); throw new RuntimeException("User not found"); } client = clientOption.get(); diff --git a/services/users/src/main/java/com/workup/users/commands/FreelancerGetPhotoCommand.java b/services/users/src/main/java/com/workup/users/commands/FreelancerGetPhotoCommand.java index 99867f8a..8a6f4dd8 100644 --- a/services/users/src/main/java/com/workup/users/commands/FreelancerGetPhotoCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/FreelancerGetPhotoCommand.java @@ -5,15 +5,20 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class FreelancerGetPhotoCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(FreelancerGetPhotoCommand.class); @Override public FreelancerGetPhotoResponse Run(FreelancerGetPhotoRequest request) { + logger.info("[i] Getting Photo for Freelancer with id: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); if (!freelancerOptional.isPresent()) { + logger.error("[x] Freelancer Doesn't Exist"); return FreelancerGetPhotoResponse.builder() .withStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR) .build(); diff --git a/services/users/src/main/java/com/workup/users/commands/FreelancerGetProfileBriefCommand.java b/services/users/src/main/java/com/workup/users/commands/FreelancerGetProfileBriefCommand.java index fddec877..75837df7 100644 --- a/services/users/src/main/java/com/workup/users/commands/FreelancerGetProfileBriefCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/FreelancerGetProfileBriefCommand.java @@ -5,15 +5,20 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class FreelancerGetProfileBriefCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(FreelancerGetProfileBriefCommand.class); @Override public FreelancerGetProfileBriefResponse Run(FreelancerGetProfileBriefRequest request) { + logger.info("[i] Getting Profile Brief for Freelancer with id: " + request.getUserId()); Optional freelancer = freelancerRepository.findById(request.getUserId()); if (!freelancer.isPresent()) { + logger.error("[x] Freelancer Doesn't Exist"); return FreelancerGetProfileBriefResponse.builder() .withStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR) .build(); diff --git a/services/users/src/main/java/com/workup/users/commands/FreelancerGetProfileCommand.java b/services/users/src/main/java/com/workup/users/commands/FreelancerGetProfileCommand.java index 384fca09..1391956d 100644 --- a/services/users/src/main/java/com/workup/users/commands/FreelancerGetProfileCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/FreelancerGetProfileCommand.java @@ -5,15 +5,20 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class FreelancerGetProfileCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(FreelancerGetProfileCommand.class); @Override public FreelancerGetProfileResponse Run(FreelancerGetProfileRequest request) { + logger.info("[i] Getting Profile for Freelancer with id: " + request.getUserId()); Optional freelancer = freelancerRepository.findById(request.getUserId()); if (!freelancer.isPresent()) { + logger.error("[x] Freelancer Doesn't Exist"); return FreelancerGetProfileResponse.builder() .withStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR) .build(); diff --git a/services/users/src/main/java/com/workup/users/commands/FreelancerGetResumeCommand.java b/services/users/src/main/java/com/workup/users/commands/FreelancerGetResumeCommand.java index ef11b3ea..f2b286b8 100644 --- a/services/users/src/main/java/com/workup/users/commands/FreelancerGetResumeCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/FreelancerGetResumeCommand.java @@ -5,15 +5,20 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class FreelancerGetResumeCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(FreelancerGetResumeCommand.class); @Override public FreelancerGetResumeResponse Run(FreelancerGetResumeRequest request) { + logger.info("[i] Getting Resume for Freelancer with id: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); if (!freelancerOptional.isPresent()) { + logger.error("[x] Freelancer Doesn't Exist"); return FreelancerGetResumeResponse.builder() .withStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR) .build(); diff --git a/services/users/src/main/java/com/workup/users/commands/FreelancerRegisterCommand.java b/services/users/src/main/java/com/workup/users/commands/FreelancerRegisterCommand.java index ff0c0a39..2dee423c 100644 --- a/services/users/src/main/java/com/workup/users/commands/FreelancerRegisterCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/FreelancerRegisterCommand.java @@ -10,21 +10,35 @@ import com.workup.shared.enums.users.UserType; import com.workup.users.db.Freelancer; import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class FreelancerRegisterCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(FreelancerRegisterCommand.class); @Override public SignUpAndInResponse Run(FreelancerRegisterRequest request) { + logger.info("[i] Registering Freelancer with Email: " + request.getEmail()); if (Objects.isNull(request.getEmail()) || Objects.isNull(request.getPassword()) || Objects.isNull(request.getFullName())) { + logger.error("[x] Missing Required Fields"); return SignUpAndInResponse.builder() .withStatusCode(HttpStatusCode.BAD_REQUEST) .withSuccess(false) .build(); } try { + // check if registered already as client + if (clientRepository.findByEmail(request.getEmail()).isPresent()) { + logger.error("[x] User with email" + request.getEmail() + " already registered as client"); + return SignUpAndInResponse.builder() + .withStatusCode(HttpStatusCode.BAD_REQUEST) + .withSuccess(false) + .withErrorMessage("User already registered as client") + .build(); + } Freelancer freelancer = Freelancer.builder() .withEmail(request.getEmail()) @@ -50,8 +64,10 @@ public SignUpAndInResponse Run(FreelancerRegisterRequest request) { .withStatusCode(HttpStatusCode.OK) .build(); } catch (Exception e) { + logger.error("[x] Error Registering Freelancer: " + e.getMessage()); return SignUpAndInResponse.builder() .withStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR) + .withErrorMessage(e.getMessage()) .withSuccess(false) .build(); } diff --git a/services/users/src/main/java/com/workup/users/commands/FreelancerSetPhotoCommand.java b/services/users/src/main/java/com/workup/users/commands/FreelancerSetPhotoCommand.java index 13aa1acb..cf7a00cf 100644 --- a/services/users/src/main/java/com/workup/users/commands/FreelancerSetPhotoCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/FreelancerSetPhotoCommand.java @@ -5,16 +5,20 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class FreelancerSetPhotoCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(FreelancerSetPhotoCommand.class); @Override public FreelancerSetPhotoResponse Run(FreelancerSetPhotoRequest request) { - + logger.info("[i] Setting Photo for Freelancer with id: " + request.getUserId()); Optional freelancerOption = freelancerRepository.findById(request.getUserId()); if (!freelancerOption.isPresent()) { + logger.error("[x] Freelancer Doesn't Exist"); throw new RuntimeException("User not found"); } diff --git a/services/users/src/main/java/com/workup/users/commands/FreelancerSetProfileCommand.java b/services/users/src/main/java/com/workup/users/commands/FreelancerSetProfileCommand.java index 6e2622a6..81f1fb91 100644 --- a/services/users/src/main/java/com/workup/users/commands/FreelancerSetProfileCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/FreelancerSetProfileCommand.java @@ -5,13 +5,17 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class FreelancerSetProfileCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(FreelancerSetProfileCommand.class); @Override public FreelancerSetProfileResponse Run(FreelancerSetProfileRequest request) { + logger.info("[i] Setting Profile for Freelancer with id: " + request.getUserId()); Freelancer freelancer; if (request.getUserId() == null) { freelancer = Freelancer.builder().withId(null).build(); @@ -19,6 +23,7 @@ public FreelancerSetProfileResponse Run(FreelancerSetProfileRequest request) { Optional freelancerOption = freelancerRepository.findById(request.getUserId()); if (!freelancerOption.isPresent()) { + logger.error("[x] Freelancer Doesn't Exist"); throw new RuntimeException("User not found"); } freelancer = freelancerOption.get(); diff --git a/services/users/src/main/java/com/workup/users/commands/FreelancerSetResumeCommand.java b/services/users/src/main/java/com/workup/users/commands/FreelancerSetResumeCommand.java index d1fc0a9a..c6c51e7e 100644 --- a/services/users/src/main/java/com/workup/users/commands/FreelancerSetResumeCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/FreelancerSetResumeCommand.java @@ -5,16 +5,20 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class FreelancerSetResumeCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(FreelancerSetResumeCommand.class); @Override public FreelancerSetResumeResponse Run(FreelancerSetResumeRequest request) { - + logger.info("[i] Setting Resume for Freelancer with id: " + request.getUserId()); Optional freelancerOption = freelancerRepository.findById(request.getUserId()); if (!freelancerOption.isPresent()) { + logger.error("[x] Freelancer Doesn't Exist"); throw new RuntimeException("User not found"); } diff --git a/services/users/src/main/java/com/workup/users/commands/GetFreelancerAchievementsCommand.java b/services/users/src/main/java/com/workup/users/commands/GetFreelancerAchievementsCommand.java index 41cb08f8..d1541dff 100644 --- a/services/users/src/main/java/com/workup/users/commands/GetFreelancerAchievementsCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/GetFreelancerAchievementsCommand.java @@ -9,17 +9,24 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class GetFreelancerAchievementsCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(GetFreelancerAchievementsCommand.class); + @Override public GetFreelancerAchievementsResponse Run(GetFreelancerAchievementsRequest request) { + logger.info("Get Freelancer Achievements - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return GetFreelancerAchievementsResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); List achievements = convertToAchievementViewList(freelancer.getAchievements()); return GetFreelancerAchievementsResponse.builder() diff --git a/services/users/src/main/java/com/workup/users/commands/GetFreelancerEducationsCommand.java b/services/users/src/main/java/com/workup/users/commands/GetFreelancerEducationsCommand.java index 6e27ee60..2be131f9 100644 --- a/services/users/src/main/java/com/workup/users/commands/GetFreelancerEducationsCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/GetFreelancerEducationsCommand.java @@ -9,17 +9,24 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class GetFreelancerEducationsCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(GetFreelancerEducationsCommand.class); + @Override public GetFreelancerEducationsResponse Run(GetFreelancerEducationsRequest request) { + logger.info("Get Freelancer Educations - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return GetFreelancerEducationsResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); List educations = convertToEducationViewList(freelancer.getEducations()); return GetFreelancerEducationsResponse.builder() diff --git a/services/users/src/main/java/com/workup/users/commands/GetFreelancerExperiencesCommand.java b/services/users/src/main/java/com/workup/users/commands/GetFreelancerExperiencesCommand.java index bccce631..ead3a9b3 100644 --- a/services/users/src/main/java/com/workup/users/commands/GetFreelancerExperiencesCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/GetFreelancerExperiencesCommand.java @@ -9,18 +9,24 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class GetFreelancerExperiencesCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(GetFreelancerExperiencesCommand.class); @Override public GetFreelancerExperiencesResponse Run(GetFreelancerExperiencesRequest request) { + logger.info("Get Freelancer Experiences - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return GetFreelancerExperiencesResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); List experiences = convertToExperienceViewList(freelancer.getExperiences()); return GetFreelancerExperiencesResponse.builder() diff --git a/services/users/src/main/java/com/workup/users/commands/GetFreelancerLanguagesCommand.java b/services/users/src/main/java/com/workup/users/commands/GetFreelancerLanguagesCommand.java index f49a80c6..f6426927 100644 --- a/services/users/src/main/java/com/workup/users/commands/GetFreelancerLanguagesCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/GetFreelancerLanguagesCommand.java @@ -6,17 +6,24 @@ import com.workup.users.db.Freelancer; import java.util.List; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class GetFreelancerLanguagesCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(GetFreelancerLanguagesCommand.class); + @Override public GetFreelancerLanguagesResponse Run(GetFreelancerLanguagesRequest request) { + logger.info("Get Freelancer Languages - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return GetFreelancerLanguagesResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); List languages = freelancer.getLanguages(); return GetFreelancerLanguagesResponse.builder() diff --git a/services/users/src/main/java/com/workup/users/commands/GetFreelancerSkillsCommand.java b/services/users/src/main/java/com/workup/users/commands/GetFreelancerSkillsCommand.java index 79c4c009..338f513d 100644 --- a/services/users/src/main/java/com/workup/users/commands/GetFreelancerSkillsCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/GetFreelancerSkillsCommand.java @@ -6,18 +6,24 @@ import com.workup.users.db.Freelancer; import java.util.List; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class GetFreelancerSkillsCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(GetFreelancerSkillsCommand.class); @Override public GetFreelancerSkillsResponse Run(GetFreelancerSkillsRequest request) { + logger.info("Get Freelancer Skills - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return GetFreelancerSkillsResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); List skills = freelancer.getSkills(); return GetFreelancerSkillsResponse.builder() diff --git a/services/users/src/main/java/com/workup/users/commands/LoginCommand.java b/services/users/src/main/java/com/workup/users/commands/LoginCommand.java index c56b5cf7..4dbb39e4 100644 --- a/services/users/src/main/java/com/workup/users/commands/LoginCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/LoginCommand.java @@ -9,10 +9,15 @@ import com.workup.users.db.Client; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class LoginCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(LoginCommand.class); + @Override public SignUpAndInResponse Run(LoginRequest request) { + logger.info("[i] Logging in user with email: " + request.getEmail()); String email = request.getEmail(); String password = request.getPassword(); try { @@ -52,6 +57,7 @@ public SignUpAndInResponse Run(LoginRequest request) { } } + logger.error("[x] User not found or password incorrect"); // return unauthorized return SignUpAndInResponse.builder() .withSuccess(false) @@ -59,8 +65,7 @@ public SignUpAndInResponse Run(LoginRequest request) { .withErrorMessage("Invalid email or password") .build(); } catch (Exception e) { - System.out.println(e.getMessage()); - e.printStackTrace(); + logger.error("[x] Error Logging in user: " + e.getMessage()); return SignUpAndInResponse.builder() .withStatusCode(HttpStatusCode.INTERNAL_SERVER_ERROR) .withSuccess(false) diff --git a/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerAchievementCommand.java b/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerAchievementCommand.java index 1500f06f..475bf8bf 100644 --- a/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerAchievementCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerAchievementCommand.java @@ -6,17 +6,25 @@ import com.workup.users.db.Achievement; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class RemoveFreelancerAchievementCommand extends UserCommand { + private static final Logger logger = + LogManager.getLogger(RemoveFreelancerAchievementCommand.class); + @Override public RemoveFreelancerAchievementResponse Run(RemoveFreelancerAchievementRequest request) { + logger.info("Remove Freelancer Achievement - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return RemoveFreelancerAchievementResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); freelancer .getAchievements() diff --git a/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerEducationCommand.java b/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerEducationCommand.java index b7740bfc..9d0ec663 100644 --- a/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerEducationCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerEducationCommand.java @@ -6,18 +6,24 @@ import com.workup.users.db.Education; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class RemoveFreelancerEducationCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(RemoveFreelancerEducationCommand.class); @Override public RemoveFreelancerEducationResponse Run(RemoveFreelancerEducationRequest request) { + logger.info("Remove Freelancer Education - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return RemoveFreelancerEducationResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); freelancer .getEducations() diff --git a/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerExperienceCommand.java b/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerExperienceCommand.java index c34467b0..4b06d935 100644 --- a/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerExperienceCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerExperienceCommand.java @@ -6,17 +6,25 @@ import com.workup.users.db.Experience; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class RemoveFreelancerExperienceCommand extends UserCommand { + private static final Logger logger = + LogManager.getLogger(RemoveFreelancerExperienceCommand.class); + @Override public RemoveFreelancerExperienceResponse Run(RemoveFreelancerExperienceRequest request) { + logger.info("Remove Freelancer Experience - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return RemoveFreelancerExperienceResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); freelancer .getExperiences() diff --git a/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerLanguageCommand.java b/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerLanguageCommand.java index 35d7b5ef..a9786037 100644 --- a/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerLanguageCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerLanguageCommand.java @@ -5,17 +5,24 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class RemoveFreelancerLanguageCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(RemoveFreelancerLanguageCommand.class); + @Override public RemoveFreelancerLanguageResponse Run(RemoveFreelancerLanguageRequest request) { + logger.info("Remove Freelancer Language - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return RemoveFreelancerLanguageResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); freelancer.getLanguages().remove(request.getLanguageToRemove()); freelancerRepository.save(freelancer); diff --git a/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerSkillCommand.java b/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerSkillCommand.java index 4fee235d..1656945e 100644 --- a/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerSkillCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/RemoveFreelancerSkillCommand.java @@ -5,17 +5,24 @@ import com.workup.shared.enums.HttpStatusCode; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class RemoveFreelancerSkillCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(RemoveFreelancerSkillCommand.class); + @Override public RemoveFreelancerSkillResponse Run(RemoveFreelancerSkillRequest request) { + logger.info("Remove Freelancer Skill - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return RemoveFreelancerSkillResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); freelancer.getSkills().remove(request.getSkillToRemove()); freelancerRepository.save(freelancer); diff --git a/services/users/src/main/java/com/workup/users/commands/UpdateFreelancerAchievementCommand.java b/services/users/src/main/java/com/workup/users/commands/UpdateFreelancerAchievementCommand.java index f94005e0..7644da23 100644 --- a/services/users/src/main/java/com/workup/users/commands/UpdateFreelancerAchievementCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/UpdateFreelancerAchievementCommand.java @@ -6,17 +6,25 @@ import com.workup.users.db.Achievement; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class UpdateFreelancerAchievementCommand extends UserCommand { + private static final Logger logger = + LogManager.getLogger(UpdateFreelancerAchievementCommand.class); + @Override public UpdateFreelancerAchievementResponse Run(UpdateFreelancerAchievementRequest request) { + logger.info("Update Freelancer Achievement - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return UpdateFreelancerAchievementResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); Achievement updatedAchievement = Achievement.builder() diff --git a/services/users/src/main/java/com/workup/users/commands/UpdateFreelancerEducationCommand.java b/services/users/src/main/java/com/workup/users/commands/UpdateFreelancerEducationCommand.java index 97aaaae1..37c79d4e 100644 --- a/services/users/src/main/java/com/workup/users/commands/UpdateFreelancerEducationCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/UpdateFreelancerEducationCommand.java @@ -6,18 +6,24 @@ import com.workup.users.db.Education; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class UpdateFreelancerEducationCommand extends UserCommand { + private static final Logger logger = LogManager.getLogger(UpdateFreelancerEducationCommand.class); @Override public UpdateFreelancerEducationResponse Run(UpdateFreelancerEducationRequest request) { + logger.info("Update Freelancer Education - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return UpdateFreelancerEducationResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); Education updatedEducation = Education.builder() diff --git a/services/users/src/main/java/com/workup/users/commands/UpdateFreelancerExperienceCommand.java b/services/users/src/main/java/com/workup/users/commands/UpdateFreelancerExperienceCommand.java index 95a6ac8a..e54c2029 100644 --- a/services/users/src/main/java/com/workup/users/commands/UpdateFreelancerExperienceCommand.java +++ b/services/users/src/main/java/com/workup/users/commands/UpdateFreelancerExperienceCommand.java @@ -6,17 +6,25 @@ import com.workup.users.db.Experience; import com.workup.users.db.Freelancer; import java.util.Optional; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; public class UpdateFreelancerExperienceCommand extends UserCommand { + private static final Logger logger = + LogManager.getLogger(UpdateFreelancerExperienceCommand.class); + @Override public UpdateFreelancerExperienceResponse Run(UpdateFreelancerExperienceRequest request) { + logger.info("Update Freelancer Experience - Freelancer ID: " + request.getUserId()); Optional freelancerOptional = freelancerRepository.findById(request.getUserId()); - if (freelancerOptional.isEmpty()) + if (freelancerOptional.isEmpty()) { + logger.error("Freelancer Not Found - Freelancer ID: " + request.getUserId()); return UpdateFreelancerExperienceResponse.builder() .withStatusCode(HttpStatusCode.NOT_FOUND) .withErrorMessage("Freelancer Doesn't Exist") .build(); + } Freelancer freelancer = freelancerOptional.get(); Experience updatedExperience = Experience.builder() diff --git a/services/users/src/main/java/com/workup/users/db/Client.java b/services/users/src/main/java/com/workup/users/db/Client.java index acff0d3c..de3fe980 100644 --- a/services/users/src/main/java/com/workup/users/db/Client.java +++ b/services/users/src/main/java/com/workup/users/db/Client.java @@ -1,5 +1,6 @@ package com.workup.users.db; +import java.io.Serializable; import java.util.Date; import lombok.Builder; import lombok.Getter; @@ -13,7 +14,7 @@ @Getter @Setter @Document -public class Client { +public class Client implements Serializable { @Id private ObjectId id; @Indexed(unique = true) diff --git a/services/users/src/main/java/com/workup/users/db/Freelancer.java b/services/users/src/main/java/com/workup/users/db/Freelancer.java index a51f4a7e..80d4d2c2 100644 --- a/services/users/src/main/java/com/workup/users/db/Freelancer.java +++ b/services/users/src/main/java/com/workup/users/db/Freelancer.java @@ -1,5 +1,6 @@ package com.workup.users.db; +import java.io.Serializable; import java.util.Date; import java.util.List; import lombok.Builder; @@ -15,7 +16,7 @@ @Getter @Setter @Document(collection = "Freelancer") -public class Freelancer { +public class Freelancer implements Serializable { @Id private ObjectId id; @Indexed(unique = true) diff --git a/services/users/src/main/java/com/workup/users/repositories/ClientRepository.java b/services/users/src/main/java/com/workup/users/repositories/ClientRepository.java index b3af5feb..520b73b4 100644 --- a/services/users/src/main/java/com/workup/users/repositories/ClientRepository.java +++ b/services/users/src/main/java/com/workup/users/repositories/ClientRepository.java @@ -2,9 +2,15 @@ import com.workup.users.db.Client; import java.util.Optional; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.mongodb.repository.MongoRepository; public interface ClientRepository extends MongoRepository { // find by clientEmail + @Cacheable(value = "clients", key = "#email") Optional findByEmail(String email); + + @CacheEvict(value = "clients", key = "#entity.email") + S save(S entity); } diff --git a/services/users/src/main/java/com/workup/users/repositories/FreelancerRepository.java b/services/users/src/main/java/com/workup/users/repositories/FreelancerRepository.java index 74b38922..bdce7c86 100644 --- a/services/users/src/main/java/com/workup/users/repositories/FreelancerRepository.java +++ b/services/users/src/main/java/com/workup/users/repositories/FreelancerRepository.java @@ -2,9 +2,15 @@ import com.workup.users.db.Freelancer; import java.util.Optional; +import org.springframework.cache.annotation.CacheEvict; +import org.springframework.cache.annotation.Cacheable; import org.springframework.data.mongodb.repository.MongoRepository; public interface FreelancerRepository extends MongoRepository { // find by freelancerEmail + @Cacheable(value = "freelancers", key = "#email") Optional findByEmail(String email); + + @CacheEvict(value = "freelancers", key = "#entity.email") + S save(S entity); } diff --git a/services/users/src/main/resources/application.properties b/services/users/src/main/resources/application.properties index 29c286b8..472d896d 100644 --- a/services/users/src/main/resources/application.properties +++ b/services/users/src/main/resources/application.properties @@ -1,5 +1,6 @@ spring.data.mongodb.uri=${USERS_DB_URI} spring.data.mongodb.database=mydatabase +spring.data.mongodb.auto-index-creation=true spring.rabbitmq.host=${USERS_MQ_HOST} spring.rabbitmq.port=5672 @@ -7,3 +8,7 @@ spring.rabbitmq.username=guest spring.rabbitmq.password=guest SECRET_KEY=${USERS_SECRET_KEY} +spring.cache.type=redis +spring.cache.host=${REDIS_URL} +spring.cache.port=6379 +spring.cache.redis.time-to-live=60000 diff --git a/services/users/src/main/resources/log4j2.xml b/services/users/src/main/resources/log4j2.xml new file mode 100644 index 00000000..43243f82 --- /dev/null +++ b/services/users/src/main/resources/log4j2.xml @@ -0,0 +1,52 @@ + + + logs/users + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/services/users/src/test/java/com/workup/users/UsersApplicationTests.java b/services/users/src/test/java/com/workup/users/UsersApplicationTests.java index 7b160a70..a03853d7 100644 --- a/services/users/src/test/java/com/workup/users/UsersApplicationTests.java +++ b/services/users/src/test/java/com/workup/users/UsersApplicationTests.java @@ -27,6 +27,7 @@ import org.springframework.context.annotation.Import; import org.springframework.test.context.DynamicPropertyRegistry; import org.springframework.test.context.DynamicPropertySource; +import org.testcontainers.containers.GenericContainer; import org.testcontainers.containers.MongoDBContainer; import org.testcontainers.containers.RabbitMQContainer; import org.testcontainers.junit.jupiter.Container; @@ -45,6 +46,11 @@ class UsersApplicationTests { static final MongoDBContainer mongoDBContainer = new MongoDBContainer("mongo:7.0").withExposedPorts(27017); + // redis container + @Container + static final GenericContainer redisContainer = + new GenericContainer("redis:7.2.4").withExposedPorts(6379); + @Autowired private AmqpTemplate template; @Autowired private ClientRepository paymentRequestRepository; @Autowired private ExperienceRepository experienceRepository; @@ -78,6 +84,9 @@ static void setDatasourceProperties(DynamicPropertyRegistry registry) { registry.add("spring.rabbitmq.port", rabbitMQContainer::getFirstMappedPort); registry.add("spring.rabbitmq.username", rabbitMQContainer::getAdminUsername); registry.add("spring.rabbitmq.password", rabbitMQContainer::getAdminPassword); + // add redis properties + registry.add("spring.cache.host", redisContainer::getHost); + registry.add("spring.cache.port", redisContainer::getFirstMappedPort); } @Test diff --git a/shared/pom.xml b/shared/pom.xml index 17c02a8c..cec90cd1 100644 --- a/shared/pom.xml +++ b/shared/pom.xml @@ -1,42 +1,53 @@ - 4.0.0 - com.workup - shared - 0.0.1-SNAPSHOT - shared - - 21 - 21 - - - com.workup - main - 1.0-SNAPSHOT - - - - com.fasterxml.jackson.dataformat - jackson-dataformat-xml - 2.16.0 - - - org.springframework.boot - spring-boot-starter-data-redis - 3.1.2 - - - redis.clients - jedis - 3.7.0 - - - org.projectlombok - lombok - 1.18.30 - provided - - + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> + 4.0.0 + com.workup + shared + 0.0.1-SNAPSHOT + shared + + 21 + 21 + + + com.workup + main + 1.0-SNAPSHOT + + + + com.fasterxml.jackson.dataformat + jackson-dataformat-xml + 2.16.0 + + + org.springframework.boot + spring-boot-starter-data-redis + 3.1.2 + + + org.springframework.boot + spring-boot-starter-logging + + + + + redis.clients + jedis + 3.7.0 + + + org.projectlombok + lombok + 1.18.30 + provided + + + org.springframework.boot + spring-boot-starter-log4j2 + 3.2.5 + + \ No newline at end of file diff --git a/shared/src/main/java/com/workup/shared/commands/CommandMap.java b/shared/src/main/java/com/workup/shared/commands/CommandMap.java index 8d34facd..ece18e29 100644 --- a/shared/src/main/java/com/workup/shared/commands/CommandMap.java +++ b/shared/src/main/java/com/workup/shared/commands/CommandMap.java @@ -24,4 +24,8 @@ public R getCommand(String command) throws Exception { setupCommand(commandInstance); return commandInstance; } + + public void replaceCommand(String command, Class newCommand) { + commands.put(command, newCommand); + } } diff --git a/shared/src/main/java/com/workup/shared/commands/controller/ContinueRequest.java b/shared/src/main/java/com/workup/shared/commands/controller/ContinueRequest.java index 947802a6..99f68a77 100644 --- a/shared/src/main/java/com/workup/shared/commands/controller/ContinueRequest.java +++ b/shared/src/main/java/com/workup/shared/commands/controller/ContinueRequest.java @@ -1,6 +1,11 @@ package com.workup.shared.commands.controller; +import lombok.Builder; +import lombok.extern.jackson.Jacksonized; + /** Makes a service start accepting requests and acquire resources again. */ +@Builder +@Jacksonized public class ContinueRequest { // No fields are required? } diff --git a/shared/src/main/java/com/workup/shared/commands/controller/FreezeRequest.java b/shared/src/main/java/com/workup/shared/commands/controller/FreezeRequest.java index 563c88c2..3f841e89 100644 --- a/shared/src/main/java/com/workup/shared/commands/controller/FreezeRequest.java +++ b/shared/src/main/java/com/workup/shared/commands/controller/FreezeRequest.java @@ -1,6 +1,11 @@ package com.workup.shared.commands.controller; +import lombok.Builder; +import lombok.extern.jackson.Jacksonized; + /** Makes a service stop accepting requests and release resources. */ +@Builder +@Jacksonized public class FreezeRequest { // No fields are required? } diff --git a/shared/src/main/java/com/workup/shared/commands/controller/SetErrorReportingLevelRequest.java b/shared/src/main/java/com/workup/shared/commands/controller/SetLoggingLevelRequest.java similarity index 68% rename from shared/src/main/java/com/workup/shared/commands/controller/SetErrorReportingLevelRequest.java rename to shared/src/main/java/com/workup/shared/commands/controller/SetLoggingLevelRequest.java index f7b15132..e035135a 100644 --- a/shared/src/main/java/com/workup/shared/commands/controller/SetErrorReportingLevelRequest.java +++ b/shared/src/main/java/com/workup/shared/commands/controller/SetLoggingLevelRequest.java @@ -1,6 +1,5 @@ package com.workup.shared.commands.controller; -import com.workup.shared.enums.ErrorLevel; import lombok.Builder; import lombok.Getter; import lombok.extern.jackson.Jacksonized; @@ -9,6 +8,6 @@ @Getter @Builder(setterPrefix = "with") @Jacksonized -public class SetErrorReportingLevelRequest { - ErrorLevel errorLevel; +public class SetLoggingLevelRequest { + String level; } diff --git a/shared/src/main/java/com/workup/shared/commands/controller/UpdateCommandRequest.java b/shared/src/main/java/com/workup/shared/commands/controller/UpdateCommandRequest.java index 1d2fbafa..b8c01ed0 100644 --- a/shared/src/main/java/com/workup/shared/commands/controller/UpdateCommandRequest.java +++ b/shared/src/main/java/com/workup/shared/commands/controller/UpdateCommandRequest.java @@ -9,5 +9,6 @@ @Builder(setterPrefix = "with") @Jacksonized public class UpdateCommandRequest { - String name; + String commandName; + byte[] byteCode; } diff --git a/shared/src/main/java/com/workup/shared/enums/ErrorLevel.java b/shared/src/main/java/com/workup/shared/enums/ErrorLevel.java deleted file mode 100644 index 444baebe..00000000 --- a/shared/src/main/java/com/workup/shared/enums/ErrorLevel.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.workup.shared.enums; - -public enum ErrorLevel { - INFO, - WARNING, - ERROR, - FATAL -} diff --git a/shared/src/main/java/com/workup/shared/enums/ThreadPoolSize.java b/shared/src/main/java/com/workup/shared/enums/ThreadPoolSize.java new file mode 100644 index 00000000..4a6df8bc --- /dev/null +++ b/shared/src/main/java/com/workup/shared/enums/ThreadPoolSize.java @@ -0,0 +1,5 @@ +package com.workup.shared.enums; + +public class ThreadPoolSize { + public static int POOL_SIZE = 50; +} diff --git a/webserver/src/main/resources/application.properties b/webserver/src/main/resources/application.properties index ddd689b1..42437f86 100644 --- a/webserver/src/main/resources/application.properties +++ b/webserver/src/main/resources/application.properties @@ -4,5 +4,6 @@ spring.rabbitmq.host=${WEBSERVER_MQ_HOST} spring.rabbitmq.port=5672 spring.rabbitmq.username=guest spring.rabbitmq.password=guest +spring.rabbitmq.template.reply-timeout=60000 auth.secret = ${WEBSERVER_SECRET_KEY}