diff --git a/.gitignore b/.gitignore index c3ddd612c..ddc5d975d 100644 --- a/.gitignore +++ b/.gitignore @@ -44,6 +44,7 @@ vendor/mms-agent/settings.py apps/social/spam.py venv* /backups +config/mongodb_keyfile.key # Docker Jinja templates docker/haproxy/haproxy.consul.cfg diff --git a/.vscode/settings.json b/.vscode/settings.json index 4afc405e2..ee123cf5a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -21,5 +21,7 @@ "static/*.js": true, "blog/.jekyll-cache": true, "blog/_site": true, + "docker/volumes": true, + "requirements.txt": true, // It's just a symlink to config/requirements.txt, which has git history }, } diff --git a/Makefile b/Makefile index 7ae7e6f71..0d75d6fe3 100644 --- a/Makefile +++ b/Makefile @@ -20,12 +20,14 @@ rebuild: #creates newsblur, builds new images, and creates/refreshes SSL keys nb: pull - - CURRENT_UID=${CURRENT_UID} CURRENT_GID=${CURRENT_GID} docker-compose down + - RUNWITHMAKEBUILD=True CURRENT_UID=${CURRENT_UID} CURRENT_GID=${CURRENT_GID} docker-compose down - [[ -d config/certificates ]] && echo "keys exist" || make keys - cd node && npm install & cd .. - RUNWITHMAKEBUILD=True CURRENT_UID=${CURRENT_UID} CURRENT_GID=${CURRENT_GID} docker-compose up -d --build --remove-orphans - RUNWITHMAKEBUILD=True docker-compose exec newsblur_web ./manage.py migrate - RUNWITHMAKEBUILD=True docker-compose exec newsblur_web ./manage.py loaddata config/fixtures/bootstrap.json +coffee: + - coffee -c -w **/*.coffee shell: - RUNWITHMAKEBUILD=True CURRENT_UID=${CURRENT_UID} CURRENT_GID=${CURRENT_GID} docker-compose exec newsblur_web ./manage.py shell_plus @@ -37,11 +39,15 @@ debug: - CURRENT_UID=${CURRENT_UID} CURRENT_GID=${CURRENT_GID} docker attach ${newsblur} log: - RUNWITHMAKEBUILD=True docker-compose logs -f --tail 20 newsblur_web newsblur_node - +logweb: log +logcelery: + - RUNWITHMAKEBUILD=True docker-compose logs -f --tail 20 task_celery +logtask: logcelery logmongo: - RUNWITHMAKEBUILD=True docker-compose logs -f db_mongo -alllogs: +alllogs: - RUNWITHMAKEBUILD=True docker-compose logs -f --tail 20 +logall: alllogs # brings down containers down: - RUNWITHMAKEBUILD=True docker-compose -f docker-compose.yml down @@ -69,6 +75,10 @@ keys: # Digital Ocean / Terraform list: - doctl -t `cat /srv/secrets-newsblur/keys/digital_ocean.token` compute droplet list +sizes: + - doctl -t `cat /srv/secrets-newsblur/keys/digital_ocean.token` compute size list +ratelimit: + - doctl -t `cat /srv/secrets-newsblur/keys/digital_ocean.token` account ratelimit ansible-deps: ansible-galaxy install -p roles -r ansible/roles/requirements.yml --roles-path ansible/roles tfrefresh: @@ -79,6 +89,8 @@ apply: terraform -chdir=terraform apply -refresh=false -parallelism=15 inventory: - ./ansible/utils/generate_inventory.py +oldinventory: + - OLD=1 ./ansible/utils/generate_inventory.py # Docker pull: diff --git a/ansible/all.yml b/ansible/all.yml index 01a81542f..92e3a475b 100644 --- a/ansible/all.yml +++ b/ansible/all.yml @@ -11,7 +11,7 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} diff --git a/ansible/env_vars/base.yml b/ansible/env_vars/base.yml index efb9f4af5..7dc03420b 100644 --- a/ansible/env_vars/base.yml +++ b/ansible/env_vars/base.yml @@ -7,6 +7,9 @@ git_secrets_repo: ssh://git@github.com/samuelclay/newsblur-secrets create_user: nb local_key: "{{ lookup('file', lookup('env','HOME') + '/.ssh/id_rsa.pub') }}" copy_local_key: "{{ lookup('file', '/srv/secrets-newsblur/keys/docker.key.pub') }}" +mongodb_keyfile: "{{ lookup('file', '/srv/secrets-newsblur/keys/mongodb_keyfile.key') }}" +mongodb_username: "{{ lookup('ini', 'mongodb_username section=admin file=/srv/secrets-newsblur/configs/mongodb_auth.ini') }}" +mongodb_password: "{{ lookup('ini', 'mongodb_password section=admin file=/srv/secrets-newsblur/configs/mongodb_auth.ini') }}" sys_packages: [ 'git', 'python3', diff --git a/ansible/inventories/digital_ocean.yml b/ansible/inventories/digital_ocean.yml index fcfbbf64d..0acbf18c4 100644 --- a/ansible/inventories/digital_ocean.yml +++ b/ansible/inventories/digital_ocean.yml @@ -29,7 +29,7 @@ groups: elasticsearch: inventory_hostname.startswith('db-elasticsearch') redis: inventory_hostname.startswith('db-redis') postgres: inventory_hostname.startswith('db-postgres') - mongo: inventory_hostname.startswith('db-mongo') + mongo: inventory_hostname.startswith('db-mongo') and not inventory_hostname.startswith('db-mongo-analytics') mongo_analytics: inventory_hostname.startswith('db-mongo-analytics') consul: inventory_hostname.startswith('db-consul') metrics: inventory_hostname.startswith('db-metrics') diff --git a/ansible/playbooks/deploy_task.yml b/ansible/playbooks/deploy_task.yml index 4386ca585..3bb1cb727 100644 --- a/ansible/playbooks/deploy_task.yml +++ b/ansible/playbooks/deploy_task.yml @@ -24,7 +24,11 @@ rescue: - name: Restart celery become: yes - command: "docker start task-work" + command: "docker start {{ item.service_name }}" + when: item.service_name in inventory_hostname + with_items: + - service_name: task-celery + - service_name: task-work - name: Stop celery become: yes diff --git a/ansible/playbooks/setup_app.yml b/ansible/playbooks/setup_app.yml index ff38a4e13..b5968cad0 100644 --- a/ansible/playbooks/setup_app.yml +++ b/ansible/playbooks/setup_app.yml @@ -11,7 +11,7 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} diff --git a/ansible/playbooks/setup_blog.yml b/ansible/playbooks/setup_blog.yml index 27b413732..4180e155d 100644 --- a/ansible/playbooks/setup_blog.yml +++ b/ansible/playbooks/setup_blog.yml @@ -11,7 +11,7 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} diff --git a/ansible/playbooks/setup_consul_manager.yml b/ansible/playbooks/setup_consul_manager.yml index 67cd76ec7..7d73383bd 100644 --- a/ansible/playbooks/setup_consul_manager.yml +++ b/ansible/playbooks/setup_consul_manager.yml @@ -11,7 +11,7 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-manager', tags: 'consul'} diff --git a/ansible/playbooks/setup_debug.yml b/ansible/playbooks/setup_debug.yml index 3722df9b4..89a6ca682 100644 --- a/ansible/playbooks/setup_debug.yml +++ b/ansible/playbooks/setup_debug.yml @@ -11,7 +11,7 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} diff --git a/ansible/playbooks/setup_discovery.yml b/ansible/playbooks/setup_discovery.yml index 80235e54f..708c95d20 100644 --- a/ansible/playbooks/setup_discovery.yml +++ b/ansible/playbooks/setup_discovery.yml @@ -11,7 +11,7 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} # - {role: 'dnsmasq', tags: 'dnsmasq'} # - {role: 'consul', tags: 'consul'} # - {role: 'consul-client', tags: 'consul'} diff --git a/ansible/playbooks/setup_elasticsearch.yml b/ansible/playbooks/setup_elasticsearch.yml index c87c4978d..463bde46d 100644 --- a/ansible/playbooks/setup_elasticsearch.yml +++ b/ansible/playbooks/setup_elasticsearch.yml @@ -10,10 +10,10 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} - - {role: 'node-exporter', tags: ['node-exporter', 'metrics']} - {role: 'elasticsearch', tags: 'elasticsearch'} + - {role: 'node-exporter', tags: ['node-exporter', 'metrics']} - {role: 'monitor', tags: 'monitor'} diff --git a/ansible/playbooks/setup_metrics.yml b/ansible/playbooks/setup_metrics.yml index 15f574383..59f478d3e 100644 --- a/ansible/playbooks/setup_metrics.yml +++ b/ansible/playbooks/setup_metrics.yml @@ -11,7 +11,7 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} diff --git a/ansible/playbooks/setup_mongo.yml b/ansible/playbooks/setup_mongo.yml index 3ffd1b576..3ae5cdfa2 100644 --- a/ansible/playbooks/setup_mongo.yml +++ b/ansible/playbooks/setup_mongo.yml @@ -1,6 +1,6 @@ --- - name: SETUP -> mongo containers - hosts: mongo + hosts: mongo, mongo_analytics vars: - update_apt_cache: yes - motd_role: db @@ -11,11 +11,12 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} - - {role: 'node-exporter', tags: ['node-exporter', 'metrics']} - {role: 'mongo', tags: 'mongo'} + - {role: 'node-exporter', tags: ['node-exporter', 'metrics']} + - {role: 'mongo-exporter', tags: 'mongo-exporter'} - {role: 'monitor', tags: 'monitor'} - - {role: 'benchmark', tags: 'benchmark'} + # - {role: 'benchmark', tags: 'benchmark'} diff --git a/ansible/playbooks/setup_node.yml b/ansible/playbooks/setup_node.yml index 4acb0af85..cc2528fcd 100644 --- a/ansible/playbooks/setup_node.yml +++ b/ansible/playbooks/setup_node.yml @@ -11,7 +11,7 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} diff --git a/ansible/playbooks/setup_postgres.yml b/ansible/playbooks/setup_postgres.yml index 601523a4b..51f7f418a 100644 --- a/ansible/playbooks/setup_postgres.yml +++ b/ansible/playbooks/setup_postgres.yml @@ -11,7 +11,7 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} diff --git a/ansible/playbooks/setup_redis.yml b/ansible/playbooks/setup_redis.yml index 4bee96d70..fad6732d4 100644 --- a/ansible/playbooks/setup_redis.yml +++ b/ansible/playbooks/setup_redis.yml @@ -10,7 +10,7 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} diff --git a/ansible/playbooks/setup_staging.yml b/ansible/playbooks/setup_staging.yml index daadb7b49..7e89ac5f7 100644 --- a/ansible/playbooks/setup_staging.yml +++ b/ansible/playbooks/setup_staging.yml @@ -12,7 +12,7 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} diff --git a/ansible/playbooks/setup_task.yml b/ansible/playbooks/setup_task.yml index f52595491..5b68ecaa2 100644 --- a/ansible/playbooks/setup_task.yml +++ b/ansible/playbooks/setup_task.yml @@ -10,7 +10,7 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} diff --git a/ansible/playbooks/setup_www.yml b/ansible/playbooks/setup_www.yml index afde42513..9e6bb4443 100644 --- a/ansible/playbooks/setup_www.yml +++ b/ansible/playbooks/setup_www.yml @@ -11,11 +11,10 @@ - {role: 'base', tags: 'base'} - {role: 'ufw', tags: 'ufw'} - {role: 'docker', tags: 'docker'} - - {role: 'repo', tags: 'repo'} + - {role: 'repo', tags: ['repo', 'pull']} - {role: 'dnsmasq', tags: 'dnsmasq'} - {role: 'consul', tags: 'consul'} - {role: 'consul-client', tags: 'consul'} - {role: 'node-exporter', tags: ['node-exporter', 'metrics']} - - {role: 'monitor', tags: 'monitor'} - {role: 'letsencrypt', tags: 'letsencrypt'} - {role: 'haproxy', tags: 'haproxy'} diff --git a/ansible/roles/celery_task/tasks/main.yml b/ansible/roles/celery_task/tasks/main.yml index ca358cff6..83c77ca51 100644 --- a/ansible/roles/celery_task/tasks/main.yml +++ b/ansible/roles/celery_task/tasks/main.yml @@ -32,6 +32,7 @@ image: newsblur/newsblur_python3 state: started pull: yes + hostname: "{{ inventory_hostname }}" container_default_behavior: no_defaults env: DOCKERBUILD: "" @@ -73,7 +74,7 @@ container_default_behavior: no_defaults env: AUTOHEAL_CONTAINER_LABEL: all - restart_policy: always + restart_policy: unless-stopped volumes: - /var/run/docker.sock:/var/run/docker.sock @@ -104,50 +105,57 @@ - container_name: task-work changed_when: app_changed.changed +- name: Ensure permissions on sanity checker log + become: yes + file: + path: /var/log/sanity_checker.log + state: touch + mode: 0666 + - name: Add sanity checkers cronjob for feeds fetched become: yes - cron: - name: feeds_fetched_sanity_checker - user: root - cron_file: /etc/cron.hourly/feeds_fetched_sanity_checker - job: >- - docker pull newsblur/newsblur_python3:latest; - docker run --rm -it - -v /srv/newsblur/:/srv/newsblur - -h `cat /etc/hostname` - --network=newsblurnet newsblur/newsblur_python3 /srv/newsblur/utils/monitor_task_fetches.py + copy: + owner: root + dest: /etc/cron.d/feeds_fetched_sanity_checker + mode: 0744 + content: | + MAILTO="" + SHELL=/bin/sh + PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + 0 * * * * nb sudo docker run -v /srv/newsblur/:/srv/newsblur -v /var/log/sanity_checker.log:/var/log/sanity_checker.log -h `cat /etc/hostname` --network=newsblurnet newsblur/newsblur_python3 /srv/newsblur/utils/monitor_task_fetches.py >> /var/log/sanity_checker.log + when: "'task-work' in inventory_hostname" tags: - sanity-checker - name: Add sanity checkers cronjob for newsletter become: yes - cron: - name: newsletter_sanity_checker - user: root - cron_file: /etc/cron.hourly/newsletter_sanity_checker - job: >- - docker pull newsblur/newsblur_python3:latest; - docker run --rm -it - -v /srv/newsblur/:/srv/newsblur - -h `cat /etc/hostname` - --network=newsblurnet newsblur/newsblur_python3 /srv/newsblur/utils/monitor_newsletter_delivery.py + copy: + owner: root + dest: /etc/cron.d/newsletter_sanity_checker + mode: 0744 + content: | + MAILTO="" + SHELL=/bin/sh + PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + 0 * * * * nb sudo docker run -v /srv/newsblur/:/srv/newsblur -v /var/log/sanity_checker.log:/var/log/sanity_checker.log -h `cat /etc/hostname` --network=newsblurnet newsblur/newsblur_python3 /srv/newsblur/utils/monitor_newsletter_delivery.py >> /var/log/sanity_checker.log + when: "'task-work' in inventory_hostname" tags: - sanity-checker - name: Add sanity checkers cronjob for work queue become: yes - cron: - name: work_queue_sanity_checker - user: root - cron_file: /etc/cron.hourly/work_queue_sanity_checker - job: >- - docker pull newsblur/newsblur_python3:latest; - docker run --rm -it - -v /srv/newsblur/:/srv/newsblur - -h `cat /etc/hostname` - --network=newsblurnet newsblur/newsblur_python3 /srv/newsblur/utils/monitor_work_queue.py + copy: + owner: root + dest: /etc/cron.d/work_queue_sanity_checker + mode: 0744 + content: | + MAILTO="" + SHELL=/bin/sh + PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin + 0 * * * * nb sudo docker run -v /srv/newsblur/:/srv/newsblur -v /var/log/sanity_checker.log:/var/log/sanity_checker.log -h `cat /etc/hostname` --network=newsblurnet newsblur/newsblur_python3 /srv/newsblur/utils/monitor_work_queue.py >> /var/log/sanity_checker.log + when: "'task-work' in inventory_hostname" tags: - sanity-checker diff --git a/ansible/roles/dnsmasq/templates/dnsmasq-10-consul.j2 b/ansible/roles/dnsmasq/templates/dnsmasq-10-consul.j2 index bca8852ca..fb6287919 100644 --- a/ansible/roles/dnsmasq/templates/dnsmasq-10-consul.j2 +++ b/ansible/roles/dnsmasq/templates/dnsmasq-10-consul.j2 @@ -7,3 +7,12 @@ server=/consul/127.0.0.1#8600 {# dnsmasq should not needlessly read /etc/resolv.conf #} no-resolv + +interface=lo +interface=eth0 +interface=eth1 + +bind-interfaces +# log-dhcp +# log-queries +# log-facility=/var/log/dnsmasq.log diff --git a/ansible/roles/docker/tasks/main.yml b/ansible/roles/docker/tasks/main.yml index 00c5377f3..1cb85eb94 100755 --- a/ansible/roles/docker/tasks/main.yml +++ b/ansible/roles/docker/tasks/main.yml @@ -24,10 +24,7 @@ - name: Turn off Docker iptables firewall exclusion become: yes - copy: + template: + src: daemon.json dest: /etc/docker/daemon.json - content: | - { - "iptables": false - } notify: restart docker diff --git a/ansible/roles/docker/templates/daemon.json b/ansible/roles/docker/templates/daemon.json new file mode 100644 index 000000000..a75f725f7 --- /dev/null +++ b/ansible/roles/docker/templates/daemon.json @@ -0,0 +1,3 @@ +{ + "iptables": false +} diff --git a/ansible/roles/elasticsearch/tasks/main.yml b/ansible/roles/elasticsearch/tasks/main.yml index 563f59062..6ee0b8384 100644 --- a/ansible/roles/elasticsearch/tasks/main.yml +++ b/ansible/roles/elasticsearch/tasks/main.yml @@ -1,15 +1,48 @@ --- +- name: Permissions for elasticsearch + become: yes + file: + state: directory + mode: 0777 + path: /var/log/elasticsearch + +- name: Permissions for elasticsearch volume + become: yes + file: + state: directory + path: /srv/newsblur/docker/volumes + recurse: yes + owner: nb + group: nb + +- name: Make docker network for newsblurnet + become: yes + docker_network: + name: newsblurnet + notify: restart docker + - name: Start Elasticsearch Docker container become: yes docker_container: name: elasticsearch - image: elasticsearch:1.7.6 + image: elasticsearch:7.14.0 state: started + hostname: "{{ inventory_hostname }}" ports: - '9200:9200' restart_policy: unless-stopped + container_default_behavior: no_defaults + networks_cli_compatible: yes + # network_mode: host + network_mode: default + networks: + - name: newsblurnet + aliases: + - elasticsearch + user: 1000:1001 volumes: - /srv/newsblur/docker/volumes/elasticsearch:/usr/share/elasticsearch/data + - /var/log/elasticsearch/:/var/log/elasticsearch/ - name: Register elasticsearch in consul tags: consul diff --git a/ansible/roles/grafana/tasks/main.yml b/ansible/roles/grafana/tasks/main.yml index 93efba385..298b633cc 100644 --- a/ansible/roles/grafana/tasks/main.yml +++ b/ansible/roles/grafana/tasks/main.yml @@ -20,6 +20,7 @@ name: grafana image: grafana/grafana:7.5.7 restart_policy: unless-stopped + hostname: "{{ inventory_hostname }}" user: root networks_cli_compatible: yes network_mode: default diff --git a/ansible/roles/haproxy/tasks/main.yml b/ansible/roles/haproxy/tasks/main.yml index 2f7412f5a..08dd1b2a7 100644 --- a/ansible/roles/haproxy/tasks/main.yml +++ b/ansible/roles/haproxy/tasks/main.yml @@ -84,6 +84,7 @@ # - "80:80" # - "443:443" # - "1936:1936" + hostname: "{{ inventory_hostname }}" restart_policy: unless-stopped container_default_behavior: no_defaults command: "haproxy -f /srv/newsblur/docker/haproxy/haproxy.consul.cfg" diff --git a/ansible/roles/mongo-exporter/tasks/main.yml b/ansible/roles/mongo-exporter/tasks/main.yml index 90a6d639e..1529fd466 100644 --- a/ansible/roles/mongo-exporter/tasks/main.yml +++ b/ansible/roles/mongo-exporter/tasks/main.yml @@ -16,7 +16,8 @@ networks: - name: newsblurnet env: - MONGODB_URI: 'mongodb://db-mongo.service.nyc1.consul:27017/admin?' + MONGODB_URI: 'mongodb://{{ inventory_hostname }}.node.nyc1.consul:27017/admin?' + # MONGODB_URI: 'mongodb://{{ mongodb_username }}:{{ mongodb_password }}@{{ inventory_hostname }}.node.nyc1.consul:27017/admin?authSource=admin' ports: - '9216:9216' @@ -29,4 +30,4 @@ notify: - reload consul - name: Command to register mongo-exporter - command: "consul services register /etc/consul.d/mongo-exporter.json" \ No newline at end of file + command: "consul services register /etc/consul.d/mongo-exporter.json" diff --git a/ansible/roles/mongo/tasks/main.yml b/ansible/roles/mongo/tasks/main.yml index 3596d4059..0eceea311 100644 --- a/ansible/roles/mongo/tasks/main.yml +++ b/ansible/roles/mongo/tasks/main.yml @@ -6,12 +6,53 @@ mode: 0777 path: /var/log/mongodb +- name: Get the volume name + shell: ls /dev/disk/by-id/ | grep -v part + register: volume_name_raw + +- set_fact: + volume_name: "{{ volume_name_raw.stdout }}" + +- debug: + msg: "{{ volume_name }}" + +- name: Create the mount point + become: yes + file: + path: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}" + state: directory + +- name: Mount volume read-write + become: yes + mount: + path: "/mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}" + src: "/dev/disk/by-id/{{ volume_name }}" + fstype: xfs + opts: defaults,discard + state: mounted + +- name: Copy MongoDB keyfile + copy: + content: "{{ mongodb_keyfile }}" + dest: /srv/newsblur/config/mongodb_keyfile.key + owner: nb + mode: 0400 + tags: + - keyfile + - name: Make docker network for newsblurnet become: yes docker_network: name: newsblurnet notify: restart docker +- name: Make backup directory + become: yes + file: + path: /opt/mongo/newsblur/backup/ + state: directory + mode: 0666 + - name: Start db-mongo docker container become: yes docker_container: @@ -19,19 +60,53 @@ image: mongo:3.6 state: started container_default_behavior: no_defaults + hostname: "{{ inventory_hostname }}" restart_policy: unless-stopped networks_cli_compatible: yes - network_mode: default - networks: - - name: newsblurnet - ports: - - "27017:27017" + network_mode: host + # network_mode: default + # networks: + # - name: newsblurnet + # aliases: + # - mongo + # ports: + # - "27017:27017" command: --config /etc/mongod.conf volumes: - /mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}:/data/db - /srv/newsblur/ansible/roles/mongo/templates/mongo.conf:/etc/mongod.conf + - /srv/newsblur/config/mongodb_keyfile.key:/srv/newsblur/config/mongodb_keyfile.key - /var/log/mongodb/:/var/log/mongodb/ - - /opt/mongo/newsblur/backup:/backup/' + - /opt/mongo/newsblur/backup/:/backup/ + when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-mongo', 'db-mongo-secondary'] + +- name: Start db-mongo-analytics docker container + become: yes + docker_container: + name: mongo + image: mongo:3.6 + state: started + container_default_behavior: no_defaults + hostname: "{{ inventory_hostname }}" + restart_policy: unless-stopped + networks_cli_compatible: yes + # network_mode: host + network_mode: default + networks: + - name: newsblurnet + aliases: + - mongo + ports: + - "27017:27017" + command: --config /etc/mongod.conf + user: 1000:1001 + volumes: + - /mnt/{{ inventory_hostname | regex_replace('db-|-', '') }}:/data/db + - /srv/newsblur/ansible/roles/mongo/templates/mongo.analytics.conf:/etc/mongod.conf + - /srv/newsblur/config/mongodb_keyfile.key:/srv/newsblur/config/mongodb_keyfile.key + - /var/log/mongodb/:/var/log/mongodb/ + - /opt/mongo/newsblur/backup/:/backup/ + when: (inventory_hostname | regex_replace('[0-9]+', '')) == 'db-mongo-analytics' - name: Register mongo in consul tags: consul @@ -39,7 +114,7 @@ template: src: consul_service.json dest: /etc/consul.d/mongo.json - when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-mongo', 'db-mongo-secondary'] or inventory_hostname.startswith('db2') + when: (inventory_hostname | regex_replace('[0-9]+', '')) in ['db-mongo', 'db-mongo-secondary'] notify: - reload consul @@ -49,7 +124,7 @@ template: src: consul_service.analytics.json dest: /etc/consul.d/mongo.json - when: (inventory_hostname | regex_replace('[0-9]+', '')) == 'db-mongo-analytics' or inventory_hostname.startswith('db3') + when: (inventory_hostname | regex_replace('[0-9]+', '')) == 'db-mongo-analytics' notify: - reload consul @@ -81,38 +156,24 @@ name: mongo backup minute: "0" hour: "4" - job: - collections=( - classifier_tag - classifier_author - classifier_feed - classifier_title - userstories - shared_stories - category - category_site - sent_emails - social_profile - social_subscription - social_services - statistics - user_search - feedback - ) - for collection in collections; do - echo Dumping $collection - now=$(date '+%Y-%m-%d-%H-%M') + job: /srv/newsblur/docker/mongo/backup_mongo.sh + when: '"db-mongo-secondary1" in inventory_hostname' + tags: + - mongo-backup - docker exec -it mongo mongodump --db newsblur --collection $collection -o /backup/backup_mongo_${now} - - echo Compressing /opt/mongo/newsblur/backup/backup_mongo_${now}.tgz - tar -zcf /opt/mongo/newsblur/backup/backup_mongo_${now}.tgz /opt/mongo/newsblur/backup/backup_mongo_${now}) - - done; +- name: Add mongo starred_stories+stories backup + cron: + name: mongo starred/shared/all stories backup + minute: "0" + hour: "5" + job: /srv/newsblur/docker/mongo/backup_mongo_stories.sh + when: '"db-mongo-secondary1" in inventory_hostname' + tags: + - mongo-backup - echo Uploading backups to S3 - docker run --rm - -v /srv/newsblur:/srv/newsblur - -v /opt/mongo/newsblur/backup/:/opt/mongo/newsblur/backup/ - --network=newsblurnet - newsblur/newsblur_python3:latest /srv/newsblur/utils/backups/backup_mongo.py +# Renaming a db-mongo3 to db-mongo2: +# - Change hostname to db-mongo2 on Digital Ocean (doctl) +# - Change hostname to db-mongo2 in /etc/hostname +# - Symlink /mnt/mongo2 to /mnt/mongo3 +# - tf state mv "digitalocean_droplet.db-mongo-primary[2]" "digitalocean_droplet.db-mongo-primary[1]" +# - tf state mv "digitalocean_volume.mongo_volume[2]" "digitalocean_volume.mongo_volume[1]" diff --git a/ansible/roles/mongo/templates/consul_service.analytics.json b/ansible/roles/mongo/templates/consul_service.analytics.json index 5bbdb0d0c..e3287710c 100644 --- a/ansible/roles/mongo/templates/consul_service.analytics.json +++ b/ansible/roles/mongo/templates/consul_service.analytics.json @@ -8,7 +8,7 @@ "port": 27017, "checks": [{ "id": "mongo-analytics-ping", - "http": "{% if inventory_hostname.startswith('db-mongo') %}http://{{ ansible_ssh_host }}:5579/db_check/mongo{% else %}http://{{ ansible_ssh_host }}:5000/db_check/mongo{% endif %}", + "http": "http://{{ ansible_ssh_host }}:5579/db_check/mongo_analytics", "interval": "15s" }] } diff --git a/ansible/roles/mongo/templates/consul_service.json b/ansible/roles/mongo/templates/consul_service.json index fb1744b2e..15263611b 100644 --- a/ansible/roles/mongo/templates/consul_service.json +++ b/ansible/roles/mongo/templates/consul_service.json @@ -8,7 +8,7 @@ "port": 27017, "checks": [{ "id": "mongo-ping", - "http": "{% if inventory_hostname.startswith('db-mongo') %}http://{{ ansible_ssh_host }}:5579/db_check/mongo{% else %}http://{{ ansible_ssh_host }}:5000/db_check/mongo{% endif %}", + "http": "http://{{ ansible_ssh_host }}:5579/db_check/mongo", "interval": "15s", "failures_before_critical": 4 }] diff --git a/ansible/roles/mongo/templates/mongo.analytics.conf b/ansible/roles/mongo/templates/mongo.analytics.conf new file mode 100644 index 000000000..01e1b080a --- /dev/null +++ b/ansible/roles/mongo/templates/mongo.analytics.conf @@ -0,0 +1,48 @@ +# mongod.conf + +# for documentation of all options, see: +# http://docs.mongodb.org/manual/reference/configuration-options/ + +# Where and how to store data. +storage: + dbPath: /data/db + journal: + enabled: true +# engine: +# mmapv1: +# wiredTiger: + +# where to write logging data. +systemLog: + destination: file + logAppend: true + path: /var/log/mongodb/mongod.log + +# network interfaces +net: + port: 27017 + bindIpAll: true + +# how the process runs +processManagement: + timeZoneInfo: /usr/share/zoneinfo + +security: + keyFile: /srv/newsblur/config/mongodb_keyfile.key + authorization: enabled +# transitionToAuth: true + +operationProfiling: + mode: slowOp + slowOpThresholdMs: 1000 + +# replication: +# replSetName: nbset + +#sharding: + +## Enterprise-Only Options: + +#auditLog: + +#snmp: diff --git a/ansible/roles/mongo/templates/mongo.conf b/ansible/roles/mongo/templates/mongo.conf index f65ce8d8a..714fa3890 100644 --- a/ansible/roles/mongo/templates/mongo.conf +++ b/ansible/roles/mongo/templates/mongo.conf @@ -27,7 +27,10 @@ net: processManagement: timeZoneInfo: /usr/share/zoneinfo -# security: +security: + keyFile: /srv/newsblur/config/mongodb_keyfile.key + authorization: enabled + # transitionToAuth: true operationProfiling: mode: slowOp diff --git a/ansible/roles/monitor/tasks/main.yml b/ansible/roles/monitor/tasks/main.yml index 0af0dd6aa..37512d41c 100644 --- a/ansible/roles/monitor/tasks/main.yml +++ b/ansible/roles/monitor/tasks/main.yml @@ -15,7 +15,7 @@ lineinfile: path: /srv/newsblur/newsblur_web/app_env.py line: 'SERVER_NAME = "{{ inventory_hostname }}"' - + - name: Make docker network for newsblurnet become: yes docker_network: @@ -40,3 +40,8 @@ - name: newsblurnet ports: - "5579:5579" + +- name: Restart monitor + become: yes + shell: + cmd: docker restart monitor diff --git a/ansible/roles/node-exporter/tasks/main.yml b/ansible/roles/node-exporter/tasks/main.yml index 56f38b08d..5d46c2577 100644 --- a/ansible/roles/node-exporter/tasks/main.yml +++ b/ansible/roles/node-exporter/tasks/main.yml @@ -5,6 +5,10 @@ name: node-exporter image: prom/node-exporter container_default_behavior: no_defaults + networks_cli_compatible: yes + network_mode: default + networks: + - name: newsblurnet restart_policy: unless-stopped ports: - '9100:9100' @@ -18,4 +22,4 @@ dest: /etc/consul.d/node-exporter.json notify: - reload consul - when: disable_consul_services_ie_staging is not defined \ No newline at end of file + when: disable_consul_services_ie_staging is not defined diff --git a/ansible/roles/node/tasks/main.yml b/ansible/roles/node/tasks/main.yml index 36b35ffd9..0487f606b 100644 --- a/ansible/roles/node/tasks/main.yml +++ b/ansible/roles/node/tasks/main.yml @@ -1,7 +1,7 @@ --- - name: Copy node secrets copy: - src: /srv/secrets-newsblur/settings/dotenv.env + src: /srv/secrets-newsblur/settings/node_settings.env dest: /srv/newsblur/node/.env register: app_changed notify: restart node @@ -18,6 +18,44 @@ path: /srv/newsblur/node/.env line: 'SERVER_NAME = "{{ inventory_hostname }}"' +- name: Get the volume name + shell: ls /dev/disk/by-id/ | grep -v part + register: volume_name_raw + when: '"node-page" in inventory_hostname' + +- set_fact: + volume_name: "{{ volume_name_raw.stdout }}" + when: '"node-page" in inventory_hostname' + +- debug: + msg: "{{ volume_name }}" + when: '"node-page" in inventory_hostname' + +- name: Create the mount point + become: yes + file: + path: "/mnt/{{ inventory_hostname | regex_replace('-', '') }}" + state: directory + when: '"node-page" in inventory_hostname' + +- name: Mount volume read-write + become: yes + mount: + path: "/mnt/{{ inventory_hostname | regex_replace('-', '') }}" + src: "/dev/disk/by-id/{{ volume_name }}" + fstype: xfs + opts: defaults,discard + state: mounted + when: '"node-page" in inventory_hostname' + +- name: Symlink node-page volume from /srv/originals + become: yes + file: + dest: /srv/originals + src: "/mnt/{{ inventory_hostname | regex_replace('-', '') }}" + state: link + when: '"node-page" in inventory_hostname' + - name: Make docker network for newsblurnet become: yes docker_network: @@ -35,6 +73,7 @@ pull: true networks_cli_compatible: yes network_mode: default + hostname: "{{ inventory_hostname }}" networks: - name: newsblurnet ports: @@ -44,6 +83,8 @@ restart_policy: unless-stopped volumes: - /srv/newsblur/node:/srv/node + - /srv/originals:/srv/originals + - "/mnt/{{ inventory_hostname | regex_replace('-', '') }}:/mnt/{{ inventory_hostname | regex_replace('-', '') }}" with_items: - node-socket - node-page @@ -59,6 +100,7 @@ image: "{{ item.image }}" state: started container_default_behavior: no_defaults + hostname: "{{ inventory_hostname }}" pull: true ports: - "{{ item.ports }}" @@ -108,4 +150,4 @@ -v /srv/newsblur:/srv/newsblur --network=newsblurnet --hostname {{ ansible_hostname }} - newsblur/newsblur_python3 /srv/newsblur/utils/monitor_disk_usage.py $OUTPUT \ No newline at end of file + newsblur/newsblur_python3 /srv/newsblur/utils/monitor_disk_usage.py $OUTPUT diff --git a/ansible/roles/postgres/tasks/main.yml b/ansible/roles/postgres/tasks/main.yml index aa271a577..23b576ad1 100644 --- a/ansible/roles/postgres/tasks/main.yml +++ b/ansible/roles/postgres/tasks/main.yml @@ -5,7 +5,7 @@ name: postgres image: postgres:13.1 state: started - hostname: postgres + hostname: "{{ inventory_hostname }}" env: POSTGRES_USER: newsblur POSTGRES_PASSWORD: newsblur @@ -62,4 +62,4 @@ -v /backup/:/backup/ --network=newsblurnet newsblur/newsblur_python3 - /srv/newsblur/utils/backups/backup_psql.py $BUCKET \ No newline at end of file + /srv/newsblur/utils/backups/backup_psql.py $BUCKET diff --git a/ansible/roles/prometheus/tasks/main.yml b/ansible/roles/prometheus/tasks/main.yml index 6175781e1..a85689026 100644 --- a/ansible/roles/prometheus/tasks/main.yml +++ b/ansible/roles/prometheus/tasks/main.yml @@ -2,7 +2,7 @@ - name: Template file for prometheus vars: - monitor_server: "{{ 'staging.newsblur.com' if disable_consul_services_ie_staging is defined else 'beta.newsblur.com' }}" + monitor_server: "{{ 'staging.newsblur.com' if disable_consul_services_ie_staging is defined else 'newsblur.com' }}" template: src: /srv/newsblur/docker/prometheus/prometheus.consul.yml.j2 dest: /srv/newsblur/docker/prometheus/prometheus.yml @@ -42,4 +42,4 @@ container_default_behavior: no_defaults volumes: - /srv/newsblur/docker/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml - - /srv/newsblur/docker/volumes/prometheus_data:/prometheus + - /mnt/metrics/prometheus_data:/prometheus diff --git a/ansible/roles/redis/tasks/main.yml b/ansible/roles/redis/tasks/main.yml index ca7c4093b..6bb241fcd 100644 --- a/ansible/roles/redis/tasks/main.yml +++ b/ansible/roles/redis/tasks/main.yml @@ -5,6 +5,7 @@ name: redis image: redis:6.2.1 state: started + hostname: "{{ inventory_hostname }}" ports: - 6379:6379 restart_policy: unless-stopped diff --git a/ansible/roles/ufw/tasks/main.yml b/ansible/roles/ufw/tasks/main.yml index a91ab15da..cbaed1ede 100644 --- a/ansible/roles/ufw/tasks/main.yml +++ b/ansible/roles/ufw/tasks/main.yml @@ -30,6 +30,10 @@ with_items: - 10.0.0.0/8 - 172.18.0.0/16 + - 172.17.0.0/16 + tags: + - firewall + - ufw - name: Allow all access from inventory hosts old + new become: yes diff --git a/ansible/roles/web/tasks/main.yml b/ansible/roles/web/tasks/main.yml index 5b8daf058..b643c68a5 100644 --- a/ansible/roles/web/tasks/main.yml +++ b/ansible/roles/web/tasks/main.yml @@ -60,6 +60,7 @@ DOCKERBUILD: "" state: started command: gunicorn --config /srv/newsblur/config/gunicorn_conf.py newsblur_web.wsgi:application + hostname: "{{ inventory_hostname }}" networks_cli_compatible: yes network_mode: default networks: diff --git a/ansible/setup.yml b/ansible/setup.yml index ed07a7e1a..958d65a8d 100644 --- a/ansible/setup.yml +++ b/ansible/setup.yml @@ -10,7 +10,7 @@ - import_playbook: playbooks/setup_postgres.yml when: "'postgres' in group_names" - import_playbook: playbooks/setup_mongo.yml - when: "'mongo' in group_names" + when: "'mongo' in group_names or 'mongo_analytics' in group_names" - import_playbook: playbooks/setup_redis.yml when: "'redis' in group_names" - import_playbook: playbooks/setup_elasticsearch.yml diff --git a/apps/api/views.py b/apps/api/views.py index 14fe16379..357fdd021 100644 --- a/apps/api/views.py +++ b/apps/api/views.py @@ -85,8 +85,8 @@ def add_site_load_script(request, token): starred_counts = {} def image_base64(image_name, path='icons/circular/'): - image_file = open(os.path.join(settings.MEDIA_ROOT, 'img/%s%s' % (path, image_name))) - return base64.b64encode(image_file.read()) + image_file = open(os.path.join(settings.MEDIA_ROOT, 'img/%s%s' % (path, image_name)), 'rb') + return base64.b64encode(image_file.read()).decode('utf-8') accept_image = image_base64('newuser_icn_setup.png') error_image = image_base64('newuser_icn_sharewith_active.png') @@ -500,7 +500,7 @@ def save_story(request, token=None): def ip_addresses(request): import digitalocean - doapi = digitalocean.Manager(token=settings.DO_TOKEN_FABRIC) + doapi = digitalocean.Manager(token=settings.DO_TOKEN_API_IPADDRESSES) droplets = doapi.get_all_droplets() addresses = '\n'.join([d.ip_address for d in droplets]) return HttpResponse(addresses, content_type='text/plain') diff --git a/apps/feed_import/views.py b/apps/feed_import/views.py index 3b95e306d..85ef980b1 100644 --- a/apps/feed_import/views.py +++ b/apps/feed_import/views.py @@ -12,6 +12,7 @@ from django.http import HttpResponse, HttpResponseRedirect from django.conf import settings from django.urls import reverse from django.contrib.auth import login as login_user +from mongoengine.errors import ValidationError from apps.reader.forms import SignupForm from apps.reader.models import UserSubscription from apps.feed_import.models import OAuthToken @@ -36,10 +37,10 @@ def opml_upload(request): xml_opml = file.read() try: UploadedOPML.objects.create(user_id=request.user.pk, opml_file=xml_opml) - except (UnicodeDecodeError, InvalidStringData): + except (UnicodeDecodeError, ValidationError, InvalidStringData): folders = None code = -1 - message = "There was a Unicode decode error when reading your OPML file." + message = "There was a Unicode decode error when reading your OPML file. Ensure it's a text file with a .opml or .xml extension. Is it a zip file?" opml_importer = OPMLImporter(xml_opml, request.user) try: diff --git a/apps/monitor/views/newsblur_dbtimes.py b/apps/monitor/views/newsblur_dbtimes.py index 0c309e4a2..622c1e3fc 100755 --- a/apps/monitor/views/newsblur_dbtimes.py +++ b/apps/monitor/views/newsblur_dbtimes.py @@ -12,9 +12,17 @@ class DbTimes(View): 'sql_avg': MStatistics.get('latest_sql_avg'), 'mongo_avg': MStatistics.get('latest_mongo_avg'), 'redis_avg': MStatistics.get('latest_redis_avg'), + 'redis_user_avg': MStatistics.get('latest_redis_user_avg'), + 'redis_story_avg': MStatistics.get('latest_redis_story_avg'), + 'redis_session_avg': MStatistics.get('latest_redis_session_avg'), + 'redis_pubsub_avg': MStatistics.get('latest_redis_pubsub_avg'), 'task_sql_avg': MStatistics.get('latest_task_sql_avg'), 'task_mongo_avg': MStatistics.get('latest_task_mongo_avg'), 'task_redis_avg': MStatistics.get('latest_task_redis_avg'), + 'task_redis_user_avg': MStatistics.get('latest_task_redis_user_avg'), + 'task_redis_story_avg': MStatistics.get('latest_task_redis_story_avg'), + 'task_redis_session_avg': MStatistics.get('latest_task_redis_session_avg'), + 'task_redis_pubsub_avg': MStatistics.get('latest_task_redis_pubsub_avg'), } chart_name = "db_times" chart_type = "counter" diff --git a/apps/notifications/models.py b/apps/notifications/models.py index d5d12e814..52a8b63bf 100644 --- a/apps/notifications/models.py +++ b/apps/notifications/models.py @@ -4,7 +4,6 @@ import html import redis import re import mongoengine as mongo -from boto.ses.connection import BotoServerError from django.conf import settings from django.contrib.auth.models import User from django.contrib.sites.models import Site @@ -22,7 +21,7 @@ from utils import mongoengine_fields from apns2.errors import BadDeviceToken, Unregistered from apns2.client import APNsClient from apns2.payload import Payload -from bs4 import BeautifulSoup, Tag +from bs4 import BeautifulSoup import urllib.parse class NotificationFrequency(enum.Enum): @@ -311,11 +310,11 @@ class MUserFeedNotification(mongo.Document): from_email='NewsBlur <%s>' % from_address, to=[to_address]) msg.attach_alternative(html, "text/html") - try: - msg.send() - except BotoServerError as e: - logging.user(usersub.user, '~BMStory notification by email error: ~FR%s' % e) - return + # try: + msg.send() + # except BotoServerError as e: + # logging.user(usersub.user, '~BMStory notification by email error: ~FR%s' % e) + # return logging.user(usersub.user, '~BMStory notification by email: ~FY~SB%s~SN~BM~FY/~SB%s' % (story['story_title'][:50], usersub.feed.feed_title[:50])) diff --git a/apps/profile/management/commands/check_db.py b/apps/profile/management/commands/check_db.py index 9055d71d9..41e9662ea 100644 --- a/apps/profile/management/commands/check_db.py +++ b/apps/profile/management/commands/check_db.py @@ -13,6 +13,6 @@ class Command(BaseCommand): c = db_conn.cursor() connected = True print("Connected to postgres") - except OperationalError: - print("Waiting for db_postgres") + except OperationalError as e: + print(f"Waiting for db_postgres: {e}") time.sleep(5) diff --git a/apps/profile/middleware.py b/apps/profile/middleware.py index 27dbe8153..ddba1e1ba 100644 --- a/apps/profile/middleware.py +++ b/apps/profile/middleware.py @@ -146,15 +146,27 @@ class SQLLogToConsoleMiddleware: for query in queries: if query.get('mongo'): query['sql'] = "~FM%s: %s" % (query['mongo']['collection'], query['mongo']['query']) + elif query.get('db_redis'): + query['sql'] = "~FC%s" % (query['db_redis']['query']) elif query.get('redis'): query['sql'] = "~FC%s" % (query['redis']['query']) + elif query.get('redis_user'): + query['sql'] = "~FC%s" % (query['redis_user']['query']) + elif query.get('redis_story'): + query['sql'] = "~FC%s" % (query['redis_story']['query']) + elif query.get('redis_session'): + query['sql'] = "~FC%s" % (query['redis_session']['query']) + elif query.get('redis_pubsub'): + query['sql'] = "~FC%s" % (query['redis_pubsub']['query']) + elif 'sql' not in query: + logging.debug(" ***> Query log missing: %s" % query) else: query['sql'] = re.sub(r'SELECT (.*?) FROM', 'SELECT * FROM', query['sql']) query['sql'] = re.sub(r'SELECT', '~FYSELECT', query['sql']) query['sql'] = re.sub(r'INSERT', '~FGINSERT', query['sql']) query['sql'] = re.sub(r'UPDATE', '~FY~SBUPDATE', query['sql']) query['sql'] = re.sub(r'DELETE', '~FR~SBDELETE', query['sql']) - if settings.DEBUG and settings.DEBUG_QUERIES: + if settings.DEBUG and settings.DEBUG_QUERIES and not getattr(settings, 'DEBUG_QUERIES_SUMMARY_ONLY', False): t = Template("{% for sql in sqllog %}{% if not forloop.first %} {% endif %}[{{forloop.counter}}] ~FC{{sql.time}}s~FW: {{sql.sql|safe}}{% if not forloop.last %}\n{% endif %}{% endfor %}") logging.debug(t.render(Context({ 'sqllog': queries, @@ -164,9 +176,17 @@ class SQLLogToConsoleMiddleware: times_elapsed = { 'sql': sum([float(q['time']) for q in queries if not q.get('mongo') and - not q.get('redis')]), + not q.get('redis_user') and + not q.get('redis_story') and + not q.get('redis_session') and + not q.get('redis_pubsub') and + not q.get('db_redis')]), 'mongo': sum([float(q['time']) for q in queries if q.get('mongo')]), - 'redis': sum([float(q['time']) for q in queries if q.get('redis')]), + 'db_redis': sum([float(q['time']) for q in queries if q.get('db_redis')]), + 'redis_user': sum([float(q['time']) for q in queries if q.get('redis_user')]), + 'redis_story': sum([float(q['time']) for q in queries if q.get('redis_story')]), + 'redis_session': sum([float(q['time']) for q in queries if q.get('redis_session')]), + 'redis_pubsub': sum([float(q['time']) for q in queries if q.get('redis_pubsub')]), } setattr(request, 'sql_times_elapsed', times_elapsed) else: diff --git a/apps/profile/models.py b/apps/profile/models.py index 4de45bf86..a306b0aee 100644 --- a/apps/profile/models.py +++ b/apps/profile/models.py @@ -1125,12 +1125,13 @@ def paypal_signup(sender, **kwargs): try: user = User.objects.get(username__iexact=ipn_obj.custom) except User.DoesNotExist: - user = User.objects.get(email__iexact=ipn_obj.payer_email) - except User.DoesNotExist: - logging.debug(" ---> Paypal subscription not found during flagging: %s/%s" % ( - ipn_obj.payer_email, - ipn_obj.custom)) - return {"code": -1, "message": "User doesn't exist."} + try: + user = User.objects.get(email__iexact=ipn_obj.payer_email) + except User.DoesNotExist: + logging.debug(" ---> Paypal subscription not found during flagging: %s/%s" % ( + ipn_obj.payer_email, + ipn_obj.custom)) + return {"code": -1, "message": "User doesn't exist."} logging.user(user, "~BC~SB~FBPaypal subscription signup") try: @@ -1149,12 +1150,13 @@ def paypal_payment_history_sync(sender, **kwargs): try: user = User.objects.get(username__iexact=ipn_obj.custom) except User.DoesNotExist: - user = User.objects.get(email__iexact=ipn_obj.payer_email) - except User.DoesNotExist: - logging.debug(" ---> Paypal subscription not found during flagging: %s/%s" % ( - ipn_obj.payer_email, - ipn_obj.custom)) - return {"code": -1, "message": "User doesn't exist."} + try: + user = User.objects.get(email__iexact=ipn_obj.payer_email) + except User.DoesNotExist: + logging.debug(" ---> Paypal subscription not found during flagging: %s/%s" % ( + ipn_obj.payer_email, + ipn_obj.custom)) + return {"code": -1, "message": "User doesn't exist."} logging.user(user, "~BC~SB~FBPaypal subscription payment") try: @@ -1168,13 +1170,13 @@ def paypal_payment_was_flagged(sender, **kwargs): try: user = User.objects.get(username__iexact=ipn_obj.custom) except User.DoesNotExist: - if ipn_obj.payer_email: + try: user = User.objects.get(email__iexact=ipn_obj.payer_email) - except User.DoesNotExist: - logging.debug(" ---> Paypal subscription not found during flagging: %s/%s" % ( - ipn_obj.payer_email, - ipn_obj.custom)) - return {"code": -1, "message": "User doesn't exist."} + except User.DoesNotExist: + logging.debug(" ---> Paypal subscription not found during flagging: %s/%s" % ( + ipn_obj.payer_email, + ipn_obj.custom)) + return {"code": -1, "message": "User doesn't exist."} try: user.profile.setup_premium_history() diff --git a/apps/push/models.py b/apps/push/models.py index 1180d771d..00fe7e5e3 100644 --- a/apps/push/models.py +++ b/apps/push/models.py @@ -154,7 +154,7 @@ class PushSubscription(models.Model): if needs_update: logging.debug(u' ---> [%-30s] ~FR~BKUpdating PuSH hub/topic: %s / %s' % ( - unicode(self.feed)[:30], hub_url, self_url)) + self.feed, hub_url, self_url)) expiration_time = self.lease_expires - datetime.now() seconds = expiration_time.days*86400 + expiration_time.seconds try: @@ -163,7 +163,7 @@ class PushSubscription(models.Model): lease_seconds=seconds) except TimeoutError: logging.debug(u' ---> [%-30s] ~FR~BKTimed out updating PuSH hub/topic: %s / %s' % ( - unicode(self.feed)[:30], hub_url, self_url)) + self.feed, hub_url, self_url)) def __str__(self): @@ -173,6 +173,3 @@ class PushSubscription(models.Model): verified = u'unverified' return u'to %s on %s: %s' % ( self.topic, self.hub, verified) - - def __str__(self): - return str(unicode(self)) diff --git a/apps/push/views.py b/apps/push/views.py index 38c051cdd..16a1c43a7 100644 --- a/apps/push/views.py +++ b/apps/push/views.py @@ -32,7 +32,7 @@ def push_callback(request, push_id): subscription.save() subscription.feed.setup_push() - logging.debug(' ---> [%-30s] [%s] ~BBVerified PuSH' % (unicode(subscription.feed)[:30], subscription.feed_id)) + logging.debug(' ---> [%-30s] [%s] ~BBVerified PuSH' % (subscription.feed, subscription.feed_id)) verified.send(sender=subscription) @@ -46,7 +46,7 @@ def push_callback(request, push_id): latest_push_date = datetime.datetime.strptime(latest_push, '%Y-%m-%d %H:%M:%S') latest_push_date_delta = datetime.datetime.now() - latest_push_date if latest_push_date > datetime.datetime.now() - datetime.timedelta(minutes=1): - logging.debug(' ---> [%-30s] ~SN~FBSkipping feed fetch, pushed %s seconds ago' % (unicode(subscription.feed)[:30], latest_push_date_delta.seconds)) + logging.debug(' ---> [%-30s] ~SN~FBSkipping feed fetch, pushed %s seconds ago' % (subscription.feed, latest_push_date_delta.seconds)) return HttpResponse('Slow down, you just pushed %s seconds ago...' % latest_push_date_delta.seconds, status=429) # XXX TODO: Optimize this by removing feedparser. It just needs to find out @@ -62,7 +62,7 @@ def push_callback(request, push_id): MFetchHistory.add(feed_id=subscription.feed_id, fetch_type='push') else: - logging.debug(' ---> [%-30s] ~FBSkipping feed fetch, no actives: %s' % (unicode(subscription.feed)[:30], subscription.feed)) + logging.debug(' ---> [%-30s] ~FBSkipping feed fetch, no actives: %s' % (subscription.feed, subscription.feed)) return HttpResponse('OK') return Http404 diff --git a/apps/reader/urls.py b/apps/reader/urls.py index 3d2ac3356..9c109ab1c 100644 --- a/apps/reader/urls.py +++ b/apps/reader/urls.py @@ -22,7 +22,8 @@ urlpatterns = [ url(r'^starred_stories', views.load_starred_stories, name='load-starred-stories'), url(r'^read_stories', views.load_read_stories, name='load-read-stories'), url(r'^starred_story_hashes', views.starred_story_hashes, name='starred-story-hashes'), - url(r'^starred_rss/(?P\d+)/(?P\w+)/(?P[-\w]+)?/?$', views.starred_stories_rss_feed, name='starred-stories-rss-feed'), + url(r'^starred_rss/(?P\d+)/(?P\w+)/?$', views.starred_stories_rss_feed, name='starred-stories-rss-feed'), + url(r'^starred_rss/(?P\d+)/(?P\w+)/(?P[-\w]+)?/?$', views.starred_stories_rss_feed_tag, name='starred-stories-rss-feed-tag'), url(r'^folder_rss/(?P\d+)/(?P\w+)/(?P\w+)/(?P[-\w]+)?/?$', views.folder_rss_feed, name='folder-rss-feed'), url(r'^unread_story_hashes', views.unread_story_hashes, name='unread-story-hashes'), url(r'^starred_counts', views.starred_counts, name='starred-counts'), diff --git a/apps/reader/views.py b/apps/reader/views.py index 5a5ec1c0b..cefb515da 100644 --- a/apps/reader/views.py +++ b/apps/reader/views.py @@ -1,6 +1,5 @@ import datetime import time -import boto import redis import requests import random @@ -77,7 +76,6 @@ ALLOWED_SUBDOMAINS = [ 'debug', 'debug3', 'nb', - 'old', ] def get_subdomain(request): @@ -891,9 +889,9 @@ def load_feed_page(request, feed_id): if settings.BACKED_BY_AWS['pages_on_s3'] and feed.s3_page: if settings.PROXY_S3_PAGES: - key = settings.S3_CONN.get_bucket(settings.S3_PAGES_BUCKET_NAME).get_key(feed.s3_pages_key) + key = settings.S3_CONN.Bucket(settings.S3_PAGES_BUCKET_NAME).Object(key=feed.s3_pages_key) if key: - compressed_data = key.get_contents_as_string() + compressed_data = key.get()["Body"] response = HttpResponse(compressed_data, content_type="text/html; charset=utf-8") response['Content-Encoding'] = 'gzip' @@ -1070,38 +1068,50 @@ def starred_story_hashes(request): return dict(starred_story_hashes=story_hashes) -def starred_stories_rss_feed(request, user_id, secret_token, tag_slug): +def starred_stories_rss_feed(request, user_id, secret_token): + return starred_stories_rss_feed_tag(request, user_id, secret_token, tag_slug=None) + +def starred_stories_rss_feed_tag(request, user_id, secret_token, tag_slug): try: user = User.objects.get(pk=user_id) except User.DoesNotExist: raise Http404 - try: - tag_counts = MStarredStoryCounts.objects.get(user_id=user_id, slug=tag_slug) - except MStarredStoryCounts.MultipleObjectsReturned: - tag_counts = MStarredStoryCounts.objects(user_id=user_id, slug=tag_slug).first() - except MStarredStoryCounts.DoesNotExist: - raise Http404 + if tag_slug: + try: + tag_counts = MStarredStoryCounts.objects.get(user_id=user_id, slug=tag_slug) + except MStarredStoryCounts.MultipleObjectsReturned: + tag_counts = MStarredStoryCounts.objects(user_id=user_id, slug=tag_slug).first() + except MStarredStoryCounts.DoesNotExist: + raise Http404 + else: + _, starred_count = MStarredStoryCounts.user_counts(user.pk, include_total=True) data = {} - data['title'] = "Saved Stories - %s" % tag_counts.tag + if tag_slug: + data['title'] = "Saved Stories - %s" % tag_counts.tag + else: + data['title'] = "Saved Stories" data['link'] = "%s%s" % ( settings.NEWSBLUR_URL, reverse('saved-stories-tag', kwargs=dict(tag_name=tag_slug))) - data['description'] = "Stories saved by %s on NewsBlur with the tag \"%s\"." % (user.username, - tag_counts.tag) + if tag_slug: + data['description'] = "Stories saved by %s on NewsBlur with the tag \"%s\"." % (user.username, + tag_counts.tag) + else: + data['description'] = "Stories saved by %s on NewsBlur." % (user.username) data['lastBuildDate'] = datetime.datetime.utcnow() data['generator'] = 'NewsBlur - %s' % settings.NEWSBLUR_URL data['docs'] = None data['author_name'] = user.username data['feed_url'] = "%s%s" % ( settings.NEWSBLUR_URL, - reverse('starred-stories-rss-feed', + reverse('starred-stories-rss-feed-tag', kwargs=dict(user_id=user_id, secret_token=secret_token, tag_slug=tag_slug)), ) rss = feedgenerator.Atom1Feed(**data) - if not tag_counts.tag: + if not tag_slug or not tag_counts.tag: starred_stories = MStarredStory.objects( user_id=user.pk ).order_by('-starred_date').limit(25) @@ -1131,8 +1141,8 @@ def starred_stories_rss_feed(request, user_id, secret_token, tag_slug): logging.user(request, "~FBGenerating ~SB%s~SN's saved story RSS feed (%s, %s stories): ~FM%s" % ( user.username, - tag_counts.tag, - tag_counts.count, + tag_counts.tag if tag_slug else "[All stories]", + tag_counts.count if tag_slug else starred_count, request.META.get('HTTP_USER_AGENT', "")[:24] )) return HttpResponse(rss.writeString('utf-8'), content_type='application/rss+xml') @@ -2693,11 +2703,11 @@ def send_story_email(request): cc=cc, headers={'Reply-To': "%s <%s>" % (from_name, from_email)}) msg.attach_alternative(html, "text/html") - try: - msg.send() - except boto.ses.connection.BotoServerError as e: - code = -1 - message = "Email error: %s" % str(e) + # try: + msg.send() + # except boto.ses.connection.BotoServerError as e: + # code = -1 + # message = "Email error: %s" % str(e) share_user_profile.save_sent_email() diff --git a/apps/rss_feeds/icon_importer.py b/apps/rss_feeds/icon_importer.py index 76c139b46..e819358fa 100644 --- a/apps/rss_feeds/icon_importer.py +++ b/apps/rss_feeds/icon_importer.py @@ -15,7 +15,7 @@ import base64 import http.client from PIL import BmpImagePlugin, PngImagePlugin, Image from socket import error as SocketError -from boto.s3.key import Key +import boto3 from io import BytesIO from django.conf import settings from django.http import HttpResponse @@ -106,12 +106,14 @@ class IconImporter(object): def save_to_s3(self, image_str): expires = datetime.datetime.now() + datetime.timedelta(days=60) expires = expires.strftime("%a, %d %b %Y %H:%M:%S GMT") - k = Key(settings.S3_CONN.get_bucket(settings.S3_ICONS_BUCKET_NAME)) - k.key = self.feed.s3_icons_key - k.set_metadata('Content-Type', 'image/png') - k.set_metadata('Expires', expires) - k.set_contents_from_string(base64.b64decode(image_str)) - k.set_acl('public-read') + base64.b64decode(image_str) + settings.S3_CONN.Object(settings.S3_ICONS_BUCKET_NAME, + self.feed.s3_icons_key).put(Body=base64.b64decode(image_str), + ExtraArgs={ + 'Content-Type': 'image/png', + 'Expires': expires, + 'ACL': 'public-read', + }) self.feed.s3_icon = True self.feed.save() @@ -217,8 +219,8 @@ class IconImporter(object): except requests.ConnectionError: pass elif settings.BACKED_BY_AWS.get('pages_on_s3') and self.feed.s3_page: - key = settings.S3_CONN.get_bucket(settings.S3_PAGES_BUCKET_NAME).get_key(self.feed.s3_pages_key) - compressed_content = key.get_contents_as_string() + key = settings.S3_CONN.Bucket(settings.S3_PAGES_BUCKET_NAME).Object(key=self.feed.s3_pages_key) + compressed_content = key.get()["Body"].read() stream = BytesIO(compressed_content) gz = gzip.GzipFile(fileobj=stream) try: diff --git a/apps/rss_feeds/management/commands/backup_mongo.py b/apps/rss_feeds/management/commands/backup_mongo.py deleted file mode 100644 index 33293bb9d..000000000 --- a/apps/rss_feeds/management/commands/backup_mongo.py +++ /dev/null @@ -1,8 +0,0 @@ -from django.core.management.base import BaseCommand -from apps.rss_feeds.tasks import BackupMongo - -class Command(BaseCommand): - option_list = BaseCommand.option_list - - def handle(self, *args, **options): - BackupMongo().apply() \ No newline at end of file diff --git a/apps/rss_feeds/models.py b/apps/rss_feeds/models.py index f80442222..d6016d903 100755 --- a/apps/rss_feeds/models.py +++ b/apps/rss_feeds/models.py @@ -2827,6 +2827,8 @@ class MStory(mongo.Document): continue if image_url and len(image_url) >= 1024: continue + if 'feedburner.com' in image_url: + continue image_url = urllib.parse.urljoin(self.story_permalink, image_url) image_urls.append(image_url) diff --git a/apps/rss_feeds/page_importer.py b/apps/rss_feeds/page_importer.py index e0fecfa84..5d1cba785 100644 --- a/apps/rss_feeds/page_importer.py +++ b/apps/rss_feeds/page_importer.py @@ -10,7 +10,6 @@ from django.contrib.sites.models import Site from django.utils.encoding import smart_bytes from mongoengine.queryset import NotUniqueError from socket import error as SocketError -from boto.s3.key import Key from django.conf import settings from django.utils.text import compress_string as compress_string_with_gzip from utils import log as logging @@ -323,13 +322,16 @@ class PageImporter(object): def save_page_s3(self, html): - k = Key(settings.S3_CONN.get_bucket(settings.S3_PAGES_BUCKET_NAME)) - k.key = self.feed.s3_pages_key - k.set_metadata('Content-Encoding', 'gzip') - k.set_metadata('Content-Type', 'text/html') - k.set_metadata('Access-Control-Allow-Origin', '*') - k.set_contents_from_string(compress_string_with_gzip(html.encode('utf-8'))) - k.set_acl('public-read') + s3_object = settings.S3_CONN.Object(settings.S3_PAGES_BUCKET_NAME, + self.feed.s3_pages_key) + s3_object.put(Body=compress_string_with_gzip(html.encode('utf-8')), + ExtraArgs={ + 'Content-Type': 'text/html', + 'Content-Encoding': 'gzip', + 'Access-Control-Allow-Origin': '*', + 'Expires': expires, + 'ACL': 'public-read', + }) try: feed_page = MFeedPage.objects.get(feed_id=self.feed.pk) @@ -345,8 +347,7 @@ class PageImporter(object): return True def delete_page_s3(self): - k = Key(settings.S3_CONN.get_bucket(settings.S3_PAGES_BUCKET_NAME)) - k.key = self.feed.s3_pages_key + k = settings.S3_CONN.Bucket(settings.S3_PAGES_BUCKET_NAME).Object(key=self.feed.s3_pages_key) k.delete() self.feed.s3_page = False diff --git a/apps/rss_feeds/tasks.py b/apps/rss_feeds/tasks.py index d0925e2c6..be12652d8 100644 --- a/apps/rss_feeds/tasks.py +++ b/apps/rss_feeds/tasks.py @@ -6,7 +6,6 @@ import redis from newsblur_web.celeryapp import app from celery.exceptions import SoftTimeLimitExceeded from utils import log as logging -from utils import s3_utils as s3 from django.conf import settings from apps.profile.middleware import DBProfilerMiddleware from utils.mongo_raw_log_middleware import MongoDumpMiddleware @@ -189,33 +188,6 @@ def PushFeeds(feed_id, xml): if feed: feed.update(options=options) -@app.task(name='backup-mongo', ignore_result=True) -def BackupMongo(): - COLLECTIONS = "classifier_tag classifier_author classifier_feed classifier_title userstories starred_stories shared_stories category category_site sent_emails social_profile social_subscription social_services statistics feedback" - - date = time.strftime('%Y-%m-%d-%H-%M') - collections = COLLECTIONS.split(' ') - db_name = 'newsblur' - dir_name = 'backup_mongo_%s' % date - filename = '%s.tgz' % dir_name - - os.mkdir(dir_name) - - for collection in collections: - cmd = 'mongodump --db %s --collection %s -o %s' % (db_name, collection, dir_name) - logging.debug(' ---> ~FMDumping ~SB%s~SN: %s' % (collection, cmd)) - os.system(cmd) - - cmd = 'tar -jcf %s %s' % (filename, dir_name) - os.system(cmd) - - logging.debug(' ---> ~FRUploading ~SB~FM%s~SN~FR to S3...' % filename) - s3.save_file_in_s3(filename) - shutil.rmtree(dir_name) - os.remove(filename) - logging.debug(' ---> ~FRFinished uploading ~SB~FM%s~SN~FR to S3.' % filename) - - @app.task() def ScheduleImmediateFetches(feed_ids, user_id=None): from apps.rss_feeds.models import Feed diff --git a/apps/rss_feeds/text_importer.py b/apps/rss_feeds/text_importer.py index e1638444f..4300a5ab9 100644 --- a/apps/rss_feeds/text_importer.py +++ b/apps/rss_feeds/text_importer.py @@ -1,11 +1,11 @@ import requests import urllib3 import zlib +from vendor import readability from simplejson.decoder import JSONDecodeError from requests.packages.urllib3.exceptions import LocationParseError from socket import error as SocketError from mongoengine.queryset import NotUniqueError -from vendor.readability import readability from lxml.etree import ParserError from utils import log as logging from utils.feed_functions import timelimit, TimeoutError @@ -57,6 +57,7 @@ class TextImporter: if not use_mercury or not results: logging.user(self.request, "~SN~FRFailed~FY to fetch ~FGoriginal text~FY with Mercury, trying readability...", warn_color=False) + results = self.fetch_manually(skip_save=skip_save, return_document=return_document) return results @@ -106,10 +107,18 @@ class TextImporter: if not resp: return + @timelimit(5) + def extract_text(resp): + try: + text = resp.text + except (LookupError, TypeError): + text = resp.content + return text try: - text = resp.text - except (LookupError, TypeError): - text = resp.content + text = extract_text(resp) + except TimeoutError: + logging.user(self.request, "~SN~FRFailed~FY to fetch ~FGoriginal text~FY: timed out on resp.text") + return # if self.debug: # logging.user(self.request, "~FBOriginal text's website: %s" % text) @@ -227,6 +236,8 @@ class TextImporter: headers["content-type"] = "application/json" headers["x-api-key"] = mercury_api_key domain = Site.objects.get_current().domain + if settings.DOCKERBUILD: + domain = 'haproxy' url = f"https://{domain}/rss_feeds/original_text_fetcher?url={url}" try: diff --git a/apps/social/models.py b/apps/social/models.py index 05dd7b7d9..0d0d2a041 100644 --- a/apps/social/models.py +++ b/apps/social/models.py @@ -161,6 +161,9 @@ class MSocialProfile(mongo.Document): profile = cls.objects.create(user_id=user_id) profile.save() + if not profile.username: + profile.save() + return profile @property @@ -171,6 +174,8 @@ class MSocialProfile(mongo.Document): return None def save(self, *args, **kwargs): + if not self.username: + self.import_user_fields() if not self.subscription_count: self.count_follows(skip_save=True) if self.bio and len(self.bio) > MSocialProfile.bio.max_length: @@ -433,6 +438,11 @@ class MSocialProfile(mongo.Document): return [u for u in self.follower_user_ids if u != self.user_id] return self.follower_user_ids + def import_user_fields(self): + user = User.objects.get(pk=self.user_id) + self.username = user.username + self.email = user.email + def count_follows(self, skip_save=False): self.subscription_count = UserSubscription.objects.filter(user__pk=self.user_id).count() self.shared_stories_count = MSharedStory.objects.filter(user_id=self.user_id).count() diff --git a/apps/social/urls.py b/apps/social/urls.py index 57647c997..1db6b2262 100644 --- a/apps/social/urls.py +++ b/apps/social/urls.py @@ -29,6 +29,7 @@ urlpatterns = [ # url(r'^remove_like_reply/?$', views.remove_like_reply, name='social-remove-like-reply'), url(r'^comment/(?P\w+)/reply/(?P\w+)/?$', views.comment_reply, name='social-comment-reply'), url(r'^comment/(?P\w+)/?$', views.comment, name='social-comment'), + url(r'^rss/(?P\d+)/?$', views.shared_stories_rss_feed, name='shared-stories-rss-feed'), url(r'^rss/(?P\d+)/(?P[-\w]+)?$', views.shared_stories_rss_feed, name='shared-stories-rss-feed'), url(r'^stories/(?P\w+)/(?P[-\w]+)?/?$', views.load_social_stories, name='load-social-stories'), url(r'^page/(?P\w+)/(?P[-\w]+)?/?$', views.load_social_page, name='load-social-page'), diff --git a/apps/social/views.py b/apps/social/views.py index 1dc82252d..03690df1f 100644 --- a/apps/social/views.py +++ b/apps/social/views.py @@ -1315,7 +1315,7 @@ def shared_stories_rss_feed_noid(request): return index @ratelimit(minutes=1, requests=5) -def shared_stories_rss_feed(request, user_id, username): +def shared_stories_rss_feed(request, user_id, username=None): try: user = User.objects.get(pk=user_id) except User.DoesNotExist: diff --git a/apps/statistics/models.py b/apps/statistics/models.py index f4cb5bf37..a52d67eb9 100644 --- a/apps/statistics/models.py +++ b/apps/statistics/models.py @@ -198,12 +198,26 @@ class MStatistics(mongo.Document): r = redis.Redis(connection_pool=settings.REDIS_STATISTICS_POOL) db_times = {} latest_db_times = {} - - for db in ['sql', 'mongo', 'redis', 'task_sql', 'task_mongo', 'task_redis']: + + for db in ['sql', + 'mongo', + 'redis', + 'redis_user', + 'redis_story', + 'redis_session', + 'redis_pubsub', + 'task_sql', + 'task_mongo', + 'task_redis', + 'task_redis_user', + 'task_redis_story', + 'task_redis_session', + 'task_redis_pubsub', + ]: db_times[db] = [] for hour in range(24): start_hours_ago = now - datetime.timedelta(hours=hour+1) - + pipe = r.pipeline() for m in range(60): minute = start_hours_ago + datetime.timedelta(minutes=m) @@ -239,9 +253,17 @@ class MStatistics(mongo.Document): ('latest_sql_avg', latest_db_times['sql']), ('latest_mongo_avg', latest_db_times['mongo']), ('latest_redis_avg', latest_db_times['redis']), + ('latest_redis_user_avg', latest_db_times['redis_user']), + ('latest_redis_story_avg', latest_db_times['redis_story']), + ('latest_redis_session_avg',latest_db_times['redis_session']), + ('latest_redis_pubsub_avg', latest_db_times['redis_pubsub']), ('latest_task_sql_avg', latest_db_times['task_sql']), ('latest_task_mongo_avg', latest_db_times['task_mongo']), ('latest_task_redis_avg', latest_db_times['task_redis']), + ('latest_task_redis_user_avg', latest_db_times['task_redis_user']), + ('latest_task_redis_story_avg', latest_db_times['task_redis_story']), + ('latest_task_redis_session_avg',latest_db_times['task_redis_session']), + ('latest_task_redis_pubsub_avg', latest_db_times['task_redis_pubsub']), ) for key, value in values: cls.objects(key=key).update_one(upsert=True, set__key=key, set__value=value) diff --git a/fabfile.py b/archive/fabfile.py similarity index 100% rename from fabfile.py rename to archive/fabfile.py diff --git a/config/munin/mongodb_replset_lag b/config/munin/mongodb_replset_lag index 7c7f446fd..f5e14ec5b 100755 --- a/config/munin/mongodb_replset_lag +++ b/config/munin/mongodb_replset_lag @@ -28,10 +28,10 @@ class MongoReplicaSetLag(MuninMongoDBPlugin): member_state = member['state'] optime = member['optime'] if member_state == PRIMARY_STATE: - primary_optime = optime.time + primary_optime = optime['ts'].time elif member_state == SECONDARY_STATE: - if not oldest_secondary_optime or optime.time < oldest_secondary_optime: - oldest_secondary_optime = optime.time + if not oldest_secondary_optime or optime['ts'].time < oldest_secondary_optime: + oldest_secondary_optime = optime['ts'].time if not primary_optime or not oldest_secondary_optime: raise Exception("Replica set is not healthy") diff --git a/config/requirements.txt b/config/requirements.txt index ad0ac7b68..9cf27016f 100644 --- a/config/requirements.txt +++ b/config/requirements.txt @@ -7,9 +7,8 @@ attrs==21.1.0 beautifulsoup4==4.9.3 billiard==3.6.4.0 bleach==3.2.1 -boto==2.49.0 -boto3==1.17.67 -botocore==1.20.67 +boto3==1.18.12 +botocore==1.21.12 celery==4.4.7 certifi==2020.12.5 cffi==1.14.5 @@ -38,7 +37,7 @@ dopy==0.3.7 elasticsearch==7.12.1 factory-boy==3.2.0 Faker==8.8.2 -feedparser==6.0.2 +feedparser>=6,<7 filelock==3.0.12 Flask==1.1.2 Flask-BasicAuth==0.2.0 @@ -99,12 +98,11 @@ pytz==2020.4 PyYAML==5.3.1 pyzmq==22.0.3 raven==6.10.0 +# readability-lxml==0.8.1.1 # Was vendorized due to noscript # Vendorized again due to 0.8.1.1 not out yet redis==3.5.3 requests==2.25.0 requests-oauthlib==1.3.0 -s3transfer==0.4.2 scipy==1.5.4 -seacucumber==1.5.2 sentry-sdk==1.0.0 sgmllib3k==1.0.0 simplejson==3.17.2 diff --git a/docker-compose.yml b/docker-compose.yml index 105e630e9..9d05a1954 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,8 +21,8 @@ services: nofile: soft: 10000 hard: 10000 - expose: - - 8000 + ports: + - 8000:8000 # only use gunicorn if the TEST env variable is not "True" entrypoint: /bin/sh -c newsblur_web/entrypoint.sh volumes: @@ -120,7 +120,7 @@ services: db_mongo: container_name: db_mongo - image: mongo:3.6 + image: mongo:4.0 restart: unless-stopped ports: - 29019:29019 diff --git a/docker/haproxy/haproxy.consul.cfg.j2 b/docker/haproxy/haproxy.consul.cfg.j2 index 29a51db5e..1ee58e647 100644 --- a/docker/haproxy/haproxy.consul.cfg.j2 +++ b/docker/haproxy/haproxy.consul.cfg.j2 @@ -63,6 +63,7 @@ frontend public use_backend node_favicon if { path_beg /rss_feeds/icon/ } use_backend node_text if { path_beg /rss_feeds/original_text_fetcher } use_backend node_images if { hdr_end(host) -i imageproxy.newsblur.com } + use_backend node_images if { hdr_end(host) -i imageproxy2.newsblur.com } use_backend node_page if { path_beg /original_page/ } use_backend blog if { hdr_end(host) -i blog.newsblur.com } use_backend blog if { hdr_end(host) -i blog2.newsblur.com } @@ -171,14 +172,14 @@ backend mongo {% for host in groups.mongo %} server {{host}} {{host}}.node.nyc1.consul:5579 {% endfor %} -{# + backend mongo_analytics - option httpchk GET /db_check/mongo + option httpchk GET /db_check/mongo_analytics default-server check inter 2000ms resolvers consul resolve-prefer ipv4 resolve-opts allow-dup-ip init-addr none {% for host in groups.mongo_analytics %} server {{host}} {{host}}.node.nyc1.consul:5579 {% endfor %} -#} + backend db_redis_user option httpchk GET /db_check/redis server db-redis-user db-redis-user.node.nyc1.consul:5579 check inter 2000ms resolvers consul resolve-opts allow-dup-ip init-addr none diff --git a/docker/mongo/backup_mongo.sh b/docker/mongo/backup_mongo.sh new file mode 100755 index 000000000..be67a8c7e --- /dev/null +++ b/docker/mongo/backup_mongo.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash + +collections=( + classifier_tag + classifier_author + classifier_feed + classifier_title + # shared_stories + category + category_site + sent_emails + social_profile + social_subscription + social_services + statistics + user_search + feedback +) + +for collection in ${collections[@]}; do + now=$(date '+%Y-%m-%d-%H-%M') + echo "---> Dumping $collection - ${now}" + + docker exec -it mongo mongodump -d newsblur -c $collection -o /backup/backup_mongo +done; + +echo " ---> Compressing backup_mongo.tgz" +tar -zcf /opt/mongo/newsblur/backup/backup_mongo.tgz /opt/mongo/newsblur/backup/backup_mongo + +echo " ---> Uploading backups to S3" +docker run --rm -v /srv/newsblur:/srv/newsblur -v /opt/mongo/newsblur/backup/:/opt/mongo/newsblur/backup/ --network=newsblurnet newsblur/newsblur_python3:latest python /srv/newsblur/utils/backups/backup_mongo.py + +# Don't delete backup since the backup_mongo.py script will rm them +## rm /opt/mongo/newsblur/backup/backup_mongo_${now}.tgz +## rm /opt/mongo/newsblur/backup/backup_mongo_${now} +echo " ---> Finished uploading backups to S3: backup_mongo.tgz" diff --git a/docker/prometheus/prometheus.consul.yml.j2 b/docker/prometheus/prometheus.consul.yml.j2 index 4d51c22b6..67d89a813 100644 --- a/docker/prometheus/prometheus.consul.yml.j2 +++ b/docker/prometheus/prometheus.consul.yml.j2 @@ -3,7 +3,7 @@ global: scrape_configs: - job_name: 'node_exporter' consul_sd_configs: - - server: 'consul.newsblur.com' + - server: 'consul.service.nyc1.consul:8500' services: ['node-exporter'] relabel_configs: - source_labels: ['__meta_consul_node'] @@ -12,7 +12,7 @@ scrape_configs: - job_name: 'mongo_exporter' consul_sd_configs: - - server: 'consul.newsblur.com' + - server: 'consul.service.nyc1.consul:8500' services: ['mongo-exporter'] relabel_configs: - source_labels: ['__meta_consul_node'] @@ -21,7 +21,7 @@ scrape_configs: - job_name: 'postgres_exporter' consul_sd_configs: - - server: 'consul.newsblur.com' + - server: 'consul.service.nyc1.consul:8500' services: ['postgres-exporter'] relabel_configs: - source_labels: ['__meta_consul_node'] @@ -31,7 +31,7 @@ scrape_configs: ## config for the multiple Redis targets that the exporter will scrape - job_name: 'redis_exporter' consul_sd_configs: - - server: 'consul.newsblur.com:' + - server: 'consul.service.nyc1.consul:8500' services: ['redis-exporter'] relabel_configs: - source_labels: ['__meta_consul_service_id'] @@ -114,4 +114,4 @@ scrape_configs: static_configs: - targets: ['{{ monitor_server }}'] metrics_path: /monitor/users - scheme: https \ No newline at end of file + scheme: https diff --git a/flask_monitor/db_monitor.py b/flask_monitor/db_monitor.py index 4f01cd2b4..e0b100ddd 100644 --- a/flask_monitor/db_monitor.py +++ b/flask_monitor/db_monitor.py @@ -8,6 +8,16 @@ import elasticsearch from newsblur_web import settings +import sentry_sdk +from flask import Flask +from sentry_sdk.integrations.flask import FlaskIntegration + +sentry_sdk.init( + dsn=settings.FLASK_SENTRY_DSN, + integrations=[FlaskIntegration()], + traces_sample_rate=1.0, +) + app = Flask(__name__) PRIMARY_STATE = 1 @@ -19,7 +29,7 @@ def db_check_postgres(): settings.DATABASES['default']['NAME'], settings.DATABASES['default']['USER'], settings.DATABASES['default']['PASSWORD'], - 'postgres', + 'db-postgres.service.nyc1.consul', settings.DATABASES['default']['PORT'], ) try: @@ -67,18 +77,23 @@ def db_check_mysql(): @app.route("/db_check/mongo") def db_check_mongo(): try: - client = pymongo.MongoClient('mongodb://mongo') + # The `mongo` hostname below is a reference to the newsblurnet docker network, where 172.18.0.0/16 is defined + client = pymongo.MongoClient(f"mongodb://{settings.MONGO_DB['username']}:{settings.MONGO_DB['password']}@{settings.SERVER_NAME}/?authSource=admin") db = client.newsblur except: abort(503) try: - stories = db.stories.count() + stories = db.stories.estimated_document_count() except (pymongo.errors.NotMasterError, pymongo.errors.ServerSelectionTimeoutError): abort(504) - + except pymongo.errors.OperationFailure as e: + if 'Authentication failed' in str(e): + abort(505) + abort(506) + if not stories: - abort(504) + abort(510) status = client.admin.command('replSetGetStatus') members = status['members'] @@ -88,40 +103,45 @@ def db_check_mongo(): member_state = member['state'] optime = member['optime'] if member_state == PRIMARY_STATE: - primary_optime = optime.time + primary_optime = optime['ts'].time elif member_state == SECONDARY_STATE: - if not oldest_secondary_optime or optime.time < oldest_secondary_optime: - oldest_secondary_optime = optime.time + if not oldest_secondary_optime or optime['ts'].time < oldest_secondary_optime: + oldest_secondary_optime = optime['ts'].time if not primary_optime or not oldest_secondary_optime: - abort(505) + abort(511) - if primary_optime - oldest_secondary_optime > 100: - abort(506) + # if primary_optime - oldest_secondary_optime > 100: + # abort(512) return str(stories) -@app.route("/db_check/redis") -def db_check_redis(): +@app.route("/db_check/mongo_analytics") +def db_check_mongo_analytics(): try: - r = redis.Redis('redis', db=0) + client = pymongo.MongoClient(f"mongodb://{settings.MONGO_ANALYTICS_DB['username']}:{settings.MONGO_ANALYTICS_DB['password']}@{settings.SERVER_NAME}/?authSource=admin") + db = client.nbanalytics except: abort(503) try: - randkey = r.randomkey() - except: + fetches = db.feed_fetches.estimated_document_count() + except (pymongo.errors.NotMasterError, pymongo.errors.ServerSelectionTimeoutError): abort(504) - - if randkey: - return str(randkey) - else: - abort(505) + except pymongo.errors.OperationFailure as e: + if 'Authentication failed' in str(e): + abort(505) + abort(506) + + if not fetches: + abort(510) + + return str(fetches) @app.route("/db_check/redis_user") def db_check_redis_user(): try: - r = redis.Redis('redis', db=0) + r = redis.Redis('db-redis-user.service.nyc1.consul', db=0) except: abort(503) @@ -138,7 +158,7 @@ def db_check_redis_user(): @app.route("/db_check/redis_story") def db_check_redis_story(): try: - r = redis.Redis('redis', db=1) + r = redis.Redis('db-redis-story.service.nyc1.consul', db=1) except: abort(503) @@ -155,7 +175,7 @@ def db_check_redis_story(): @app.route("/db_check/redis_sessions") def db_check_redis_sessions(): try: - r = redis.Redis('redis', db=5) + r = redis.Redis('db-redis-sessions.service.nyc1.consul', db=5) except: abort(503) @@ -172,7 +192,7 @@ def db_check_redis_sessions(): @app.route("/db_check/redis_pubsub") def db_check_redis_pubsub(): try: - r = redis.Redis('redis', db=1) + r = redis.Redis('db-redis-pubsub.service.nyc1.consul', db=1) except: abort(503) @@ -189,7 +209,7 @@ def db_check_redis_pubsub(): @app.route("/db_check/elasticsearch") def db_check_elasticsearch(): try: - conn = elasticsearch.Elasticsearch('elasticsearch') + conn = elasticsearch.Elasticsearch('db-elasticsearch.service.nyc1.consul') except: abort(503) diff --git a/flask_monitor/requirements.txt b/flask_monitor/requirements.txt index 1b3aabf51..aa0726f38 100644 --- a/flask_monitor/requirements.txt +++ b/flask_monitor/requirements.txt @@ -1,4 +1,4 @@ -flask==1.1.2 +flask==2.0.1 pymongo==3.11.2 psycopg2>=2,<3 redis==3.5.3 @@ -6,7 +6,6 @@ elasticsearch>=7,<8 pymysql==0.10.1 celery>=4,<5 Django>=3.1,<3.2 -sentry-sdk==0.20.3 +sentry-sdk[flask] mongoengine==0.21.0 -boto==2.49.0 -pyyaml==5.3.1 +boto3==1.18.13 diff --git a/media/css/reader.css b/media/css/reader.css index e75e1c660..cc1e6bb7b 100644 --- a/media/css/reader.css +++ b/media/css/reader.css @@ -536,7 +536,7 @@ hr { background-color: #F7F8F5; overflow-y: auto; overflow-x: hidden; - font-size: 11px; + font-size: 12px; list-style: none; margin: 0; padding: 0; @@ -544,7 +544,7 @@ hr { height: auto; } .NB-theme-feed-size-xs .NB-feedlist { - font-size: 10px; + font-size: 11px; } .NB-theme-feed-size-m .NB-feedlist { font-size: 12px; @@ -726,10 +726,10 @@ hr { top: 1px; } .NB-theme-feed-size-s .NB-feedlist img.feed_favicon { - top: 3px; + top: 4px; } .NB-density-compact.NB-theme-feed-size-s .NB-feedlist img.feed_favicon { - top: 1px; + top: 2px; } .NB-theme-feed-size-m .NB-feedlist img.feed_favicon { top: 4px; @@ -846,8 +846,7 @@ hr { .NB-feeds-header .NB-feedlist-collapse-icon { top: 4px; } -.NB-theme-feed-size-xs .NB-feedlist .folder .folder_title .NB-feedlist-collapse-icon, -.NB-theme-feed-size-s .NB-feedlist .folder .folder_title .NB-feedlist-collapse-icon { +.NB-theme-feed-size-xs .NB-feedlist .folder .folder_title .NB-feedlist-collapse-icon { top: -1px; } .NB-theme-feed-size-xl .NB-feedlist .folder .folder_title .NB-feedlist-collapse-icon { @@ -1017,9 +1016,7 @@ hr { padding-bottom: 2px; } .NB-theme-feed-size-s .NB-feedlist .unread_count { - margin-top: 2px; - padding-top: 3px; - padding-bottom: 1px; + } .NB-theme-feed-size-l .NB-feedlist .unread_count { margin-top: 3px; @@ -1069,7 +1066,7 @@ hr { padding-bottom: 1px; } .NB-theme-feed-size-s .folder_title .unread_count { - margin-top: -3px; + margin-top: -2px; padding-top: 2px; padding-bottom: 1px; } @@ -1738,7 +1735,7 @@ hr { text-decoration: none; color: #272727; line-height: 15px; - font-size: 12px; + font-size: 13px; /* background-color: white; border-top: 1px solid #E7EDF6; border-bottom: 1px solid #FFF; @@ -1749,7 +1746,7 @@ hr { } .NB-theme-feed-size-xs .NB-story-title { - font-size: 11px; + font-size: 12px; line-height: 14px; } .NB-theme-feed-size-m .NB-story-title { @@ -1865,6 +1862,9 @@ hr { -moz-box-sizing: border-box; box-sizing: border-box; } +.NB-story-title.read .NB-storytitles-story-image { + opacity: 0.6; +} .NB-image-preview-large-left:not(.NB-story-layout-grid) .NB-story-title .NB-storytitles-story-image { right: inherit; left: 8px; @@ -1924,7 +1924,7 @@ hr { } .NB-story-title.read a.story_title { - color: #a2a2a2; + color: #969696; } .NB-storytitles-title { overflow-wrap: break-word; @@ -1940,10 +1940,10 @@ hr { font-size: 11px; } .NB-theme-feed-size-xs .NB-story-title .NB-storytitles-author { - font-size: 9px; + font-size: 10px; } .NB-theme-feed-size-s .NB-story-title .NB-storytitles-author { - font-size: 10px; + font-size: 11px; } .NB-theme-feed-size-l .NB-story-title .NB-storytitles-author { font-size: 12px; @@ -2015,7 +2015,7 @@ hr { } .NB-theme-feed-size-xs .NB-storytitles-content-preview { - font-size: 10px; + font-size: 11px; line-height: 13px; } .NB-theme-feed-size-s .NB-storytitles-content-preview { @@ -2108,7 +2108,7 @@ hr { .NB-story-title .NB-story-feed .feed_title { display: block; - font-size: 10px; + font-size: 11px; position: absolute; left: 20px; top: 0; @@ -2121,7 +2121,7 @@ hr { white-space: nowrap; } .NB-theme-feed-size-xs .NB-story-title .NB-story-feed .feed_title { - font-size: 9px; + font-size: 10px; height: 12px; } .NB-theme-feed-size-m .NB-story-title .NB-story-feed .feed_title { @@ -2340,9 +2340,6 @@ hr { grid-gap: 2rem; padding: 2rem; } -.NB-layout-grid .NB-story-content-container { - background-color: white; -} .NB-layout-grid .NB-end-line { margin: 0 -2rem -2rem; @@ -2754,7 +2751,17 @@ hr { body { font-family: 'Whitney SSm A', 'Whitney SSm B', "Lucida Grande", Verdana, "Helvetica Neue", Helvetica, sans-serif; } - +.NB-theme-feed-font-whitney { + font-family: 'Whitney SSm A', 'Whitney SSm B', "Lucida Grande", Verdana, "Helvetica Neue", Helvetica, sans-serif; +} +.NB-theme-feed-font-lucida { + font-family: "Lucida Grande", Verdana, "Helvetica Neue", Helvetica, sans-serif; + /* font-family: Verdana, "Helvetica Neue", Helvetica, sans-serif; */ +} +.NB-theme-feed-font-gotham { + font-family: 'Gotham Narrow A', 'Gotham Narrow B', "Helvetica Neue", Helvetica, sans-serif; + /* font-family: "Helvetica Neue", Helvetica, sans-serif; */ +} .NB-theme-sans-serif #story_pane { font-family: "Helvetica Neue", "Helvetica", sans-serif; } @@ -3092,25 +3099,26 @@ body { max-width: 100%; } .NB-feed-story .NB-feed-story-content img { - max-width: max-content !important; - margin-left: -28px; - width: calc(100% - 56px * -1) !important; + max-width: 100% !important; + width: auto !important; height: auto; /* See http://www.newsblur.com/site/1031643/le-21me for width: auto, height: auto */ } .NB-feed-story .NB-feed-story-content img.NB-medium-image { - max-width: max-content !important; - margin: 0; - width: auto !important; } .NB-feed-story .NB-feed-story-content img.NB-small-image { - max-width: max-content !important; - margin: 0; - width: auto !important; } .NB-feed-story .NB-feed-story-content img.NB-large-image { + max-width: max-content !important; + margin-left: -28px !important; + width: calc(100% - 56px * -1) !important; } +.NB-feed-story .NB-feed-story-content img.NB-table-image.NB-large-image { + margin: 0; + width: 100% !important; +} + .NB-feed-story .NB-feed-story-content figure { margin: 0; } @@ -3530,6 +3538,7 @@ body { overflow: hidden; position: relative; min-height: 192px; + background-color: white; } .NB-narrow-content .NB-story-content-container { min-height: 108px; @@ -5190,40 +5199,48 @@ background: transparent; background: transparent url("/media/embed/icons/circular/menu_icn_settings.png") no-repeat center center; } -.NB-filter-popover .segmented-control.NB-options-feed-font-size li, -.NB-style-popover .NB-options-feed-font-size li, +.NB-filter-popover .segmented-control.NB-options-feed-size li, +.NB-style-popover .NB-options-feed-size li, .NB-style-popover .NB-options-story-font-size li { width: 45px; padding: 2px 0; line-height: 16px; font-weight: bold; } -.NB-filter-popover .segmented-control.NB-options-feed-font-size li { +.NB-filter-popover .segmented-control.NB-options-feed-size li { width: 50px; } -.NB-filter-popover .segmented-control li.NB-options-font-size-xs, -.NB-style-popover li.NB-options-font-size-xs { +.NB-filter-popover .segmented-control li.NB-options-feed-size-xs, +.NB-style-popover li.NB-options-font-size-xs, +.NB-style-popover li.NB-options-feed-size-xs { font-size: 9px; padding: 3px 0 1px; } -.NB-filter-popover .segmented-control .NB-options-font-size-s, -.NB-style-popover .NB-options-font-size-s { +.NB-filter-popover .segmented-control .NB-options-feed-size-s, +.NB-style-popover .NB-options-font-size-s, +.NB-style-popover .NB-options-feed-size-s { font-size: 10px; } -.NB-filter-popover .segmented-control li.NB-options-font-size-m, -.NB-style-popover li.NB-options-font-size-m { +.NB-filter-popover .segmented-control li.NB-options-feed-size-m, +.NB-style-popover li.NB-options-font-size-m, +.NB-style-popover li.NB-options-feed-size-m { font-size: 12px; padding: 3px 0 1px; } -.NB-filter-popover .segmented-control .NB-options-font-size-l, -.NB-style-popover .NB-options-font-size-l { +.NB-filter-popover .segmented-control .NB-options-feed-size-l, +.NB-style-popover .NB-options-font-size-l, +.NB-style-popover .NB-options-feed-size-l { font-size: 13px; } -.NB-filter-popover .segmented-control li.NB-options-font-size-xl, -.NB-style-popover li.NB-options-font-size-xl { +.NB-filter-popover .segmented-control li.NB-options-feed-size-xl, +.NB-style-popover li.NB-options-font-size-xl, +.NB-style-popover li.NB-options-feed-size-xl { font-size: 15px; } - +.NB-filter-popover .segmented-control.NB-options-feed-font li { + padding: 4px 15px; + width: auto; +} .NB-style-popover .NB-options-line-spacing { margin-top: 6px; } @@ -7954,6 +7971,46 @@ form.opml_import_form input { .NB-menu-manage .NB-menu-manage-theme .segmented-control li { padding: 4px 8px; } +.NB-menu-manage .NB-menu-manage-font .NB-menu-manage-image { + background: transparent url('/media/img/icons/circular/menu_icn_font.png') no-repeat 0 0; + background-size: 18px; +} +.NB-menu-manage .NB-menu-manage-font .segmented-control { + margin: 2px 0 0 36px; +} +.NB-menu-manage .NB-menu-manage-font .segmented-control li { + padding: 4px 8px; +} +.NB-menu-manage .NB-menu-manage-size .NB-menu-manage-image { + background: transparent url('/media/img/icons/circular/menu_icn_size.png') no-repeat 0 0; + background-size: 18px; +} +.NB-menu-manage .NB-menu-manage-size .segmented-control { + margin: 2px 0 0 36px; +} +.NB-menu-manage .NB-menu-manage-size .segmented-control li { + font-weight: bold; +} +.NB-menu-manage .NB-menu-manage-size .segmented-control li.NB-options-feed-size-xs { + font-size: 9px; + padding: 7px 12px 6px; +} +.NB-menu-manage .NB-menu-manage-size .segmented-control li.NB-options-feed-size-s { + font-size: 10px; + padding: 6px 12px 5px; +} +.NB-menu-manage .NB-menu-manage-size .segmented-control li.NB-options-feed-size-m { + font-size: 12px; + padding: 5px 12px 4px; +} +.NB-menu-manage .NB-menu-manage-size .segmented-control li.NB-options-feed-size-l { + font-size: 13px; + padding: 4px 12px; +} +.NB-menu-manage .NB-menu-manage-size .segmented-control li.NB-options-feed-size-xl { + font-size: 15px; + padding: 3px 12px 2px; +} .NB-menu-manage .NB-menu-manage-account .NB-menu-manage-image { background: transparent url('/media/embed/icons/circular/menu_icn_profile.png') no-repeat 0 0; background-size: 18px; @@ -12371,7 +12428,7 @@ form.opml_import_form input { .NB-modal-organizer .segmented-control li { padding: 2px 12px 0; font-size: 11px; - width: 50%; + width: 49%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; diff --git a/media/img/icons/circular/menu_icn_font.png b/media/img/icons/circular/menu_icn_font.png new file mode 100644 index 000000000..7a897ae88 Binary files /dev/null and b/media/img/icons/circular/menu_icn_font.png differ diff --git a/media/img/icons/circular/menu_icn_size.png b/media/img/icons/circular/menu_icn_size.png new file mode 100644 index 000000000..50a878c19 Binary files /dev/null and b/media/img/icons/circular/menu_icn_size.png differ diff --git a/media/js/newsblur/models/stories.js b/media/js/newsblur/models/stories.js index 74343be60..8c82b484f 100644 --- a/media/js/newsblur/models/stories.js +++ b/media/js/newsblur/models/stories.js @@ -51,7 +51,7 @@ NEWSBLUR.Models.Story = Backbone.Model.extend({ // First do a naive strip, which is faster than rendering which makes network calls content = content && content.replace(/<(?:.|\n)*?>/gm, ' '); content = content && Inflector.stripTags(content); - content = content && content.replaceAll(' ‌', ' '); // Invisible space, boo + content = content && content.replace(/[\u00a0\u200c]/g, ' '); // Invisible space, boo content = content && content.replace(/\s+/gm, ' '); return _.string.prune(_.string.trim(content), length || 150, "..."); diff --git a/media/js/newsblur/reader/reader.js b/media/js/newsblur/reader/reader.js index 08328c30f..7c110c977 100644 --- a/media/js/newsblur/reader/reader.js +++ b/media/js/newsblur/reader/reader.js @@ -3051,6 +3051,10 @@ .removeClass('NB-theme-feed-size-l') .removeClass('NB-theme-feed-size-xl'); $body.addClass('NB-theme-feed-size-' + NEWSBLUR.Preferences['feed_size']); + $body.removeClass('NB-theme-feed-font-whitney') + .removeClass('NB-theme-feed-font-lucida') + .removeClass('NB-theme-feed-font-gotham'); + $body.addClass('NB-theme-feed-font-' + NEWSBLUR.Preferences['feed_font']); $body.removeClass('NB-line-spacing-xs') .removeClass('NB-line-spacing-s') @@ -3082,6 +3086,8 @@ NEWSBLUR.app.dashboard_rivers.right.redraw(); NEWSBLUR.app.story_titles.render(); } + + this.load_theme(); }, // =================== @@ -3369,14 +3375,26 @@ } }, + switch_feed_font: function(feed_font) { + this.model.preference('feed_font', feed_font); + this.apply_story_styling(); + }, + + switch_feed_font_size: function(feed_size) { + this.model.preference('feed_size', feed_size); + this.apply_story_styling(); + }, + switch_theme: function(theme) { this.model.preference('theme', theme); - this.load_theme(); + this.apply_story_styling(); }, load_theme: function() { var theme = NEWSBLUR.assets.theme(); var auto_theme = NEWSBLUR.assets.preference('theme'); // Add auto + var feed_font = NEWSBLUR.assets.preference('feed_font'); + var feed_size = NEWSBLUR.assets.preference('feed_size'); if (!this.flags.watching_system_theme && window.matchMedia) { var darkMediaQuery = window.matchMedia('(prefers-color-scheme: dark)'); @@ -3401,8 +3419,13 @@ this.flags.watching_system_theme = true; } + // Select theme options in manage menu on the dashboard $('.NB-theme-option').removeClass('NB-active'); $('.NB-options-theme-'+auto_theme).addClass('NB-active'); + $('.NB-feed-font-option').removeClass('NB-active'); + $('.NB-options-feed-font-'+feed_font).addClass('NB-active'); + $('.NB-feed-size-option').removeClass('NB-active'); + $('.NB-options-feed-size-'+feed_size).addClass('NB-active'); $("body").addClass('NB-theme-transitioning'); @@ -3540,7 +3563,7 @@ $.make('div', { className: 'NB-menu-manage-image' }), $.make('div', { className: 'NB-menu-manage-title' }, 'Email Newsletters') ]), - $.make('li', { className: 'NB-menu-item NB-menu-manage-import, role: "button"' }, [ + $.make('li', { className: 'NB-menu-item NB-menu-manage-import', role: "button" }, [ $.make('div', { className: 'NB-menu-manage-image' }), $.make('div', { className: 'NB-menu-manage-title' }, 'Import or upload sites') ]), @@ -3562,6 +3585,34 @@ $.make('div', { className: 'NB-menu-manage-image' }), $.make('div', { className: 'NB-menu-manage-title' }, 'Preferences') ]), + $.make('li', { className: 'NB-menu-separator' }), + $.make('li', { className: 'NB-menu-item NB-menu-manage-font' }, [ + $.make('div', { className: 'NB-menu-manage-image' }), + $.make('ul', { className: 'segmented-control NB-options-feed-font' }, [ + $.make('li', { className: 'NB-feed-font-option NB-options-feed-font-whitney NB-theme-feed-font-whitney', role: "button" }, [ + $.make('div', { className: 'NB-icon' }), + 'Whitney' + ]), + $.make('li', { className: 'NB-feed-font-option NB-options-feed-font-lucida NB-theme-feed-font-lucida', role: "button" }, [ + $.make('div', { className: 'NB-icon' }), + 'Lucida Grande' + ]), + $.make('li', { className: 'NB-feed-font-option NB-options-feed-font-gotham NB-theme-feed-font-gotham', role: "button" }, [ + $.make('div', { className: 'NB-icon' }), + 'Gotham' + ]) + ]) + ]), + $.make('li', { className: 'NB-menu-item NB-menu-manage-size' }, [ + $.make('div', { className: 'NB-menu-manage-image' }), + $.make('ul', { className: 'segmented-control NB-options-feed-size' }, [ + $.make('li', { className: 'NB-feed-size-option NB-options-feed-size-xs', role: "button" }, 'XS'), + $.make('li', { className: 'NB-feed-size-option NB-options-feed-size-s', role: "button" }, 'S'), + $.make('li', { className: 'NB-feed-size-option NB-options-feed-size-m', role: "button" }, 'M'), + $.make('li', { className: 'NB-feed-size-option NB-options-feed-size-l', role: "button" }, 'L'), + $.make('li', { className: 'NB-feed-size-option NB-options-feed-size-xl', role: "button" }, 'XL') + ]) + ]), $.make('li', { className: 'NB-menu-item NB-menu-manage-theme' }, [ $.make('div', { className: 'NB-menu-manage-image' }), $.make('ul', { className: 'segmented-control NB-options-theme' }, [ @@ -3585,6 +3636,16 @@ $(".NB-options-theme-light", $manage_menu).toggleClass('NB-active', theme == 'light'); $(".NB-options-theme-dark", $manage_menu).toggleClass('NB-active', theme == 'dark'); $(".NB-options-theme-auto", $manage_menu).toggleClass('NB-active', theme == 'auto'); + var feed_font = this.model.preference('feed_font'); + $(".NB-options-feed-font-whitney", $manage_menu).toggleClass('NB-active', feed_font == 'whitney'); + $(".NB-options-feed-font-lucida", $manage_menu).toggleClass('NB-active', feed_font == 'lucida'); + $(".NB-options-feed-font-gotham", $manage_menu).toggleClass('NB-active', feed_font == 'gotham'); + var feed_size = this.model.preference('feed_size'); + $(".NB-options-feed-size-xs", $manage_menu).toggleClass('NB-active', feed_size == 'xs'); + $(".NB-options-feed-size-s", $manage_menu).toggleClass('NB-active', feed_size == 's'); + $(".NB-options-feed-size-m", $manage_menu).toggleClass('NB-active', feed_size == 'm'); + $(".NB-options-feed-size-l", $manage_menu).toggleClass('NB-active', feed_size == 'l'); + $(".NB-options-feed-size-xl", $manage_menu).toggleClass('NB-active', feed_size == 'xl'); } else if (type == 'feed') { var feed = this.model.get_feed(feed_id); if (!feed) return; @@ -6486,6 +6547,44 @@ e.preventDefault(); self.switch_theme('auto'); }); + $.targetIs(e, { tagSelector: '.NB-menu-manage-font' }, function($t, $p){ + e.preventDefault(); + }); + $.targetIs(e, { tagSelector: '.NB-options-feed-font-whitney' }, function($t, $p){ + e.preventDefault(); + self.switch_feed_font('whitney'); + }); + $.targetIs(e, { tagSelector: '.NB-options-feed-font-lucida' }, function($t, $p){ + e.preventDefault(); + self.switch_feed_font('lucida'); + }); + $.targetIs(e, { tagSelector: '.NB-options-feed-font-gotham' }, function($t, $p){ + e.preventDefault(); + self.switch_feed_font('gotham'); + }); + $.targetIs(e, { tagSelector: '.NB-menu-manage-size' }, function($t, $p){ + e.preventDefault(); + }); + $.targetIs(e, { tagSelector: '.NB-options-feed-size-xs' }, function($t, $p){ + e.preventDefault(); + self.switch_feed_font_size('xs'); + }); + $.targetIs(e, { tagSelector: '.NB-options-feed-size-s' }, function($t, $p){ + e.preventDefault(); + self.switch_feed_font_size('s'); + }); + $.targetIs(e, { tagSelector: '.NB-options-feed-size-m' }, function($t, $p){ + e.preventDefault(); + self.switch_feed_font_size('m'); + }); + $.targetIs(e, { tagSelector: '.NB-options-feed-size-l' }, function($t, $p){ + e.preventDefault(); + self.switch_feed_font_size('l'); + }); + $.targetIs(e, { tagSelector: '.NB-options-feed-size-xl' }, function($t, $p){ + e.preventDefault(); + self.switch_feed_font_size('xl'); + }); $.targetIs(e, { tagSelector: '.NB-menu-manage-logout' }, function($t, $p){ e.preventDefault(); e.stopPropagation(); diff --git a/media/js/newsblur/reader/reader_feed_exception.js b/media/js/newsblur/reader/reader_feed_exception.js index 68ea20944..b0205504a 100644 --- a/media/js/newsblur/reader/reader_feed_exception.js +++ b/media/js/newsblur/reader/reader_feed_exception.js @@ -7,7 +7,7 @@ NEWSBLUR.ReaderFeedException = function(feed_id, options) { this.options = $.extend({}, defaults, options); this.model = NEWSBLUR.assets; - this.feed_id = feed_id; + this.feed_id = _.isString(feed_id) && _.string.startsWith(feed_id, 'feed:') ? parseInt(feed_id.replace('feed:', ''), 10) : feed_id; this.feed = this.model.get_feed(feed_id); this.folder_title = this.options.folder_title; this.folder = this.folder_title && NEWSBLUR.assets.get_folder(this.folder_title); @@ -24,7 +24,7 @@ _.extend(NEWSBLUR.ReaderFeedException.prototype, { if (this.folder) { NEWSBLUR.Modal.prototype.initialize_folder.call(this, this.folder_title); } else { - NEWSBLUR.Modal.prototype.initialize_feed.call(this, this.feed_id); + NEWSBLUR.Modal.prototype.initialize_feed.call(this, this.feed_id); } this.make_modal(); if (this.feed) { diff --git a/media/js/newsblur/reader/reader_organizer.js b/media/js/newsblur/reader/reader_organizer.js index 202dbad23..18afc3ec7 100644 --- a/media/js/newsblur/reader/reader_organizer.js +++ b/media/js/newsblur/reader/reader_organizer.js @@ -200,7 +200,7 @@ _.extend(NEWSBLUR.ReaderOrganizer.prototype, { var $error = $(".NB-error-move", this.$modal); var $delete = $(".NB-action-delete", this.$modal); var count = this.feedlist.folder_view.highlighted_count_unique_folders(); - console.log(['change_selection', count]); + // console.log(['change_selection', count]); $title.text(count ? count + " selected" : "Select"); $error.text(''); diff --git a/media/js/newsblur/views/feed_options_popover.js b/media/js/newsblur/views/feed_options_popover.js index c7350c124..2483f645c 100644 --- a/media/js/newsblur/views/feed_options_popover.js +++ b/media/js/newsblur/views/feed_options_popover.js @@ -133,12 +133,26 @@ NEWSBLUR.FeedOptionsPopover = NEWSBLUR.ReaderPopover.extend({ $.make('img', { className: 'NB-icon', src: NEWSBLUR.Globals['MEDIA_URL']+'img/reader/image_preview_small_right.png' }) ]) ])), - $.make('ul', { className: 'segmented-control NB-options-feed-font-size' }, [ - $.make('li', { className: 'NB-view-setting-option NB-options-font-size-xs', role: "button" }, 'XS'), - $.make('li', { className: 'NB-view-setting-option NB-options-font-size-s', role: "button" }, 'S'), - $.make('li', { className: 'NB-view-setting-option NB-options-font-size-m NB-active', role: "button" }, 'M'), - $.make('li', { className: 'NB-view-setting-option NB-options-font-size-l', role: "button" }, 'L'), - $.make('li', { className: 'NB-view-setting-option NB-options-font-size-xl', role: "button" }, 'XL') + $.make('ul', { className: 'segmented-control NB-options-feed-font' }, [ + $.make('li', { className: 'NB-view-setting-option NB-view-setting-feed-font-whitney NB-theme-feed-font-whitney', role: "button" }, [ + $.make('div', { className: 'NB-icon' }), + 'Whitney' + ]), + $.make('li', { className: 'NB-view-setting-option NB-view-setting-feed-font-lucida NB-theme-feed-font-lucida', role: "button" }, [ + $.make('div', { className: 'NB-icon' }), + 'Lucida Grande' + ]), + $.make('li', { className: 'NB-view-setting-option NB-view-setting-feed-font-gotham NB-theme-feed-font-gotham', role: "button" }, [ + $.make('div', { className: 'NB-icon' }), + 'Gotham' + ]) + ]), + $.make('ul', { className: 'segmented-control NB-options-feed-size' }, [ + $.make('li', { className: 'NB-view-setting-option NB-options-feed-size-xs', role: "button" }, 'XS'), + $.make('li', { className: 'NB-view-setting-option NB-options-feed-size-s', role: "button" }, 'S'), + $.make('li', { className: 'NB-view-setting-option NB-options-feed-size-m NB-active', role: "button" }, 'M'), + $.make('li', { className: 'NB-view-setting-option NB-options-feed-size-l', role: "button" }, 'L'), + $.make('li', { className: 'NB-view-setting-option NB-options-feed-size-xl', role: "button" }, 'XL') ]) ]), (is_feed && $.make('div', { className: 'NB-popover-section' }, [ @@ -185,7 +199,8 @@ NEWSBLUR.FeedOptionsPopover = NEWSBLUR.ReaderPopover.extend({ var image_preview = NEWSBLUR.assets.preference('image_preview'); var content_preview = NEWSBLUR.assets.preference('show_content_preview'); var infrequent = parseInt(NEWSBLUR.assets.preference('infrequent_stories_per_month'), 10); - var feed_font_size = NEWSBLUR.assets.preference('feed_size'); + var feed_size = NEWSBLUR.assets.preference('feed_size'); + var feed_font = NEWSBLUR.assets.preference('feed_font'); var $oldest = this.$('.NB-view-setting-order-oldest'); var $newest = this.$('.NB-view-setting-order-newest'); @@ -224,8 +239,10 @@ NEWSBLUR.FeedOptionsPopover = NEWSBLUR.ReaderPopover.extend({ $image_preview_sr.toggleClass('NB-active', image_preview == "small-right"); $image_preview_ll.toggleClass('NB-active', image_preview == "large-left"); $image_preview_lr.toggleClass('NB-active', image_preview == "1" || image_preview == "large-right"); - this.$('.NB-options-feed-font-size li').removeClass('NB-active'); - this.$('.NB-options-feed-font-size .NB-options-font-size-'+feed_font_size).addClass('NB-active'); + this.$('.NB-options-feed-size li').removeClass('NB-active'); + this.$('.NB-options-feed-size .NB-options-feed-size-'+feed_size).addClass('NB-active'); + this.$('.NB-options-feed-font .NB-view-setting-option').removeClass('NB-active'); + this.$('.NB-options-feed-font .NB-view-setting-feed-font-'+feed_font).addClass('NB-active'); var frequencies = [5, 15, 30, 60, 90]; for (var f in frequencies) { @@ -312,16 +329,22 @@ NEWSBLUR.FeedOptionsPopover = NEWSBLUR.ReaderPopover.extend({ } else if ($target.hasClass("NB-view-setting-infrequent-90")) { NEWSBLUR.assets.preference('infrequent_stories_per_month', 90); NEWSBLUR.reader.reload_feed(); - } else if ($target.hasClass("NB-options-font-size-xs")) { + } else if ($target.hasClass("NB-options-feed-size-xs")) { this.update_feed_font_size('xs'); - } else if ($target.hasClass("NB-options-font-size-s")) { + } else if ($target.hasClass("NB-options-feed-size-s")) { this.update_feed_font_size('s'); - } else if ($target.hasClass("NB-options-font-size-m")) { + } else if ($target.hasClass("NB-options-feed-size-m")) { this.update_feed_font_size('m'); - } else if ($target.hasClass("NB-options-font-size-l")) { + } else if ($target.hasClass("NB-options-feed-size-l")) { this.update_feed_font_size('l'); - } else if ($target.hasClass("NB-options-font-size-xl")) { + } else if ($target.hasClass("NB-options-feed-size-xl")) { this.update_feed_font_size('xl'); + } else if ($target.hasClass("NB-view-setting-feed-font-whitney")) { + this.update_feed_font('whitney'); + } else if ($target.hasClass("NB-view-setting-feed-font-lucida")) { + this.update_feed_font('lucida'); + } else if ($target.hasClass("NB-view-setting-feed-font-gotham")) { + this.update_feed_font('gotham'); } if (NEWSBLUR.reader.flags.search) { @@ -336,6 +359,11 @@ NEWSBLUR.FeedOptionsPopover = NEWSBLUR.ReaderPopover.extend({ NEWSBLUR.reader.apply_story_styling(); }, + update_feed_font: function(setting) { + NEWSBLUR.assets.preference('feed_font', setting); + NEWSBLUR.reader.apply_story_styling(); + }, + update_feed: function(setting) { var changed = NEWSBLUR.assets.view_setting(this.options.feed_id, setting); if (!changed) return; diff --git a/media/js/newsblur/views/story_detail_view.js b/media/js/newsblur/views/story_detail_view.js index d9b2a154c..27b57b325 100644 --- a/media/js/newsblur/views/story_detail_view.js +++ b/media/js/newsblur/views/story_detail_view.js @@ -97,7 +97,6 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({ this.render_comments(); this.attach_handlers(); // if (!this.model.get('image_urls') || (this.model.get('image_urls') && this.model.get('image_urls').length == 0)) { - this.watch_images_load(); // } return this; @@ -127,21 +126,40 @@ NEWSBLUR.Views.StoryDetailView = Backbone.View.extend({ this.attach_fitvid_handler(); this.render_starred_tags(); this.apply_starred_story_selections(); + this.watch_images_load(); }, watch_images_load: function () { - var pane_width = NEWSBLUR.reader.$s.$story_pane.width() - 28*2; // 28px to compensate for both margins + var pane_width; + if (this.options.inline_story_title) { + pane_width = this.$el.width(); + } + if (!pane_width) { + pane_width = NEWSBLUR.reader.$s.$story_pane.width() + } + if (!pane_width) { + pane_width = NEWSBLUR.reader.$s.$story_titles.width(); + } + pane_width = pane_width - (28 + 2); // 28px to compensate for both margins + var has_tables = this.$("table").length; this.$el.imagesLoaded(_.bind(function() { var largest = 0; var $largest; // console.log(["Images loaded", this.model.get('story_title').substr(0, 30), this.$("img")]); this.$("img").each(function() { - // console.log(["Largest?", this.width, largest, this.src]); + // console.log(["Largest?", this.width, this.naturalWidth, this.height, this.naturalHeight, largest, pane_width, this.src]); if (this.width > 60 && this.width > largest) { largest = this.width; $largest = $(this); } + $(this).removeClass('NB-large-image').removeClass('NB-medium-image').removeClass('NB-small-image'); + if (pane_width >= 900) return; + + if (has_tables) { + // Can't even calculate widths because with tables, nothing fits + $(this).addClass('NB-table-image'); + } if (this.naturalWidth >= pane_width && this.naturalHeight >= 50) { $(this).addClass('NB-large-image'); } else if (this.naturalWidth >= 100 && this.naturalHeight >= 50) { diff --git a/media/js/newsblur/views/story_options_popover.js b/media/js/newsblur/views/story_options_popover.js index 5d7460be9..3b4033295 100644 --- a/media/js/newsblur/views/story_options_popover.js +++ b/media/js/newsblur/views/story_options_popover.js @@ -19,7 +19,7 @@ NEWSBLUR.StoryOptionsPopover = NEWSBLUR.ReaderPopover.extend({ events: { "click .NB-font-family-option": "change_font_family", "click .NB-story-font-size-option": "change_story_font_size", - "click .NB-feed-font-size-option": "change_feed_font_size", + "click .NB-feed-size-option": "change_feed_font_size", "click .NB-view-setting-option": "change_view_setting", "click .NB-line-spacing-option": "change_line_spacing", "click .NB-story-titles-pane-option": "change_story_titles_pane", @@ -159,12 +159,12 @@ NEWSBLUR.StoryOptionsPopover = NEWSBLUR.ReaderPopover.extend({ $.make('li', { className: 'NB-line-spacing-option NB-options-line-spacing-xl', role: "button" }, $.make('div', { className: 'NB-icon' })) ]), $.make('div', { className: 'NB-popover-section-title' }, 'Feed title styling'), - $.make('ul', { className: 'segmented-control NB-options-feed-font-size' }, [ - $.make('li', { className: 'NB-feed-font-size-option NB-options-font-size-xs', role: "button" }, 'XS'), - $.make('li', { className: 'NB-feed-font-size-option NB-options-font-size-s', role: "button" }, 'S'), - $.make('li', { className: 'NB-feed-font-size-option NB-options-font-size-m NB-active', role: "button" }, 'M'), - $.make('li', { className: 'NB-feed-font-size-option NB-options-font-size-l', role: "button" }, 'L'), - $.make('li', { className: 'NB-feed-font-size-option NB-options-font-size-xl', role: "button" }, 'XL') + $.make('ul', { className: 'segmented-control NB-options-feed-size' }, [ + $.make('li', { className: 'NB-feed-size-option NB-options-feed-size-xs', role: "button" }, 'XS'), + $.make('li', { className: 'NB-feed-size-option NB-options-feed-size-s', role: "button" }, 'S'), + $.make('li', { className: 'NB-feed-size-option NB-options-feed-size-m NB-active', role: "button" }, 'M'), + $.make('li', { className: 'NB-feed-size-option NB-options-feed-size-l', role: "button" }, 'L'), + $.make('li', { className: 'NB-feed-size-option NB-options-feed-size-xl', role: "button" }, 'XL') ]), (this.options.show_contentpreview && $.make('ul', { className: 'segmented-control NB-menu-manage-view-setting-contentpreview' }, [ $.make('li', { className: 'NB-view-setting-option NB-view-setting-contentpreview-title', role: "button" }, 'Title only'), @@ -209,10 +209,10 @@ NEWSBLUR.StoryOptionsPopover = NEWSBLUR.ReaderPopover.extend({ this.$('.NB-options-font-family-'+font_family).addClass('NB-active'); this.$('.NB-view-setting-option').removeClass('NB-active'); - this.$('.NB-feed-font-size-option').removeClass('NB-active'); + this.$('.NB-feed-size-option').removeClass('NB-active'); this.$('.NB-story-font-size-option').removeClass('NB-active'); this.$('.NB-options-story-font-size .NB-options-font-size-'+story_font_size).addClass('NB-active'); - this.$('.NB-options-feed-font-size .NB-options-font-size-'+feed_font_size).addClass('NB-active'); + this.$('.NB-options-feed-size .NB-options-feed-size-'+feed_font_size).addClass('NB-active'); this.$('.NB-line-spacing-option').removeClass('NB-active'); this.$('.NB-options-line-spacing-'+line_spacing).addClass('NB-active'); @@ -296,15 +296,15 @@ NEWSBLUR.StoryOptionsPopover = NEWSBLUR.ReaderPopover.extend({ change_feed_font_size: function(e) { var $target = $(e.target); - if ($target.hasClass("NB-options-font-size-xs")) { + if ($target.hasClass("NB-options-feed-size-xs")) { this.update_feed_font_size('xs'); - } else if ($target.hasClass("NB-options-font-size-s")) { + } else if ($target.hasClass("NB-options-feed-size-s")) { this.update_feed_font_size('s'); - } else if ($target.hasClass("NB-options-font-size-m")) { + } else if ($target.hasClass("NB-options-feed-size-m")) { this.update_feed_font_size('m'); - } else if ($target.hasClass("NB-options-font-size-l")) { + } else if ($target.hasClass("NB-options-feed-size-l")) { this.update_feed_font_size('l'); - } else if ($target.hasClass("NB-options-font-size-xl")) { + } else if ($target.hasClass("NB-options-feed-size-xl")) { this.update_feed_font_size('xl'); } diff --git a/newsblur_web/docker_local_settings.py b/newsblur_web/docker_local_settings.py index 5c6e81c6c..2214191ad 100644 --- a/newsblur_web/docker_local_settings.py +++ b/newsblur_web/docker_local_settings.py @@ -21,7 +21,8 @@ DOCKERBUILD = True DEBUG = False DEBUG = True DEBUG_ASSETS = True -# DEBUG_QUERIES = True +DEBUG_QUERIES = True +DEBUG_QUERIES_SUMMARY_ONLY = True MEDIA_URL = '/media/' IMAGES_URL = '/imageproxy' SECRET_KEY = 'YOUR SECRET KEY' @@ -94,8 +95,7 @@ MONGO_DB = { } MONGO_ANALYTICS_DB = { 'name': 'nbanalytics', - 'host': 'db_mongo', - 'port': 29019, + 'host': 'db_mongo:29019', } MONGODB_SLAVE = { diff --git a/newsblur_web/settings.py b/newsblur_web/settings.py index 6decd09b0..cdddd0c48 100644 --- a/newsblur_web/settings.py +++ b/newsblur_web/settings.py @@ -34,7 +34,7 @@ from sentry_sdk.integrations.celery import CeleryIntegration import django.http import re from mongoengine import connect -from boto.s3.connection import S3Connection, OrdinaryCallingFormat +import boto3 from utils import jammit # =================== @@ -94,6 +94,8 @@ AUTO_PREMIUM_NEW_USERS = True AUTO_ENABLE_NEW_USERS = True ENFORCE_SIGNUP_CAPTCHA = False PAYPAL_TEST = False +DATA_UPLOAD_MAX_MEMORY_SIZE = 5242880 # 5 MB +FILE_UPLOAD_MAX_MEMORY_SIZE = 5242880 # 5 MB # Uncomment below to force all feeds to store this many stories. Default is to cut # off at 25 stories for single subscriber non-premium feeds and 500 for popular feeds. @@ -119,6 +121,7 @@ MIDDLEWARE = ( 'apps.profile.middleware.ServerHostnameMiddleware', 'oauth2_provider.middleware.OAuth2TokenMiddleware', # 'debug_toolbar.middleware.DebugToolbarMiddleware', + 'utils.request_introspection_middleware.DumpRequestMiddleware', 'apps.profile.middleware.DBProfilerMiddleware', 'apps.profile.middleware.SQLLogToConsoleMiddleware', 'utils.mongo_raw_log_middleware.MongoDumpMiddleware', @@ -232,6 +235,10 @@ LOGGING = { 'level': 'DEBUG', 'propagate': True, }, + 'subdomains.middleware': { + 'level': 'ERROR', + 'propagate': False, + } }, 'filters': { 'require_debug_false': { @@ -559,8 +566,11 @@ S3_AVATARS_BUCKET_NAME = 'avatars.newsblur.com' if DOCKERBUILD: from newsblur_web.docker_local_settings import * -else: + +try: from newsblur_web.local_settings import * +except ModuleNotFoundError: + pass try: from newsblur_web.task_env import * @@ -579,9 +589,11 @@ if not DEBUG: 'django_ses', ) + sentry_sdk.init( dsn=SENTRY_DSN, integrations=[DjangoIntegration(), RedisIntegration(), CeleryIntegration()], + server_name=SERVER_NAME, # Set traces_sample_rate to 1.0 to capture 100% # of transactions for performance monitoring. @@ -686,10 +698,11 @@ MONGO_ANALYTICS_DB_DEFAULTS = { 'alias': 'nbanalytics', } MONGO_ANALYTICS_DB = dict(MONGO_ANALYTICS_DB_DEFAULTS, **MONGO_ANALYTICS_DB) -MONGO_ANALYTICS_DB_NAME = MONGO_ANALYTICS_DB.pop('name') -# MONGO_ANALYTICS_URI = 'mongodb://%s' % (MONGO_ANALYTICS_DB.pop('host'),) -# MONGOANALYTICSDB = connect(MONGO_ANALYTICS_DB.pop('name'), host=MONGO_ANALYTICS_URI, **MONGO_ANALYTICS_DB) -MONGOANALYTICSDB = connect(MONGO_ANALYTICS_DB_NAME, **MONGO_ANALYTICS_DB) +if 'username' in MONGO_ANALYTICS_DB: + MONGOANALYTICSDB = connect(db=MONGO_ANALYTICS_DB['name'], host=f"mongodb://{MONGO_ANALYTICS_DB['username']}:{MONGO_ANALYTICS_DB['password']}@{MONGO_ANALYTICS_DB['host']}/?authSource=admin", alias="nbanalytics") +else: + MONGOANALYTICSDB = connect(db=MONGO_ANALYTICS_DB['name'], host=f"mongodb://{MONGO_ANALYTICS_DB['host']}/", alias="nbanalytics") + # ========= # = Redis = @@ -759,21 +772,17 @@ accept_content = ['pickle', 'json', 'msgpack', 'yaml'] JAMMIT = jammit.JammitAssets(ROOT_DIR) -if DEBUG: - MIDDLEWARE += ('utils.request_introspection_middleware.DumpRequestMiddleware',) - # MIDDLEWARE += ('utils.exception_middleware.ConsoleExceptionMiddleware',) - # ======= # = AWS = # ======= S3_CONN = None if BACKED_BY_AWS.get('pages_on_s3') or BACKED_BY_AWS.get('icons_on_s3'): - S3_CONN = S3Connection(S3_ACCESS_KEY, S3_SECRET, calling_format=OrdinaryCallingFormat()) - # if BACKED_BY_AWS.get('pages_on_s3'): - # S3_PAGES_BUCKET = S3_CONN.get_bucket(S3_PAGES_BUCKET_NAME) - # if BACKED_BY_AWS.get('icons_on_s3'): - # S3_ICONS_BUCKET = S3_CONN.get_bucket(S3_ICONS_BUCKET_NAME) + boto_session = boto3.Session( + aws_access_key_id=S3_ACCESS_KEY, + aws_secret_access_key=S3_SECRET, + ) + S3_CONN = boto_session.resource('s3') django.http.request.host_validation_re = re.compile(r"^([a-z0-9.-_\-]+|\[[a-f0-9]*:[a-f0-9:]+\])(:\d+)?$") diff --git a/node/favicons.coffee b/node/favicons.coffee index 58545c6b8..182ef7834 100644 --- a/node/favicons.coffee +++ b/node/favicons.coffee @@ -6,6 +6,8 @@ favicons = (app) => ENV_DEV = process.env.NODE_ENV == 'development' or process.env.NODE_ENV == 'development' ENV_PROD = process.env.NODE_ENV == 'production' ENV_DOCKER = process.env.NODE_ENV == 'docker' + MONGODB_USERNAME = process.env.MONGODB_USERNAME + MONGODB_PASSWORD = process.env.MONGODB_PASSWORD MONGODB_SERVER = "db_mongo" if ENV_DEV MONGODB_SERVER = 'localhost' @@ -27,7 +29,7 @@ favicons = (app) => log.debug "Running as production server" if ENV_PROD - url = "mongodb://#{MONGODB_SERVER}:#{MONGODB_PORT}/newsblur?replicaSet=nbset&readPreference=secondaryPreferred" + url = "mongodb://#{MONGODB_USERNAME}:#{MONGODB_PASSWORD}@#{MONGODB_SERVER}:#{MONGODB_PORT}/newsblur?replicaSet=nbset&readPreference=secondaryPreferred&authSource=admin" else url = "mongodb://#{MONGODB_SERVER}:#{MONGODB_PORT}/newsblur" diff --git a/node/favicons.js b/node/favicons.js index 8710fcf69..590da1d53 100644 --- a/node/favicons.js +++ b/node/favicons.js @@ -7,11 +7,13 @@ log = require('./log.js'); favicons = (app) => { - var ENV_DEBUG, ENV_DEV, ENV_DOCKER, ENV_PROD, MONGODB_PORT, MONGODB_SERVER, url; + var ENV_DEBUG, ENV_DEV, ENV_DOCKER, ENV_PROD, MONGODB_PASSWORD, MONGODB_PORT, MONGODB_SERVER, MONGODB_USERNAME, url; ENV_DEBUG = process.env.NODE_ENV === 'debug'; ENV_DEV = process.env.NODE_ENV === 'development' || process.env.NODE_ENV === 'development'; ENV_PROD = process.env.NODE_ENV === 'production'; ENV_DOCKER = process.env.NODE_ENV === 'docker'; + MONGODB_USERNAME = process.env.MONGODB_USERNAME; + MONGODB_PASSWORD = process.env.MONGODB_PASSWORD; MONGODB_SERVER = "db_mongo"; if (ENV_DEV) { MONGODB_SERVER = 'localhost'; @@ -33,7 +35,7 @@ log.debug("Running as production server"); } if (ENV_PROD) { - url = `mongodb://${MONGODB_SERVER}:${MONGODB_PORT}/newsblur?replicaSet=nbset&readPreference=secondaryPreferred`; + url = `mongodb://${MONGODB_USERNAME}:${MONGODB_PASSWORD}@${MONGODB_SERVER}:${MONGODB_PORT}/newsblur?replicaSet=nbset&readPreference=secondaryPreferred&authSource=admin`; } else { url = `mongodb://${MONGODB_SERVER}:${MONGODB_PORT}/newsblur`; } diff --git a/node/package-lock.json b/node/package-lock.json index 66dd30c00..d35a49124 100644 --- a/node/package-lock.json +++ b/node/package-lock.json @@ -1,8 +1,2799 @@ { "name": "newsblur", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "newsblur", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@postlight/mercury-parser": "^2.2.0", + "@sentry/node": "^6.3.5", + "@sentry/tracing": "^6.2.2", + "bufferutil": "^4.0.3", + "cjs": "0.0.11", + "connect-busboy": "^0.0.2", + "dotenv": "^8.2.0", + "esm": "^3.2.25", + "express": "^4.17.1", + "json-loader": "^0.5.7", + "mkdirp": "^0.5.5", + "moment-timezone": "^0.5.31", + "mongodb": "^3.6.3", + "psl": "^1.8.0", + "qs": "^6.9.4", + "redis": "^2.8.0", + "socket.io": "^3.1.1", + "socket.io-client": "^3.1.1", + "supervisor": "^0.12.0", + "utf-8-validate": "^5.0.4", + "ws": "^7.4.2" + }, + "devDependencies": {} + }, + "node_modules/@babel/runtime-corejs2": { + "version": "7.10.2", + "resolved": "https://registry.npmjs.org/@babel/runtime-corejs2/-/runtime-corejs2-7.10.2.tgz", + "integrity": "sha512-ZLwsFnNm3WpIARU1aLFtufjMHsmEnc8TjtrfAjmbgMbeoyR+LuQoyESoNdTfeDhL6IdY12SpeycXMgSgl8XGXA==", + "dependencies": { + "core-js": "^2.6.5", + "regenerator-runtime": "^0.13.4" + } + }, + "node_modules/@postlight/ci-failed-test-reporter": { + "version": "1.0.26", + "resolved": "https://registry.npmjs.org/@postlight/ci-failed-test-reporter/-/ci-failed-test-reporter-1.0.26.tgz", + "integrity": "sha512-xfXzxyOiKhco7Gx2OLTe9b66b0dFJw0elg94KGHoQXf5F8JqqFvdo35J8wayGOor64CSMvn+4Bjlu2NKV+yTGA==", + "dependencies": { + "dotenv": "^6.2.0", + "node-fetch": "^2.3.0" + }, + "bin": { + "ciftr": "cli.js" + } + }, + "node_modules/@postlight/ci-failed-test-reporter/node_modules/dotenv": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-6.2.0.tgz", + "integrity": "sha512-HygQCKUBSFl8wKQZBSemMywRWcEDNidvNbjGVyZu3nbZ8qq9ubiPoGLMdRDpfSrpkkm9BXYFkpKxxFX38o/76w==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@postlight/mercury-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@postlight/mercury-parser/-/mercury-parser-2.2.0.tgz", + "integrity": "sha512-nz6dIvCAaiv74o1vhhp0BRsUe+ysPbZG5mVNpJmgLoI/goOBqRMM3Yg8uXtnv++e7tzKFSXdls8b2/zKk1qL0Q==", + "bundleDependencies": [ + "jquery", + "moment-timezone", + "browser-request" + ], + "dependencies": { + "@babel/runtime-corejs2": "^7.2.0", + "@postlight/ci-failed-test-reporter": "^1.0", + "browser-request": "github:postlight/browser-request#feat-add-headers-to-response", + "cheerio": "^0.22.0", + "difflib": "github:postlight/difflib.js", + "ellipsize": "0.1.0", + "iconv-lite": "0.5.0", + "jquery": "^3.4.1", + "moment": "^2.23.0", + "moment-parseformat": "3.0.0", + "moment-timezone": "0.5.26", + "postman-request": "^2.88.1-postman.7.1", + "request-promise": "^4.2.2", + "string-direction": "^0.1.2", + "turndown": "^5.0.3", + "url": "^0.11.0", + "valid-url": "^1.0.9", + "wuzzy": "^0.1.4", + "yargs-parser": "^13.0.0" + }, + "bin": { + "mercury-parser": "cli.js" + }, + "engines": { + "node": ">=8.10" + } + }, + "node_modules/@postlight/mercury-parser/node_modules/jquery": { + "version": "3.4.1", + "inBundle": true, + "license": "MIT" + }, + "node_modules/@postlight/mercury-parser/node_modules/moment": { + "version": "2.23.0", + "inBundle": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@postlight/mercury-parser/node_modules/moment-timezone": { + "version": "0.5.26", + "inBundle": true, + "license": "MIT", + "dependencies": { + "moment": ">= 2.9.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@postman/form-data": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@postman/form-data/-/form-data-3.1.0.tgz", + "integrity": "sha512-6x1UHKQ45Sv5yLFjqhhtyk3YGOF9677RVRQjfr32Bkt45pH8yIlqcpPxiIR4/ZEs3GFk5vl5j9ymmdLTt0HR6Q==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@postman/tunnel-agent": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/@postman/tunnel-agent/-/tunnel-agent-0.6.3.tgz", + "integrity": "sha512-k57fzmAZ2PJGxfOA4SGR05ejorHbVAa/84Hxh/2nAztjNXc4ZjOm9NUIk6/Z6LCrBvJZqjRZbN8e/nROVUPVdg==", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@sentry/core": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.3.5.tgz", + "integrity": "sha512-VR2ibDy33mryD0mT6d9fGhKjdNzS2FSwwZPe9GvmNOjkyjly/oV91BKVoYJneCqOeq8fyj2lvkJGKuupdJNDqg==", + "dependencies": { + "@sentry/hub": "6.3.5", + "@sentry/minimal": "6.3.5", + "@sentry/types": "6.3.5", + "@sentry/utils": "6.3.5", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/core/node_modules/@sentry/hub": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.3.5.tgz", + "integrity": "sha512-ZYFo7VYKwdPVjuV9BDFiYn+MpANn6eZMz5QDBfZ2dugIvIVbuOyOOLx8PSa3ZXJoVTZZ7s2wD2fi/ZxKjNjZOQ==", + "dependencies": { + "@sentry/types": "6.3.5", + "@sentry/utils": "6.3.5", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/core/node_modules/@sentry/minimal": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.3.5.tgz", + "integrity": "sha512-4RqIGAU0+8iI/1sw0GYPTr4SUA88/i2+JPjFJ+qloh5ANVaNwhFPRChw+Ys9xpre8LV9JZrEsEf8AvQr4fkNbA==", + "dependencies": { + "@sentry/hub": "6.3.5", + "@sentry/types": "6.3.5", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/core/node_modules/@sentry/types": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.3.5.tgz", + "integrity": "sha512-tY/3pkAmGYJ3F0BtwInsdt/uclNvF8aNG7XHsTPQNzk7BkNVWjCXx0sjxi6CILirl5nwNxYxVeTr2ZYAEZ/dSQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/core/node_modules/@sentry/utils": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.3.5.tgz", + "integrity": "sha512-kHUcZ37QYlNzz7c9LVdApITXHaNmQK7+sw/If3M/qpff1fd5XoecA8laLfcYuz+Cw5mRhVmdhPcCRM3Xi1IGXg==", + "dependencies": { + "@sentry/types": "6.3.5", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/hub": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.2.2.tgz", + "integrity": "sha512-VR6uQGRYt6RP633FHShlSLj0LUKGVrlTeSlwCoooWM5FR9lmi6akAaweuxpG78/kZvXrAWpjX6/nuYwHKGwzGA==", + "dependencies": { + "@sentry/types": "6.2.2", + "@sentry/utils": "6.2.2", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/minimal": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.2.2.tgz", + "integrity": "sha512-l0IgoGQgg1lTd4qDU8bQn25sbZBg8PwIHfuTLbGMlRr1flDXHOM1UXajWK/UKbAPelnU7M2JBSVzgl7PwjprzA==", + "dependencies": { + "@sentry/hub": "6.2.2", + "@sentry/types": "6.2.2", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/node": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@sentry/node/-/node-6.3.5.tgz", + "integrity": "sha512-scPB+DoAEPaqkYuyb8d/gVWbFmX5PhaYSNHybeHncaP/P4itLdq/AoAWGNxl0Hj4EQokfT4OZWxaaJi7SCYnaw==", + "dependencies": { + "@sentry/core": "6.3.5", + "@sentry/hub": "6.3.5", + "@sentry/tracing": "6.3.5", + "@sentry/types": "6.3.5", + "@sentry/utils": "6.3.5", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/node/node_modules/@sentry/hub": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.3.5.tgz", + "integrity": "sha512-ZYFo7VYKwdPVjuV9BDFiYn+MpANn6eZMz5QDBfZ2dugIvIVbuOyOOLx8PSa3ZXJoVTZZ7s2wD2fi/ZxKjNjZOQ==", + "dependencies": { + "@sentry/types": "6.3.5", + "@sentry/utils": "6.3.5", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/node/node_modules/@sentry/minimal": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.3.5.tgz", + "integrity": "sha512-4RqIGAU0+8iI/1sw0GYPTr4SUA88/i2+JPjFJ+qloh5ANVaNwhFPRChw+Ys9xpre8LV9JZrEsEf8AvQr4fkNbA==", + "dependencies": { + "@sentry/hub": "6.3.5", + "@sentry/types": "6.3.5", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/node/node_modules/@sentry/tracing": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.3.5.tgz", + "integrity": "sha512-TNKAST1ge2g24BlTfVxNp4gP5t3drbi0OVCh8h8ah+J7UjHSfdiqhd9W2h5qv1GO61gGlpWeN/TyioyQmOxu0Q==", + "dependencies": { + "@sentry/hub": "6.3.5", + "@sentry/minimal": "6.3.5", + "@sentry/types": "6.3.5", + "@sentry/utils": "6.3.5", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/node/node_modules/@sentry/types": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.3.5.tgz", + "integrity": "sha512-tY/3pkAmGYJ3F0BtwInsdt/uclNvF8aNG7XHsTPQNzk7BkNVWjCXx0sjxi6CILirl5nwNxYxVeTr2ZYAEZ/dSQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/node/node_modules/@sentry/utils": { + "version": "6.3.5", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.3.5.tgz", + "integrity": "sha512-kHUcZ37QYlNzz7c9LVdApITXHaNmQK7+sw/If3M/qpff1fd5XoecA8laLfcYuz+Cw5mRhVmdhPcCRM3Xi1IGXg==", + "dependencies": { + "@sentry/types": "6.3.5", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/tracing": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@sentry/tracing/-/tracing-6.2.2.tgz", + "integrity": "sha512-mAkPoqtofNfka/u9rOVVDQPaEoTmr0AQh654g9ZqsaqsOJLKjB4FDLVNubWs90fjeKqHiYkI3ZHPak2TzHBPkw==", + "dependencies": { + "@sentry/hub": "6.2.2", + "@sentry/minimal": "6.2.2", + "@sentry/types": "6.2.2", + "@sentry/utils": "6.2.2", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/types": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.2.2.tgz", + "integrity": "sha512-Y/1sRtw3a5JU4YdNBig8lLSVJ1UdYtuge+QP1CVLcLSAbq07Ok1bvF+Z+BlNcnHqle2Fl8aKuryG5Yu86enOyQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/@sentry/utils": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.2.2.tgz", + "integrity": "sha512-qaee6X6VDNZ8HeO83/veaKw0KuhDE7j1R+Yryme3PywFzsoTzutDrEQjb7gvcHAhBaAYX8IHUBHgxcFI9BxI+w==", + "dependencies": { + "@sentry/types": "6.2.2", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@types/component-emitter": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@types/component-emitter/-/component-emitter-1.2.10.tgz", + "integrity": "sha512-bsjleuRKWmGqajMerkzox19aGbscQX5rmmvvXl3wlIp5gMG1HgkiwPxsN5p070fBDKTNSPgojVbuY1+HWMbFhg==" + }, + "node_modules/@types/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-y7mImlc/rNkvCRmg8gC3/lj87S7pTUIJ6QGjwHR9WQJcFs+ZMTOaoPrkdFA/YdbuqVEmEbb5RdhVxMkAcgOnpg==" + }, + "node_modules/@types/cors": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.9.tgz", + "integrity": "sha512-zurD1ibz21BRlAOIKP8yhrxlqKx6L9VCwkB5kMiP6nZAhoF5MvC7qS1qPA7nRcr1GJolfkQC7/EAL4hdYejLtg==" + }, + "node_modules/@types/node": { + "version": "14.14.23", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.23.tgz", + "integrity": "sha512-pkKXgf96TELUhrc8C01J0e+If5qb0WqF+WF09FbCINywIk+iUEQgMlA8IxgdZ79qQ88Bljw+9NcqwvWGXknWDw==" + }, + "node_modules/abab": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/abab/-/abab-2.0.3.tgz", + "integrity": "sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==" + }, + "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/accepts/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/acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-globals": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.3.4.tgz", + "integrity": "sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==", + "dependencies": { + "acorn": "^6.0.1", + "acorn-walk": "^6.0.1" + } + }, + "node_modules/acorn-globals/node_modules/acorn": { + "version": "6.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.4.1.tgz", + "integrity": "sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-walk": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-6.2.0.tgz", + "integrity": "sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.2.tgz", + "integrity": "sha512-k+V+hzjm5q/Mr8ef/1Y9goCmlsK4I6Sm74teeyGvFk1XrOsbsKLjEdrvny42CZ+a8sXbk8KWpY/bDwS+FLL2UQ==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "node_modules/array-equal": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, + "node_modules/asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "dependencies": { + "safer-buffer": "~2.1.0" + } + }, + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/async-limiter": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", + "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", + "engines": { + "node": "*" + } + }, + "node_modules/aws4": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.0.tgz", + "integrity": "sha512-3YDiu347mtVtjpyV3u5kVqQLP242c06zwDOgpeRnybmXlYYsLbtTrUBUm8i8srONt+FWobl5aibnU1030PeeuA==" + }, + "node_modules/backo2": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" + }, + "node_modules/base64-arraybuffer": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=", + "engines": { + "node": ">= 0.6.0" + } + }, + "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/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "dependencies": { + "tweetnacl": "^0.14.3" + } + }, + "node_modules/bl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/bl/-/bl-2.2.1.tgz", + "integrity": "sha512-6Pesp1w0DEX1N550i/uGV/TqucVL4AM/pgThFSN/Qq9si1/DF9aIHs1BxD8V/QU0HoeHO6cQRTAuYnLPKq1e4g==", + "dependencies": { + "readable-stream": "^2.3.5", + "safe-buffer": "^5.1.1" + } + }, + "node_modules/bluebird": { + "version": "2.11.0", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz", + "integrity": "sha1-U0uQM8AiyVecVro7Plpcqvu2UOE=" + }, + "node_modules/body-parser": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.0.tgz", + "integrity": "sha512-dhEPs72UPbDnAQJ9ZKMNTP6ptJaionhP5cBb541nXPlW60Jepo9RV/a4fX4XWW9CuFNK22krhrj1+rgzifNCsw==", + "dependencies": { + "bytes": "3.1.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "on-finished": "~2.3.0", + "qs": "6.7.0", + "raw-body": "2.4.0", + "type-is": "~1.6.17" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/body-parser/node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" + }, + "node_modules/brotli": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.2.tgz", + "integrity": "sha1-UlqcrU/LqWR119OI9q7LE+7VL0Y=", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/browser-process-hrtime": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz", + "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" + }, + "node_modules/browser-request": { + "version": "0.3.2", + "resolved": "git+ssh://git@github.com/postlight/browser-request.git#38faa5b85741aabfca61aa37d1ef044d68969ddf", + "integrity": "sha512-TOvTWJ0BrWcB8Ach1AvdSBuczm2fsJdBlmo8D4N8fei7xfboW9VEk67zfriCiBo3/19Xe1waSstCEcLFUeBCjA==", + "engines": [ + "node" + ], + "dependencies": { + "http-headers": "^3.0.1" + } + }, + "node_modules/bson": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bson/-/bson-1.1.5.tgz", + "integrity": "sha512-kDuEzldR21lHciPQAIulLs1LZlCXdLziXI6Mb/TDkwXhb//UORJNPXgcRs2CuO4H0DcMkpfT3/ySsP3unoZjBg==", + "engines": { + "node": ">=0.6.19" + } + }, + "node_modules/bufferutil": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.3.tgz", + "integrity": "sha512-yEYTwGndELGvfXsImMBLop58eaGW+YdONi1fNjTINSY98tmMmFijBG6WXgdkfuLNt4imzQNtIE+eBp1PVpMCSw==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.2.0" + } + }, + "node_modules/busboy": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-0.2.13.tgz", + "integrity": "sha1-kPxPajln2BVhb8l2v6jlau0MWLY=", + "dependencies": { + "dicer": "0.2.5", + "readable-stream": "1.1.x" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/busboy/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/busboy/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "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/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "node_modules/cheerio": { + "version": "0.22.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz", + "integrity": "sha1-qbqoYKP5tZWmuBsahocxIe06Jp4=", + "dependencies": { + "css-select": "~1.2.0", + "dom-serializer": "~0.1.0", + "entities": "~1.1.1", + "htmlparser2": "^3.9.1", + "lodash.assignin": "^4.0.9", + "lodash.bind": "^4.1.4", + "lodash.defaults": "^4.0.1", + "lodash.filter": "^4.4.0", + "lodash.flatten": "^4.2.0", + "lodash.foreach": "^4.3.0", + "lodash.map": "^4.4.0", + "lodash.merge": "^4.4.0", + "lodash.pick": "^4.2.1", + "lodash.reduce": "^4.4.0", + "lodash.reject": "^4.4.0", + "lodash.some": "^4.4.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cjs": { + "version": "0.0.11", + "resolved": "https://registry.npmjs.org/cjs/-/cjs-0.0.11.tgz", + "integrity": "sha1-H+Uo69qPnuIfgaf3spmo1wsUpHw=", + "dependencies": { + "sync-channel": "*" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "node_modules/connect-busboy": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/connect-busboy/-/connect-busboy-0.0.2.tgz", + "integrity": "sha1-rFyclmchcYheV2xmsr/ZXTuxEJc=", + "dependencies": { + "busboy": "*" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "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-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/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, + "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.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. Please, upgrade your dependencies to the actual version of core-js.", + "hasInstallScript": true + }, + "node_modules/core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/css-select": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-1.2.0.tgz", + "integrity": "sha1-KzoRBTnFNV8c2NMUYj6HCxIeyFg=", + "dependencies": { + "boolbase": "~1.0.0", + "css-what": "2.1", + "domutils": "1.5.1", + "nth-check": "~1.0.1" + } + }, + "node_modules/css-what": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-2.1.3.tgz", + "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==", + "engines": { + "node": "*" + } + }, + "node_modules/cssom": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.8.tgz", + "integrity": "sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==" + }, + "node_modules/cssstyle": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-1.4.0.tgz", + "integrity": "sha512-GBrLZYZ4X4x6/QEoBnIrqb8B/f5l4+8me2dkom/j1Gtbxy0kBv6OGzKuAsGM75bkGwGAFkt56Iwg28S3XTZgSA==", + "dependencies": { + "cssom": "0.3.x" + } + }, + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "dependencies": { + "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/data-urls": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.1.0.tgz", + "integrity": "sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==", + "dependencies": { + "abab": "^2.0.0", + "whatwg-mimetype": "^2.2.0", + "whatwg-url": "^7.0.0" + } + }, + "node_modules/data-urls/node_modules/whatwg-url": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-7.1.0.tgz", + "integrity": "sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/denque": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/denque/-/denque-1.4.1.tgz", + "integrity": "sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ==", + "engines": { + "node": ">=0.10" + } + }, + "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/dicer": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/dicer/-/dicer-0.2.5.tgz", + "integrity": "sha1-WZbAhrszIYyBLAkL3cCc0S+stw8=", + "dependencies": { + "readable-stream": "1.1.x", + "streamsearch": "0.1.2" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/dicer/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/dicer/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/difflib": { + "version": "0.2.6", + "resolved": "git+ssh://git@github.com/postlight/difflib.js.git#32e8e38c7fcd935241b9baab71bb432fd9b166ed", + "integrity": "sha512-uFNs7czGYLWdMP22WQhD/vlFen/CuKzC+KiajNCj+ik2Ah/I9i2AFyMWkBjFgbVFGhv95kBHOtx7tgF6IVngqA==", + "dependencies": { + "heap": ">= 0.2.0" + } + }, + "node_modules/dom-serializer": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-0.1.1.tgz", + "integrity": "sha512-l0IU0pPzLWSHBcieZbpOKgkIn3ts3vAh7ZuFyXNwJxJXk/c4Gwj9xaTJwIDVQCXawWD0qb3IzMGH5rglQaO0XA==", + "dependencies": { + "domelementtype": "^1.3.0", + "entities": "^1.1.1" + } + }, + "node_modules/domelementtype": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-1.3.1.tgz", + "integrity": "sha512-BSKB+TSpMpFI/HOxCNr1O8aMOTZ8hT3pM3GQ0w/mWRmkhEDSFJkkyzz4XQsBV44BChwGkrDfMyjVD0eA2aFV3w==" + }, + "node_modules/domexception": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", + "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", + "dependencies": { + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/domhandler": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-2.4.2.tgz", + "integrity": "sha512-JiK04h0Ht5u/80fdLMCEmV4zkNh2BcoMFBmZ/91WtYZ8qVXSKjiw7fXMgFPnHcSZgOo3XdinHvmnDUeMf5R4wA==", + "dependencies": { + "domelementtype": "1" + } + }, + "node_modules/domutils": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-1.5.1.tgz", + "integrity": "sha1-3NhIiib1Y9YQeeSMn3t+Mjc2gs8=", + "dependencies": { + "dom-serializer": "0", + "domelementtype": "1" + } + }, + "node_modules/dotenv": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.2.0.tgz", + "integrity": "sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==", + "engines": { + "node": ">=8" + } + }, + "node_modules/double-ended-queue": { + "version": "2.1.0-0", + "resolved": "https://registry.npmjs.org/double-ended-queue/-/double-ended-queue-2.1.0-0.tgz", + "integrity": "sha1-ED01J/0xUo9AGIEwyEHv3XgmTlw=" + }, + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "dependencies": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "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/ellipsize": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ellipsize/-/ellipsize-0.1.0.tgz", + "integrity": "sha1-nUNoLUS5GtFuvYQmisEDFwplU/g=" + }, + "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/engine.io": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-4.1.1.tgz", + "integrity": "sha512-t2E9wLlssQjGw0nluF6aYyfX8LwYU8Jj0xct+pAhfWfv/YrBn6TSNtEYsgxHIfaMqfrLx07czcMg9bMN6di+3w==", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~4.0.0", + "ws": "~7.4.2" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/engine.io-client": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-4.1.1.tgz", + "integrity": "sha512-iYasV/EttP/2pLrdowe9G3zwlNIFhwny8VSIh+vPlMnYZqSzLsTzSLa9hFy015OrH1s4fzoYxeHjVkO8hSFKwg==", + "dependencies": { + "base64-arraybuffer": "0.1.4", + "component-emitter": "~1.3.0", + "debug": "~4.3.1", + "engine.io-parser": "~4.0.1", + "has-cors": "1.1.0", + "parseqs": "0.0.6", + "parseuri": "0.0.6", + "ws": "~7.4.2", + "xmlhttprequest-ssl": "~1.5.4", + "yeast": "0.1.2" + } + }, + "node_modules/engine.io-parser": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.2.tgz", + "integrity": "sha512-sHfEQv6nmtJrq6TKuIz5kyEKH/qSdK56H/A+7DnAuUPWosnIZAS2NHNcPLmyjtY3cGS/MqJdZbUjW97JU72iYg==", + "dependencies": { + "base64-arraybuffer": "0.1.4" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/entities": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/entities/-/entities-1.1.2.tgz", + "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" + }, + "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/escodegen": { + "version": "1.14.2", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.14.2.tgz", + "integrity": "sha512-InuOIiKk8wwuOFg6x9BQXbzjrQhtyXh46K9bqVTPzSo2FnyMBaYGBMC6PhQy7yxxil9vIedFBweQBMK74/7o8A==", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=4.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express": { + "version": "4.17.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.17.1.tgz", + "integrity": "sha512-mHJ9O79RqluphRrcw2X/GTh3k9tVv8YcoyY4Kkh4WDMUYKRZUq0h1o0w2rrrxBqM7VoeUVqgb27xlEMXTnYt4g==", + "dependencies": { + "accepts": "~1.3.7", + "array-flatten": "1.1.1", + "body-parser": "1.19.0", + "content-disposition": "0.5.3", + "content-type": "~1.0.4", + "cookie": "0.4.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.5", + "qs": "6.7.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.1.2", + "send": "0.17.1", + "serve-static": "1.14.1", + "setprototypeof": "1.1.1", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/cookie": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.0.tgz", + "integrity": "sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/express/node_modules/qs": { + "version": "6.7.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.7.0.tgz", + "integrity": "sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "engines": [ + "node >=0.6.0" + ] + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=" + }, + "node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", + "engines": { + "node": "*" + } + }, + "node_modules/form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=", + "engines": { + "node": ">= 0.6" + } + }, + "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/getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "dependencies": { + "assert-plus": "^1.0.0" + } + }, + "node_modules/har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "engines": { + "node": ">=4" + } + }, + "node_modules/har-validator": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz", + "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==", + "deprecated": "this library is no longer supported", + "dependencies": { + "ajv": "^6.5.5", + "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/has-cors": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" + }, + "node_modules/heap": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.6.tgz", + "integrity": "sha1-CH4fELBGky/IWU3Z5tN4r8nR5aw=" + }, + "node_modules/html-encoding-sniffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", + "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", + "dependencies": { + "whatwg-encoding": "^1.0.1" + } + }, + "node_modules/htmlparser2": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-3.10.1.tgz", + "integrity": "sha512-IgieNijUMbkDovyoKObU1DUhm1iwNYE/fuifEoEHfd1oZKZDaONBSkal7Y01shxsM49R4XaMdGez3WnF9UfiCQ==", + "dependencies": { + "domelementtype": "^1.3.1", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^3.1.1" + } + }, + "node_modules/htmlparser2/node_modules/readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/htmlparser2/node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/htmlparser2/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/http-headers": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/http-headers/-/http-headers-3.0.2.tgz", + "integrity": "sha512-87E1I+2Wg4dxxz4rcxElo3dxO/w1ZtgL1yA0Sb6vH3qU16vRKq1NjWQv9SCY3ly2OQROcoxHZOUpmelS+k6wOw==", + "dependencies": { + "next-line": "^1.1.0" + } + }, + "node_modules/http-signature": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.4.tgz", + "integrity": "sha512-CbG3io8gUSIxNNSgq+XMjgpTMzAeVRipxVXjuGrDhH5M1a2kZ03w20s8FCLR1NjnnJj10KbvabvckmtQcYNb9g==", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.14.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", + "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/iconv-lite": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.0.tgz", + "integrity": "sha512-NnEhI9hIEKHOzJ4f697DMz9IQEXr/MMJ5w64vN2/4Ai+wRnvV7SBrL0KLoRlwaKVghOc7LQ5YkPLuX146b6Ydw==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "node_modules/jsdom": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.12.0.tgz", + "integrity": "sha512-y8Px43oyiBM13Zc1z780FrfNLJCXTL40EWlty/LXUtcjykRBNgLlCjWXpfSPBl2iv+N7koQN+dvqszHZgT/Fjw==", + "dependencies": { + "abab": "^2.0.0", + "acorn": "^5.5.3", + "acorn-globals": "^4.1.0", + "array-equal": "^1.0.0", + "cssom": ">= 0.3.2 < 0.4.0", + "cssstyle": "^1.0.0", + "data-urls": "^1.0.0", + "domexception": "^1.0.1", + "escodegen": "^1.9.1", + "html-encoding-sniffer": "^1.0.2", + "left-pad": "^1.3.0", + "nwsapi": "^2.0.7", + "parse5": "4.0.0", + "pn": "^1.1.0", + "request": "^2.87.0", + "request-promise-native": "^1.0.5", + "sax": "^1.2.4", + "symbol-tree": "^3.2.2", + "tough-cookie": "^2.3.4", + "w3c-hr-time": "^1.0.1", + "webidl-conversions": "^4.0.2", + "whatwg-encoding": "^1.0.3", + "whatwg-mimetype": "^2.1.0", + "whatwg-url": "^6.4.1", + "ws": "^5.2.0", + "xml-name-validator": "^3.0.0" + } + }, + "node_modules/jsdom/node_modules/ws": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz", + "integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==", + "dependencies": { + "async-limiter": "~1.0.0" + } + }, + "node_modules/json-loader": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz", + "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w==" + }, + "node_modules/json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "node_modules/jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "node_modules/left-pad": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", + "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", + "deprecated": "use String.prototype.padStart()" + }, + "node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dependencies": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" + }, + "node_modules/lodash.assignin": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.assignin/-/lodash.assignin-4.2.0.tgz", + "integrity": "sha1-uo31+4QesKPoBEIysOJjqNxqKKI=" + }, + "node_modules/lodash.bind": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lodash.bind/-/lodash.bind-4.2.1.tgz", + "integrity": "sha1-euMBfpOWIqwxt9fX3LGzTbFpDTU=" + }, + "node_modules/lodash.defaults": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" + }, + "node_modules/lodash.filter": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.filter/-/lodash.filter-4.6.0.tgz", + "integrity": "sha1-ZosdSYFgOuHMWm+nYBQ+SAtMSs4=" + }, + "node_modules/lodash.flatten": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" + }, + "node_modules/lodash.foreach": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.foreach/-/lodash.foreach-4.5.0.tgz", + "integrity": "sha1-Gmo16s5AEoDH8G3d7DUWWrJ+PlM=" + }, + "node_modules/lodash.map": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.map/-/lodash.map-4.6.0.tgz", + "integrity": "sha1-dx7Hg540c9nEzeKLGTlMNWL09tM=" + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/lodash.pick": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", + "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=" + }, + "node_modules/lodash.reduce": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reduce/-/lodash.reduce-4.6.0.tgz", + "integrity": "sha1-8atrg5KZrUj3hKu/R2WW8DuRTTs=" + }, + "node_modules/lodash.reject": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.reject/-/lodash.reject-4.6.0.tgz", + "integrity": "sha1-gNZJLcFHCGS79YNTO2UfQqn1JBU=" + }, + "node_modules/lodash.some": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/lodash.some/-/lodash.some-4.6.0.tgz", + "integrity": "sha1-G7nzFO9ri63tE7VJFpsqlF62jk0=" + }, + "node_modules/lodash.sortby": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=" + }, + "node_modules/lru_map": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", + "integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=" + }, + "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/memory-pager": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz", + "integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==", + "optional": true + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "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.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "dependencies": { + "mime-db": "1.44.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "node_modules/mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "engines": { + "node": "*" + } + }, + "node_modules/moment-parseformat": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/moment-parseformat/-/moment-parseformat-3.0.0.tgz", + "integrity": "sha512-dVgXe6b6DLnv4CHG7a1zUe5mSXaIZ3c6lSHm/EKeVeQI2/4pwe0VRde8OyoCE1Ro2lKT5P6uT9JElF7KDLV+jw==" + }, + "node_modules/moment-timezone": { + "version": "0.5.32", + "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.32.tgz", + "integrity": "sha512-Z8QNyuQHQAmWucp8Knmgei8YNo28aLjJq6Ma+jy1ZSpSk5nyfRT8xgUbSQvD2+2UajISfenndwvFuH3NGS+nvA==", + "dependencies": { + "moment": ">= 2.9.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mongodb": { + "version": "3.6.3", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.6.3.tgz", + "integrity": "sha512-rOZuR0QkodZiM+UbQE5kDsJykBqWi0CL4Ec2i1nrGrUI3KO11r6Fbxskqmq3JK2NH7aW4dcccBuUujAP0ERl5w==", + "dependencies": { + "bl": "^2.2.1", + "bson": "^1.1.4", + "denque": "^1.4.1", + "require_optional": "^1.0.1", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4" + }, + "optionalDependencies": { + "saslprep": "^1.0.0" + }, + "peerDependenciesMeta": { + "aws4": { + "optional": true + }, + "bson-ext": { + "optional": true + }, + "kerberos": { + "optional": true + }, + "mongodb-client-encryption": { + "optional": true + }, + "mongodb-extjson": { + "optional": true + }, + "snappy": { + "optional": true + } + } + }, + "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/next-line": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-line/-/next-line-1.1.0.tgz", + "integrity": "sha1-/K5XhTBStqm66CCOQN19PC0wRgM=" + }, + "node_modules/node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/node-gyp-build": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.2.3.tgz", + "integrity": "sha512-MN6ZpzmfNCRM+3t57PTJHgHyw/h4OWnZ6mR8P5j/uZtqQr46RRuDE/P+g3n0YR/AiYXeWixZZzaip77gdICfRg==", + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/nth-check": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-1.0.2.tgz", + "integrity": "sha512-WeBOdju8SnzPN5vTUJYxYUxLeXpCaVP5i5e0LF8fg7WORF2Wd7wFX/pk0tYZk7s8T+J7VLy0Da6J1+wCT0AtHg==", + "dependencies": { + "boolbase": "~1.0.0" + } + }, + "node_modules/nwsapi": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", + "integrity": "sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==" + }, + "node_modules/oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "engines": { + "node": "*" + } + }, + "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/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/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dependencies": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/parse5": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", + "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==" + }, + "node_modules/parseqs": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz", + "integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w==" + }, + "node_modules/parseuri": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz", + "integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow==" + }, + "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-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, + "node_modules/performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "node_modules/pn": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", + "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==" + }, + "node_modules/postman-request": { + "version": "2.88.1-postman.23", + "resolved": "https://registry.npmjs.org/postman-request/-/postman-request-2.88.1-postman.23.tgz", + "integrity": "sha512-ftqsjGCGKjk23c+gy85aw1Ubs1MIsULhkZ5D9IMuKP8jiGJXW7avNk9jMVfFcyONayvVllfyJugPHZydbt1baA==", + "dependencies": { + "@postman/form-data": "~3.1.0", + "@postman/tunnel-agent": "^0.6.3", + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "brotli": "~1.3.2", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "har-validator": "~5.1.3", + "http-signature": "~1.3.1", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "stream-length": "^1.0.2", + "tough-cookie": "~2.5.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postman-request/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/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/proxy-addr": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.6.tgz", + "integrity": "sha512-dh/frvCBVmSsDYzw6n926jv974gddhkFPfiN8hPOi30Wax25QZyZEGveluCgliBnqmuM+UJmBErbAUFIoDbjOw==", + "dependencies": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/qs": { + "version": "6.9.4", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.4.tgz", + "integrity": "sha512-A1kFqHekCTM7cz0udomYUoYNWjBebHm/5wzU/XqrBRBNWectVH0QIiN+NEcZ0Dte5hvzHwbr8+XQmguPhJ6WdQ==", + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "engines": { + "node": ">=0.4.x" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.0.tgz", + "integrity": "sha512-4Oz8DUIwdvoa5qMJelxipzi/iJIi40O5cGV1wNYp5hvZP8ZN0T+jiNkL0QepXs+EsQ9XJ8ipEDoiH70ySUJP3Q==", + "dependencies": { + "bytes": "3.1.0", + "http-errors": "1.7.2", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/redis": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/redis/-/redis-2.8.0.tgz", + "integrity": "sha512-M1OkonEQwtRmZv4tEWF2VgpG0JWJ8Fv1PhlgT5+B+uNq2cA3Rt1Yt/ryoR+vQNOQcIEgdCdfH0jr3bDpihAw1A==", + "dependencies": { + "double-ended-queue": "^2.1.0-0", + "redis-commands": "^1.2.0", + "redis-parser": "^2.6.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/redis-commands": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/redis-commands/-/redis-commands-1.4.0.tgz", + "integrity": "sha512-cu8EF+MtkwI4DLIT0x9P8qNTLFhQD4jLfxLR0cCNkeGzs87FN6879JOJwNQR/1zD7aSYNbU0hgsV9zGY71Itvw==" + }, + "node_modules/redis-parser": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/redis-parser/-/redis-parser-2.6.0.tgz", + "integrity": "sha1-Uu0J2srBCPGmMcB+m2mUHnoZUEs=", + "engines": { + "node": ">=0.10.0" + } + }, + "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/request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "dependencies": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/request-promise": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/request-promise/-/request-promise-4.2.5.tgz", + "integrity": "sha512-ZgnepCykFdmpq86fKGwqntyTiUrHycALuGggpyCZwMvGaZWgxW6yagT0FHkgo5LzYvOaCNvxYwWYIjevSH1EDg==", + "deprecated": "request-promise has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", + "dependencies": { + "bluebird": "^3.5.0", + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request-promise-core": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz", + "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==", + "dependencies": { + "lodash": "^4.17.15" + }, + "engines": { + "node": ">=0.10.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request-promise-native": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz", + "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==", + "deprecated": "request-promise-native has been deprecated because it extends the now deprecated request package, see https://github.com/request/request/issues/3142", + "dependencies": { + "request-promise-core": "1.1.3", + "stealthy-require": "^1.1.1", + "tough-cookie": "^2.3.3" + }, + "engines": { + "node": ">=0.12.0" + }, + "peerDependencies": { + "request": "^2.34" + } + }, + "node_modules/request-promise/node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/request/node_modules/http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "dependencies": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" + } + }, + "node_modules/request/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/require_optional": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/require_optional/-/require_optional-1.0.1.tgz", + "integrity": "sha512-qhM/y57enGWHAe3v/NcwML6a3/vfESLe/sGM2dII+gEO0BpKRUkWZow/tyloNqJyN6kXSl3RyyM8Ll5D/sJP8g==", + "dependencies": { + "resolve-from": "^2.0.0", + "semver": "^5.1.0" + } + }, + "node_modules/resolve-from": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=", + "engines": { + "node": ">=0.10.0" + } + }, + "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/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "node_modules/saslprep": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/saslprep/-/saslprep-1.0.3.tgz", + "integrity": "sha512-/MY/PEMbk2SuY5sScONwhUDsV2p77Znkb/q3nSVstq/yQzYJOH/Azh29p9oJLsl3LnQwSvZDKagDGBsBwSooag==", + "optional": true, + "dependencies": { + "sparse-bitfield": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, + "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/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/send": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/send/-/send-0.17.1.tgz", + "integrity": "sha512-BsVKsiGcQMFwT8UxypobUKyv7irCNRHk1T0G680vk88yf6LBByGcZJOTJCrTP2xVN6yI+XjPJcNuE3V4fT9sAg==", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.7.2", + "mime": "1.6.0", + "ms": "2.1.1", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node_modules/serve-static": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.1.tgz", + "integrity": "sha512-JMrvUwE54emCYWlTI+hGrGv5I8dEwmco/00EvkzIIsR7MqrHonbD9pO2MOfFnpFntl7ecpZs+3mW+XbQZu9QCg==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==" + }, + "node_modules/socket.io": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-3.1.1.tgz", + "integrity": "sha512-7cBWdsDC7bbyEF6WbBqffjizc/H4YF1wLdZoOzuYfo2uMNSFjJKuQ36t0H40o9B20DO6p+mSytEd92oP4S15bA==", + "dependencies": { + "@types/cookie": "^0.4.0", + "@types/cors": "^2.8.8", + "@types/node": "^14.14.10", + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.1", + "engine.io": "~4.1.0", + "socket.io-adapter": "~2.1.0", + "socket.io-parser": "~4.0.3" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.1.0.tgz", + "integrity": "sha512-+vDov/aTsLjViYTwS9fPy5pEtTkrbEKsw2M+oVSoFGw6OD1IpvlV1VPhUzNbofCQ8oyMbdYJqDtGdmHQK6TdPg==" + }, + "node_modules/socket.io-client": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-3.1.1.tgz", + "integrity": "sha512-BLgIuCjI7Sf3mDHunKddX9zKR/pbkP7IACM3sJS3jha+zJ6/pGKRV6Fz5XSBHCfUs9YzT8kYIqNwOOuFNLtnYA==", + "dependencies": { + "@types/component-emitter": "^1.2.10", + "backo2": "~1.0.2", + "component-emitter": "~1.3.0", + "debug": "~4.3.1", + "engine.io-client": "~4.1.0", + "parseuri": "0.0.6", + "socket.io-parser": "~4.0.4" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io-parser": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.0.4.tgz", + "integrity": "sha512-t+b0SS+IxG7Rxzda2EVvyBZbvFPBCjJoyHuE0P//7OAsN23GItzDRdWa6ALxZI/8R5ygK7jAR6t028/z+7295g==", + "dependencies": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sparse-bitfield": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", + "optional": true, + "dependencies": { + "memory-pager": "^1.0.2" + } + }, + "node_modules/sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "dependencies": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/stealthy-require": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", + "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stream-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/stream-length/-/stream-length-1.0.2.tgz", + "integrity": "sha1-gnfzy+5JpNqrz9tOL0qbXp8snwA=", + "dependencies": { + "bluebird": "^2.6.2" + } + }, + "node_modules/streamsearch": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", + "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" + }, + "node_modules/string-direction": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/string-direction/-/string-direction-0.1.2.tgz", + "integrity": "sha1-PYRT5ydKLkShQrPchEnftk2a3jo=" + }, + "node_modules/supervisor": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/supervisor/-/supervisor-0.12.0.tgz", + "integrity": "sha1-3n5jNwFbKRhRwQ81OMSn8EkX7ME=", + "bin": { + "node-supervisor": "lib/cli-wrapper.js", + "supervisor": "lib/cli-wrapper.js" + }, + "engines": { + "node": ">=0.6.0" + } + }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==" + }, + "node_modules/sync-channel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/sync-channel/-/sync-channel-0.0.6.tgz", + "integrity": "sha1-C62DDY8fEbjOYSVJ4Nlje7BlKFA=" + }, + "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/tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "dependencies": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tr46": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", + "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/turndown": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/turndown/-/turndown-5.0.3.tgz", + "integrity": "sha512-popfGXEiedpq6F5saRIAThKxq/bbEPVFnsDnUdjaDGIre9f3/OL9Yi/yPbPcZ7RYUDpekghr666bBfZPrwNnhQ==", + "dependencies": { + "jsdom": "^11.9.0" + } + }, + "node_modules/tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + }, + "node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dependencies": { + "prelude-ls": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/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/uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "dependencies": { + "punycode": "1.3.2", + "querystring": "0.2.0" + } + }, + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + }, + "node_modules/utf-8-validate": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.4.tgz", + "integrity": "sha512-MEF05cPSq3AwJ2C7B7sHAA6i53vONoZbMGX8My5auEVm6W+dJ2Jd/TZPyGJ5CH42V2XtbI5FD28HeHeqlPzZ3Q==", + "hasInstallScript": true, + "dependencies": { + "node-gyp-build": "^4.2.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", + "bin": { + "uuid": "bin/uuid" + } + }, + "node_modules/valid-url": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", + "integrity": "sha1-HBRHm0DxOXp1eC8RXkCGRHQzogA=" + }, + "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/verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "engines": [ + "node >=0.6.0" + ], + "dependencies": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "node_modules/w3c-hr-time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz", + "integrity": "sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==", + "dependencies": { + "browser-process-hrtime": "^1.0.0" + } + }, + "node_modules/webidl-conversions": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", + "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==" + }, + "node_modules/whatwg-encoding": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz", + "integrity": "sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==", + "dependencies": { + "iconv-lite": "0.4.24" + } + }, + "node_modules/whatwg-encoding/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/whatwg-mimetype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz", + "integrity": "sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==" + }, + "node_modules/whatwg-url": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.5.0.tgz", + "integrity": "sha512-rhRZRqx/TLJQWUpQ6bmrt2UV4f0HCQ463yQuONJqC6fO2VoEb1pTYddbe59SkYq87aoM5A3bdhMZiUiVws+fzQ==", + "dependencies": { + "lodash.sortby": "^4.7.0", + "tr46": "^1.0.1", + "webidl-conversions": "^4.0.2" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ws": { + "version": "7.4.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/wuzzy": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/wuzzy/-/wuzzy-0.1.5.tgz", + "integrity": "sha512-Gw3l/NBXvB2hVGF3KCOo+B4x6PbeK63Xn6y0pPPou5vd2Omj4vGu0XkoJ1sJ2YDBPee+vQXYeON/FVHSYidlNA==", + "dependencies": { + "lodash": "^4.17.15" + } + }, + "node_modules/xml-name-validator": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", + "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==" + }, + "node_modules/xmlhttprequest-ssl": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-1.5.5.tgz", + "integrity": "sha1-wodrBhaKrcQOV9l+gRkayPQ5iz4=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yeast": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz", + "integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk=" + } + }, "dependencies": { "@babel/runtime-corejs2": { "version": "7.10.2", @@ -55,13 +2846,6 @@ "yargs-parser": "^13.0.0" }, "dependencies": { - "http-headers": { - "version": "3.0.2", - "bundled": true, - "requires": { - "next-line": "^1.1.0" - } - }, "jquery": { "version": "3.4.1", "bundled": true @@ -76,10 +2860,6 @@ "requires": { "moment": ">= 2.9.0" } - }, - "next-line": { - "version": "1.1.0", - "bundled": true } } }, @@ -494,8 +3274,9 @@ "integrity": "sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==" }, "browser-request": { - "version": "github:postlight/browser-request#38faa5b85741aabfca61aa37d1ef044d68969ddf", - "from": "github:postlight/browser-request#feat-add-headers-to-response", + "version": "git+ssh://git@github.com/postlight/browser-request.git#38faa5b85741aabfca61aa37d1ef044d68969ddf", + "integrity": "sha512-TOvTWJ0BrWcB8Ach1AvdSBuczm2fsJdBlmo8D4N8fei7xfboW9VEk67zfriCiBo3/19Xe1waSstCEcLFUeBCjA==", + "from": "browser-request@github:postlight/browser-request#feat-add-headers-to-response", "requires": { "http-headers": "^3.0.1" } @@ -774,8 +3555,9 @@ } }, "difflib": { - "version": "github:postlight/difflib.js#32e8e38c7fcd935241b9baab71bb432fd9b166ed", - "from": "github:postlight/difflib.js", + "version": "git+ssh://git@github.com/postlight/difflib.js.git#32e8e38c7fcd935241b9baab71bb432fd9b166ed", + "integrity": "sha512-uFNs7czGYLWdMP22WQhD/vlFen/CuKzC+KiajNCj+ik2Ah/I9i2AFyMWkBjFgbVFGhv95kBHOtx7tgF6IVngqA==", + "from": "difflib@github:postlight/difflib.js", "requires": { "heap": ">= 0.2.0" } @@ -2010,16 +4792,16 @@ "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz", "integrity": "sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo=" }, - "string-direction": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/string-direction/-/string-direction-0.1.2.tgz", - "integrity": "sha1-PYRT5ydKLkShQrPchEnftk2a3jo=" - }, "string_decoder": { "version": "0.10.31", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=" }, + "string-direction": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/string-direction/-/string-direction-0.1.2.tgz", + "integrity": "sha1-PYRT5ydKLkShQrPchEnftk2a3jo=" + }, "supervisor": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/supervisor/-/supervisor-0.12.0.tgz", @@ -2226,7 +5008,8 @@ "ws": { "version": "7.4.2", "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz", - "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==" + "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA==", + "requires": {} }, "wuzzy": { "version": "0.1.5", diff --git a/templates/base.html b/templates/base.html index 82f677288..6f96e89bb 100644 --- a/templates/base.html +++ b/templates/base.html @@ -73,6 +73,7 @@ 'story_layout' : 'split', 'collapsed_folders' : [], 'story_styling' : 'sans-serif', + 'feed_font' : 'whitney', 'feed_size' : 'm', 'story_size' : 'm', 'story_line_spacing' : 'm', diff --git a/templates/maintenance_off.html b/templates/maintenance_off.html index fc5be933c..120824b50 100644 --- a/templates/maintenance_off.html +++ b/templates/maintenance_off.html @@ -85,10 +85,7 @@

NewsBlur is in maintenance mode

-

3:00a ET: Snapshotting is done, backup has been verified (woohoo!), and now the MongoDB cluster is syncing. Should be about an hour from now and all will be well.

-

12:15a ET: Snapshotting looks about half-way done. This unplanned downtime has made it to the top of Hacker News. If you'd like to comment or learn more details, I posted on the NewsBlur thread.

-

10:35p ET: Looks like the snapshot will take 10 hours to make. Ordinarily this wouldn't be a problem becuase the service is running and a snapshot would be made on a secondary DB. But all of the Mongo DBs faithfully deleted their data, so I'm taking a snapshot of a recent good backup. Once done, I can replicare the DB and we'll be back.

-

9:54p ET: Holy moly, when I switched to a new Mongo DB server, a hacker deleted all of NewsBlur’s mongo data and is now holding NewsBlur’s data hostage. I’m dipping into a backup from a few hours ago and will keep you all updated.

+

Performing some much needed maintenance to the MongoDB server. This should take between 5 and 10 minutes.

To pass the time, check out what's popular on MLTSHP.

diff --git a/templates/reader/manage_module.xhtml b/templates/reader/manage_module.xhtml index 490bf10c3..b314aeb9a 100644 --- a/templates/reader/manage_module.xhtml +++ b/templates/reader/manage_module.xhtml @@ -13,18 +13,53 @@