+# build folders and similar which are not needed for the docker build
ui/node_modules
-ui/dist
server/target
docker/dev/volumes
docker/federation/volumes
+docker/federation-test/volumes
.git
+ansible
+
+# exceptions, needed for federation-test build
+
+!server/target/debug/lemmy_server
+# local ansible configuration
ansible/inventory
ansible/inventory_dev
ansible/passwords/
+
+# docker build files
docker/lemmy_mine.hjson
docker/dev/env_deploy.sh
+docker/federation/volumes
+docker/federation-test/volumes
+docker/dev/volumes
+
+# local build files
build/
-.idea/
ui/src/translations
-docker/dev/volumes
-docker/federation-test/volumes
+
+# ide config
+.idea/
- Full vote scores `(+/-)` like old reddit.
- Themes, including light, dark, and solarized.
- Emojis with autocomplete support. Start typing `:`
- - User tagging using `@`, Community tagging using `#`.
+ - User tagging using `@`, Community tagging using `!`.
- Integrated image uploading in both posts and comments.
- A post can consist of a title and any combination of self text, a URL, or nothing else.
- Notifications, on comment replies and when you're tagged.
-#!/bin/sh
+#!/bin/bash
set -e
+BRANCH=$1
+
+git checkout $BRANCH
+
export COMPOSE_DOCKER_CLI_BUILD=1
export DOCKER_BUILDKIT=1
# Rebuilding dev docker
-docker-compose build
-docker tag dev_lemmy:latest dessalines/lemmy:test
-docker push dessalines/lemmy:test
+sudo docker build . -f "docker/dev/Dockerfile" -t "dessalines/lemmy:$BRANCH"
+sudo docker push "dessalines/lemmy:$BRANCH"
# Run the playbook
-pushd ../../../lemmy-ansible
-ansible-playbook -i test playbooks/site.yml --vault-password-file vault_pass
+pushd ../lemmy-ansible
+ansible-playbook -i test playbooks/site.yml
popd
--- /dev/null
+#!/bin/bash
+set -e
+
+pushd ../../server/
+cargo build
+popd
+
+sudo docker build ../../ --file ../federation/Dockerfile --tag lemmy-federation:latest
+
+sudo docker-compose --file ../federation/docker-compose.yml --project-directory . up -d
+
+pushd ../../ui
+yarn
+echo "Waiting for Lemmy to start..."
+while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8540/api/v1/site')" != "200" ]]; do sleep 1; done
+while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8550/api/v1/site')" != "200" ]]; do sleep 1; done
+while [[ "$(curl -s -o /dev/null -w '%{http_code}' 'localhost:8560/api/v1/site')" != "200" ]]; do sleep 1; done
+yarn api-test || true
+popd
+
+sudo docker-compose --file ../federation/docker-compose.yml --project-directory . down
+
+sudo rm -r volumes/
--- /dev/null
+FROM ekidd/rust-musl-builder:1.42.0-openssl11
+
+USER root
+RUN mkdir /app/dist/documentation/ -p \
+ && addgroup --gid 1001 lemmy \
+ && adduser --disabled-password --shell /bin/sh -u 1001 --ingroup lemmy lemmy
+
+# Copy resources
+COPY server/config/defaults.hjson /app/config/defaults.hjson
+COPY ui/dist /app/dist
+COPY server/target/debug/lemmy_server /app/lemmy
+
+RUN chown lemmy:lemmy /app/ -R
+USER lemmy
+EXPOSE 8536
+WORKDIR /app
+CMD ["/app/lemmy"]
--- /dev/null
+version: '3.3'
+
+services:
+ nginx:
+ image: nginx:1.17-alpine
+ ports:
+ - "8540:8540"
+ - "8550:8550"
+ - "8560:8560"
+ volumes:
+ # Hack to make this work from both docker/federation/ and docker/federation-test/
+ - ../federation/nginx.conf:/etc/nginx/nginx.conf
+ depends_on:
+ - lemmy_alpha
+ - pictrs_alpha
+ - lemmy_beta
+ - pictrs_beta
+ - lemmy_gamma
+ - pictrs_gamma
+ - iframely
+ restart: "always"
+
+ lemmy_alpha:
+ image: lemmy-federation:latest
+ environment:
+ - LEMMY_HOSTNAME=lemmy_alpha:8540
+ - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_alpha:5432/lemmy
+ - LEMMY_JWT_SECRET=changeme
+ - LEMMY_FRONT_END_DIR=/app/dist
+ - LEMMY_FEDERATION__ENABLED=true
+ - LEMMY_FEDERATION__TLS_ENABLED=false
+ - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy_beta,lemmy_gamma
+ - LEMMY_PORT=8540
+ - LEMMY_SETUP__ADMIN_USERNAME=lemmy_alpha
+ - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
+ - LEMMY_SETUP__SITE_NAME=lemmy_alpha
+ - RUST_BACKTRACE=1
+ - RUST_LOG=debug
+ restart: always
+ depends_on:
+ - postgres_alpha
+ postgres_alpha:
+ image: postgres:12-alpine
+ environment:
+ - POSTGRES_USER=lemmy
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=lemmy
+ volumes:
+ - ./volumes/postgres_alpha:/var/lib/postgresql/data
+ restart: always
+ pictrs_alpha:
+ image: asonix/pictrs:v0.1.13-r0
+ user: 991:991
+ volumes:
+ - ./volumes/pictrs_alpha:/mnt
+ restart: always
+
+ lemmy_beta:
+ image: lemmy-federation:latest
+ environment:
+ - LEMMY_HOSTNAME=lemmy_beta:8550
+ - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_beta:5432/lemmy
+ - LEMMY_JWT_SECRET=changeme
+ - LEMMY_FRONT_END_DIR=/app/dist
+ - LEMMY_FEDERATION__ENABLED=true
+ - LEMMY_FEDERATION__TLS_ENABLED=false
+ - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy_alpha,lemmy_gamma
+ - LEMMY_PORT=8550
+ - LEMMY_SETUP__ADMIN_USERNAME=lemmy_beta
+ - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
+ - LEMMY_SETUP__SITE_NAME=lemmy_beta
+ - RUST_BACKTRACE=1
+ - RUST_LOG=debug
+ restart: always
+ depends_on:
+ - postgres_beta
+ postgres_beta:
+ image: postgres:12-alpine
+ environment:
+ - POSTGRES_USER=lemmy
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=lemmy
+ volumes:
+ - ./volumes/postgres_beta:/var/lib/postgresql/data
+ restart: always
+ pictrs_beta:
+ image: asonix/pictrs:v0.1.13-r0
+ user: 991:991
+ volumes:
+ - ./volumes/pictrs_beta:/mnt
+ restart: always
+
+ lemmy_gamma:
+ image: lemmy-federation:latest
+ environment:
+ - LEMMY_HOSTNAME=lemmy_gamma:8560
+ - LEMMY_DATABASE_URL=postgres://lemmy:password@postgres_gamma:5432/lemmy
+ - LEMMY_JWT_SECRET=changeme
+ - LEMMY_FRONT_END_DIR=/app/dist
+ - LEMMY_FEDERATION__ENABLED=true
+ - LEMMY_FEDERATION__TLS_ENABLED=false
+ - LEMMY_FEDERATION__ALLOWED_INSTANCES=lemmy_alpha,lemmy_beta
+ - LEMMY_PORT=8560
+ - LEMMY_SETUP__ADMIN_USERNAME=lemmy_gamma
+ - LEMMY_SETUP__ADMIN_PASSWORD=lemmy
+ - LEMMY_SETUP__SITE_NAME=lemmy_gamma
+ - RUST_BACKTRACE=1
+ - RUST_LOG=debug
+ restart: always
+ depends_on:
+ - postgres_gamma
+ postgres_gamma:
+ image: postgres:12-alpine
+ environment:
+ - POSTGRES_USER=lemmy
+ - POSTGRES_PASSWORD=password
+ - POSTGRES_DB=lemmy
+ volumes:
+ - ./volumes/postgres_gamma:/var/lib/postgresql/data
+ restart: always
+ pictrs_gamma:
+ image: asonix/pictrs:v0.1.13-r0
+ user: 991:991
+ volumes:
+ - ./volumes/pictrs_gamma:/mnt
+ restart: always
+
+ iframely:
+ image: dogbin/iframely:latest
+ volumes:
+ - ../iframely.config.local.js:/iframely/config.local.js:ro
+ restart: always
--- /dev/null
+events {
+ worker_connections 1024;
+}
+
+http {
+ server {
+ listen 8540;
+ server_name 127.0.0.1;
+ access_log off;
+
+ # Upload limit for pictshare
+ client_max_body_size 50M;
+
+ location / {
+ proxy_pass http://lemmy_alpha:8540;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ # WebSocket support
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+
+ # pict-rs images
+ location /pictrs {
+ location /pictrs/image {
+ proxy_pass http://pictrs_alpha:8080/image;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+ # Block the import
+ return 403;
+ }
+
+ location /iframely/ {
+ proxy_pass http://iframely:80/;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+ }
+
+ server {
+ listen 8550;
+ server_name 127.0.0.1;
+ access_log off;
+
+ # Upload limit for pictshare
+ client_max_body_size 50M;
+
+ location / {
+ proxy_pass http://lemmy_beta:8550;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ # WebSocket support
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+
+ # pict-rs images
+ location /pictrs {
+ location /pictrs/image {
+ proxy_pass http://pictrs_beta:8080/image;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+ # Block the import
+ return 403;
+ }
+
+ location /iframely/ {
+ proxy_pass http://iframely:80/;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+ }
+
+ server {
+ listen 8560;
+ server_name 127.0.0.1;
+ access_log off;
+
+ # Upload limit for pictshare
+ client_max_body_size 50M;
+
+ location / {
+ proxy_pass http://lemmy_gamma:8560;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ # WebSocket support
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+
+ # pict-rs images
+ location /pictrs {
+ location /pictrs/image {
+ proxy_pass http://pictrs_gamma:8080/image;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+ # Block the import
+ return 403;
+ }
+
+ location /iframely/ {
+ proxy_pass http://iframely:80/;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ }
+ }
+}
--- /dev/null
+#!/bin/bash
+set -e
+
+# already start rust build in the background
+pushd ../../server/ || exit
+cargo build &
+popd || exit
+
+if [ "$1" = "-yarn" ]; then
+ pushd ../../ui/ || exit
+ yarn
+ yarn build
+ popd || exit
+fi
+
+# wait for rust build to finish
+pushd ../../server/ || exit
+cargo build
+popd || exit
+
+sudo docker build ../../ --file Dockerfile -t lemmy-federation:latest
+
+for Item in alpha beta gamma ; do
+ sudo mkdir -p volumes/pictrs_$Item
+ sudo chown -R 991:991 volumes/pictrs_$Item
+done
+
+sudo docker-compose up
Start typing...
- `@a_user_name` to get a list of usernames.
-- `#a_community` to get a list of communities.
+- `!a_community` to get a list of communities.
- `:emoji` to get a list of emojis.
## Sorting
git pull federation federation
```
-## Running
+## Running locally
You need to have the following packages installed, the Docker service needs to be running.
```
After the build is finished and the docker-compose setup is running, open [127.0.0.1:8540](http://127.0.0.1:8540) and
-[127.0.0.1:8541](http://127.0.0.1:8541) in your browser to use the test instances. You can login as admin with
-username `lemmy` and password `lemmy`, or create new accounts.
+[127.0.0.1:8550](http://127.0.0.1:8550) in your browser to use the test instances. You can login as admin with
+username `lemmy_alpha` and `lemmy_beta` respectively, with password `lemmy`.
-Please get in touch if you want to contribute to this, so we can coordinate things and avoid duplicate work.
+## Running on a server
+
+Note that federation is currently in alpha. Only use it for testing, not on any production server, and be aware
+that you might have to wipe the instance data at one point or another.
+
+Follow the normal installation instructions, either with [Ansible](administration_install_ansible.md) or
+[manually](administration_install_docker.md). Then replace the line `image: dessalines/lemmy:v0.x.x` in
+`/lemmy/docker-compose.yml` with `image: dessalines/lemmy:federation`. Also add the following in
+`/lemmy/lemmy.hjson`:
+
+```
+ federation: {
+ enabled: true
+ allowed_instances: example.com
+ }
+```
+
+Afterwards, and whenver you want to update to the latest version, run these commands on the server:
+
+```
+cd /lemmy/
+sudo docker-compose pull
+sudo docker-compose up -d
+```
tab_spaces = 2
-edition="2018"
\ No newline at end of file
+edition="2018"
+imports_layout="HorizontalVertical"
+merge_imports=true
+reorder_imports=true
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
-name = "activitypub"
-version = "0.2.0"
+name = "activitystreams"
+version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "464cb473bfb402b857cc15b1153974c203a43f1485da4dda15cd17a738548958"
dependencies = [
- "activitystreams-derive 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "activitystreams-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "activitystreams-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)",
+ "activitystreams-derive",
+ "chrono",
+ "mime",
+ "serde 1.0.114",
+ "serde_json",
+ "thiserror",
+ "url",
]
[[package]]
name = "activitystreams-derive"
-version = "0.2.0"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c39ba5929399e9f921055bac76dd8f47419fa5b6b6da1ac4c1e82b94ed0ac7b4"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
-name = "activitystreams-traits"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
+name = "activitystreams-ext"
+version = "0.1.0"
+source = "git+https://git.asonix.dog/asonix/activitystreams-ext#e5c97f4ea9f60e49bc7ff27fb0fb515d3190fd25"
dependencies = [
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)",
- "thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "activitystreams-new",
+ "serde 1.0.114",
+ "serde_json",
]
[[package]]
-name = "activitystreams-types"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
+name = "activitystreams-new"
+version = "0.1.0"
+source = "git+https://git.asonix.dog/asonix/activitystreams-sketch#99c7e9aa5596eda846a1ebd5978ca72d11d4c08a"
dependencies = [
- "activitystreams-derive 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "activitystreams-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)",
+ "activitystreams",
+ "serde 1.0.114",
+ "serde_json",
+ "typed-builder",
]
[[package]]
name = "actix"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4af87564ff659dee8f9981540cac9418c45e910c8072fdedd643a262a38fcaf"
dependencies = [
- "actix-http 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix_derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "trust-dns-proto 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "trust-dns-resolver 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "actix-http",
+ "actix-rt",
+ "actix_derive",
+ "bitflags",
+ "bytes",
+ "crossbeam-channel",
+ "derive_more",
+ "futures",
+ "lazy_static",
+ "log",
+ "parking_lot",
+ "pin-project",
+ "smallvec",
+ "tokio",
+ "tokio-util 0.2.0",
+ "trust-dns-proto",
+ "trust-dns-resolver",
]
[[package]]
name = "actix-codec"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380"
dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "tokio",
+ "tokio-util 0.2.0",
]
[[package]]
name = "actix-connect"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c95cc9569221e9802bf4c377f6c18b90ef10227d787611decf79fd47d2a8e76c"
dependencies = [
- "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-utils 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "trust-dns-proto 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "trust-dns-resolver 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "actix-codec",
+ "actix-rt",
+ "actix-service",
+ "actix-utils",
+ "derive_more",
+ "either",
+ "futures",
+ "http",
+ "log",
+ "trust-dns-proto",
+ "trust-dns-resolver",
]
[[package]]
name = "actix-files"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "193b22cb1f7b4ff12a4eb2415d6d19e47e44ea93e05930b30d05375ea29d3529"
dependencies = [
- "actix-http 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-web 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
- "mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "v_htmlescape 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "actix-http",
+ "actix-service",
+ "actix-web",
+ "bitflags",
+ "bytes",
+ "derive_more",
+ "futures-core",
+ "futures-util",
+ "log",
+ "mime",
+ "mime_guess",
+ "percent-encoding",
+ "v_htmlescape",
]
[[package]]
name = "actix-http"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-connect 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-threadpool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-utils 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "brotli2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "copyless 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "h2 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
- "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
+checksum = "c16664cc4fdea8030837ad5a845eb231fb93fc3c5c171edfefb52fad92ce9019"
+dependencies = [
+ "actix-codec",
+ "actix-connect",
+ "actix-rt",
+ "actix-service",
+ "actix-threadpool",
+ "actix-utils",
+ "base64 0.11.0",
+ "bitflags",
+ "brotli2",
+ "bytes",
+ "chrono",
+ "copyless",
+ "derive_more",
+ "either",
+ "encoding_rs",
+ "failure",
+ "flate2",
+ "futures-channel",
+ "futures-core",
+ "futures-util",
+ "fxhash",
+ "h2",
+ "http",
+ "httparse",
+ "indexmap",
+ "language-tags",
+ "lazy_static",
+ "log",
+ "mime",
+ "percent-encoding",
+ "pin-project",
+ "rand 0.7.3",
+ "regex",
+ "serde 1.0.114",
+ "serde_json",
+ "serde_urlencoded",
+ "sha1",
+ "slab",
+ "time",
]
[[package]]
name = "actix-macros"
-version = "0.1.1"
+version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a60f9ba7c4e6df97f3aacb14bb5c0cd7d98a49dcbaed0d7f292912ad9a6a3ed2"
dependencies = [
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote",
+ "syn",
]
[[package]]
name = "actix-router"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9d7a10ca4d94e8c8e7a87c5173aba1b97ba9a6563ca02b0e1cd23531093d3ec8"
dependencies = [
- "bytestring 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytestring",
+ "http",
+ "log",
+ "regex",
+ "serde 1.0.114",
]
[[package]]
name = "actix-rt"
version = "1.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227"
dependencies = [
- "actix-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-threadpool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "copyless 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
+ "actix-macros",
+ "actix-threadpool",
+ "copyless",
+ "futures-channel",
+ "futures-util",
+ "smallvec",
+ "tokio",
]
[[package]]
name = "actix-server"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e6d74b464215a473c973a2d7d03a69cc10f4ce1f4b38a7659c5193dc5c675630"
dependencies = [
- "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-utils 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
- "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "actix-codec",
+ "actix-rt",
+ "actix-service",
+ "actix-utils",
+ "futures-channel",
+ "futures-util",
+ "log",
+ "mio",
+ "mio-uds",
+ "num_cpus",
+ "slab",
+ "socket2",
]
[[package]]
name = "actix-service"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3e4fc95dfa7e24171b2d0bb46b85f8ab0e8499e4e3caec691fc4ea65c287564"
dependencies = [
- "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-util",
+ "pin-project",
]
[[package]]
name = "actix-testing"
-version = "1.0.0"
+version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "47239ca38799ab74ee6a8a94d1ce857014b2ac36f242f70f3f75a66f691e791c"
dependencies = [
- "actix-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-server 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
+ "actix-macros",
+ "actix-rt",
+ "actix-server",
+ "actix-service",
+ "log",
+ "socket2",
]
[[package]]
name = "actix-threadpool"
-version = "0.3.1"
+version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91164716d956745c79dcea5e66d2aa04506549958accefcede5368c70f2fd4ff"
dependencies = [
- "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "derive_more",
+ "futures-channel",
+ "lazy_static",
+ "log",
+ "num_cpus",
+ "parking_lot",
+ "threadpool",
]
[[package]]
name = "actix-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a4e5b4faaf105e9a6d389c606c298dcdb033061b00d532af9df56ff3a54995a8"
dependencies = [
- "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-utils 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "actix-codec",
+ "actix-rt",
+ "actix-service",
+ "actix-utils",
+ "derive_more",
+ "either",
+ "futures",
+ "log",
]
[[package]]
name = "actix-utils"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fcf8f5631bf01adec2267808f00e228b761c60c0584cc9fa0b5364f41d147f4e"
dependencies = [
- "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "actix-codec",
+ "actix-rt",
+ "actix-service",
+ "bitflags",
+ "bytes",
+ "either",
+ "futures",
+ "log",
+ "pin-project",
+ "slab",
]
[[package]]
name = "actix-web"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-http 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-router 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-server 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-testing 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-threadpool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-utils 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-web-codegen 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "awc 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
- "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
- "pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+checksum = "3158e822461040822f0dbf1735b9c2ce1f95f93b651d7a7aded00b1efbb1f635"
+dependencies = [
+ "actix-codec",
+ "actix-http",
+ "actix-macros",
+ "actix-router",
+ "actix-rt",
+ "actix-server",
+ "actix-service",
+ "actix-testing",
+ "actix-threadpool",
+ "actix-tls",
+ "actix-utils",
+ "actix-web-codegen",
+ "awc",
+ "bytes",
+ "derive_more",
+ "encoding_rs",
+ "futures",
+ "fxhash",
+ "log",
+ "mime",
+ "net2",
+ "pin-project",
+ "regex",
+ "serde 1.0.114",
+ "serde_json",
+ "serde_urlencoded",
+ "time",
+ "url",
]
[[package]]
name = "actix-web-actors"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc1bd41bd66c4e9b5274cec87aac30168e63d64e96fd19db38edef6b46ba2982"
dependencies = [
- "actix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-http 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-web 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "actix",
+ "actix-codec",
+ "actix-http",
+ "actix-web",
+ "bytes",
+ "futures",
+ "pin-project",
]
[[package]]
name = "actix-web-codegen"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a71bf475cbe07281d0b3696abb48212db118e7e23219f13596ce865235ff5766"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "actix_derive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b95aceadaf327f18f0df5962fedc1bde2f870566a0b9f65c89508a3b1f79334c"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[package]]
+name = "addr2line"
+version = "0.12.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "602d785912f476e480434627e8732e6766b760c045bbf897d9dfaa9f4fbd399c"
+dependencies = [
+ "gimli",
]
[[package]]
name = "adler32"
-version = "1.0.4"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "567b077b825e468cc974f0020d4082ee6e03132512f207ef1a02fd5d00d1f32d"
[[package]]
name = "aho-corasick"
-version = "0.7.10"
+version = "0.7.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "043164d8ba5c4c3035fec9bbee8647c0261d788f3474306f93bb65901cae0e86"
dependencies = [
- "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr",
]
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8",
]
[[package]]
name = "arc-swap"
-version = "0.4.5"
+version = "0.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4d25d88fd6b8041580a654f9d0c581a047baee2b3efee13275f2fc392fc75034"
[[package]]
name = "arrayvec"
-version = "0.4.12"
+version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
-]
+checksum = "cff77d8686867eceff3105329d4698d96c2391c176d5d03adc90c7389162b5b8"
[[package]]
name = "ascii_utils"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
[[package]]
name = "async-trait"
-version = "0.1.30"
+version = "0.1.36"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a265e3abeffdce30b2e26b7a11b222fe37c6067404001b434101457d0385eb92"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "attohttpc"
version = "0.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "93610ce1c035e8a273fe56a19852e42798f3c019ca2726e52d2971197f116525"
dependencies = [
- "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustls 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "webpki-roots 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "http",
+ "log",
+ "rustls",
+ "url",
+ "webpki",
+ "webpki-roots",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
- "hermit-abi 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hermit-abi",
+ "libc",
+ "winapi 0.3.8",
]
[[package]]
name = "autocfg"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
[[package]]
name = "autocfg"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
[[package]]
name = "awc"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d7601d4d1d7ef2335d6597a41b5fe069f6ab799b85f53565ab390e7b7065aac5"
dependencies = [
- "actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-http 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
- "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "actix-codec",
+ "actix-http",
+ "actix-rt",
+ "actix-service",
+ "base64 0.11.0",
+ "bytes",
+ "derive_more",
+ "futures-core",
+ "log",
+ "mime",
+ "percent-encoding",
+ "rand 0.7.3",
+ "serde 1.0.114",
+ "serde_json",
+ "serde_urlencoded",
]
[[package]]
name = "backtrace"
-version = "0.3.46"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "backtrace-sys 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "backtrace-sys"
-version = "0.1.35"
+version = "0.3.49"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05100821de9e028f12ae3d189176b41ee198341eb8f369956407fea2f5cc666c"
dependencies = [
- "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "addr2line",
+ "cfg-if",
+ "libc",
+ "miniz_oxide",
+ "object",
+ "rustc-demangle",
]
[[package]]
name = "base64"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
dependencies = [
- "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder",
+ "safemem",
]
[[package]]
name = "base64"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
dependencies = [
- "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byteorder",
]
[[package]]
name = "base64"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
[[package]]
name = "base64"
-version = "0.12.0"
+version = "0.12.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e223af0dc48c96d4f8342ec01a4974f139df863896b316681efd36742f22cc67"
[[package]]
name = "bcrypt"
-version = "0.7.0"
+version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "41b70db86f3c560199b0dada79a22b9a924622384abb2a756a9707ffcce077f2"
dependencies = [
- "base64 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "blowfish 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "base64 0.12.2",
+ "blowfish",
+ "byteorder",
+ "getrandom",
]
[[package]]
name = "bitflags"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]]
name = "block-buffer"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
dependencies = [
- "block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "block-padding",
+ "byte-tools",
+ "byteorder",
+ "generic-array",
]
[[package]]
name = "block-cipher-trait"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774"
dependencies = [
- "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "generic-array",
]
[[package]]
name = "block-padding"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
dependencies = [
- "byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "byte-tools",
]
[[package]]
name = "blowfish"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6aeb80d00f2688459b8542068abd974cfb101e7a82182414a99b5026c0d85cc3"
dependencies = [
- "block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "block-cipher-trait",
+ "byteorder",
+ "opaque-debug",
]
[[package]]
name = "brotli-sys"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd"
dependencies = [
- "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc",
+ "libc",
]
[[package]]
name = "brotli2"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e"
dependencies = [
- "brotli-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "brotli-sys",
+ "libc",
]
[[package]]
name = "bufstream"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
[[package]]
name = "bumpalo"
-version = "3.2.1"
+version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e8c087f005730276d1096a652e92a8bacee2e2472bcc9715a74d2bec38b5820"
[[package]]
name = "byte-tools"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
[[package]]
name = "byteorder"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
[[package]]
name = "bytes"
-version = "0.5.4"
+version = "0.5.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "118cf036fbb97d0816e3c34b2d7a1e8cfc60f68fcf63d550ddbe9bd5f59c213b"
+dependencies = [
+ "loom",
+]
[[package]]
name = "bytestring"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363"
dependencies = [
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes",
]
[[package]]
name = "cc"
-version = "1.0.50"
+version = "1.0.54"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7bbb73db36c1246e9034e307d0fba23f9a2e251faa47ade70c1bd252220c8311"
[[package]]
name = "cfg-if"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "chrono"
version = "0.4.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
dependencies = [
- "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-integer",
+ "num-traits 0.2.12",
+ "serde 1.0.114",
+ "time",
]
[[package]]
name = "clap"
-version = "2.33.0"
+version = "2.33.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdfa80d47f954d53a35a64987ca1422f495b8d6483c0fe9f7117b36c2a792129"
dependencies = [
- "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ansi_term",
+ "atty",
+ "bitflags",
+ "strsim 0.8.0",
+ "textwrap",
+ "unicode-width",
+ "vec_map",
]
[[package]]
name = "cloudabi"
version = "0.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
]
[[package]]
name = "comrak"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e17058cc536cf290563e88787d7b9e6030ce4742943017cc2ffb71f88034021c"
dependencies = [
- "clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "twoway 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "typed-arena 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "clap",
+ "entities",
+ "lazy_static",
+ "pest",
+ "pest_derive",
+ "regex",
+ "twoway",
+ "typed-arena",
+ "unicode_categories",
]
[[package]]
name = "config"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
dependencies = [
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde-hjson 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static",
+ "nom 5.1.2",
+ "serde 1.0.114",
+ "serde-hjson",
]
[[package]]
name = "copyless"
-version = "0.1.4"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2df960f5d869b2dd8532793fde43eb5427cceb126c929747a26823ab0eeb536"
[[package]]
name = "core-foundation"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
dependencies = [
- "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation-sys",
+ "libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
[[package]]
name = "crc32fast"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
]
[[package]]
name = "crossbeam-channel"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
dependencies = [
- "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils",
+ "maybe-uninit",
]
[[package]]
name = "crossbeam-utils"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
dependencies = [
- "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg 1.0.0",
+ "cfg-if",
+ "lazy_static",
]
[[package]]
name = "darling"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
dependencies = [
- "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "darling_core",
+ "darling_macro",
]
[[package]]
name = "darling_core"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
dependencies = [
- "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fnv",
+ "ident_case",
+ "proc-macro2",
+ "quote",
+ "strsim 0.9.3",
+ "syn",
]
[[package]]
name = "darling_macro"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
dependencies = [
- "darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "darling_core",
+ "quote",
+ "syn",
]
[[package]]
name = "derive_builder"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
dependencies = [
- "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "darling",
+ "derive_builder_core",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "derive_builder_core"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
dependencies = [
- "darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "derive_more"
-version = "0.99.5"
+version = "0.99.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc655351f820d774679da6cdc23355a93de496867d8203496675162e17b1d671"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "diesel"
-version = "1.4.4"
+version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2de9deab977a153492a1468d1b1c0662c1cf39e5ea87d0c060ecd59ef18d8c"
dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "diesel_derives 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
+ "byteorder",
+ "chrono",
+ "diesel_derives",
+ "pq-sys",
+ "r2d2",
+ "serde_json",
]
[[package]]
name = "diesel_derives"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "diesel_migrations"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf3cde8413353dc7f5d72fa8ce0b99a560a359d2c5ef1e5817ca731cd9008f4c"
dependencies = [
- "migrations_internals 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "migrations_macros 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "migrations_internals",
+ "migrations_macros",
]
[[package]]
name = "digest"
version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
dependencies = [
- "generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "generic-array",
]
[[package]]
name = "dotenv"
version = "0.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
[[package]]
name = "dtoa"
-version = "0.4.5"
+version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "134951f4028bdadb9b84baf4232681efbf277da25144b9b0ad65df75946c422b"
[[package]]
name = "either"
version = "1.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
[[package]]
name = "email"
version = "0.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "91549a51bb0241165f13d57fc4c72cef063b4088fb078b019ecbf464a45f22e4"
dependencies = [
- "base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "base64 0.9.3",
+ "chrono",
+ "encoding",
+ "lazy_static",
+ "rand 0.4.6",
+ "time",
+ "version_check 0.1.5",
]
[[package]]
name = "encoding"
version = "0.2.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
dependencies = [
- "encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding-index-japanese",
+ "encoding-index-korean",
+ "encoding-index-simpchinese",
+ "encoding-index-singlebyte",
+ "encoding-index-tradchinese",
]
[[package]]
name = "encoding-index-japanese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
dependencies = [
- "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_index_tests",
]
[[package]]
name = "encoding-index-korean"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
dependencies = [
- "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_index_tests",
]
[[package]]
name = "encoding-index-simpchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
dependencies = [
- "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_index_tests",
]
[[package]]
name = "encoding-index-singlebyte"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
dependencies = [
- "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_index_tests",
]
[[package]]
name = "encoding-index-tradchinese"
version = "1.20141219.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
dependencies = [
- "encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_index_tests",
]
[[package]]
name = "encoding_index_tests"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
[[package]]
name = "encoding_rs"
-version = "0.8.22"
+version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
]
[[package]]
name = "entities"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
[[package]]
name = "enum-as-inner"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bc4bfcfacb61d231109d1d55202c1f33263319668b168843e02ad4652725ec9c"
dependencies = [
- "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
- "atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)",
- "humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "atty",
+ "humantime",
+ "log",
+ "regex",
+ "termcolor",
]
[[package]]
name = "failure"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
dependencies = [
- "backtrace 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "backtrace",
+ "failure_derive",
]
[[package]]
name = "failure_derive"
-version = "0.1.7"
+version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
- "synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "synstructure",
]
[[package]]
name = "fake-simd"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
[[package]]
name = "fast_chemail"
version = "0.9.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4"
dependencies = [
- "ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ascii_utils",
]
[[package]]
name = "flate2"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
+ "crc32fast",
+ "libc",
+ "miniz_oxide",
]
[[package]]
name = "fnv"
-version = "1.0.6"
+version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
- "foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]]
name = "fuchsia-cprng"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]]
name = "fuchsia-zircon"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
+ "fuchsia-zircon-sys",
]
[[package]]
name = "fuchsia-zircon-sys"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
[[package]]
name = "futures"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1e05b85ec287aac0dc34db7d4a569323df697f9c55b99b15d6b4ef8cde49f613"
dependencies = [
- "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-channel",
+ "futures-core",
+ "futures-executor",
+ "futures-io",
+ "futures-sink",
+ "futures-task",
+ "futures-util",
]
[[package]]
name = "futures-channel"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f366ad74c28cca6ba456d95e6422883cfb4b252a83bed929c83abfdbbf2967d5"
dependencies = [
- "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core",
+ "futures-sink",
]
[[package]]
name = "futures-core"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59f5fff90fd5d971f936ad674802482ba441b6f09ba5e15fd8b39145582ca399"
[[package]]
name = "futures-executor"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10d6bb888be1153d3abeb9006b11b02cf5e9b209fda28693c31ae1e4e012e314"
dependencies = [
- "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-core",
+ "futures-task",
+ "futures-util",
]
[[package]]
name = "futures-io"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "de27142b013a8e869c14957e6d2edeef89e97c289e69d042ee3a49acd8b51789"
[[package]]
name = "futures-macro"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d0b5a30a4328ab5473878237c447333c093297bded83a4983d10f4deea240d39"
dependencies = [
- "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro-hack",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "futures-sink"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3f2032893cb734c7a05d85ce0cc8b8c4075278e93b24b66f9de99d6eb0fa8acc"
[[package]]
name = "futures-task"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bdb66b5f09e22019b1ab0830f7785bcea8e7a42148683f99214f73f8ec21a626"
+dependencies = [
+ "once_cell",
+]
[[package]]
name = "futures-util"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8764574ff08b701a084482c3c7031349104b07ac897393010494beaa18ce32c6"
dependencies = [
- "futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "futures-channel",
+ "futures-core",
+ "futures-io",
+ "futures-macro",
+ "futures-sink",
+ "futures-task",
+ "memchr",
+ "pin-project",
+ "pin-utils",
+ "proc-macro-hack",
+ "proc-macro-nested",
+ "slab",
]
[[package]]
name = "fxhash"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
+dependencies = [
+ "byteorder",
+]
+
+[[package]]
+name = "generator"
+version = "0.6.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "add72f17bb81521258fcc8a7a3245b1e184e916bfbe34f0ea89558f440df5c68"
dependencies = [
- "byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc",
+ "libc",
+ "log",
+ "rustc_version",
+ "winapi 0.3.8",
]
[[package]]
name = "generic-array"
version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
dependencies = [
- "typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "typenum",
]
[[package]]
name = "getrandom"
version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
+ "libc",
+ "wasi",
]
+[[package]]
+name = "gimli"
+version = "0.21.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bcc8e0c9bce37868955864dbecd2b1ab2bdf967e6f28066d65aaac620444b65c"
+
[[package]]
name = "h2"
-version = "0.2.4"
+version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79b7246d7e4b979c03fa093da39cfb3617a96bbeee6310af63991668d7e843ff"
dependencies = [
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "futures-sink",
+ "futures-util",
+ "http",
+ "indexmap",
+ "log",
+ "slab",
+ "tokio",
+ "tokio-util 0.3.1",
]
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
dependencies = [
- "unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-segmentation",
]
[[package]]
name = "hermit-abi"
-version = "0.1.11"
+version = "0.1.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9586eedd4ce6b3c498bc3b4dd92fc9f11166aa908a914071953768066c67909"
dependencies = [
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc",
]
[[package]]
name = "hostname"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e"
dependencies = [
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc",
+ "winutil",
]
[[package]]
name = "hostname"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
dependencies = [
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "match_cfg 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc",
+ "match_cfg",
+ "winapi 0.3.8",
]
[[package]]
name = "htmlescape"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
[[package]]
name = "http"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
dependencies = [
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes",
+ "fnv",
+ "itoa",
+]
+
+[[package]]
+name = "http-signature-normalization"
+version = "0.5.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "648233553603e7bb55bc1ea08a514661e212c09c10f6434507894273d8b5e773"
+dependencies = [
+ "chrono",
+ "thiserror",
]
[[package]]
name = "httparse"
version = "1.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
- "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quick-error",
]
[[package]]
name = "ident_case"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
[[package]]
name = "idna"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
dependencies = [
- "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)",
+ "matches",
+ "unicode-bidi",
+ "unicode-normalization",
]
[[package]]
name = "indexmap"
-version = "1.3.2"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c398b2b113b55809ceb9ee3e753fcbac793f1956663f3c36549c1346015c2afe"
dependencies = [
- "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg 1.0.0",
]
[[package]]
name = "iovec"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
dependencies = [
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc",
]
[[package]]
name = "ipconfig"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7e2f18aece9709094573a9f24f483c4f65caa4298e2f7ae1b71cc65d853fad7"
dependencies = [
- "socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "socket2",
+ "widestring",
+ "winapi 0.3.8",
+ "winreg",
+]
+
+[[package]]
+name = "itertools"
+version = "0.9.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "284f18f85651fe11e8a991b2adb42cb078325c996ed026d994719efcfca1d54b"
+dependencies = [
+ "either",
]
[[package]]
name = "itoa"
-version = "0.4.5"
+version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dc6f3ad7b9d11a0c00842ff8de1b60ee58661048eb8049ed33c73594f359d7e6"
[[package]]
name = "js-sys"
-version = "0.3.37"
+version = "0.3.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ce10c23ad2ea25ceca0093bd3192229da4c5b3c0f2de499c1ecac0d98d452177"
dependencies = [
- "wasm-bindgen 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
+ "wasm-bindgen",
]
[[package]]
name = "jsonwebtoken"
-version = "7.1.0"
+version = "7.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b1f325ae57ddcf609f02d891486ce740f5bbd0cc3e93f9bffaacdf6594b21404"
dependencies = [
- "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "ring 0.16.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)",
- "simple_asn1 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "base64 0.12.2",
+ "pem",
+ "ring",
+ "serde 1.0.114",
+ "serde_json",
+ "simple_asn1",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
dependencies = [
- "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.2.8",
+ "winapi-build",
]
[[package]]
name = "language-tags"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "lemmy_server"
version = "0.0.1"
dependencies = [
- "activitypub 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-files 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-web 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "actix-web-actors 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "attohttpc 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "bcrypt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "comrak 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "config 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "diesel 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "jsonwebtoken 7.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lettre 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "lettre_email 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "rss 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)",
- "sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "strum 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "strum_macros 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
+ "activitystreams",
+ "activitystreams-ext",
+ "activitystreams-new",
+ "actix",
+ "actix-files",
+ "actix-rt",
+ "actix-web",
+ "actix-web-actors",
+ "attohttpc",
+ "base64 0.12.2",
+ "bcrypt",
+ "chrono",
+ "comrak",
+ "config",
+ "diesel",
+ "diesel_migrations",
+ "dotenv",
+ "env_logger",
+ "failure",
+ "futures",
+ "htmlescape",
+ "http",
+ "http-signature-normalization",
+ "itertools",
+ "jsonwebtoken",
+ "lazy_static",
+ "lettre",
+ "lettre_email",
+ "log",
+ "openssl",
+ "percent-encoding",
+ "rand 0.7.3",
+ "regex",
+ "rss",
+ "serde 1.0.114",
+ "serde_json",
+ "sha2",
+ "strum",
+ "strum_macros",
+ "tokio",
+ "url",
+ "uuid 0.8.1",
]
[[package]]
name = "lettre"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bf43f3202a879fbdab4ecafec3349b0139f81d31c626246d53bcbb546253ffaa"
dependencies = [
- "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)",
+ "base64 0.10.1",
+ "bufstream",
+ "fast_chemail",
+ "hostname 0.1.5",
+ "log",
+ "native-tls",
+ "nom 4.2.3",
+ "serde 1.0.114",
+ "serde_derive",
+ "serde_json",
]
[[package]]
name = "lettre_email"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fd02480f8dcf48798e62113974d6ccca2129a51d241fa20f1ea349c8a42559d5"
dependencies = [
- "base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "email 0.0.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "lettre 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
- "time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "base64 0.10.1",
+ "email",
+ "lettre",
+ "mime",
+ "time",
+ "uuid 0.7.4",
]
[[package]]
name = "lexical-core"
-version = "0.6.2"
+version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "db65c6da02e61f55dae90a0ae427b2a5f6b3e8db09f58d10efab23af92592616"
dependencies = [
- "arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "arrayvec",
+ "bitflags",
+ "cfg-if",
+ "ryu",
+ "static_assertions",
]
[[package]]
name = "libc"
-version = "0.2.69"
+version = "0.2.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9457b06509d27052635f90d6466700c65095fdf75409b3fbdd903e988b886f49"
[[package]]
name = "linked-hash-map"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd"
dependencies = [
- "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_test 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 0.8.23",
+ "serde_test",
]
[[package]]
name = "linked-hash-map"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8dd5a6d5999d9907cda8ed67bbd137d3af8085216c2ac62de5be860bd41f304a"
[[package]]
name = "lock_api"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
dependencies = [
- "scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "scopeguard",
]
[[package]]
name = "log"
version = "0.4.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
+]
+
+[[package]]
+name = "loom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4ecc775857611e1df29abba5c41355cdf540e7e9d4acfdf0f355eefee82330b7"
+dependencies = [
+ "cfg-if",
+ "generator",
+ "scoped-tls",
]
[[package]]
name = "lru-cache"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
dependencies = [
- "linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "linked-hash-map 0.5.3",
]
[[package]]
name = "maplit"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
[[package]]
name = "match_cfg"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
[[package]]
name = "matches"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
[[package]]
name = "maybe-uninit"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
[[package]]
name = "memchr"
version = "2.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
[[package]]
name = "migrations_internals"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b4fc84e4af020b837029e017966f86a1c2d5e83e64b589963d5047525995860"
dependencies = [
- "diesel 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "diesel",
]
[[package]]
name = "migrations_macros"
version = "1.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c"
dependencies = [
- "migrations_internals 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "migrations_internals",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "mime"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
[[package]]
name = "mime_guess"
version = "2.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
dependencies = [
- "mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "mime",
+ "unicase",
]
[[package]]
name = "miniz_oxide"
-version = "0.3.6"
+version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435"
dependencies = [
- "adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "adler32",
]
[[package]]
name = "mio"
-version = "0.6.21"
+version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fce347092656428bc8eaf6201042cb551b8d67855af7374542a92a0fbfcac430"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
- "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
+ "fuchsia-zircon",
+ "fuchsia-zircon-sys",
+ "iovec",
+ "kernel32-sys",
+ "libc",
+ "log",
+ "miow",
+ "net2",
+ "slab",
+ "winapi 0.2.8",
]
[[package]]
name = "mio-uds"
-version = "0.6.7"
+version = "0.6.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afcb699eb26d4332647cc848492bbc15eafb26f08d0304550d5aa1f612e066f0"
dependencies = [
- "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
+ "iovec",
+ "libc",
+ "mio",
]
[[package]]
name = "miow"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
dependencies = [
- "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "kernel32-sys",
+ "net2",
+ "winapi 0.2.8",
+ "ws2_32-sys",
]
[[package]]
name = "native-tls"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d"
dependencies = [
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)",
- "schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)",
- "security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static",
+ "libc",
+ "log",
+ "openssl",
+ "openssl-probe",
+ "openssl-sys",
+ "schannel",
+ "security-framework",
+ "security-framework-sys",
+ "tempfile",
]
[[package]]
name = "net2"
-version = "0.2.33"
+version = "0.2.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ba7c918ac76704fb42afcbbb43891e72731f3dcca3bef2a19786297baf14af7"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
+ "libc",
+ "winapi 0.3.8",
]
-[[package]]
-name = "nodrop"
-version = "0.1.14"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-
[[package]]
name = "nom"
version = "4.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
dependencies = [
- "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr",
+ "version_check 0.1.5",
]
[[package]]
name = "nom"
-version = "5.1.1"
+version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ffb4262d26ed83a1c0a33a38fe2bb15797329c85770da05e6b828ddb782627af"
dependencies = [
- "lexical-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lexical-core",
+ "memchr",
+ "version_check 0.9.2",
]
[[package]]
name = "num-bigint"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
dependencies = [
- "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)",
- "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg 1.0.0",
+ "num-integer",
+ "num-traits 0.2.12",
]
[[package]]
name = "num-integer"
-version = "0.1.42"
+version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b"
dependencies = [
- "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg 1.0.0",
+ "num-traits 0.2.12",
]
[[package]]
name = "num-traits"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
dependencies = [
- "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num-traits 0.2.12",
]
[[package]]
name = "num-traits"
-version = "0.2.11"
+version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611"
dependencies = [
- "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg 1.0.0",
]
[[package]]
name = "num_cpus"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
dependencies = [
- "hermit-abi 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hermit-abi",
+ "libc",
]
+[[package]]
+name = "object"
+version = "0.20.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1ab52be62400ca80aa00285d25253d7f7c437b7375c4de678f5405d3afe82ca5"
+
+[[package]]
+name = "once_cell"
+version = "1.4.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0b631f7e854af39a1739f401cf34a8a013dfe09eac4fa4dba91e9768bd28168d"
+
[[package]]
name = "opaque-debug"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
[[package]]
name = "openssl"
version = "0.10.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd"
dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
+ "cfg-if",
+ "foreign-types",
+ "lazy_static",
+ "libc",
+ "openssl-sys",
]
[[package]]
name = "openssl-probe"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
[[package]]
name = "openssl-sys"
version = "0.9.58"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
dependencies = [
- "autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)",
- "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg 1.0.0",
+ "cc",
+ "libc",
+ "pkg-config",
+ "vcpkg",
]
[[package]]
name = "parking_lot"
version = "0.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
dependencies = [
- "lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "parking_lot_core 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lock_api",
+ "parking_lot_core",
]
[[package]]
name = "parking_lot_core"
-version = "0.7.1"
+version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d58c7c768d4ba344e3e8d72518ac13e259d7c7ade24167003b8488e10b6740a3"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
- "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
+ "cloudabi",
+ "libc",
+ "redox_syscall",
+ "smallvec",
+ "winapi 0.3.8",
]
[[package]]
name = "pem"
-version = "0.7.0"
+version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "59698ea79df9bf77104aefd39cc3ec990cb9693fb59c3b0a70ddf2646fdffb4b"
dependencies = [
- "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "base64 0.12.2",
+ "once_cell",
+ "regex",
]
[[package]]
name = "percent-encoding"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pest"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
dependencies = [
- "ucd-trie 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ucd-trie",
]
[[package]]
name = "pest_derive"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
dependencies = [
- "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "pest_generator 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pest",
+ "pest_generator",
]
[[package]]
name = "pest_generator"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
dependencies = [
- "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "pest_meta 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pest",
+ "pest_meta",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "pest_meta"
version = "2.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
dependencies = [
- "maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "maplit",
+ "pest",
+ "sha-1",
]
[[package]]
name = "pin-project"
-version = "0.4.9"
+version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "12e3a6cdbfe94a5e4572812a0201f8c0ed98c1c452c7b8563ce2276988ef9c17"
dependencies = [
- "pin-project-internal 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "pin-project-internal",
]
[[package]]
name = "pin-project-internal"
-version = "0.4.9"
+version = "0.4.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a0ffd45cf79d88737d7cc85bfd5d2894bee1139b356e616fe85dc389c61aaf7"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "pin-project-lite"
-version = "0.1.4"
+version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "282adbf10f2698a7a77f8e983a74b2d18176c19a7fd32a45446139ae7b02b715"
[[package]]
name = "pin-utils"
-version = "0.1.0-alpha.4"
+version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184"
[[package]]
name = "pkg-config"
version = "0.3.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
[[package]]
name = "ppv-lite86"
-version = "0.2.6"
+version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "237a5ed80e274dbc66f86bd59c1e25edc039660be53194b5fe0a482e0f2612ea"
[[package]]
name = "pq-sys"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda"
dependencies = [
- "vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "vcpkg",
]
[[package]]
name = "proc-macro-hack"
-version = "0.5.15"
+version = "0.5.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7e0456befd48169b9f13ef0f0ad46d492cf9d2dbb918bcf38e01eed4ce3ec5e4"
[[package]]
name = "proc-macro-nested"
-version = "0.1.4"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eba180dafb9038b050a4c280019bbedf9f2467b61e5d892dcad585bb57aadc5a"
[[package]]
name = "proc-macro2"
-version = "1.0.10"
+version = "1.0.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "beae6331a816b1f65d04c45b078fd8e6c93e8071771f41b8163255bbd8d7c8fa"
dependencies = [
- "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-xid",
]
[[package]]
name = "quick-error"
version = "1.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
[[package]]
name = "quick-xml"
version = "0.17.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0"
dependencies = [
- "encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)",
- "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "encoding_rs",
+ "memchr",
]
[[package]]
name = "quote"
-version = "1.0.3"
+version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "aa563d17ecb180e500da1cfd2b028310ac758de548efdd203e18f283af693f37"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
]
[[package]]
name = "r2d2"
version = "0.8.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af"
dependencies = [
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "scheduled-thread-pool 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log",
+ "parking_lot",
+ "scheduled-thread-pool",
]
[[package]]
name = "rand"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
dependencies = [
- "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "fuchsia-cprng",
+ "libc",
+ "rand_core 0.3.1",
+ "rdrand",
+ "winapi 0.3.8",
]
[[package]]
name = "rand"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
dependencies = [
- "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg 0.1.7",
+ "libc",
+ "rand_chacha 0.1.1",
+ "rand_core 0.4.2",
+ "rand_hc 0.1.0",
+ "rand_isaac",
+ "rand_jitter",
+ "rand_os",
+ "rand_pcg",
+ "rand_xorshift",
+ "winapi 0.3.8",
]
[[package]]
name = "rand"
version = "0.7.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
dependencies = [
- "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "getrandom",
+ "libc",
+ "rand_chacha 0.2.2",
+ "rand_core 0.5.1",
+ "rand_hc 0.2.0",
]
[[package]]
name = "rand_chacha"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
dependencies = [
- "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg 0.1.7",
+ "rand_core 0.3.1",
]
[[package]]
name = "rand_chacha"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
dependencies = [
- "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ppv-lite86",
+ "rand_core 0.5.1",
]
[[package]]
name = "rand_core"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
dependencies = [
- "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.4.2",
]
[[package]]
name = "rand_core"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
[[package]]
name = "rand_core"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
dependencies = [
- "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
+ "getrandom",
]
[[package]]
name = "rand_hc"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
dependencies = [
- "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.1",
]
[[package]]
name = "rand_hc"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
dependencies = [
- "rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.5.1",
]
[[package]]
name = "rand_isaac"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
dependencies = [
- "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.1",
]
[[package]]
name = "rand_jitter"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
dependencies = [
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc",
+ "rand_core 0.4.2",
+ "winapi 0.3.8",
]
[[package]]
name = "rand_os"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
dependencies = [
- "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cloudabi",
+ "fuchsia-cprng",
+ "libc",
+ "rand_core 0.4.2",
+ "rdrand",
+ "winapi 0.3.8",
]
[[package]]
name = "rand_pcg"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
dependencies = [
- "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "autocfg 0.1.7",
+ "rand_core 0.4.2",
]
[[package]]
name = "rand_xorshift"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
dependencies = [
- "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.1",
]
[[package]]
name = "rdrand"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
dependencies = [
- "rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand_core 0.3.1",
]
[[package]]
name = "redox_syscall"
version = "0.1.56"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
[[package]]
name = "regex"
-version = "1.3.7"
+version = "1.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9c3780fcf44b193bc4d09f36d2a3c87b251da4a046c87795a0d35f4f927ad8e6"
dependencies = [
- "aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)",
- "thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "aho-corasick",
+ "memchr",
+ "regex-syntax",
+ "thread_local",
]
[[package]]
name = "regex-syntax"
-version = "0.6.17"
+version = "0.6.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "26412eb97c6b088a6997e05f69403a802a92d520de2f8e63c2b65f9e0f47c4e8"
[[package]]
name = "remove_dir_all"
-version = "0.5.2"
+version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7"
dependencies = [
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8",
]
[[package]]
name = "resolv-conf"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a"
dependencies = [
- "hostname 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "hostname 0.3.1",
+ "quick-error",
]
[[package]]
name = "ring"
-version = "0.16.12"
+version = "0.16.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "952cd6b98c85bbc30efa1ba5783b8abf12fec8b3287ffa52605b9432313e34e4"
dependencies = [
- "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "web-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cc",
+ "libc",
+ "once_cell",
+ "spin",
+ "untrusted",
+ "web-sys",
+ "winapi 0.3.8",
]
[[package]]
name = "rss"
version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "99979205510c60f80a119dedbabd0b8426517384edf205322f8bcd51796bcef9"
dependencies = [
- "derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "derive_builder",
+ "quick-xml",
]
[[package]]
name = "rustc-demangle"
version = "0.1.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
[[package]]
name = "rustc_version"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
dependencies = [
- "semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "semver",
]
[[package]]
name = "rustls"
version = "0.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1"
dependencies = [
- "base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "ring 0.16.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "sct 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "base64 0.11.0",
+ "log",
+ "ring",
+ "sct",
+ "webpki",
]
[[package]]
name = "ryu"
-version = "1.0.3"
+version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "71d301d4193d031abdd79ff7e3dd721168a9572ef3fe51a1517aba235bd8f86e"
[[package]]
name = "safemem"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
[[package]]
name = "schannel"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static",
+ "winapi 0.3.8",
]
[[package]]
name = "scheduled-thread-pool"
version = "0.2.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0988d7fdf88d5e5fcf5923a0f1e8ab345f3e98ab4bc6bc45a2d5ff7f7458fbf6"
dependencies = [
- "parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "parking_lot",
]
+[[package]]
+name = "scoped-tls"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "332ffa32bf586782a3efaeb58f127980944bbc8c4d6913a86107ac2a5ab24b28"
+
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "sct"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c"
dependencies = [
- "ring 0.16.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ring",
+ "untrusted",
]
[[package]]
name = "security-framework"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535"
dependencies = [
- "bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bitflags",
+ "core-foundation",
+ "core-foundation-sys",
+ "libc",
+ "security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405"
dependencies = [
- "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "core-foundation-sys",
+ "libc",
]
[[package]]
name = "semver"
version = "0.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
dependencies = [
- "semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "semver-parser",
]
[[package]]
name = "semver-parser"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
[[package]]
name = "serde"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
[[package]]
name = "serde"
-version = "1.0.106"
+version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5317f7588f0a5078ee60ef675ef96735a1442132dc645eb1d12c018620ed8cd3"
dependencies = [
- "serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde_derive",
]
[[package]]
name = "serde-hjson"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8"
dependencies = [
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)",
- "regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static",
+ "linked-hash-map 0.3.0",
+ "num-traits 0.1.43",
+ "regex",
+ "serde 0.8.23",
]
[[package]]
name = "serde_derive"
-version = "1.0.106"
+version = "1.0.114"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a0be94b04690fbaed37cddffc5c134bf537c8e3329d53e982fe04c374978f8e"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "serde_json"
-version = "1.0.52"
+version = "1.0.55"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ec2c5d7e739bc07a3e73381a39d61fdb5f671c60c1df26a130690665803d8226"
dependencies = [
- "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
+ "indexmap",
+ "itoa",
+ "ryu",
+ "serde 1.0.114",
]
[[package]]
name = "serde_test"
version = "0.8.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5"
dependencies = [
- "serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)",
+ "serde 0.8.23",
]
[[package]]
name = "serde_urlencoded"
version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
dependencies = [
- "dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)",
- "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "dtoa",
+ "itoa",
+ "serde 1.0.114",
+ "url",
]
[[package]]
name = "sha-1"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
dependencies = [
- "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "block-buffer",
+ "digest",
+ "fake-simd",
+ "opaque-debug",
]
[[package]]
name = "sha1"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
[[package]]
name = "sha2"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69"
dependencies = [
- "block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "block-buffer",
+ "digest",
+ "fake-simd",
+ "opaque-debug",
]
[[package]]
name = "signal-hook-registry"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41"
dependencies = [
- "arc-swap 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
+ "arc-swap",
+ "libc",
]
[[package]]
name = "simple_asn1"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2b25ecba7165254f0c97d6c22a64b1122a03634b18d20a34daf21e18f892e618"
dependencies = [
- "chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "chrono",
+ "num-bigint",
+ "num-traits 0.2.12",
]
[[package]]
name = "slab"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
[[package]]
name = "smallvec"
-version = "1.3.0"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c7cb5678e1615754284ec264d9bb5b4c27d2018577fd90ac0ceb578591ed5ee4"
[[package]]
name = "socket2"
version = "0.3.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
+ "libc",
+ "redox_syscall",
+ "winapi 0.3.8",
]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "static_assertions"
-version = "0.3.4"
+version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
[[package]]
name = "strum"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b"
[[package]]
name = "strum_macros"
version = "0.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c"
dependencies = [
- "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "heck",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "syn"
-version = "1.0.17"
+version = "1.0.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e8d5d96e8cbb005d6959f119f773bfaebb5684296108fb32600c00cde305b2cd"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "unicode-xid",
]
[[package]]
name = "synstructure"
-version = "0.12.3"
+version = "0.12.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b834f2d66f734cb897113e34aaff2f1ab4719ca946f9a7358dba8f8064148701"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
- "unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "unicode-xid",
]
[[package]]
name = "tempfile"
version = "3.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
- "remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
+ "libc",
+ "rand 0.7.3",
+ "redox_syscall",
+ "remove_dir_all",
+ "winapi 0.3.8",
]
[[package]]
name = "termcolor"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
dependencies = [
- "winapi-util 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
- "unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
+ "unicode-width",
]
[[package]]
name = "thiserror"
-version = "1.0.19"
+version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08"
dependencies = [
- "thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thiserror-impl",
]
[[package]]
name = "thiserror-impl"
-version = "1.0.19"
+version = "1.0.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "thread_local"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
dependencies = [
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "lazy_static",
]
[[package]]
name = "threadpool"
-version = "1.7.1"
+version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d050e60b33d41c19108b32cea32164033a9013fe3b46cbd4457559bfbf77afaa"
dependencies = [
- "num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "num_cpus",
]
[[package]]
name = "time"
-version = "0.1.42"
+version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ca8a50ef2360fbd1eeb0ecd46795a87a19024eb4b53c5dc916ca1fd95fe62438"
dependencies = [
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc",
+ "winapi 0.3.8",
]
+[[package]]
+name = "tinyvec"
+version = "0.3.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "53953d2d3a5ad81d9f844a32f14ebb121f50b650cd59d0ee2a07cf13c617efed"
+
[[package]]
name = "tokio"
-version = "0.2.20"
+version = "0.2.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d099fa27b9702bed751524694adbe393e18b36b204da91eb1cbbbbb4a5ee2d58"
dependencies = [
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)",
- "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
- "mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes",
+ "fnv",
+ "futures-core",
+ "iovec",
+ "lazy_static",
+ "libc",
+ "memchr",
+ "mio",
+ "mio-uds",
+ "pin-project-lite",
+ "signal-hook-registry",
+ "slab",
+ "winapi 0.3.8",
]
[[package]]
name = "tokio-util"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930"
dependencies = [
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio",
]
[[package]]
name = "tokio-util"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
dependencies = [
- "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bytes",
+ "futures-core",
+ "futures-sink",
+ "log",
+ "pin-project-lite",
+ "tokio",
]
[[package]]
name = "trust-dns-proto"
version = "0.18.0-alpha.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2a7f3a2ab8a919f5eca52a468866a67ed7d3efa265d48a652a9a3452272b413f"
dependencies = [
- "async-trait 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)",
- "enum-as-inner 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "async-trait",
+ "enum-as-inner",
+ "failure",
+ "futures",
+ "idna",
+ "lazy_static",
+ "log",
+ "rand 0.7.3",
+ "smallvec",
+ "socket2",
+ "tokio",
+ "url",
]
[[package]]
name = "trust-dns-resolver"
version = "0.18.0-alpha.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6f90b1502b226f8b2514c6d5b37bafa8c200d7ca4102d57dc36ee0f3b7a04a2f"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "ipconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "resolv-conf 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)",
- "trust-dns-proto 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
+ "failure",
+ "futures",
+ "ipconfig",
+ "lazy_static",
+ "log",
+ "lru-cache",
+ "resolv-conf",
+ "smallvec",
+ "tokio",
+ "trust-dns-proto",
]
[[package]]
name = "twoway"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc"
dependencies = [
- "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "unchecked-index 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "memchr",
+ "unchecked-index",
]
[[package]]
name = "typed-arena"
version = "1.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d"
+
+[[package]]
+name = "typed-builder"
+version = "0.6.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "85fc4459191c621a53ef6c6ca5642e6e0e5ccc61f3e5b8ad6b6ab5317f0200fb"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
[[package]]
name = "typenum"
version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]]
name = "ucd-trie"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
[[package]]
name = "unchecked-index"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
[[package]]
name = "unicase"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
dependencies = [
- "version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "version_check 0.9.2",
]
[[package]]
name = "unicode-bidi"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
dependencies = [
- "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "matches",
]
[[package]]
name = "unicode-normalization"
-version = "0.1.12"
+version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6fb19cf769fa8c6a80a162df694621ebeb4dafb606470b2b2fce0be40a98a977"
dependencies = [
- "smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
[[package]]
name = "unicode-width"
version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
[[package]]
name = "unicode-xid"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
[[package]]
name = "unicode_categories"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "untrusted"
-version = "0.7.0"
+version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]]
name = "url"
version = "2.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
dependencies = [
- "idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "idna",
+ "matches",
+ "percent-encoding",
+ "serde 1.0.114",
]
[[package]]
name = "uuid"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
+dependencies = [
+ "rand 0.6.5",
+]
+
+[[package]]
+name = "uuid"
+version = "0.8.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11"
dependencies = [
- "rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "rand 0.7.3",
+ "serde 1.0.114",
]
[[package]]
name = "v_escape"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "660b101c07b5d0863deb9e7fb3138777e858d6d2a79f9e6049a27d1cc77c6da6"
dependencies = [
- "v_escape_derive 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "v_escape_derive",
]
[[package]]
name = "v_escape_derive"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c2ca2a14bc3fc5b64d188b087a7d3a927df87b152e941ccfbc66672e20c467ae"
dependencies = [
- "nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
+ "nom 4.2.3",
+ "proc-macro2",
+ "quote",
+ "syn",
]
[[package]]
name = "v_htmlescape"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e33e939c0d8cf047514fb6ba7d5aac78bc56677a6938b2ee67000b91f2e97e41"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "v_escape 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
+ "v_escape",
]
[[package]]
name = "vcpkg"
-version = "0.2.8"
+version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
[[package]]
name = "vec_map"
-version = "0.8.1"
+version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
[[package]]
name = "version_check"
-version = "0.9.1"
+version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "wasi"
version = "0.9.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
[[package]]
name = "wasm-bindgen"
-version = "0.2.60"
+version = "0.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "4c2dc4aa152834bc334f506c1a06b866416a8b6697d5c9f75b9a689c8486def0"
dependencies = [
- "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "wasm-bindgen-macro 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
+ "cfg-if",
+ "wasm-bindgen-macro",
]
[[package]]
name = "wasm-bindgen-backend"
-version = "0.2.60"
+version = "0.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ded84f06e0ed21499f6184df0e0cb3494727b0c5da89534e0fcc55c51d812101"
dependencies = [
- "bumpalo 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
- "wasm-bindgen-shared 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
+ "bumpalo",
+ "lazy_static",
+ "log",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
-version = "0.2.60"
+version = "0.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "838e423688dac18d73e31edce74ddfac468e37b1506ad163ffaf0a46f703ffe3"
dependencies = [
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "wasm-bindgen-macro-support 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
+ "quote",
+ "wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
-version = "0.2.60"
+version = "0.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3156052d8ec77142051a533cdd686cba889537b213f948cd1d20869926e68e92"
dependencies = [
- "proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)",
- "syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)",
- "wasm-bindgen-backend 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
- "wasm-bindgen-shared 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
-version = "0.2.60"
+version = "0.2.63"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c9ba19973a58daf4db6f352eda73dc0e289493cd29fb2632eb172085b6521acd"
[[package]]
name = "web-sys"
-version = "0.3.37"
+version = "0.3.40"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7b72fe77fd39e4bd3eaa4412fd299a0be6b3dfe9d2597e2f1c20beb968f41d17"
dependencies = [
- "js-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)",
- "wasm-bindgen 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)",
+ "js-sys",
+ "wasm-bindgen",
]
[[package]]
name = "webpki"
-version = "0.21.2"
+version = "0.21.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ab146130f5f790d45f82aeeb09e55a256573373ec64409fc19a6fb82fb1032ae"
dependencies = [
- "ring 0.16.12 (registry+https://github.com/rust-lang/crates.io-index)",
- "untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "ring",
+ "untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f8eff4b7516a57307f9349c64bf34caa34b940b66fed4b2fb3136cb7386e5739"
dependencies = [
- "webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)",
+ "webpki",
]
[[package]]
name = "widestring"
-version = "0.4.0"
+version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a763e303c0e0f23b0da40888724762e802a8ffefbc22de4127ef42493c2ea68c"
[[package]]
name = "winapi"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
[[package]]
name = "winapi"
version = "0.3.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
dependencies = [
- "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi-i686-pc-windows-gnu",
+ "winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-build"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
-version = "0.1.4"
+version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "winreg"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
dependencies = [
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8",
]
[[package]]
name = "winutil"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e"
dependencies = [
- "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
+ "winapi 0.3.8",
]
[[package]]
name = "ws2_32-sys"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
dependencies = [
- "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[metadata]
-"checksum activitypub 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d538a21b137ec0f63cc579ef4afa4ab13aa85b4f8af15a033683edd97c50718d"
-"checksum activitystreams-derive 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "65608fdeae5eb05485d5b71a3d2242d76b2b7413608c196d47eb4dff3eed7b85"
-"checksum activitystreams-traits 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a0c2a3958d240f40eff1f31b5f679a6e0d4ce2a16812886a3ec0164f3a2ca517"
-"checksum activitystreams-types 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "0598820663a59e5eaafeeedd3a7f7efc93db4ed6172905baec05503095ba5c0e"
-"checksum actix 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4af87564ff659dee8f9981540cac9418c45e910c8072fdedd643a262a38fcaf"
-"checksum actix-codec 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "09e55f0a5c2ca15795035d90c46bd0e73a5123b72f68f12596d6ba5282051380"
-"checksum actix-connect 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c95cc9569221e9802bf4c377f6c18b90ef10227d787611decf79fd47d2a8e76c"
-"checksum actix-files 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "301482841d3d74483a446ead63cb7d362e187d2c8b603f13d91995621ea53c46"
-"checksum actix-http 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c16664cc4fdea8030837ad5a845eb231fb93fc3c5c171edfefb52fad92ce9019"
-"checksum actix-macros 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "21705adc76bbe4bc98434890e73a89cd00c6015e5704a60bb6eea6c3b72316b6"
-"checksum actix-router 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9d7a10ca4d94e8c8e7a87c5173aba1b97ba9a6563ca02b0e1cd23531093d3ec8"
-"checksum actix-rt 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "143fcc2912e0d1de2bcf4e2f720d2a60c28652ab4179685a1ee159e0fb3db227"
-"checksum actix-server 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "582a7173c281a4f46b5aa168a11e7f37183dcb71177a39312cc2264da7a632c9"
-"checksum actix-service 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d3e4fc95dfa7e24171b2d0bb46b85f8ab0e8499e4e3caec691fc4ea65c287564"
-"checksum actix-testing 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "48494745b72d0ea8ff0cf874aaf9b622a3ee03d7081ee0c04edea4f26d32c911"
-"checksum actix-threadpool 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf4082192601de5f303013709ff84d81ca6a1bc4af7fb24f367a500a23c6e84e"
-"checksum actix-tls 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a4e5b4faaf105e9a6d389c606c298dcdb033061b00d532af9df56ff3a54995a8"
-"checksum actix-utils 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "fcf8f5631bf01adec2267808f00e228b761c60c0584cc9fa0b5364f41d147f4e"
-"checksum actix-web 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3158e822461040822f0dbf1735b9c2ce1f95f93b651d7a7aded00b1efbb1f635"
-"checksum actix-web-actors 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "dc1bd41bd66c4e9b5274cec87aac30168e63d64e96fd19db38edef6b46ba2982"
-"checksum actix-web-codegen 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4f00371942083469785f7e28c540164af1913ee7c96a4534acb9cea92c39f057"
-"checksum actix_derive 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b95aceadaf327f18f0df5962fedc1bde2f870566a0b9f65c89508a3b1f79334c"
-"checksum adler32 1.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5d2e7343e7fc9de883d1b0341e0b13970f764c14101234857d2ddafa1cb1cac2"
-"checksum aho-corasick 0.7.10 (registry+https://github.com/rust-lang/crates.io-index)" = "8716408b8bc624ed7f65d223ddb9ac2d044c0547b6fa4b0d554f3a9540496ada"
-"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
-"checksum arc-swap 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d663a8e9a99154b5fb793032533f6328da35e23aac63d5c152279aa8ba356825"
-"checksum arrayvec 0.4.12 (registry+https://github.com/rust-lang/crates.io-index)" = "cd9fd44efafa8690358b7408d253adf110036b88f55672a933f01d616ad9b1b9"
-"checksum ascii_utils 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "71938f30533e4d95a6d17aa530939da3842c2ab6f4f84b9dae68447e4129f74a"
-"checksum async-trait 0.1.30 (registry+https://github.com/rust-lang/crates.io-index)" = "da71fef07bc806586090247e971229289f64c210a278ee5ae419314eb386b31d"
-"checksum attohttpc 0.14.0 (registry+https://github.com/rust-lang/crates.io-index)" = "93610ce1c035e8a273fe56a19852e42798f3c019ca2726e52d2971197f116525"
-"checksum atty 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
-"checksum autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2"
-"checksum autocfg 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
-"checksum awc 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d7601d4d1d7ef2335d6597a41b5fe069f6ab799b85f53565ab390e7b7065aac5"
-"checksum backtrace 0.3.46 (registry+https://github.com/rust-lang/crates.io-index)" = "b1e692897359247cc6bb902933361652380af0f1b7651ae5c5013407f30e109e"
-"checksum backtrace-sys 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)" = "7de8aba10a69c8e8d7622c5710229485ec32e9d55fdad160ea559c086fdcd118"
-"checksum base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e"
-"checksum base64 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7"
-"checksum base64 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3"
-"checksum base64 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "489d6c0ed21b11d038c31b6ceccca973e65d73ba3bd8ecb9a2babf5546164643"
-"checksum bcrypt 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6f02d7d008a57bcb2251ba115b803934e02315edbde9a861c88713493e381b63"
-"checksum bitflags 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
-"checksum block-buffer 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b"
-"checksum block-cipher-trait 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "1c924d49bd09e7c06003acda26cd9742e796e34282ec6c1189404dee0c1f4774"
-"checksum block-padding 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5"
-"checksum blowfish 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6aeb80d00f2688459b8542068abd974cfb101e7a82182414a99b5026c0d85cc3"
-"checksum brotli-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4445dea95f4c2b41cde57cc9fee236ae4dbae88d8fcbdb4750fc1bb5d86aaecd"
-"checksum brotli2 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0cb036c3eade309815c15ddbacec5b22c4d1f3983a774ab2eac2e3e9ea85568e"
-"checksum bufstream 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "40e38929add23cdf8a366df9b0e088953150724bcbe5fc330b0d8eb3b328eec8"
-"checksum bumpalo 3.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "12ae9db68ad7fac5fe51304d20f016c911539251075a214f8e663babefa35187"
-"checksum byte-tools 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7"
-"checksum byteorder 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "08c48aae112d48ed9f069b33538ea9e3e90aa263cfa3d1c24309612b1f7472de"
-"checksum bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "130aac562c0dd69c56b3b1cc8ffd2e17be31d0b6c25b61c96b76231aa23e39e1"
-"checksum bytestring 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fc7c05fa5172da78a62d9949d662d2ac89d4cc7355d7b49adee5163f1fb3f363"
-"checksum cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)" = "95e28fa049fda1c330bcf9d723be7663a899c4679724b34c81e9f5a326aab8cd"
-"checksum cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
-"checksum chrono 0.4.11 (registry+https://github.com/rust-lang/crates.io-index)" = "80094f509cf8b5ae86a4966a39b3ff66cd7e2a3e594accec3743ff3fabeab5b2"
-"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
-"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
-"checksum comrak 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e17058cc536cf290563e88787d7b9e6030ce4742943017cc2ffb71f88034021c"
-"checksum config 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3"
-"checksum copyless 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6ff9c56c9fb2a49c05ef0e431485a22400af20d33226dc0764d891d09e724127"
-"checksum core-foundation 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57d24c7a13c43e870e37c1556b74555437870a04514f7685f5b354e090567171"
-"checksum core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b3a71ab494c0b5b860bdc8407ae08978052417070c2ced38573a9157ad75b8ac"
-"checksum crc32fast 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ba125de2af0df55319f41944744ad91c71113bf74a4646efff39afe1f6842db1"
-"checksum crossbeam-channel 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cced8691919c02aac3cb0a1bc2e9b73d89e832bf9a06fc579d4e71b68a2da061"
-"checksum crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
-"checksum darling 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "0d706e75d87e35569db781a9b5e2416cff1236a47ed380831f959382ccd5f858"
-"checksum darling_core 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c960ae2da4de88a91b2d920c2a7233b400bc33cb28453a2987822d8392519b"
-"checksum darling_macro 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d9b5a2f4ac4969822c62224815d069952656cadc7084fdca9751e6d959189b72"
-"checksum derive_builder 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a2658621297f2cf68762a6f7dc0bb7e1ff2cfd6583daef8ee0fed6f7ec468ec0"
-"checksum derive_builder_core 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2791ea3e372c8495c0bc2033991d76b512cd799d07491fbd6890124db9458bef"
-"checksum derive_more 0.99.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e2323f3f47db9a0e77ce7a300605d8d2098597fc451ed1a97bb1f6411bb550a7"
-"checksum diesel 1.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "33d7ca63eb2efea87a7f56a283acc49e2ce4b2bd54adf7465dc1d81fef13d8fc"
-"checksum diesel_derives 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "45f5098f628d02a7a0f68ddba586fb61e80edec3bdc1be3b921f4ceec60858d3"
-"checksum diesel_migrations 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3cde8413353dc7f5d72fa8ce0b99a560a359d2c5ef1e5817ca731cd9008f4c"
-"checksum digest 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5"
-"checksum dotenv 0.15.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f"
-"checksum dtoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4358a9e11b9a09cf52383b451b49a169e8d797b68aa02301ff586d70d9661ea3"
-"checksum either 1.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
-"checksum email 0.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "91549a51bb0241165f13d57fc4c72cef063b4088fb078b019ecbf464a45f22e4"
-"checksum encoding 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec"
-"checksum encoding-index-japanese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91"
-"checksum encoding-index-korean 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81"
-"checksum encoding-index-simpchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7"
-"checksum encoding-index-singlebyte 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a"
-"checksum encoding-index-tradchinese 1.20141219.5 (registry+https://github.com/rust-lang/crates.io-index)" = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18"
-"checksum encoding_index_tests 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569"
-"checksum encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)" = "cd8d03faa7fe0c1431609dfad7bbe827af30f82e1e2ae6f7ee4fca6bd764bc28"
-"checksum entities 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b5320ae4c3782150d900b79807611a59a99fc9a1d61d686faafc24b93fc8d7ca"
-"checksum enum-as-inner 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bc4bfcfacb61d231109d1d55202c1f33263319668b168843e02ad4652725ec9c"
-"checksum env_logger 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
-"checksum failure 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86"
-"checksum failure_derive 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "030a733c8287d6213886dd487564ff5c8f6aae10278b3588ed177f9d18f8d231"
-"checksum fake-simd 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed"
-"checksum fast_chemail 0.9.6 (registry+https://github.com/rust-lang/crates.io-index)" = "495a39d30d624c2caabe6312bfead73e7717692b44e0b32df168c275a2e8e9e4"
-"checksum flate2 1.0.14 (registry+https://github.com/rust-lang/crates.io-index)" = "2cfff41391129e0a856d6d822600b8d71179d46879e310417eb9c762eb178b42"
-"checksum fnv 1.0.6 (registry+https://github.com/rust-lang/crates.io-index)" = "2fad85553e09a6f881f739c29f0b00b0f01357c743266d478b68951ce23285f3"
-"checksum foreign-types 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
-"checksum foreign-types-shared 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
-"checksum fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
-"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82"
-"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7"
-"checksum futures 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5c329ae8753502fb44ae4fc2b622fa2a94652c41e795143765ba0927f92ab780"
-"checksum futures-channel 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0c77d04ce8edd9cb903932b608268b3fffec4163dc053b3b402bf47eac1f1a8"
-"checksum futures-core 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f25592f769825e89b92358db00d26f965761e094951ac44d3663ef25b7ac464a"
-"checksum futures-executor 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f674f3e1bcb15b37284a90cedf55afdba482ab061c407a9c0ebbd0f3109741ba"
-"checksum futures-io 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a638959aa96152c7a4cddf50fcb1e3fede0583b27157c26e67d6f99904090dc6"
-"checksum futures-macro 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "9a5081aa3de1f7542a794a397cde100ed903b0630152d0973479018fd85423a7"
-"checksum futures-sink 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "3466821b4bc114d95b087b850a724c6f83115e929bc88f1fa98a3304a944c8a6"
-"checksum futures-task 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7b0a34e53cf6cdcd0178aa573aed466b646eb3db769570841fda0c7ede375a27"
-"checksum futures-util 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "22766cf25d64306bedf0384da004d05c9974ab104fcc4528f1236181c18004c5"
-"checksum fxhash 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
-"checksum generic-array 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
-"checksum getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
-"checksum h2 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "377038bf3c89d18d6ca1431e7a5027194fbd724ca10592b9487ede5e8e144f42"
-"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
-"checksum hermit-abi 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "8a0d737e0f947a1864e93d33fdef4af8445a00d1ed8dc0c8ddb73139ea6abf15"
-"checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e"
-"checksum hostname 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "3c731c3e10504cc8ed35cfe2f1db4c9274c3d35fa486e3b31df46f068ef3e867"
-"checksum htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163"
-"checksum http 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "28d569972648b2c512421b5f2a405ad6ac9666547189d0c5477a3f200f3e02f9"
-"checksum httparse 1.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "cd179ae861f0c2e53da70d892f5f3029f9594be0c41dc5269cd371691b1dc2f9"
-"checksum humantime 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
-"checksum ident_case 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39"
-"checksum idna 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "02e2673c30ee86b5b96a9cb52ad15718aa1f966f5ab9ad54a8b95d5ca33120a9"
-"checksum indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "076f042c5b7b98f31d205f1249267e12a6518c1481e9dae9764af19b707d2292"
-"checksum iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b2b3ea6ff95e175473f8ffe6a7eb7c00d054240321b84c57051175fe3c1e075e"
-"checksum ipconfig 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa79fa216fbe60834a9c0737d7fcd30425b32d1c58854663e24d4c4b328ed83f"
-"checksum itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "b8b7a7c0c47db5545ed3fef7468ee7bb5b74691498139e4b3f6a20685dc6dd8e"
-"checksum js-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "6a27d435371a2fa5b6d2b028a74bbdb1234f308da363226a2854ca3ff8ba7055"
-"checksum jsonwebtoken 7.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d11f9e80a85927748a334df8e4f6782a04033517bb28f3863a563daad882da7f"
-"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
-"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
-"checksum lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
-"checksum lettre 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "bf43f3202a879fbdab4ecafec3349b0139f81d31c626246d53bcbb546253ffaa"
-"checksum lettre_email 0.9.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fd02480f8dcf48798e62113974d6ccca2129a51d241fa20f1ea349c8a42559d5"
-"checksum lexical-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890"
-"checksum libc 0.2.69 (registry+https://github.com/rust-lang/crates.io-index)" = "99e85c08494b21a9054e7fe1374a732aeadaff3980b6990b94bfd3a70f690005"
-"checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd"
-"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
-"checksum lock_api 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "c4da24a77a3d8a6d4862d95f72e6fdb9c09a643ecdb402d754004a557f2bec75"
-"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
-"checksum lru-cache 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "31e24f1ad8321ca0e8a1e0ac13f23cb668e6f5466c2c57319f6a5cf1cc8e3b1c"
-"checksum maplit 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d"
-"checksum match_cfg 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ffbee8634e0d45d258acb448e7eaab3fce7a0a467395d4d9f228e3c1f01fb2e4"
-"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
-"checksum maybe-uninit 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
-"checksum memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400"
-"checksum migrations_internals 1.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2b4fc84e4af020b837029e017966f86a1c2d5e83e64b589963d5047525995860"
-"checksum migrations_macros 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9753f12909fd8d923f75ae5c3258cae1ed3c8ec052e1b38c93c21a6d157f789c"
-"checksum mime 0.3.16 (registry+https://github.com/rust-lang/crates.io-index)" = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d"
-"checksum mime_guess 2.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212"
-"checksum miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "aa679ff6578b1cddee93d7e82e263b94a575e0bfced07284eb0c037c1d2416a5"
-"checksum mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)" = "302dec22bcf6bae6dfb69c647187f4b4d0fb6f535521f7bc022430ce8e12008f"
-"checksum mio-uds 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "966257a94e196b11bb43aca423754d87429960a768de9414f3691d6957abf125"
-"checksum miow 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "8c1f2f3b1cf331de6896aabf6e9d55dca90356cc9960cca7eaaf408a355ae919"
-"checksum native-tls 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2b0d88c06fe90d5ee94048ba40409ef1d9315d86f6f38c2efdaad4fb50c58b2d"
-"checksum net2 0.2.33 (registry+https://github.com/rust-lang/crates.io-index)" = "42550d9fb7b6684a6d404d9fa7250c2eb2646df731d1c06afc06dcee9e1bcf88"
-"checksum nodrop 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb"
-"checksum nom 4.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2ad2a91a8e869eeb30b9cb3119ae87773a8f4ae617f41b1eb9c154b2905f7bd6"
-"checksum nom 5.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0b471253da97532da4b61552249c521e01e736071f71c1a4f7ebbfbf0a06aad6"
-"checksum num-bigint 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304"
-"checksum num-integer 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "3f6ea62e9d81a77cd3ee9a2a5b9b609447857f3d358704331e4ef39eb247fcba"
-"checksum num-traits 0.1.43 (registry+https://github.com/rust-lang/crates.io-index)" = "92e5113e9fd4cc14ded8e499429f396a20f98c772a47cc8622a736e1ec843c31"
-"checksum num-traits 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)" = "c62be47e61d1842b9170f0fdeec8eba98e60e90e5446449a0545e5152acd7096"
-"checksum num_cpus 1.13.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3"
-"checksum opaque-debug 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c"
-"checksum openssl 0.10.29 (registry+https://github.com/rust-lang/crates.io-index)" = "cee6d85f4cb4c4f59a6a85d5b68a233d280c82e29e822913b9c8b129fbf20bdd"
-"checksum openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
-"checksum openssl-sys 0.9.58 (registry+https://github.com/rust-lang/crates.io-index)" = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de"
-"checksum parking_lot 0.10.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d3a704eb390aafdc107b0e392f56a82b668e3a71366993b5340f5833fd62505e"
-"checksum parking_lot_core 0.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0e136c1904604defe99ce5fd71a28d473fa60a12255d511aa78a9ddf11237aeb"
-"checksum pem 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a1581760c757a756a41f0ee3ff01256227bdf64cb752839779b95ffb01c59793"
-"checksum percent-encoding 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
-"checksum pest 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "10f4872ae94d7b90ae48754df22fd42ad52ce740b8f370b03da4835417403e53"
-"checksum pest_derive 2.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "833d1ae558dc601e9a60366421196a8d94bc0ac980476d0b67e1d0988d72b2d0"
-"checksum pest_generator 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "99b8db626e31e5b81787b9783425769681b347011cc59471e33ea46d2ea0cf55"
-"checksum pest_meta 2.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "54be6e404f5317079812fc8f9f5279de376d8856929e21c184ecf6bbd692a11d"
-"checksum pin-project 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6f6a7f5eee6292c559c793430c55c00aea9d3b3d1905e855806ca4d7253426a2"
-"checksum pin-project-internal 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "8988430ce790d8682672117bc06dda364c0be32d3abd738234f19f3240bad99a"
-"checksum pin-project-lite 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "237844750cfbb86f67afe27eee600dfbbcb6188d734139b534cbfbf4f96792ae"
-"checksum pin-utils 0.1.0-alpha.4 (registry+https://github.com/rust-lang/crates.io-index)" = "5894c618ce612a3fa23881b152b608bafb8c56cfc22f434a3ba3120b40f7b587"
-"checksum pkg-config 0.3.17 (registry+https://github.com/rust-lang/crates.io-index)" = "05da548ad6865900e60eaba7f589cc0783590a92e940c26953ff81ddbab2d677"
-"checksum ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
-"checksum pq-sys 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "6ac25eee5a0582f45a67e837e350d784e7003bd29a5f460796772061ca49ffda"
-"checksum proc-macro-hack 0.5.15 (registry+https://github.com/rust-lang/crates.io-index)" = "0d659fe7c6d27f25e9d80a1a094c223f5246f6a6596453e09d7229bf42750b63"
-"checksum proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694"
-"checksum proc-macro2 1.0.10 (registry+https://github.com/rust-lang/crates.io-index)" = "df246d292ff63439fea9bc8c0a270bed0e390d5ebd4db4ba15aba81111b5abe3"
-"checksum quick-error 1.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0"
-"checksum quick-xml 0.17.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fe1e430bdcf30c9fdc25053b9c459bb1a4672af4617b6c783d7d91dc17c6bbb0"
-"checksum quote 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2bdc6c187c65bca4260c9011c9e3132efe4909da44726bad24cf7572ae338d7f"
-"checksum r2d2 0.8.8 (registry+https://github.com/rust-lang/crates.io-index)" = "1497e40855348e4a8a40767d8e55174bce1e445a3ac9254ad44ad468ee0485af"
-"checksum rand 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293"
-"checksum rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)" = "6d71dacdc3c88c1fde3885a3be3fbab9f35724e6ce99467f7d9c5026132184ca"
-"checksum rand 0.7.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
-"checksum rand_chacha 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "556d3a1ca6600bfcbab7c7c91ccb085ac7fbbcd70e008a98742e7847f4f7bcef"
-"checksum rand_chacha 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
-"checksum rand_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b"
-"checksum rand_core 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc"
-"checksum rand_core 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
-"checksum rand_hc 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7b40677c7be09ae76218dc623efbf7b18e34bced3f38883af07bb75630a21bc4"
-"checksum rand_hc 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
-"checksum rand_isaac 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ded997c9d5f13925be2a6fd7e66bf1872597f759fd9dd93513dd7e92e5a5ee08"
-"checksum rand_jitter 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "1166d5c91dc97b88d1decc3285bb0a99ed84b05cfd0bc2341bdf2d43fc41e39b"
-"checksum rand_os 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "7b75f676a1e053fc562eafbb47838d67c84801e38fc1ba459e8f180deabd5071"
-"checksum rand_pcg 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "abf9b09b01790cfe0364f52bf32995ea3c39f4d2dd011eac241d2914146d0b44"
-"checksum rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "cbf7e9e623549b0e21f6e97cf8ecf247c1a8fd2e8a992ae265314300b2455d5c"
-"checksum rdrand 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2"
-"checksum redox_syscall 0.1.56 (registry+https://github.com/rust-lang/crates.io-index)" = "2439c63f3f6139d1b57529d16bc3b8bb855230c8efcc5d3a896c8bea7c3b1e84"
-"checksum regex 1.3.7 (registry+https://github.com/rust-lang/crates.io-index)" = "a6020f034922e3194c711b82a627453881bc4682166cabb07134a10c26ba7692"
-"checksum regex-syntax 0.6.17 (registry+https://github.com/rust-lang/crates.io-index)" = "7fe5bd57d1d7414c6b5ed48563a2c855d995ff777729dcd91c369ec7fea395ae"
-"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
-"checksum resolv-conf 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "11834e137f3b14e309437a8276714eed3a80d1ef894869e510f2c0c0b98b9f4a"
-"checksum ring 0.16.12 (registry+https://github.com/rust-lang/crates.io-index)" = "1ba5a8ec64ee89a76c98c549af81ff14813df09c3e6dc4766c3856da48597a0c"
-"checksum rss 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "99979205510c60f80a119dedbabd0b8426517384edf205322f8bcd51796bcef9"
-"checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
-"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
-"checksum rustls 0.17.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1"
-"checksum ryu 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76"
-"checksum safemem 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072"
-"checksum schannel 0.1.19 (registry+https://github.com/rust-lang/crates.io-index)" = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
-"checksum scheduled-thread-pool 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "0988d7fdf88d5e5fcf5923a0f1e8ab345f3e98ab4bc6bc45a2d5ff7f7458fbf6"
-"checksum scopeguard 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
-"checksum sct 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e3042af939fca8c3453b7af0f1c66e533a15a86169e39de2657310ade8f98d3c"
-"checksum security-framework 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "64808902d7d99f78eaddd2b4e2509713babc3dc3c85ad6f4c447680f3c01e535"
-"checksum security-framework-sys 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "17bf11d99252f512695eb468de5516e5cf75455521e69dfe343f3b74e4748405"
-"checksum semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
-"checksum semver-parser 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
-"checksum serde 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "9dad3f759919b92c3068c696c15c3d17238234498bbdcc80f2c469606f948ac8"
-"checksum serde 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)" = "36df6ac6412072f67cf767ebbde4133a5b2e88e76dc6187fa7104cd16f783399"
-"checksum serde-hjson 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6a3a4e0ea8a88553209f6cc6cfe8724ecad22e1acf372793c27d995290fe74f8"
-"checksum serde_derive 1.0.106 (registry+https://github.com/rust-lang/crates.io-index)" = "9e549e3abf4fb8621bd1609f11dfc9f5e50320802273b12f3811a67e6716ea6c"
-"checksum serde_json 1.0.52 (registry+https://github.com/rust-lang/crates.io-index)" = "a7894c8ed05b7a3a279aeb79025fdec1d3158080b75b98a08faf2806bb799edd"
-"checksum serde_test 0.8.23 (registry+https://github.com/rust-lang/crates.io-index)" = "110b3dbdf8607ec493c22d5d947753282f3bae73c0f56d322af1e8c78e4c23d5"
-"checksum serde_urlencoded 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9ec5d77e2d4c73717816afac02670d5c4f534ea95ed430442cad02e7a6e32c97"
-"checksum sha-1 0.8.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df"
-"checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d"
-"checksum sha2 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "27044adfd2e1f077f649f59deb9490d3941d674002f7d062870a60ebe9bd47a0"
-"checksum signal-hook-registry 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94f478ede9f64724c5d173d7bb56099ec3e2d9fc2774aac65d34b8b890405f41"
-"checksum simple_asn1 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2b25ecba7165254f0c97d6c22a64b1122a03634b18d20a34daf21e18f892e618"
-"checksum slab 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
-"checksum smallvec 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "05720e22615919e4734f6a99ceae50d00226c3c5aca406e102ebc33298214e0a"
-"checksum socket2 0.3.12 (registry+https://github.com/rust-lang/crates.io-index)" = "03088793f677dce356f3ccc2edb1b314ad191ab702a5de3faf49304f7e104918"
-"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
-"checksum static_assertions 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3"
-"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
-"checksum strsim 0.9.3 (registry+https://github.com/rust-lang/crates.io-index)" = "6446ced80d6c486436db5c078dde11a9f73d42b57fb273121e160b84f63d894c"
-"checksum strum 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "57bd81eb48f4c437cadc685403cad539345bf703d78e63707418431cecd4522b"
-"checksum strum_macros 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)" = "87c85aa3f8ea653bfd3ddf25f7ee357ee4d204731f6aa9ad04002306f6e2774c"
-"checksum syn 1.0.17 (registry+https://github.com/rust-lang/crates.io-index)" = "0df0eb663f387145cab623dea85b09c2c5b4b0aef44e945d928e682fce71bb03"
-"checksum synstructure 0.12.3 (registry+https://github.com/rust-lang/crates.io-index)" = "67656ea1dc1b41b1451851562ea232ec2e5a80242139f7e679ceccfb5d61f545"
-"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9"
-"checksum termcolor 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
-"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
-"checksum thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344"
-"checksum thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479"
-"checksum thread_local 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d40c6d1b69745a6ec6fb1ca717914848da4b44ae29d9b3080cbee91d72a69b14"
-"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865"
-"checksum time 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "db8dcfca086c1143c9270ac42a2bbd8a7ee477b78ac8e45b19abfb0cbede4b6f"
-"checksum tokio 0.2.20 (registry+https://github.com/rust-lang/crates.io-index)" = "05c1d570eb1a36f0345a5ce9c6c6e665b70b73d11236912c0b477616aeec47b1"
-"checksum tokio-util 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "571da51182ec208780505a32528fc5512a8fe1443ab960b3f2f3ef093cd16930"
-"checksum tokio-util 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "be8242891f2b6cbef26a2d7e8605133c2c554cd35b3e4948ea892d6d68436499"
-"checksum trust-dns-proto 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2a7f3a2ab8a919f5eca52a468866a67ed7d3efa265d48a652a9a3452272b413f"
-"checksum trust-dns-resolver 0.18.0-alpha.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6f90b1502b226f8b2514c6d5b37bafa8c200d7ca4102d57dc36ee0f3b7a04a2f"
-"checksum twoway 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "6b40075910de3a912adbd80b5d8bad6ad10a23eeb1f5bf9d4006839e899ba5bc"
-"checksum typed-arena 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9b2228007eba4120145f785df0f6c92ea538f5a3635a612ecf4e334c8c1446d"
-"checksum typenum 1.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
-"checksum ucd-trie 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "56dee185309b50d1f11bfedef0fe6d036842e3fb77413abef29f8f8d1c5d4c1c"
-"checksum unchecked-index 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eeba86d422ce181a719445e51872fa30f1f7413b62becb52e95ec91aa262d85c"
-"checksum unicase 2.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6"
-"checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
-"checksum unicode-normalization 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)" = "5479532badd04e128284890390c1e876ef7a993d0570b3597ae43dfa1d59afa4"
-"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
-"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479"
-"checksum unicode-xid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "826e7639553986605ec5979c7dd957c7895e93eabed50ab2ffa7f6128a75097c"
-"checksum unicode_categories 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
-"checksum untrusted 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "60369ef7a31de49bcb3f6ca728d4ba7300d9a1658f94c727d4cab8c8d9f4aece"
-"checksum url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "829d4a8476c35c9bf0bbce5a3b23f4106f79728039b726d292bb93bc106787cb"
-"checksum uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "90dbc611eb48397705a6b0f6e917da23ae517e4d127123d2cf7674206627d32a"
-"checksum v_escape 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)" = "660b101c07b5d0863deb9e7fb3138777e858d6d2a79f9e6049a27d1cc77c6da6"
-"checksum v_escape_derive 0.5.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c2ca2a14bc3fc5b64d188b087a7d3a927df87b152e941ccfbc66672e20c467ae"
-"checksum v_htmlescape 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)" = "e33e939c0d8cf047514fb6ba7d5aac78bc56677a6938b2ee67000b91f2e97e41"
-"checksum vcpkg 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "3fc439f2794e98976c88a2a2dafce96b930fe8010b0a256b3c2199a773933168"
-"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a"
-"checksum version_check 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd"
-"checksum version_check 0.9.1 (registry+https://github.com/rust-lang/crates.io-index)" = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce"
-"checksum wasi 0.9.0+wasi-snapshot-preview1 (registry+https://github.com/rust-lang/crates.io-index)" = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
-"checksum wasm-bindgen 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "2cc57ce05287f8376e998cbddfb4c8cb43b84a7ec55cf4551d7c00eef317a47f"
-"checksum wasm-bindgen-backend 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d967d37bf6c16cca2973ca3af071d0a2523392e4a594548155d89a678f4237cd"
-"checksum wasm-bindgen-macro 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "8bd151b63e1ea881bb742cd20e1d6127cef28399558f3b5d415289bc41eee3a4"
-"checksum wasm-bindgen-macro-support 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "d68a5b36eef1be7868f668632863292e37739656a80fc4b9acec7b0bd35a4931"
-"checksum wasm-bindgen-shared 0.2.60 (registry+https://github.com/rust-lang/crates.io-index)" = "daf76fe7d25ac79748a37538b7daeed1c7a6867c92d3245c12c6222e4a20d639"
-"checksum web-sys 0.3.37 (registry+https://github.com/rust-lang/crates.io-index)" = "2d6f51648d8c56c366144378a33290049eafdd784071077f6fe37dae64c1c4cb"
-"checksum webpki 0.21.2 (registry+https://github.com/rust-lang/crates.io-index)" = "f1f50e1972865d6b1adb54167d1c8ed48606004c2c9d0ea5f1eeb34d95e863ef"
-"checksum webpki-roots 0.19.0 (registry+https://github.com/rust-lang/crates.io-index)" = "f8eff4b7516a57307f9349c64bf34caa34b940b66fed4b2fb3136cb7386e5739"
-"checksum widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6"
-"checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
-"checksum winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)" = "8093091eeb260906a183e6ae1abdba2ef5ef2257a21801128899c3fc699229c6"
-"checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
-"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
-"checksum winapi-util 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fa515c5163a99cc82bab70fd3bfdd36d827be85de63737b40fcef2ce084a436e"
-"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
-"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
-"checksum winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7daf138b6b14196e3830a588acf1e86966c694d3e8fb026fb105b8b5dca07e6e"
-"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
+ "winapi 0.2.8",
+ "winapi-build",
+]
lto = true
[dependencies]
-diesel = { version = "1.4.2", features = ["postgres","chrono", "r2d2", "64-column-tables"] }
+diesel = { version = "1.4.4", features = ["postgres","chrono","r2d2","64-column-tables","serde_json"] }
diesel_migrations = "1.4.0"
dotenv = "0.15.0"
-bcrypt = "0.7.0"
-activitypub = "0.2.0"
-chrono = "0.4.7"
+activitystreams = "0.6.2"
+activitystreams-new = { git = "https://git.asonix.dog/asonix/activitystreams-sketch" }
+activitystreams-ext = { git = "https://git.asonix.dog/asonix/activitystreams-ext" }
+bcrypt = "0.8.0"
+chrono = { version = "0.4.7", features = ["serde"] }
+serde_json = { version = "1.0.52", features = ["preserve_order"]}
failure = "0.1.8"
-serde_json = "1.0.52"
-serde = "1.0.105"
+serde = { version = "1.0.105", features = ["derive"] }
actix = "0.9.0"
actix-web = "2.0.0"
actix-files = "0.2.1"
sha2 = "0.8.1"
rss = "1.9.0"
htmlescape = "0.3.1"
+url = { version = "2.1.1", features = ["serde"] }
config = {version = "0.10.1", default-features = false, features = ["hjson"] }
percent-encoding = "2.1.0"
attohttpc = { version = "0.14.0", default-features = false, features = ["tls-rustls"] }
comrak = "0.7"
-tokio = "0.2.20"
-futures = "0.3.4"
+openssl = "0.10"
+http = "0.2.1"
+http-signature-normalization = "0.5.1"
+base64 = "0.12.1"
+tokio = "0.2.21"
+futures = "0.3.5"
+itertools = "0.9.0"
+uuid = { version = "0.8", features = ["serde", "v4"] }
--- /dev/null
+{
+ hostname: "localhost:8536"
+ federation_enabled: true
+}
\ No newline at end of file
pool_size: 5
}
# the domain name of your instance (eg "dev.lemmy.ml")
- hostname: "my_domain"
+ hostname: null
# address where lemmy should listen for incoming requests
bind: "0.0.0.0"
# port where lemmy should listen for incoming requests
jwt_secret: "changeme"
# The dir for the front end
front_end_dir: "../ui/dist"
- # whether to enable activitypub federation. this feature is in alpha, do not enable in production, as might
- # cause problems like remote instances fetching and permanently storing bad data.
- federation_enabled: false
# rate limits for various user actions, by user ip
rate_limit: {
# maximum number of messages created in interval
# interval length for registration limit
register_per_second: 3600
}
+ # settings related to activitypub federation
+ federation: {
+ # whether to enable activitypub federation. this feature is in alpha, do not enable in production.
+ enabled: false
+ # whether tls is required for activitypub. only disable this for debugging, never for producion.
+ tls_enabled: true
+ # comma seperated list of instances with which federation is allowed
+ allowed_instances: ""
+ }
# # email sending configuration
# email: {
# # hostname of the smtp server
--- /dev/null
+drop table activity;
+
+alter table user_
+drop column actor_id,
+drop column private_key,
+drop column public_key,
+drop column bio,
+drop column local,
+drop column last_refreshed_at;
+
+alter table community
+drop column actor_id,
+drop column private_key,
+drop column public_key,
+drop column local,
+drop column last_refreshed_at;
--- /dev/null
+-- The Activitypub activity table
+-- All user actions must create a row here.
+create table activity (
+ id serial primary key,
+ user_id int references user_ on update cascade on delete cascade not null, -- Ensures that the user is set up here.
+ data jsonb not null,
+ local boolean not null default true,
+ published timestamp not null default now(),
+ updated timestamp
+);
+
+-- Making sure that id is unique
+create unique index idx_activity_unique_apid on activity ((data ->> 'id'::text));
+
+-- Add federation columns to the two actor tables
+alter table user_
+-- TODO uniqueness constraints should be added on these 3 columns later
+add column actor_id character varying(255) not null default 'changeme', -- This needs to be checked and updated in code, building from the site url if local
+add column bio text, -- not on community, already has description
+add column local boolean not null default true,
+add column private_key text, -- These need to be generated from code
+add column public_key text,
+add column last_refreshed_at timestamp not null default now() -- Used to re-fetch federated actor periodically
+;
+
+-- Community
+alter table community
+add column actor_id character varying(255) not null default 'changeme', -- This needs to be checked and updated in code, building from the site url if local
+add column local boolean not null default true,
+add column private_key text, -- These need to be generated from code
+add column public_key text,
+add column last_refreshed_at timestamp not null default now() -- Used to re-fetch federated actor periodically
+;
+
+-- Don't worry about rebuilding the views right now.
+
--- /dev/null
+alter table post
+drop column ap_id,
+drop column local;
+
+alter table comment
+drop column ap_id,
+drop column local;
--- /dev/null
+-- Add federation columns to post, comment
+
+alter table post
+-- TODO uniqueness constraints should be added on these 3 columns later
+add column ap_id character varying(255) not null default 'changeme', -- This needs to be checked and updated in code, building from the site url if local
+add column local boolean not null default true
+;
+
+alter table comment
+-- TODO uniqueness constraints should be added on these 3 columns later
+add column ap_id character varying(255) not null default 'changeme', -- This needs to be checked and updated in code, building from the site url if local
+add column local boolean not null default true
+;
+
--- /dev/null
+-- User table
+drop view user_view cascade;
+
+alter table user_
+add column fedi_name varchar(40) not null default 'changeme';
+
+alter table user_
+add constraint user__name_fedi_name_key unique (name, fedi_name);
+
+-- Community
+alter table community
+add constraint community_name_key unique (name);
+
+
+create view user_view as
+select
+u.id,
+u.name,
+u.avatar,
+u.email,
+u.matrix_user_id,
+u.fedi_name,
+u.admin,
+u.banned,
+u.show_avatars,
+u.send_notifications_to_email,
+u.published,
+(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
+(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
+(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
+(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
+from user_ u;
+
+create materialized view user_mview as select * from user_view;
+
+create unique index idx_user_mview_id on user_mview (id);
--- /dev/null
+-- User table
+
+-- Need to regenerate user_view, user_mview
+drop view user_view cascade;
+
+-- Remove the fedi_name constraint, drop that useless column
+alter table user_
+drop constraint user__name_fedi_name_key;
+
+alter table user_
+drop column fedi_name;
+
+-- Community
+alter table community
+drop constraint community_name_key;
+
+create view user_view as
+select
+u.id,
+u.name,
+u.avatar,
+u.email,
+u.matrix_user_id,
+u.admin,
+u.banned,
+u.show_avatars,
+u.send_notifications_to_email,
+u.published,
+(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
+(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
+(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
+(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
+from user_ u;
+
+create materialized view user_mview as select * from user_view;
+
+create unique index idx_user_mview_id on user_mview (id);
+
--- /dev/null
+-- user_view
+drop view user_view cascade;
+
+create view user_view as
+select
+u.id,
+u.name,
+u.avatar,
+u.email,
+u.matrix_user_id,
+u.admin,
+u.banned,
+u.show_avatars,
+u.send_notifications_to_email,
+u.published,
+(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
+(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
+(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
+(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
+from user_ u;
+
+create materialized view user_mview as select * from user_view;
+
+create unique index idx_user_mview_id on user_mview (id);
+
+-- community_view
+drop view community_aggregates_view cascade;
+create view community_aggregates_view as
+select c.*,
+(select name from user_ u where c.creator_id = u.id) as creator_name,
+(select avatar from user_ u where c.creator_id = u.id) as creator_avatar,
+(select name from category ct where c.category_id = ct.id) as category_name,
+(select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers,
+(select count(*) from post p where p.community_id = c.id) as number_of_posts,
+(select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments,
+hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank
+from community c;
+
+create materialized view community_aggregates_mview as select * from community_aggregates_view;
+
+create unique index idx_community_aggregates_mview_id on community_aggregates_mview (id);
+
+create view community_view as
+with all_community as
+(
+ select
+ ca.*
+ from community_aggregates_view ca
+)
+
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join all_community ac
+
+union all
+
+select
+ac.*,
+null as user_id,
+null as subscribed
+from all_community ac
+;
+
+create view community_mview as
+with all_community as
+(
+ select
+ ca.*
+ from community_aggregates_mview ca
+)
+
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join all_community ac
+
+union all
+
+select
+ac.*,
+null as user_id,
+null as subscribed
+from all_community ac
+;
+
+-- community views
+drop view community_moderator_view;
+drop view community_follower_view;
+drop view community_user_ban_view;
+
+create view community_moderator_view as
+select *,
+(select name from user_ u where cm.user_id = u.id) as user_name,
+(select avatar from user_ u where cm.user_id = u.id),
+(select name from community c where cm.community_id = c.id) as community_name
+from community_moderator cm;
+
+create view community_follower_view as
+select *,
+(select name from user_ u where cf.user_id = u.id) as user_name,
+(select avatar from user_ u where cf.user_id = u.id),
+(select name from community c where cf.community_id = c.id) as community_name
+from community_follower cf;
+
+create view community_user_ban_view as
+select *,
+(select name from user_ u where cm.user_id = u.id) as user_name,
+(select avatar from user_ u where cm.user_id = u.id),
+(select name from community c where cm.community_id = c.id) as community_name
+from community_user_ban cm;
+
+-- post_view
+drop view post_view;
+drop view post_mview;
+drop materialized view post_aggregates_mview;
+drop view post_aggregates_view;
+
+-- regen post view
+create view post_aggregates_view as
+select
+p.*,
+(select u.banned from user_ u where p.creator_id = u.id) as banned,
+(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community,
+(select name from user_ where p.creator_id = user_.id) as creator_name,
+(select avatar from user_ where p.creator_id = user_.id) as creator_avatar,
+(select name from community where p.community_id = community.id) as community_name,
+(select removed from community c where p.community_id = c.id) as community_removed,
+(select deleted from community c where p.community_id = c.id) as community_deleted,
+(select nsfw from community c where p.community_id = c.id) as community_nsfw,
+(select count(*) from comment where comment.post_id = p.id) as number_of_comments,
+coalesce(sum(pl.score), 0) as score,
+count (case when pl.score = 1 then 1 else null end) as upvotes,
+count (case when pl.score = -1 then 1 else null end) as downvotes,
+hot_rank(coalesce(sum(pl.score) , 0),
+ (
+ case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps
+ else greatest(c.recent_comment_time, p.published)
+ end
+ )
+) as hot_rank,
+(
+ case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps
+ else greatest(c.recent_comment_time, p.published)
+ end
+) as newest_activity_time
+from post p
+left join post_like pl on p.id = pl.post_id
+left join (
+ select post_id,
+ max(published) as recent_comment_time
+ from comment
+ group by 1
+) c on p.id = c.post_id
+group by p.id, c.recent_comment_time;
+
+create materialized view post_aggregates_mview as select * from post_aggregates_view;
+
+create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id);
+
+create view post_view as
+with all_post as (
+ select
+ pa.*
+ from post_aggregates_view pa
+)
+select
+ap.*,
+u.id as user_id,
+coalesce(pl.score, 0) as my_vote,
+(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
+(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read,
+(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved
+from user_ u
+cross join all_post ap
+left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
+
+union all
+
+select
+ap.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from all_post ap
+;
+
+create view post_mview as
+with all_post as (
+ select
+ pa.*
+ from post_aggregates_mview pa
+)
+select
+ap.*,
+u.id as user_id,
+coalesce(pl.score, 0) as my_vote,
+(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
+(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read,
+(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved
+from user_ u
+cross join all_post ap
+left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
+
+union all
+
+select
+ap.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from all_post ap
+;
+
+
+-- reply_view, comment_view, user_mention
+drop view reply_view;
+drop view user_mention_view;
+drop view user_mention_mview;
+drop view comment_view;
+drop view comment_mview;
+drop materialized view comment_aggregates_mview;
+drop view comment_aggregates_view;
+
+-- reply and comment view
+create view comment_aggregates_view as
+select
+c.*,
+(select community_id from post p where p.id = c.post_id),
+(select co.name from post p, community co where p.id = c.post_id and p.community_id = co.id) as community_name,
+(select u.banned from user_ u where c.creator_id = u.id) as banned,
+(select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community,
+(select name from user_ where c.creator_id = user_.id) as creator_name,
+(select avatar from user_ where c.creator_id = user_.id) as creator_avatar,
+coalesce(sum(cl.score), 0) as score,
+count (case when cl.score = 1 then 1 else null end) as upvotes,
+count (case when cl.score = -1 then 1 else null end) as downvotes,
+hot_rank(coalesce(sum(cl.score) , 0), c.published) as hot_rank
+from comment c
+left join comment_like cl on c.id = cl.comment_id
+group by c.id;
+
+create materialized view comment_aggregates_mview as select * from comment_aggregates_view;
+
+create unique index idx_comment_aggregates_mview_id on comment_aggregates_mview (id);
+
+create view comment_view as
+with all_comment as
+(
+ select
+ ca.*
+ from comment_aggregates_view ca
+)
+
+select
+ac.*,
+u.id as user_id,
+coalesce(cl.score, 0) as my_vote,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.community_id = cf.community_id) as subscribed,
+(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+
+union all
+
+select
+ ac.*,
+ null as user_id,
+ null as my_vote,
+ null as subscribed,
+ null as saved
+from all_comment ac
+;
+
+create view comment_mview as
+with all_comment as
+(
+ select
+ ca.*
+ from comment_aggregates_mview ca
+)
+
+select
+ac.*,
+u.id as user_id,
+coalesce(cl.score, 0) as my_vote,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.community_id = cf.community_id) as subscribed,
+(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+
+union all
+
+select
+ ac.*,
+ null as user_id,
+ null as my_vote,
+ null as subscribed,
+ null as saved
+from all_comment ac
+;
+
+-- Do the reply_view referencing the comment_mview
+create view reply_view as
+with closereply as (
+ select
+ c2.id,
+ c2.creator_id as sender_id,
+ c.creator_id as recipient_id
+ from comment c
+ inner join comment c2 on c.id = c2.parent_id
+ where c2.creator_id != c.creator_id
+ -- Do union where post is null
+ union
+ select
+ c.id,
+ c.creator_id as sender_id,
+ p.creator_id as recipient_id
+ from comment c, post p
+ where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
+)
+select cv.*,
+closereply.recipient_id
+from comment_mview cv, closereply
+where closereply.id = cv.id
+;
+
+-- user mention
+create view user_mention_view as
+select
+ c.id,
+ um.id as user_mention_id,
+ c.creator_id,
+ c.post_id,
+ c.parent_id,
+ c.content,
+ c.removed,
+ um.read,
+ c.published,
+ c.updated,
+ c.deleted,
+ c.community_id,
+ c.community_name,
+ c.banned,
+ c.banned_from_community,
+ c.creator_name,
+ c.creator_avatar,
+ c.score,
+ c.upvotes,
+ c.downvotes,
+ c.hot_rank,
+ c.user_id,
+ c.my_vote,
+ c.saved,
+ um.recipient_id
+from user_mention um, comment_view c
+where um.comment_id = c.id;
+
+
+create view user_mention_mview as
+with all_comment as
+(
+ select
+ ca.*
+ from comment_aggregates_mview ca
+)
+
+select
+ ac.id,
+ um.id as user_mention_id,
+ ac.creator_id,
+ ac.post_id,
+ ac.parent_id,
+ ac.content,
+ ac.removed,
+ um.read,
+ ac.published,
+ ac.updated,
+ ac.deleted,
+ ac.community_id,
+ ac.community_name,
+ ac.banned,
+ ac.banned_from_community,
+ ac.creator_name,
+ ac.creator_avatar,
+ ac.score,
+ ac.upvotes,
+ ac.downvotes,
+ ac.hot_rank,
+ u.id as user_id,
+ coalesce(cl.score, 0) as my_vote,
+ (select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved,
+ um.recipient_id
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+left join user_mention um on um.comment_id = ac.id
+
+union all
+
+select
+ ac.id,
+ um.id as user_mention_id,
+ ac.creator_id,
+ ac.post_id,
+ ac.parent_id,
+ ac.content,
+ ac.removed,
+ um.read,
+ ac.published,
+ ac.updated,
+ ac.deleted,
+ ac.community_id,
+ ac.community_name,
+ ac.banned,
+ ac.banned_from_community,
+ ac.creator_name,
+ ac.creator_avatar,
+ ac.score,
+ ac.upvotes,
+ ac.downvotes,
+ ac.hot_rank,
+ null as user_id,
+ null as my_vote,
+ null as saved,
+ um.recipient_id
+from all_comment ac
+left join user_mention um on um.comment_id = ac.id
+;
+
--- /dev/null
+-- user_view
+drop view user_view cascade;
+
+create view user_view as
+select
+u.id,
+u.actor_id,
+u.name,
+u.avatar,
+u.email,
+u.matrix_user_id,
+u.bio,
+u.local,
+u.admin,
+u.banned,
+u.show_avatars,
+u.send_notifications_to_email,
+u.published,
+(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
+(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
+(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
+(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
+from user_ u;
+
+create materialized view user_mview as select * from user_view;
+
+create unique index idx_user_mview_id on user_mview (id);
+
+-- community_view
+drop view community_aggregates_view cascade;
+create view community_aggregates_view as
+-- Now that there's public and private keys, you have to be explicit here
+select c.id,
+c.name,
+c.title,
+c.description,
+c.category_id,
+c.creator_id,
+c.removed,
+c.published,
+c.updated,
+c.deleted,
+c.nsfw,
+c.actor_id,
+c.local,
+c.last_refreshed_at,
+(select actor_id from user_ u where c.creator_id = u.id) as creator_actor_id,
+(select local from user_ u where c.creator_id = u.id) as creator_local,
+(select name from user_ u where c.creator_id = u.id) as creator_name,
+(select avatar from user_ u where c.creator_id = u.id) as creator_avatar,
+(select name from category ct where c.category_id = ct.id) as category_name,
+(select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers,
+(select count(*) from post p where p.community_id = c.id) as number_of_posts,
+(select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments,
+hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank
+from community c;
+
+create materialized view community_aggregates_mview as select * from community_aggregates_view;
+
+create unique index idx_community_aggregates_mview_id on community_aggregates_mview (id);
+
+create view community_view as
+with all_community as
+(
+ select
+ ca.*
+ from community_aggregates_view ca
+)
+
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join all_community ac
+
+union all
+
+select
+ac.*,
+null as user_id,
+null as subscribed
+from all_community ac
+;
+
+create view community_mview as
+with all_community as
+(
+ select
+ ca.*
+ from community_aggregates_mview ca
+)
+
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join all_community ac
+
+union all
+
+select
+ac.*,
+null as user_id,
+null as subscribed
+from all_community ac
+;
+
+-- community views
+drop view community_moderator_view;
+drop view community_follower_view;
+drop view community_user_ban_view;
+
+create view community_moderator_view as
+select *,
+(select actor_id from user_ u where cm.user_id = u.id) as user_actor_id,
+(select local from user_ u where cm.user_id = u.id) as user_local,
+(select name from user_ u where cm.user_id = u.id) as user_name,
+(select avatar from user_ u where cm.user_id = u.id),
+(select actor_id from community c where cm.community_id = c.id) as community_actor_id,
+(select local from community c where cm.community_id = c.id) as community_local,
+(select name from community c where cm.community_id = c.id) as community_name
+from community_moderator cm;
+
+create view community_follower_view as
+select *,
+(select actor_id from user_ u where cf.user_id = u.id) as user_actor_id,
+(select local from user_ u where cf.user_id = u.id) as user_local,
+(select name from user_ u where cf.user_id = u.id) as user_name,
+(select avatar from user_ u where cf.user_id = u.id),
+(select actor_id from community c where cf.community_id = c.id) as community_actor_id,
+(select local from community c where cf.community_id = c.id) as community_local,
+(select name from community c where cf.community_id = c.id) as community_name
+from community_follower cf;
+
+create view community_user_ban_view as
+select *,
+(select actor_id from user_ u where cm.user_id = u.id) as user_actor_id,
+(select local from user_ u where cm.user_id = u.id) as user_local,
+(select name from user_ u where cm.user_id = u.id) as user_name,
+(select avatar from user_ u where cm.user_id = u.id),
+(select actor_id from community c where cm.community_id = c.id) as community_actor_id,
+(select local from community c where cm.community_id = c.id) as community_local,
+(select name from community c where cm.community_id = c.id) as community_name
+from community_user_ban cm;
+
+-- post_view
+drop view post_view;
+drop view post_mview;
+drop materialized view post_aggregates_mview;
+drop view post_aggregates_view;
+
+-- regen post view
+create view post_aggregates_view as
+select
+p.*,
+(select u.banned from user_ u where p.creator_id = u.id) as banned,
+(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community,
+(select actor_id from user_ where p.creator_id = user_.id) as creator_actor_id,
+(select local from user_ where p.creator_id = user_.id) as creator_local,
+(select name from user_ where p.creator_id = user_.id) as creator_name,
+(select avatar from user_ where p.creator_id = user_.id) as creator_avatar,
+(select actor_id from community where p.community_id = community.id) as community_actor_id,
+(select local from community where p.community_id = community.id) as community_local,
+(select name from community where p.community_id = community.id) as community_name,
+(select removed from community c where p.community_id = c.id) as community_removed,
+(select deleted from community c where p.community_id = c.id) as community_deleted,
+(select nsfw from community c where p.community_id = c.id) as community_nsfw,
+(select count(*) from comment where comment.post_id = p.id) as number_of_comments,
+coalesce(sum(pl.score), 0) as score,
+count (case when pl.score = 1 then 1 else null end) as upvotes,
+count (case when pl.score = -1 then 1 else null end) as downvotes,
+hot_rank(coalesce(sum(pl.score) , 0),
+ (
+ case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps
+ else greatest(c.recent_comment_time, p.published)
+ end
+ )
+) as hot_rank,
+(
+ case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps
+ else greatest(c.recent_comment_time, p.published)
+ end
+) as newest_activity_time
+from post p
+left join post_like pl on p.id = pl.post_id
+left join (
+ select post_id,
+ max(published) as recent_comment_time
+ from comment
+ group by 1
+) c on p.id = c.post_id
+group by p.id, c.recent_comment_time;
+
+create materialized view post_aggregates_mview as select * from post_aggregates_view;
+
+create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id);
+
+create view post_view as
+with all_post as (
+ select
+ pa.*
+ from post_aggregates_view pa
+)
+select
+ap.*,
+u.id as user_id,
+coalesce(pl.score, 0) as my_vote,
+(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
+(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read,
+(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved
+from user_ u
+cross join all_post ap
+left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
+
+union all
+
+select
+ap.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from all_post ap
+;
+
+create view post_mview as
+with all_post as (
+ select
+ pa.*
+ from post_aggregates_mview pa
+)
+select
+ap.*,
+u.id as user_id,
+coalesce(pl.score, 0) as my_vote,
+(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
+(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read,
+(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved
+from user_ u
+cross join all_post ap
+left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
+
+union all
+
+select
+ap.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from all_post ap
+;
+
+
+-- reply_view, comment_view, user_mention
+drop view reply_view;
+drop view user_mention_view;
+drop view user_mention_mview;
+drop view comment_view;
+drop view comment_mview;
+drop materialized view comment_aggregates_mview;
+drop view comment_aggregates_view;
+
+-- reply and comment view
+create view comment_aggregates_view as
+select
+c.*,
+(select community_id from post p where p.id = c.post_id),
+(select co.actor_id from post p, community co where p.id = c.post_id and p.community_id = co.id) as community_actor_id,
+(select co.local from post p, community co where p.id = c.post_id and p.community_id = co.id) as community_local,
+(select co.name from post p, community co where p.id = c.post_id and p.community_id = co.id) as community_name,
+(select u.banned from user_ u where c.creator_id = u.id) as banned,
+(select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community,
+(select actor_id from user_ where c.creator_id = user_.id) as creator_actor_id,
+(select local from user_ where c.creator_id = user_.id) as creator_local,
+(select name from user_ where c.creator_id = user_.id) as creator_name,
+(select avatar from user_ where c.creator_id = user_.id) as creator_avatar,
+coalesce(sum(cl.score), 0) as score,
+count (case when cl.score = 1 then 1 else null end) as upvotes,
+count (case when cl.score = -1 then 1 else null end) as downvotes,
+hot_rank(coalesce(sum(cl.score) , 0), c.published) as hot_rank
+from comment c
+left join comment_like cl on c.id = cl.comment_id
+group by c.id;
+
+create materialized view comment_aggregates_mview as select * from comment_aggregates_view;
+
+create unique index idx_comment_aggregates_mview_id on comment_aggregates_mview (id);
+
+create view comment_view as
+with all_comment as
+(
+ select
+ ca.*
+ from comment_aggregates_view ca
+)
+
+select
+ac.*,
+u.id as user_id,
+coalesce(cl.score, 0) as my_vote,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.community_id = cf.community_id) as subscribed,
+(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+
+union all
+
+select
+ ac.*,
+ null as user_id,
+ null as my_vote,
+ null as subscribed,
+ null as saved
+from all_comment ac
+;
+
+create view comment_mview as
+with all_comment as
+(
+ select
+ ca.*
+ from comment_aggregates_mview ca
+)
+
+select
+ac.*,
+u.id as user_id,
+coalesce(cl.score, 0) as my_vote,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.community_id = cf.community_id) as subscribed,
+(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+
+union all
+
+select
+ ac.*,
+ null as user_id,
+ null as my_vote,
+ null as subscribed,
+ null as saved
+from all_comment ac
+;
+
+-- Do the reply_view referencing the comment_mview
+create view reply_view as
+with closereply as (
+ select
+ c2.id,
+ c2.creator_id as sender_id,
+ c.creator_id as recipient_id
+ from comment c
+ inner join comment c2 on c.id = c2.parent_id
+ where c2.creator_id != c.creator_id
+ -- Do union where post is null
+ union
+ select
+ c.id,
+ c.creator_id as sender_id,
+ p.creator_id as recipient_id
+ from comment c, post p
+ where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
+)
+select cv.*,
+closereply.recipient_id
+from comment_mview cv, closereply
+where closereply.id = cv.id
+;
+
+-- user mention
+create view user_mention_view as
+select
+ c.id,
+ um.id as user_mention_id,
+ c.creator_id,
+ c.creator_actor_id,
+ c.creator_local,
+ c.post_id,
+ c.parent_id,
+ c.content,
+ c.removed,
+ um.read,
+ c.published,
+ c.updated,
+ c.deleted,
+ c.community_id,
+ c.community_actor_id,
+ c.community_local,
+ c.community_name,
+ c.banned,
+ c.banned_from_community,
+ c.creator_name,
+ c.creator_avatar,
+ c.score,
+ c.upvotes,
+ c.downvotes,
+ c.hot_rank,
+ c.user_id,
+ c.my_vote,
+ c.saved,
+ um.recipient_id,
+ (select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
+ (select local from user_ u where u.id = um.recipient_id) as recipient_local
+from user_mention um, comment_view c
+where um.comment_id = c.id;
+
+
+create view user_mention_mview as
+with all_comment as
+(
+ select
+ ca.*
+ from comment_aggregates_mview ca
+)
+
+select
+ ac.id,
+ um.id as user_mention_id,
+ ac.creator_id,
+ ac.creator_actor_id,
+ ac.creator_local,
+ ac.post_id,
+ ac.parent_id,
+ ac.content,
+ ac.removed,
+ um.read,
+ ac.published,
+ ac.updated,
+ ac.deleted,
+ ac.community_id,
+ ac.community_actor_id,
+ ac.community_local,
+ ac.community_name,
+ ac.banned,
+ ac.banned_from_community,
+ ac.creator_name,
+ ac.creator_avatar,
+ ac.score,
+ ac.upvotes,
+ ac.downvotes,
+ ac.hot_rank,
+ u.id as user_id,
+ coalesce(cl.score, 0) as my_vote,
+ (select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved,
+ um.recipient_id,
+ (select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
+ (select local from user_ u where u.id = um.recipient_id) as recipient_local
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+left join user_mention um on um.comment_id = ac.id
+
+union all
+
+select
+ ac.id,
+ um.id as user_mention_id,
+ ac.creator_id,
+ ac.creator_actor_id,
+ ac.creator_local,
+ ac.post_id,
+ ac.parent_id,
+ ac.content,
+ ac.removed,
+ um.read,
+ ac.published,
+ ac.updated,
+ ac.deleted,
+ ac.community_id,
+ ac.community_actor_id,
+ ac.community_local,
+ ac.community_name,
+ ac.banned,
+ ac.banned_from_community,
+ ac.creator_name,
+ ac.creator_avatar,
+ ac.score,
+ ac.upvotes,
+ ac.downvotes,
+ ac.hot_rank,
+ null as user_id,
+ null as my_vote,
+ null as saved,
+ um.recipient_id,
+ (select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
+ (select local from user_ u where u.id = um.recipient_id) as recipient_local
+from all_comment ac
+left join user_mention um on um.comment_id = ac.id
+;
+
--- /dev/null
+-- The username index
+drop index idx_user_name_lower_actor_id;
+create unique index idx_user_name_lower on user_ (lower(name));
+
--- /dev/null
+drop index idx_user_name_lower;
+create unique index idx_user_name_lower_actor_id on user_ (lower(name), lower(actor_id));
--- /dev/null
+drop materialized view private_message_mview;
+drop view private_message_view;
+
+alter table private_message
+drop column ap_id,
+drop column local;
+
+create view private_message_view as
+select
+pm.*,
+u.name as creator_name,
+u.avatar as creator_avatar,
+u2.name as recipient_name,
+u2.avatar as recipient_avatar
+from private_message pm
+inner join user_ u on u.id = pm.creator_id
+inner join user_ u2 on u2.id = pm.recipient_id;
+
+create materialized view private_message_mview as select * from private_message_view;
+
+create unique index idx_private_message_mview_id on private_message_mview (id);
--- /dev/null
+alter table private_message
+add column ap_id character varying(255) not null default 'changeme', -- This needs to be checked and updated in code, building from the site url if local
+add column local boolean not null default true
+;
+
+drop materialized view private_message_mview;
+drop view private_message_view;
+create view private_message_view as
+select
+pm.*,
+u.name as creator_name,
+u.avatar as creator_avatar,
+u.actor_id as creator_actor_id,
+u.local as creator_local,
+u2.name as recipient_name,
+u2.avatar as recipient_avatar,
+u2.actor_id as recipient_actor_id,
+u2.local as recipient_local
+from private_message pm
+inner join user_ u on u.id = pm.creator_id
+inner join user_ u2 on u2.id = pm.recipient_id;
+
+create materialized view private_message_mview as select * from private_message_view;
+
+create unique index idx_private_message_mview_id on private_message_mview (id);
-use super::*;
+use crate::{
+ api::{APIError, Oper, Perform},
+ apub::{ApubLikeableType, ApubObjectType},
+ db::{
+ comment::*,
+ comment_view::*,
+ community_view::*,
+ moderator::*,
+ post::*,
+ site_view::*,
+ user::*,
+ user_mention::*,
+ user_view::*,
+ Crud,
+ Likeable,
+ ListingType,
+ Saveable,
+ SortType,
+ },
+ naive_now,
+ remove_slurs,
+ scrape_text_for_mentions,
+ send_email,
+ settings::Settings,
+ websocket::{
+ server::{JoinCommunityRoom, SendComment},
+ UserOperation,
+ WebsocketInfo,
+ },
+ MentionData,
+};
+use diesel::{
+ r2d2::{ConnectionManager, Pool},
+ PgConnection,
+};
+use failure::Error;
+use log::error;
+use serde::{Deserialize, Serialize};
+use std::str::FromStr;
#[derive(Serialize, Deserialize)]
pub struct CreateComment {
let user_id = claims.id;
- let hostname = &format!("https://{}", Settings::get().hostname);
-
let conn = pool.get()?;
// Check for a community ban
}
// Check for a site ban
- if UserView::read(&conn, user_id)?.banned {
+ let user = User_::read(&conn, user_id)?;
+ if user.banned {
return Err(APIError::err("site_ban").into());
}
removed: None,
deleted: None,
read: None,
+ published: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_comment = match Comment::create(&conn, &comment_form) {
Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
};
- let mut recipient_ids = Vec::new();
-
- // Scan the comment for user mentions, add those rows
- let extracted_usernames = extract_usernames(&comment_form.content);
-
- for username_mention in &extracted_usernames {
- if let Ok(mention_user) = User_::read_from_name(&conn, (*username_mention).to_string()) {
- // You can't mention yourself
- // At some point, make it so you can't tag the parent creator either
- // This can cause two notifications, one for reply and the other for mention
- if mention_user.id != user_id {
- recipient_ids.push(mention_user.id);
-
- let user_mention_form = UserMentionForm {
- recipient_id: mention_user.id,
- comment_id: inserted_comment.id,
- read: None,
- };
-
- // Allow this to fail softly, since comment edits might re-update or replace it
- // Let the uniqueness handle this fail
- match UserMention::create(&conn, &user_mention_form) {
- Ok(_mention) => (),
- Err(_e) => error!("{}", &_e),
- };
-
- // Send an email to those users that have notifications on
- if mention_user.send_notifications_to_email {
- if let Some(mention_email) = mention_user.email {
- let subject = &format!(
- "{} - Mentioned by {}",
- Settings::get().hostname,
- claims.username
- );
- let html = &format!(
- "<h1>User Mention</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
- claims.username, comment_form.content, hostname
- );
- match send_email(subject, &mention_email, &mention_user.name, html) {
- Ok(_o) => _o,
- Err(e) => error!("{}", e),
- };
- }
- }
- }
- }
- }
+ let updated_comment = match Comment::update_ap_id(&conn, inserted_comment.id) {
+ Ok(comment) => comment,
+ Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
+ };
- // Send notifs to the parent commenter / poster
- match data.parent_id {
- Some(parent_id) => {
- let parent_comment = Comment::read(&conn, parent_id)?;
- if parent_comment.creator_id != user_id {
- let parent_user = User_::read(&conn, parent_comment.creator_id)?;
- recipient_ids.push(parent_user.id);
+ updated_comment.send_create(&user, &conn)?;
- if parent_user.send_notifications_to_email {
- if let Some(comment_reply_email) = parent_user.email {
- let subject = &format!(
- "{} - Reply from {}",
- Settings::get().hostname,
- claims.username
- );
- let html = &format!(
- "<h1>Comment Reply</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
- claims.username, comment_form.content, hostname
- );
- match send_email(subject, &comment_reply_email, &parent_user.name, html) {
- Ok(_o) => _o,
- Err(e) => error!("{}", e),
- };
- }
- }
- }
- }
- // Its a post
- None => {
- if post.creator_id != user_id {
- let parent_user = User_::read(&conn, post.creator_id)?;
- recipient_ids.push(parent_user.id);
-
- if parent_user.send_notifications_to_email {
- if let Some(post_reply_email) = parent_user.email {
- let subject = &format!(
- "{} - Reply from {}",
- Settings::get().hostname,
- claims.username
- );
- let html = &format!(
- "<h1>Post Reply</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
- claims.username, comment_form.content, hostname
- );
- match send_email(subject, &post_reply_email, &parent_user.name, html) {
- Ok(_o) => _o,
- Err(e) => error!("{}", e),
- };
- }
- }
- }
- }
- };
+ // Scan the comment for user mentions, add those rows
+ let mentions = scrape_text_for_mentions(&comment_form.content);
+ let recipient_ids = send_local_notifs(&conn, &mentions, &updated_comment, &user, &post);
// You like your own comment by default
let like_form = CommentLikeForm {
Err(_e) => return Err(APIError::err("couldnt_like_comment").into()),
};
+ updated_comment.send_like(&user, &conn)?;
+
let comment_view = CommentView::read(&conn, inserted_comment.id, Some(user_id))?;
let mut res = CommentResponse {
let conn = pool.get()?;
+ let user = User_::read(&conn, user_id)?;
+
let orig_comment = CommentView::read(&conn, data.edit_id, None)?;
// You are allowed to mark the comment as read even if you're banned.
}
// Check for a site ban
- if UserView::read(&conn, user_id)?.banned {
+ if user.banned {
return Err(APIError::err("site_ban").into());
}
}
let content_slurs_removed = remove_slurs(&data.content.to_owned());
+ let read_comment = Comment::read(&conn, data.edit_id)?;
+
let comment_form = CommentForm {
content: content_slurs_removed,
parent_id: data.parent_id,
removed: data.removed.to_owned(),
deleted: data.deleted.to_owned(),
read: data.read.to_owned(),
+ published: None,
updated: if data.read.is_some() {
orig_comment.updated
} else {
Some(naive_now())
},
+ ap_id: read_comment.ap_id,
+ local: read_comment.local,
};
- let _updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) {
+ let updated_comment = match Comment::update(&conn, data.edit_id, &comment_form) {
Ok(comment) => comment,
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
};
- let mut recipient_ids = Vec::new();
-
- // Scan the comment for user mentions, add those rows
- let extracted_usernames = extract_usernames(&comment_form.content);
-
- for username_mention in &extracted_usernames {
- let mention_user = User_::read_from_name(&conn, (*username_mention).to_string());
-
- if mention_user.is_ok() {
- let mention_user_id = mention_user?.id;
-
- // You can't mention yourself
- // At some point, make it so you can't tag the parent creator either
- // This can cause two notifications, one for reply and the other for mention
- if mention_user_id != user_id {
- recipient_ids.push(mention_user_id);
-
- let user_mention_form = UserMentionForm {
- recipient_id: mention_user_id,
- comment_id: data.edit_id,
- read: None,
- };
-
- // Allow this to fail softly, since comment edits might re-update or replace it
- // Let the uniqueness handle this fail
- match UserMention::create(&conn, &user_mention_form) {
- Ok(_mention) => (),
- Err(_e) => error!("{}", &_e),
- }
- }
- }
- }
-
- // Add to recipient ids
- match data.parent_id {
- Some(parent_id) => {
- let parent_comment = Comment::read(&conn, parent_id)?;
- if parent_comment.creator_id != user_id {
- let parent_user = User_::read(&conn, parent_comment.creator_id)?;
- recipient_ids.push(parent_user.id);
- }
+ if let Some(deleted) = data.deleted.to_owned() {
+ if deleted {
+ updated_comment.send_delete(&user, &conn)?;
+ } else {
+ updated_comment.send_undo_delete(&user, &conn)?;
}
- None => {
- let post = Post::read(&conn, data.post_id)?;
- recipient_ids.push(post.creator_id);
+ } else if let Some(removed) = data.removed.to_owned() {
+ if removed {
+ updated_comment.send_remove(&user, &conn)?;
+ } else {
+ updated_comment.send_undo_remove(&user, &conn)?;
}
+ } else {
+ updated_comment.send_update(&user, &conn)?;
}
+ let post = Post::read(&conn, data.post_id)?;
+
+ let mentions = scrape_text_for_mentions(&comment_form.content);
+ let recipient_ids = send_local_notifs(&conn, &mentions, &updated_comment, &user, &post);
+
// Mod tables
if let Some(removed) = data.removed.to_owned() {
let form = ModRemoveCommentForm {
}
// Check for a site ban
- if UserView::read(&conn, user_id)?.banned {
+ let user = User_::read(&conn, user_id)?;
+ if user.banned {
return Err(APIError::err("site_ban").into());
}
Ok(like) => like,
Err(_e) => return Err(APIError::err("couldnt_like_comment").into()),
};
+
+ if like_form.score == 1 {
+ comment.send_like(&user, &conn)?;
+ } else if like_form.score == -1 {
+ comment.send_dislike(&user, &conn)?;
+ }
+ } else {
+ comment.send_undo_like(&user, &conn)?;
}
// Have to refetch the comment to get the current state
Ok(GetCommentsResponse { comments })
}
}
+
+pub fn send_local_notifs(
+ conn: &PgConnection,
+ mentions: &[MentionData],
+ comment: &Comment,
+ user: &User_,
+ post: &Post,
+) -> Vec<i32> {
+ let mut recipient_ids = Vec::new();
+ let hostname = &format!("https://{}", Settings::get().hostname);
+
+ // Send the local mentions
+ for mention in mentions
+ .iter()
+ .filter(|m| m.is_local() && m.name.ne(&user.name))
+ .collect::<Vec<&MentionData>>()
+ {
+ if let Ok(mention_user) = User_::read_from_name(&conn, &mention.name) {
+ // TODO
+ // At some point, make it so you can't tag the parent creator either
+ // This can cause two notifications, one for reply and the other for mention
+ recipient_ids.push(mention_user.id);
+
+ let user_mention_form = UserMentionForm {
+ recipient_id: mention_user.id,
+ comment_id: comment.id,
+ read: None,
+ };
+
+ // Allow this to fail softly, since comment edits might re-update or replace it
+ // Let the uniqueness handle this fail
+ match UserMention::create(&conn, &user_mention_form) {
+ Ok(_mention) => (),
+ Err(_e) => error!("{}", &_e),
+ };
+
+ // Send an email to those users that have notifications on
+ if mention_user.send_notifications_to_email {
+ if let Some(mention_email) = mention_user.email {
+ let subject = &format!("{} - Mentioned by {}", Settings::get().hostname, user.name,);
+ let html = &format!(
+ "<h1>User Mention</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
+ user.name, comment.content, hostname
+ );
+ match send_email(subject, &mention_email, &mention_user.name, html) {
+ Ok(_o) => _o,
+ Err(e) => error!("{}", e),
+ };
+ }
+ }
+ }
+ }
+
+ // Send notifs to the parent commenter / poster
+ match comment.parent_id {
+ Some(parent_id) => {
+ if let Ok(parent_comment) = Comment::read(&conn, parent_id) {
+ if parent_comment.creator_id != user.id {
+ if let Ok(parent_user) = User_::read(&conn, parent_comment.creator_id) {
+ recipient_ids.push(parent_user.id);
+
+ if parent_user.send_notifications_to_email {
+ if let Some(comment_reply_email) = parent_user.email {
+ let subject = &format!("{} - Reply from {}", Settings::get().hostname, user.name,);
+ let html = &format!(
+ "<h1>Comment Reply</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
+ user.name, comment.content, hostname
+ );
+ match send_email(subject, &comment_reply_email, &parent_user.name, html) {
+ Ok(_o) => _o,
+ Err(e) => error!("{}", e),
+ };
+ }
+ }
+ }
+ }
+ }
+ }
+ // Its a post
+ None => {
+ if post.creator_id != user.id {
+ if let Ok(parent_user) = User_::read(&conn, post.creator_id) {
+ recipient_ids.push(parent_user.id);
+
+ if parent_user.send_notifications_to_email {
+ if let Some(post_reply_email) = parent_user.email {
+ let subject = &format!("{} - Reply from {}", Settings::get().hostname, user.name,);
+ let html = &format!(
+ "<h1>Post Reply</h1><br><div>{} - {}</div><br><a href={}/inbox>inbox</a>",
+ user.name, comment.content, hostname
+ );
+ match send_email(subject, &post_reply_email, &parent_user.name, html) {
+ Ok(_o) => _o,
+ Err(e) => error!("{}", e),
+ };
+ }
+ }
+ }
+ }
+ }
+ };
+ recipient_ids
+}
use super::*;
-use crate::is_valid_community_name;
+use crate::{
+ api::{APIError, Oper, Perform},
+ apub::{
+ extensions::signatures::generate_actor_keypair,
+ make_apub_endpoint,
+ ActorType,
+ EndpointType,
+ },
+ db::{Bannable, Crud, Followable, Joinable, SortType},
+ is_valid_community_name,
+ naive_from_unix,
+ naive_now,
+ slur_check,
+ slurs_vec_to_str,
+ websocket::{
+ server::{JoinCommunityRoom, SendCommunityRoomMessage},
+ UserOperation,
+ WebsocketInfo,
+ },
+};
+use diesel::{
+ r2d2::{ConnectionManager, Pool},
+ PgConnection,
+};
+use failure::Error;
+use serde::{Deserialize, Serialize};
+use std::str::FromStr;
#[derive(Serialize, Deserialize)]
pub struct GetCommunity {
id: Option<i32>,
- name: Option<String>,
+ pub name: Option<String>,
auth: Option<String>,
}
#[derive(Serialize, Deserialize)]
pub struct GetCommunityResponse {
pub community: CommunityView,
- moderators: Vec<CommunityModeratorView>,
- admins: Vec<UserView>,
+ pub moderators: Vec<CommunityModeratorView>,
+ pub admins: Vec<UserView>,
pub online: usize,
}
pub community: CommunityView,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct ListCommunities {
- sort: String,
- page: Option<i64>,
- limit: Option<i64>,
- auth: Option<String>,
+ pub sort: String,
+ pub page: Option<i64>,
+ pub limit: Option<i64>,
+ pub auth: Option<String>,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct ListCommunitiesResponse {
- communities: Vec<CommunityView>,
+ pub communities: Vec<CommunityView>,
}
#[derive(Serialize, Deserialize, Clone)]
let conn = pool.get()?;
- let community_id = match data.id {
- Some(id) => id,
+ let community = match data.id {
+ Some(id) => Community::read(&conn, id)?,
None => {
match Community::read_from_name(
&conn,
- data.name.to_owned().unwrap_or_else(|| "main".to_string()),
+ &data.name.to_owned().unwrap_or_else(|| "main".to_string()),
) {
- Ok(community) => community.id,
+ Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
}
}
};
- let community_view = match CommunityView::read(&conn, community_id, user_id) {
+ let community_view = match CommunityView::read(&conn, community.id, user_id) {
Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
};
- let moderators = match CommunityModeratorView::for_community(&conn, community_id) {
+ let moderators = match CommunityModeratorView::for_community(&conn, community.id) {
Ok(moderators) => moderators,
Err(_e) => return Err(APIError::err("couldnt_find_community").into()),
};
let online = if let Some(ws) = websocket_info {
if let Some(id) = ws.id {
- ws.chatserver
- .do_send(JoinCommunityRoom { community_id, id });
+ ws.chatserver.do_send(JoinCommunityRoom {
+ community_id: community.id,
+ id,
+ });
}
// TODO
}
// When you create a community, make sure the user becomes a moderator and a follower
+ let keypair = generate_actor_keypair()?;
+
let community_form = CommunityForm {
name: data.name.to_owned(),
title: data.title.to_owned(),
deleted: None,
nsfw: data.nsfw,
updated: None,
+ actor_id: make_apub_endpoint(EndpointType::Community, &data.name).to_string(),
+ local: true,
+ private_key: Some(keypair.private_key),
+ public_key: Some(keypair.public_key),
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = match Community::create(&conn, &community_form) {
let conn = pool.get()?;
// Check for a site ban
- if UserView::read(&conn, user_id)?.banned {
+ let user = User_::read(&conn, user_id)?;
+ if user.banned {
return Err(APIError::err("site_ban").into());
}
return Err(APIError::err("no_community_edit_allowed").into());
}
+ let read_community = Community::read(&conn, data.edit_id)?;
+
let community_form = CommunityForm {
name: data.name.to_owned(),
title: data.title.to_owned(),
deleted: data.deleted.to_owned(),
nsfw: data.nsfw,
updated: Some(naive_now()),
+ actor_id: read_community.actor_id,
+ local: read_community.local,
+ private_key: read_community.private_key,
+ public_key: read_community.public_key,
+ last_refreshed_at: None,
+ published: None,
};
- let _updated_community = match Community::update(&conn, data.edit_id, &community_form) {
+ let updated_community = match Community::update(&conn, data.edit_id, &community_form) {
Ok(community) => community,
Err(_e) => return Err(APIError::err("couldnt_update_community").into()),
};
ModRemoveCommunity::create(&conn, &form)?;
}
+ if let Some(deleted) = data.deleted.to_owned() {
+ if deleted {
+ updated_community.send_delete(&user, &conn)?;
+ } else {
+ updated_community.send_undo_delete(&user, &conn)?;
+ }
+ } else if let Some(removed) = data.removed.to_owned() {
+ if removed {
+ updated_community.send_remove(&user, &conn)?;
+ } else {
+ updated_community.send_undo_remove(&user, &conn)?;
+ }
+ }
+
let community_view = CommunityView::read(&conn, data.edit_id, Some(user_id))?;
let res = CommunityResponse {
let user_id = claims.id;
+ let conn = pool.get()?;
+
+ let community = Community::read(&conn, data.community_id)?;
let community_follower_form = CommunityFollowerForm {
community_id: data.community_id,
user_id,
};
- let conn = pool.get()?;
-
- if data.follow {
- match CommunityFollower::follow(&conn, &community_follower_form) {
- Ok(user) => user,
- Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
- };
+ if community.local {
+ if data.follow {
+ match CommunityFollower::follow(&conn, &community_follower_form) {
+ Ok(user) => user,
+ Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
+ };
+ } else {
+ match CommunityFollower::unfollow(&conn, &community_follower_form) {
+ Ok(user) => user,
+ Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
+ };
+ }
} else {
- match CommunityFollower::ignore(&conn, &community_follower_form) {
- Ok(user) => user,
- Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
- };
+ let user = User_::read(&conn, user_id)?;
+
+ if data.follow {
+ // Dont actually add to the community followers here, because you need
+ // to wait for the accept
+ user.send_follow(&community.actor_id, &conn)?;
+ } else {
+ user.send_unfollow(&community.actor_id, &conn)?;
+ match CommunityFollower::unfollow(&conn, &community_follower_form) {
+ Ok(user) => user,
+ Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
+ };
+ }
+ // TODO: this needs to return a "pending" state, until Accept is received from the remote server
}
let community_view = CommunityView::read(&conn, data.community_id, Some(user_id))?;
title: read_community.title,
description: read_community.description,
category_id: read_community.category_id,
- creator_id: data.user_id,
+ creator_id: data.user_id, // This makes the new user the community creator
removed: None,
deleted: None,
nsfw: read_community.nsfw,
updated: Some(naive_now()),
+ actor_id: read_community.actor_id,
+ local: read_community.local,
+ private_key: read_community.private_key,
+ public_key: read_community.public_key,
+ last_refreshed_at: None,
+ published: None,
};
let _updated_community = match Community::update(&conn, data.community_id, &community_form) {
-use crate::db::category::*;
-use crate::db::comment::*;
-use crate::db::comment_view::*;
-use crate::db::community::*;
-use crate::db::community_view::*;
-use crate::db::moderator::*;
-use crate::db::moderator_views::*;
-use crate::db::password_reset_request::*;
-use crate::db::post::*;
-use crate::db::post_view::*;
-use crate::db::private_message::*;
-use crate::db::private_message_view::*;
-use crate::db::site::*;
-use crate::db::site_view::*;
-use crate::db::user::*;
-use crate::db::user_mention::*;
-use crate::db::user_mention_view::*;
-use crate::db::user_view::*;
-use crate::db::*;
use crate::{
- extract_usernames, fetch_iframely_and_pictrs_data, generate_random_string, naive_from_unix,
- naive_now, remove_slurs, send_email, slur_check, slurs_vec_to_str,
+ db::{community::*, community_view::*, moderator::*, site::*, user::*, user_view::*},
+ websocket::WebsocketInfo,
};
-
-use crate::settings::Settings;
-use crate::websocket::UserOperation;
-use crate::websocket::{
- server::{
- JoinCommunityRoom, JoinPostRoom, JoinUserRoom, SendAllMessage, SendComment,
- SendCommunityRoomMessage, SendPost, SendUserRoomMessage,
- },
- WebsocketInfo,
+use diesel::{
+ r2d2::{ConnectionManager, Pool},
+ PgConnection,
};
-use diesel::r2d2::{ConnectionManager, Pool};
-use diesel::PgConnection;
use failure::Error;
-use log::{error, info};
-use serde::{Deserialize, Serialize};
-use std::str::FromStr;
pub mod comment;
pub mod community;
data: T,
}
-impl<T> Oper<T> {
- pub fn new(data: T) -> Oper<T> {
+impl<Data> Oper<Data> {
+ pub fn new(data: Data) -> Oper<Data> {
Oper { data }
}
}
-use super::*;
-
-#[derive(Serialize, Deserialize)]
+use crate::{
+ api::{APIError, Oper, Perform},
+ apub::{ApubLikeableType, ApubObjectType},
+ db::{
+ comment_view::*,
+ community_view::*,
+ moderator::*,
+ post::*,
+ post_view::*,
+ site::*,
+ site_view::*,
+ user::*,
+ user_view::*,
+ Crud,
+ Likeable,
+ ListingType,
+ Saveable,
+ SortType,
+ },
+ fetch_iframely_and_pictrs_data,
+ naive_now,
+ slur_check,
+ slurs_vec_to_str,
+ websocket::{
+ server::{JoinCommunityRoom, JoinPostRoom, SendPost},
+ UserOperation,
+ WebsocketInfo,
+ },
+};
+use diesel::{
+ r2d2::{ConnectionManager, Pool},
+ PgConnection,
+};
+use failure::Error;
+use serde::{Deserialize, Serialize};
+use std::str::FromStr;
+
+#[derive(Serialize, Deserialize, Debug)]
pub struct CreatePost {
name: String,
url: Option<String>,
pub online: usize,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct GetPosts {
type_: String,
sort: String,
auth: Option<String>,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct GetPostsResponse {
- posts: Vec<PostView>,
+ pub posts: Vec<PostView>,
}
#[derive(Serialize, Deserialize)]
}
// Check for a site ban
- if UserView::read(&conn, user_id)?.banned {
+ let user = User_::read(&conn, user_id)?;
+ if user.banned {
return Err(APIError::err("site_ban").into());
}
embed_description: iframely_description,
embed_html: iframely_html,
thumbnail_url: pictrs_thumbnail,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = match Post::create(&conn, &post_form) {
}
};
+ let updated_post = match Post::update_ap_id(&conn, inserted_post.id) {
+ Ok(post) => post,
+ Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
+ };
+
+ updated_post.send_create(&user, &conn)?;
+
// They like their own post by default
let like_form = PostLikeForm {
post_id: inserted_post.id,
score: 1,
};
- // Only add the like if the score isnt 0
let _inserted_like = match PostLike::like(&conn, &like_form) {
Ok(like) => like,
Err(_e) => return Err(APIError::err("couldnt_like_post").into()),
};
+ updated_post.send_like(&user, &conn)?;
+
// Refetch the view
let post_view = match PostView::read(&conn, inserted_post.id, Some(user_id)) {
Ok(post) => post,
}
// Check for a site ban
- if UserView::read(&conn, user_id)?.banned {
+ let user = User_::read(&conn, user_id)?;
+ if user.banned {
return Err(APIError::err("site_ban").into());
}
Ok(like) => like,
Err(_e) => return Err(APIError::err("couldnt_like_post").into()),
};
+
+ if like_form.score == 1 {
+ post.send_like(&user, &conn)?;
+ } else if like_form.score == -1 {
+ post.send_dislike(&user, &conn)?;
+ }
+ } else {
+ post.send_undo_like(&user, &conn)?;
}
let post_view = match PostView::read(&conn, data.post_id, Some(user_id)) {
}
// Check for a site ban
- if UserView::read(&conn, user_id)?.banned {
+ let user = User_::read(&conn, user_id)?;
+ if user.banned {
return Err(APIError::err("site_ban").into());
}
let (iframely_title, iframely_description, iframely_html, pictrs_thumbnail) =
fetch_iframely_and_pictrs_data(data.url.to_owned());
+ let read_post = Post::read(&conn, data.edit_id)?;
+
let post_form = PostForm {
name: data.name.to_owned(),
url: data.url.to_owned(),
embed_description: iframely_description,
embed_html: iframely_html,
thumbnail_url: pictrs_thumbnail,
+ ap_id: read_post.ap_id,
+ local: read_post.local,
+ published: None,
};
- let _updated_post = match Post::update(&conn, data.edit_id, &post_form) {
+ let updated_post = match Post::update(&conn, data.edit_id, &post_form) {
Ok(post) => post,
Err(e) => {
let err_type = if e.to_string() == "value too long for type character varying(200)" {
ModStickyPost::create(&conn, &form)?;
}
+ if let Some(deleted) = data.deleted.to_owned() {
+ if deleted {
+ updated_post.send_delete(&user, &conn)?;
+ } else {
+ updated_post.send_undo_delete(&user, &conn)?;
+ }
+ } else if let Some(removed) = data.removed.to_owned() {
+ if removed {
+ updated_post.send_remove(&user, &conn)?;
+ } else {
+ updated_post.send_undo_remove(&user, &conn)?;
+ }
+ } else {
+ updated_post.send_update(&user, &conn)?;
+ }
+
let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?;
let res = PostResponse { post: post_view };
use super::user::Register;
-use super::*;
+use crate::{
+ api::{APIError, Oper, Perform},
+ apub::fetcher::search_by_apub_id,
+ db::{
+ category::*,
+ comment_view::*,
+ community_view::*,
+ moderator::*,
+ moderator_views::*,
+ post_view::*,
+ site::*,
+ site_view::*,
+ user::*,
+ user_view::*,
+ Crud,
+ SearchType,
+ SortType,
+ },
+ naive_now,
+ settings::Settings,
+ slur_check,
+ slurs_vec_to_str,
+ websocket::{server::SendAllMessage, UserOperation, WebsocketInfo},
+};
+use diesel::{
+ r2d2::{ConnectionManager, Pool},
+ PgConnection,
+};
+use failure::Error;
+use log::{debug, info};
+use serde::{Deserialize, Serialize};
+use std::str::FromStr;
#[derive(Serialize, Deserialize)]
pub struct ListCategories {}
categories: Vec<Category>,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct Search {
q: String,
type_: String,
auth: Option<String>,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct SearchResponse {
- type_: String,
- comments: Vec<CommentView>,
- posts: Vec<PostView>,
- communities: Vec<CommunityView>,
- users: Vec<UserView>,
+ pub type_: String,
+ pub comments: Vec<CommentView>,
+ pub posts: Vec<PostView>,
+ pub communities: Vec<CommunityView>,
+ pub users: Vec<UserView>,
}
#[derive(Serialize, Deserialize)]
let conn = pool.get()?;
// TODO refactor this a little
- let site = Site::read(&conn, 1);
- let site_view = if site.is_ok() {
+ let site_view = if let Ok(_site) = Site::read(&conn, 1) {
Some(SiteView::read(&conn)?)
} else if let Some(setup) = Settings::get().setup.as_ref() {
let register = Register {
let create_site = CreateSite {
name: setup.site_name.to_owned(),
description: None,
- enable_downvotes: false,
- open_registration: false,
- enable_nsfw: false,
+ enable_downvotes: true,
+ open_registration: true,
+ enable_nsfw: true,
auth: login_response.jwt,
};
Oper::new(create_site).perform(pool, websocket_info.clone())?;
};
let mut admins = UserView::admins(&conn)?;
- if site_view.is_some() {
- let site_creator_id = site_view.to_owned().unwrap().creator_id;
- let creator_index = admins.iter().position(|r| r.id == site_creator_id).unwrap();
- let creator_user = admins.remove(creator_index);
- admins.insert(0, creator_user);
+
+ // Make sure the site creator is the top admin
+ if let Some(site_view) = site_view.to_owned() {
+ let site_creator_id = site_view.creator_id;
+ // TODO investigate why this is sometimes coming back null
+ // Maybe user_.admin isn't being set to true?
+ if let Some(creator_index) = admins.iter().position(|r| r.id == site_creator_id) {
+ let creator_user = admins.remove(creator_index);
+ admins.insert(0, creator_user);
+ }
}
let banned = UserView::banned(&conn)?;
) -> Result<SearchResponse, Error> {
let data: &Search = &self.data;
+ dbg!(&data);
+
+ let conn = pool.get()?;
+
+ match search_by_apub_id(&data.q, &conn) {
+ Ok(r) => return Ok(r),
+ Err(e) => debug!("Failed to resolve search query as activitypub ID: {}", e),
+ }
+
let user_id: Option<i32> = match &data.auth {
Some(auth) => match Claims::decode(&auth) {
Ok(claims) => {
// TODO no clean / non-nsfw searching rn
- let conn = pool.get()?;
-
match type_ {
SearchType::Posts => {
posts = PostQueryBuilder::create(&conn)
-use super::*;
-use crate::is_valid_username;
+use crate::{
+ api::{APIError, Oper, Perform},
+ apub::{
+ extensions::signatures::generate_actor_keypair,
+ make_apub_endpoint,
+ ApubObjectType,
+ EndpointType,
+ },
+ db::{
+ comment::*,
+ comment_view::*,
+ community::*,
+ community_view::*,
+ moderator::*,
+ password_reset_request::*,
+ post::*,
+ post_view::*,
+ private_message::*,
+ private_message_view::*,
+ site::*,
+ site_view::*,
+ user::*,
+ user_mention::*,
+ user_mention_view::*,
+ user_view::*,
+ Crud,
+ Followable,
+ Joinable,
+ ListingType,
+ SortType,
+ },
+ generate_random_string,
+ is_valid_username,
+ naive_from_unix,
+ naive_now,
+ remove_slurs,
+ send_email,
+ settings::Settings,
+ slur_check,
+ slurs_vec_to_str,
+ websocket::{
+ server::{JoinUserRoom, SendAllMessage, SendUserRoomMessage},
+ UserOperation,
+ WebsocketInfo,
+ },
+};
use bcrypt::verify;
+use diesel::{
+ r2d2::{ConnectionManager, Pool},
+ PgConnection,
+};
+use failure::Error;
+use log::error;
+use serde::{Deserialize, Serialize};
+use std::str::FromStr;
#[derive(Serialize, Deserialize, Debug)]
pub struct Login {
#[derive(Serialize, Deserialize, Clone)]
pub struct PrivateMessageResponse {
- message: PrivateMessageView,
+ pub message: PrivateMessageView,
}
#[derive(Serialize, Deserialize, Debug)]
return Err(APIError::err("admin_already_created").into());
}
+ let user_keypair = generate_actor_keypair()?;
if !is_valid_username(&data.username) {
return Err(APIError::err("invalid_username").into());
}
// Register the new user
let user_form = UserForm {
name: data.username.to_owned(),
- fedi_name: Settings::get().hostname,
email: data.email.to_owned(),
matrix_user_id: None,
avatar: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: make_apub_endpoint(EndpointType::User, &data.username).to_string(),
+ bio: None,
+ local: true,
+ private_key: Some(user_keypair.private_key),
+ public_key: Some(user_keypair.public_key),
+ last_refreshed_at: None,
};
// Create the user
}
};
+ let main_community_keypair = generate_actor_keypair()?;
+
// Create the main community if it doesn't exist
let main_community: Community = match Community::read(&conn, 2) {
Ok(c) => c,
Err(_e) => {
+ let default_community_name = "main";
let community_form = CommunityForm {
- name: "main".to_string(),
+ name: default_community_name.to_string(),
title: "The Default Community".to_string(),
description: Some("The Default Community".to_string()),
category_id: 1,
removed: None,
deleted: None,
updated: None,
+ actor_id: make_apub_endpoint(EndpointType::Community, default_community_name).to_string(),
+ local: true,
+ private_key: Some(main_community_keypair.private_key),
+ public_key: Some(main_community_keypair.public_key),
+ last_refreshed_at: None,
+ published: None,
};
Community::create(&conn, &community_form).unwrap()
}
let user_form = UserForm {
name: read_user.name,
- fedi_name: read_user.fedi_name,
email,
matrix_user_id: data.matrix_user_id.to_owned(),
avatar: data.avatar.to_owned(),
lang: data.lang.to_owned(),
show_avatars: data.show_avatars,
send_notifications_to_email: data.send_notifications_to_email,
+ actor_id: read_user.actor_id,
+ bio: read_user.bio,
+ local: read_user.local,
+ private_key: read_user.private_key,
+ public_key: read_user.public_key,
+ last_refreshed_at: None,
};
let updated_user = match User_::update(&conn, user_id, &user_form) {
None => {
match User_::read_from_name(
&conn,
- data
+ &data
.username
.to_owned()
.unwrap_or_else(|| "admin".to_string()),
return Err(APIError::err("not_an_admin").into());
}
- let read_user = User_::read(&conn, data.user_id)?;
-
- // TODO make addadmin easier
- let user_form = UserForm {
- name: read_user.name,
- fedi_name: read_user.fedi_name,
- email: read_user.email,
- matrix_user_id: read_user.matrix_user_id,
- avatar: read_user.avatar,
- password_encrypted: read_user.password_encrypted,
- preferred_username: read_user.preferred_username,
- updated: Some(naive_now()),
- admin: data.added,
- banned: read_user.banned,
- show_nsfw: read_user.show_nsfw,
- theme: read_user.theme,
- default_sort_type: read_user.default_sort_type,
- default_listing_type: read_user.default_listing_type,
- lang: read_user.lang,
- show_avatars: read_user.show_avatars,
- send_notifications_to_email: read_user.send_notifications_to_email,
- };
-
- match User_::update(&conn, data.user_id, &user_form) {
+ match User_::add_admin(&conn, user_id, data.added) {
Ok(user) => user,
Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
};
return Err(APIError::err("not_an_admin").into());
}
- let read_user = User_::read(&conn, data.user_id)?;
-
- // TODO make bans and addadmins easier
- let user_form = UserForm {
- name: read_user.name,
- fedi_name: read_user.fedi_name,
- email: read_user.email,
- matrix_user_id: read_user.matrix_user_id,
- avatar: read_user.avatar,
- password_encrypted: read_user.password_encrypted,
- preferred_username: read_user.preferred_username,
- updated: Some(naive_now()),
- admin: read_user.admin,
- banned: data.ban,
- show_nsfw: read_user.show_nsfw,
- theme: read_user.theme,
- default_sort_type: read_user.default_sort_type,
- default_listing_type: read_user.default_listing_type,
- lang: read_user.lang,
- show_avatars: read_user.show_avatars,
- send_notifications_to_email: read_user.send_notifications_to_email,
- };
-
- match User_::update(&conn, data.user_id, &user_form) {
+ match User_::ban_user(&conn, user_id, data.ban) {
Ok(user) => user,
Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
};
.list()?;
for reply in &replies {
- let comment_form = CommentForm {
- content: reply.to_owned().content,
- parent_id: reply.to_owned().parent_id,
- post_id: reply.to_owned().post_id,
- creator_id: reply.to_owned().creator_id,
- removed: None,
- deleted: None,
- read: Some(true),
- updated: reply.to_owned().updated,
- };
-
- let _updated_comment = match Comment::update(&conn, reply.id, &comment_form) {
+ match Comment::mark_as_read(&conn, reply.id) {
Ok(comment) => comment,
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
};
for message in &messages {
let private_message_form = PrivateMessageForm {
- content: None,
+ content: message.to_owned().content,
creator_id: message.to_owned().creator_id,
recipient_id: message.to_owned().recipient_id,
deleted: None,
read: Some(true),
updated: None,
+ ap_id: message.to_owned().ap_id,
+ local: message.local,
+ published: None,
};
let _updated_message = match PrivateMessage::update(&conn, message.id, &private_message_form)
.list()?;
for comment in &comments {
- let comment_form = CommentForm {
- content: "*Permananently Deleted*".to_string(),
- parent_id: comment.to_owned().parent_id,
- post_id: comment.to_owned().post_id,
- creator_id: comment.to_owned().creator_id,
- removed: None,
- deleted: Some(true),
- read: None,
- updated: Some(naive_now()),
- };
-
- let _updated_comment = match Comment::update(&conn, comment.id, &comment_form) {
+ let _updated_comment = match Comment::permadelete(&conn, comment.id) {
Ok(comment) => comment,
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
};
.list()?;
for post in &posts {
- let post_form = PostForm {
- name: "*Permananently Deleted*".to_string(),
- url: Some("https://deleted.com".to_string()),
- body: Some("*Permananently Deleted*".to_string()),
- creator_id: post.to_owned().creator_id,
- community_id: post.to_owned().community_id,
- removed: None,
- deleted: Some(true),
- nsfw: post.to_owned().nsfw,
- locked: None,
- stickied: None,
- updated: Some(naive_now()),
- embed_title: None,
- embed_description: None,
- embed_html: None,
- thumbnail_url: None,
- };
-
- let _updated_post = match Post::update(&conn, post.id, &post_form) {
+ let _updated_post = match Post::permadelete(&conn, post.id) {
Ok(post) => post,
Err(_e) => return Err(APIError::err("couldnt_update_post").into()),
};
let conn = pool.get()?;
// Check for a site ban
- if UserView::read(&conn, user_id)?.banned {
+ let user = User_::read(&conn, user_id)?;
+ if user.banned {
return Err(APIError::err("site_ban").into());
}
let content_slurs_removed = remove_slurs(&data.content.to_owned());
let private_message_form = PrivateMessageForm {
- content: Some(content_slurs_removed.to_owned()),
+ content: content_slurs_removed.to_owned(),
creator_id: user_id,
recipient_id: data.recipient_id,
deleted: None,
read: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_private_message = match PrivateMessage::create(&conn, &private_message_form) {
}
};
+ let updated_private_message =
+ match PrivateMessage::update_ap_id(&conn, inserted_private_message.id) {
+ Ok(private_message) => private_message,
+ Err(_e) => return Err(APIError::err("couldnt_create_private_message").into()),
+ };
+
+ updated_private_message.send_create(&user, &conn)?;
+
// Send notifications to the recipient
let recipient_user = User_::read(&conn, data.recipient_id)?;
if recipient_user.send_notifications_to_email {
fn perform(
&self,
pool: Pool<ConnectionManager<PgConnection>>,
- _websocket_info: Option<WebsocketInfo>,
+ websocket_info: Option<WebsocketInfo>,
) -> Result<PrivateMessageResponse, Error> {
let data: &EditPrivateMessage = &self.data;
let orig_private_message = PrivateMessage::read(&conn, data.edit_id)?;
// Check for a site ban
- if UserView::read(&conn, user_id)?.banned {
+ let user = User_::read(&conn, user_id)?;
+ if user.banned {
return Err(APIError::err("site_ban").into());
}
}
let content_slurs_removed = match &data.content {
- Some(content) => Some(remove_slurs(content)),
- None => None,
+ Some(content) => remove_slurs(content),
+ None => orig_private_message.content,
};
let private_message_form = PrivateMessageForm {
} else {
Some(naive_now())
},
+ ap_id: orig_private_message.ap_id,
+ local: orig_private_message.local,
+ published: None,
};
- let _updated_private_message =
+ let updated_private_message =
match PrivateMessage::update(&conn, data.edit_id, &private_message_form) {
Ok(private_message) => private_message,
Err(_e) => return Err(APIError::err("couldnt_update_private_message").into()),
};
+ if let Some(deleted) = data.deleted.to_owned() {
+ if deleted {
+ updated_private_message.send_delete(&user, &conn)?;
+ } else {
+ updated_private_message.send_undo_delete(&user, &conn)?;
+ }
+ } else {
+ updated_private_message.send_update(&user, &conn)?;
+ }
+
let message = PrivateMessageView::read(&conn, data.edit_id)?;
- Ok(PrivateMessageResponse { message })
+ let res = PrivateMessageResponse { message };
+
+ if let Some(ws) = websocket_info {
+ ws.chatserver.do_send(SendUserRoomMessage {
+ op: UserOperation::EditPrivateMessage,
+ response: res.clone(),
+ recipient_id: orig_private_message.recipient_id,
+ my_id: ws.id,
+ });
+ }
+
+ Ok(res)
}
}
--- /dev/null
+use crate::{
+ apub::{extensions::signatures::sign, is_apub_id_valid, ActorType},
+ db::{activity::insert_activity, community::Community, user::User_},
+};
+use activitystreams::{context, object::properties::ObjectProperties, public, Activity, Base};
+use diesel::PgConnection;
+use failure::{Error, _core::fmt::Debug};
+use log::debug;
+use serde::Serialize;
+use url::Url;
+
+pub fn populate_object_props(
+ props: &mut ObjectProperties,
+ addressed_ccs: Vec<String>,
+ object_id: &str,
+) -> Result<(), Error> {
+ props
+ .set_context_xsd_any_uri(context())?
+ // TODO: the activity needs a seperate id from the object
+ .set_id(object_id)?
+ // TODO: should to/cc go on the Create, or on the Post? or on both?
+ // TODO: handle privacy on the receiving side (at least ignore anything thats not public)
+ .set_to_xsd_any_uri(public())?
+ .set_many_cc_xsd_any_uris(addressed_ccs)?;
+ Ok(())
+}
+
+pub fn send_activity_to_community<A>(
+ creator: &User_,
+ conn: &PgConnection,
+ community: &Community,
+ to: Vec<String>,
+ activity: A,
+) -> Result<(), Error>
+where
+ A: Activity + Base + Serialize + Debug,
+{
+ insert_activity(&conn, creator.id, &activity, true)?;
+
+ // if this is a local community, we need to do an announce from the community instead
+ if community.local {
+ Community::do_announce(activity, &community, creator, conn)?;
+ } else {
+ send_activity(&activity, creator, to)?;
+ }
+ Ok(())
+}
+
+/// Send an activity to a list of recipients, using the correct headers etc.
+pub fn send_activity<A>(activity: &A, actor: &dyn ActorType, to: Vec<String>) -> Result<(), Error>
+where
+ A: Serialize + Debug,
+{
+ let json = serde_json::to_string(&activity)?;
+ debug!("Sending activitypub activity {} to {:?}", json, to);
+ for t in to {
+ let to_url = Url::parse(&t)?;
+ if !is_apub_id_valid(&to_url) {
+ debug!("Not sending activity to {} (invalid or blacklisted)", t);
+ continue;
+ }
+ let mut request = attohttpc::post(t).header("Host", to_url.domain().unwrap());
+ let signature = sign(&mut request, actor)?;
+ let res = request
+ .header("Signature", signature)
+ .header("Content-Type", "application/json")
+ .text(json.to_owned())
+ .send()?
+ .text()?;
+
+ debug!("Result for activity send: {:?}", res);
+ }
+ Ok(())
+}
--- /dev/null
+use crate::{
+ apub::{
+ activities::{populate_object_props, send_activity_to_community},
+ create_apub_response,
+ create_apub_tombstone_response,
+ create_tombstone,
+ fetch_webfinger_url,
+ fetcher::{
+ get_or_fetch_and_insert_remote_comment,
+ get_or_fetch_and_insert_remote_post,
+ get_or_fetch_and_upsert_remote_user,
+ },
+ ActorType,
+ ApubLikeableType,
+ ApubObjectType,
+ FromApub,
+ ToApub,
+ },
+ convert_datetime,
+ db::{
+ comment::{Comment, CommentForm},
+ community::Community,
+ post::Post,
+ user::User_,
+ Crud,
+ },
+ routes::DbPoolParam,
+ scrape_text_for_mentions,
+ MentionData,
+};
+use activitystreams::{
+ activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
+ context,
+ link::Mention,
+ object::{kind::NoteType, properties::ObjectProperties, Note},
+};
+use activitystreams_new::object::Tombstone;
+use actix_web::{body::Body, web::Path, HttpResponse, Result};
+use diesel::PgConnection;
+use failure::Error;
+use itertools::Itertools;
+use log::debug;
+use serde::Deserialize;
+
+#[derive(Deserialize)]
+pub struct CommentQuery {
+ comment_id: String,
+}
+
+/// Return the post json over HTTP.
+pub async fn get_apub_comment(
+ info: Path<CommentQuery>,
+ db: DbPoolParam,
+) -> Result<HttpResponse<Body>, Error> {
+ let id = info.comment_id.parse::<i32>()?;
+ let comment = Comment::read(&&db.get()?, id)?;
+ if !comment.deleted {
+ Ok(create_apub_response(&comment.to_apub(&db.get().unwrap())?))
+ } else {
+ Ok(create_apub_tombstone_response(&comment.to_tombstone()?))
+ }
+}
+
+impl ToApub for Comment {
+ type Response = Note;
+
+ fn to_apub(&self, conn: &PgConnection) -> Result<Note, Error> {
+ let mut comment = Note::default();
+ let oprops: &mut ObjectProperties = comment.as_mut();
+ let creator = User_::read(&conn, self.creator_id)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+
+ // Add a vector containing some important info to the "in_reply_to" field
+ // [post_ap_id, Option(parent_comment_ap_id)]
+ let mut in_reply_to_vec = vec![post.ap_id];
+
+ if let Some(parent_id) = self.parent_id {
+ let parent_comment = Comment::read(&conn, parent_id)?;
+ in_reply_to_vec.push(parent_comment.ap_id);
+ }
+
+ oprops
+ // Not needed when the Post is embedded in a collection (like for community outbox)
+ .set_context_xsd_any_uri(context())?
+ .set_id(self.ap_id.to_owned())?
+ .set_published(convert_datetime(self.published))?
+ .set_to_xsd_any_uri(community.actor_id)?
+ .set_many_in_reply_to_xsd_any_uris(in_reply_to_vec)?
+ .set_content_xsd_string(self.content.to_owned())?
+ .set_attributed_to_xsd_any_uri(creator.actor_id)?;
+
+ if let Some(u) = self.updated {
+ oprops.set_updated(convert_datetime(u))?;
+ }
+
+ Ok(comment)
+ }
+
+ fn to_tombstone(&self) -> Result<Tombstone, Error> {
+ create_tombstone(
+ self.deleted,
+ &self.ap_id,
+ self.updated,
+ NoteType.to_string(),
+ )
+ }
+}
+
+impl FromApub for CommentForm {
+ type ApubType = Note;
+
+ /// Parse an ActivityPub note received from another instance into a Lemmy comment
+ fn from_apub(note: &Note, conn: &PgConnection) -> Result<CommentForm, Error> {
+ let oprops = ¬e.object_props;
+ let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
+ let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, &conn)?;
+
+ let mut in_reply_tos = oprops.get_many_in_reply_to_xsd_any_uris().unwrap();
+ let post_ap_id = in_reply_tos.next().unwrap().to_string();
+
+ // This post, or the parent comment might not yet exist on this server yet, fetch them.
+ let post = get_or_fetch_and_insert_remote_post(&post_ap_id, &conn)?;
+
+ // The 2nd item, if it exists, is the parent comment apub_id
+ // For deeply nested comments, FromApub automatically gets called recursively
+ let parent_id: Option<i32> = match in_reply_tos.next() {
+ Some(parent_comment_uri) => {
+ let parent_comment_ap_id = &parent_comment_uri.to_string();
+ let parent_comment = get_or_fetch_and_insert_remote_comment(&parent_comment_ap_id, &conn)?;
+
+ Some(parent_comment.id)
+ }
+ None => None,
+ };
+
+ Ok(CommentForm {
+ creator_id: creator.id,
+ post_id: post.id,
+ parent_id,
+ content: oprops
+ .get_content_xsd_string()
+ .map(|c| c.to_string())
+ .unwrap(),
+ removed: None,
+ read: None,
+ published: oprops
+ .get_published()
+ .map(|u| u.as_ref().to_owned().naive_local()),
+ updated: oprops
+ .get_updated()
+ .map(|u| u.as_ref().to_owned().naive_local()),
+ deleted: None,
+ ap_id: oprops.get_id().unwrap().to_string(),
+ local: false,
+ })
+ }
+}
+
+impl ApubObjectType for Comment {
+ /// Send out information about a newly created comment, to the followers of the community.
+ fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(conn, post.community_id)?;
+ let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4());
+
+ let maa: MentionsAndAddresses =
+ collect_non_local_mentions_and_addresses(&conn, &self.content, &community)?;
+
+ let mut create = Create::new();
+ populate_object_props(&mut create.object_props, maa.addressed_ccs, &id)?;
+
+ // Set the mention tags
+ create.object_props.set_many_tag_base_boxes(maa.tags)?;
+
+ create
+ .create_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ send_activity_to_community(&creator, &conn, &community, maa.inboxes, create)?;
+ Ok(())
+ }
+
+ /// Send out information about an edited post, to the followers of the community.
+ fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(&conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+ let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4());
+
+ let maa: MentionsAndAddresses =
+ collect_non_local_mentions_and_addresses(&conn, &self.content, &community)?;
+
+ let mut update = Update::new();
+ populate_object_props(&mut update.object_props, maa.addressed_ccs, &id)?;
+
+ // Set the mention tags
+ update.object_props.set_many_tag_base_boxes(maa.tags)?;
+
+ update
+ .update_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ send_activity_to_community(&creator, &conn, &community, maa.inboxes, update)?;
+ Ok(())
+ }
+
+ fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(&conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+ let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut delete = Delete::default();
+
+ populate_object_props(
+ &mut delete.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ send_activity_to_community(
+ &creator,
+ &conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ delete,
+ )?;
+ Ok(())
+ }
+
+ fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(&conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+
+ // Generate a fake delete activity, with the correct object
+ let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut delete = Delete::default();
+
+ populate_object_props(
+ &mut delete.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ // TODO
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(
+ &mut undo.object_props,
+ vec![community.get_followers_url()],
+ &undo_id,
+ )?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(delete)?;
+
+ send_activity_to_community(
+ &creator,
+ &conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ undo,
+ )?;
+ Ok(())
+ }
+
+ fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(&conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+ let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut remove = Remove::default();
+
+ populate_object_props(
+ &mut remove.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+
+ remove
+ .remove_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ send_activity_to_community(
+ &mod_,
+ &conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ remove,
+ )?;
+ Ok(())
+ }
+
+ fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(&conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+
+ // Generate a fake delete activity, with the correct object
+ let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut remove = Remove::default();
+
+ populate_object_props(
+ &mut remove.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+
+ remove
+ .remove_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/remove/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(
+ &mut undo.object_props,
+ vec![community.get_followers_url()],
+ &undo_id,
+ )?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(remove)?;
+
+ send_activity_to_community(
+ &mod_,
+ &conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ undo,
+ )?;
+ Ok(())
+ }
+}
+
+impl ApubLikeableType for Comment {
+ fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(&conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+ let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4());
+
+ let mut like = Like::new();
+ populate_object_props(
+ &mut like.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+ like
+ .like_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ send_activity_to_community(
+ &creator,
+ &conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ like,
+ )?;
+ Ok(())
+ }
+
+ fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(&conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+ let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4());
+
+ let mut dislike = Dislike::new();
+ populate_object_props(
+ &mut dislike.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+ dislike
+ .dislike_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ send_activity_to_community(
+ &creator,
+ &conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ dislike,
+ )?;
+ Ok(())
+ }
+
+ fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(&conn)?;
+ let post = Post::read(&conn, self.post_id)?;
+ let community = Community::read(&conn, post.community_id)?;
+ let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4());
+
+ let mut like = Like::new();
+ populate_object_props(
+ &mut like.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+ like
+ .like_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ // TODO
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/like/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(
+ &mut undo.object_props,
+ vec![community.get_followers_url()],
+ &undo_id,
+ )?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(like)?;
+
+ send_activity_to_community(
+ &creator,
+ &conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ undo,
+ )?;
+ Ok(())
+ }
+}
+
+struct MentionsAndAddresses {
+ addressed_ccs: Vec<String>,
+ inboxes: Vec<String>,
+ tags: Vec<Mention>,
+}
+
+/// This takes a comment, and builds a list of to_addresses, inboxes,
+/// and mention tags, so they know where to be sent to.
+/// Addresses are the users / addresses that go in the cc field.
+fn collect_non_local_mentions_and_addresses(
+ conn: &PgConnection,
+ content: &str,
+ community: &Community,
+) -> Result<MentionsAndAddresses, Error> {
+ let mut addressed_ccs = vec![community.get_followers_url()];
+
+ // Add the mention tag
+ let mut tags = Vec::new();
+
+ // Get the inboxes for any mentions
+ let mentions = scrape_text_for_mentions(&content)
+ .into_iter()
+ // Filter only the non-local ones
+ .filter(|m| !m.is_local())
+ .collect::<Vec<MentionData>>();
+ let mut mention_inboxes = Vec::new();
+ for mention in &mentions {
+ // TODO should it be fetching it every time?
+ if let Ok(actor_id) = fetch_webfinger_url(mention) {
+ debug!("mention actor_id: {}", actor_id);
+ addressed_ccs.push(actor_id.to_owned());
+ let mention_user = get_or_fetch_and_upsert_remote_user(&actor_id, &conn)?;
+ let shared_inbox = mention_user.get_shared_inbox_url();
+ mention_inboxes.push(shared_inbox);
+ let mut mention_tag = Mention::new();
+ mention_tag
+ .link_props
+ .set_href(actor_id)?
+ .set_name_xsd_string(mention.full_name())?;
+ tags.push(mention_tag);
+ }
+ }
+
+ let mut inboxes = vec![community.get_shared_inbox_url()];
+ inboxes.extend(mention_inboxes);
+ inboxes = inboxes.into_iter().unique().collect();
+
+ Ok(MentionsAndAddresses {
+ addressed_ccs,
+ inboxes,
+ tags,
+ })
+}
-use crate::apub::make_apub_endpoint;
-use crate::db::community::Community;
-use crate::db::community_view::CommunityFollowerView;
-use crate::db::establish_unpooled_connection;
-use crate::to_datetime_utc;
-use activitypub::{actor::Group, collection::UnorderedCollection, context};
-use actix_web::body::Body;
-use actix_web::web::Path;
-use actix_web::HttpResponse;
-use serde::Deserialize;
+use crate::{
+ apub::{
+ activities::{populate_object_props, send_activity},
+ create_apub_response,
+ create_apub_tombstone_response,
+ create_tombstone,
+ extensions::{group_extensions::GroupExtension, signatures::PublicKey},
+ fetcher::get_or_fetch_and_upsert_remote_user,
+ get_shared_inbox,
+ ActorType,
+ FromApub,
+ GroupExt,
+ ToApub,
+ },
+ convert_datetime,
+ db::{
+ activity::insert_activity,
+ community::{Community, CommunityForm},
+ community_view::{CommunityFollowerView, CommunityModeratorView},
+ user::User_,
+ },
+ naive_now,
+ routes::DbPoolParam,
+};
+use activitystreams::{
+ activity::{Accept, Announce, Delete, Remove, Undo},
+ actor::{kind::GroupType, properties::ApActorProperties, Group},
+ collection::UnorderedCollection,
+ context,
+ endpoint::EndpointProperties,
+ object::properties::ObjectProperties,
+ Activity,
+ Base,
+ BaseBox,
+};
+use activitystreams_ext::Ext3;
+use activitystreams_new::{activity::Follow, object::Tombstone};
+use actix_web::{body::Body, web::Path, HttpResponse, Result};
+use diesel::PgConnection;
+use failure::{Error, _core::fmt::Debug};
+use itertools::Itertools;
+use serde::{Deserialize, Serialize};
-impl Community {
- pub fn as_group(&self) -> Group {
- let base_url = make_apub_endpoint("c", &self.name);
+#[derive(Deserialize)]
+pub struct CommunityQuery {
+ community_name: String,
+}
+
+impl ToApub for Community {
+ type Response = GroupExt;
+ // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
+ fn to_apub(&self, conn: &PgConnection) -> Result<GroupExt, Error> {
let mut group = Group::default();
+ let oprops: &mut ObjectProperties = group.as_mut();
- group.object_props.set_context_object(context()).ok();
- group.object_props.set_id_string(base_url.to_string()).ok();
- group
- .object_props
- .set_name_string(self.name.to_owned())
- .ok();
- group
- .object_props
- .set_published_utctime(to_datetime_utc(self.published))
- .ok();
- if let Some(updated) = self.updated {
- group
- .object_props
- .set_updated_utctime(to_datetime_utc(updated))
- .ok();
- }
+ // The attributed to, is an ordered vector with the creator actor_ids first,
+ // then the rest of the moderators
+ // TODO Technically the instance admins can mod the community, but lets
+ // ignore that for now
+ let moderators = CommunityModeratorView::for_community(&conn, self.id)?
+ .into_iter()
+ .map(|m| m.user_actor_id)
+ .collect();
+
+ oprops
+ .set_context_xsd_any_uri(context())?
+ .set_id(self.actor_id.to_owned())?
+ .set_name_xsd_string(self.name.to_owned())?
+ .set_published(convert_datetime(self.published))?
+ .set_many_attributed_to_xsd_any_uris(moderators)?;
- if let Some(description) = &self.description {
- group
- .object_props
- .set_summary_string(description.to_string())
- .ok();
+ if let Some(u) = self.updated.to_owned() {
+ oprops.set_updated(convert_datetime(u))?;
+ }
+ if let Some(d) = self.description.to_owned() {
+ // TODO: this should be html, also add source field with raw markdown
+ // -> same for post.content and others
+ oprops.set_content_xsd_string(d)?;
}
- group
- .ap_actor_props
- .set_inbox_string(format!("{}/inbox", &base_url))
- .ok();
- group
- .ap_actor_props
- .set_outbox_string(format!("{}/outbox", &base_url))
- .ok();
- group
- .ap_actor_props
- .set_followers_string(format!("{}/followers", &base_url))
- .ok();
+ let mut endpoint_props = EndpointProperties::default();
- group
- }
+ endpoint_props.set_shared_inbox(self.get_shared_inbox_url())?;
- pub fn followers_as_collection(&self) -> UnorderedCollection {
- let base_url = make_apub_endpoint("c", &self.name);
+ let mut actor_props = ApActorProperties::default();
- let mut collection = UnorderedCollection::default();
- collection.object_props.set_context_object(context()).ok();
- collection.object_props.set_id_string(base_url).ok();
+ actor_props
+ .set_preferred_username(self.title.to_owned())?
+ .set_inbox(self.get_inbox_url())?
+ .set_outbox(self.get_outbox_url())?
+ .set_endpoints(endpoint_props)?
+ .set_followers(self.get_followers_url())?;
- let connection = establish_unpooled_connection();
- //As we are an object, we validated that the community id was valid
- let community_followers = CommunityFollowerView::for_community(&connection, self.id).unwrap();
+ let group_extension = GroupExtension::new(conn, self.category_id, self.nsfw)?;
- let ap_followers = community_followers
- .iter()
- .map(|follower| make_apub_endpoint("u", &follower.user_name))
- .collect();
+ Ok(Ext3::new(
+ group,
+ group_extension,
+ actor_props,
+ self.get_public_key_ext(),
+ ))
+ }
- collection
- .collection_props
- .set_items_string_vec(ap_followers)
- .unwrap();
- collection
+ fn to_tombstone(&self) -> Result<Tombstone, Error> {
+ create_tombstone(
+ self.deleted,
+ &self.actor_id,
+ self.updated,
+ GroupType.to_string(),
+ )
}
}
-#[derive(Deserialize)]
-pub struct CommunityQuery {
- community_name: String,
+impl ActorType for Community {
+ fn actor_id(&self) -> String {
+ self.actor_id.to_owned()
+ }
+
+ fn public_key(&self) -> String {
+ self.public_key.to_owned().unwrap()
+ }
+ fn private_key(&self) -> String {
+ self.private_key.to_owned().unwrap()
+ }
+
+ /// As a local community, accept the follow request from a remote user.
+ fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error> {
+ let actor_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string();
+ let id = format!("{}/accept/{}", self.actor_id, uuid::Uuid::new_v4());
+
+ let mut accept = Accept::new();
+ accept
+ .object_props
+ .set_context_xsd_any_uri(context())?
+ .set_id(id)?;
+ accept
+ .accept_props
+ .set_actor_xsd_any_uri(self.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(follow.clone())?)?;
+ let to = format!("{}/inbox", actor_uri);
+
+ insert_activity(&conn, self.creator_id, &accept, true)?;
+
+ send_activity(&accept, self, vec![to])?;
+ Ok(())
+ }
+
+ fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let group = self.to_apub(conn)?;
+ let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4());
+
+ let mut delete = Delete::default();
+ populate_object_props(
+ &mut delete.object_props,
+ vec![self.get_followers_url()],
+ &id,
+ )?;
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(group)?)?;
+
+ insert_activity(&conn, self.creator_id, &delete, true)?;
+
+ // Note: For an accept, since it was automatic, no one pushed a button,
+ // the community was the actor.
+ // But for delete, the creator is the actor, and does the signing
+ send_activity(&delete, creator, self.get_follower_inboxes(&conn)?)?;
+ Ok(())
+ }
+
+ fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let group = self.to_apub(conn)?;
+ let id = format!("{}/delete/{}", self.actor_id, uuid::Uuid::new_v4());
+
+ let mut delete = Delete::default();
+ populate_object_props(
+ &mut delete.object_props,
+ vec![self.get_followers_url()],
+ &id,
+ )?;
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(group)?)?;
+
+ // TODO
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/delete/{}", self.actor_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(
+ &mut undo.object_props,
+ vec![self.get_followers_url()],
+ &undo_id,
+ )?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(delete)?;
+
+ insert_activity(&conn, self.creator_id, &undo, true)?;
+
+ // Note: For an accept, since it was automatic, no one pushed a button,
+ // the community was the actor.
+ // But for delete, the creator is the actor, and does the signing
+ send_activity(&undo, creator, self.get_follower_inboxes(&conn)?)?;
+ Ok(())
+ }
+
+ fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let group = self.to_apub(conn)?;
+ let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4());
+
+ let mut remove = Remove::default();
+ populate_object_props(
+ &mut remove.object_props,
+ vec![self.get_followers_url()],
+ &id,
+ )?;
+
+ remove
+ .remove_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(group)?)?;
+
+ insert_activity(&conn, mod_.id, &remove, true)?;
+
+ // Note: For an accept, since it was automatic, no one pushed a button,
+ // the community was the actor.
+ // But for delete, the creator is the actor, and does the signing
+ send_activity(&remove, mod_, self.get_follower_inboxes(&conn)?)?;
+ Ok(())
+ }
+
+ fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let group = self.to_apub(conn)?;
+ let id = format!("{}/remove/{}", self.actor_id, uuid::Uuid::new_v4());
+
+ let mut remove = Remove::default();
+ populate_object_props(
+ &mut remove.object_props,
+ vec![self.get_followers_url()],
+ &id,
+ )?;
+
+ remove
+ .remove_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(group)?)?;
+
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/remove/{}", self.actor_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(
+ &mut undo.object_props,
+ vec![self.get_followers_url()],
+ &undo_id,
+ )?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(remove)?;
+
+ insert_activity(&conn, mod_.id, &undo, true)?;
+
+ // Note: For an accept, since it was automatic, no one pushed a button,
+ // the community was the actor.
+ // But for remove , the creator is the actor, and does the signing
+ send_activity(&undo, mod_, self.get_follower_inboxes(&conn)?)?;
+ Ok(())
+ }
+
+ /// For a given community, returns the inboxes of all followers.
+ fn get_follower_inboxes(&self, conn: &PgConnection) -> Result<Vec<String>, Error> {
+ Ok(
+ CommunityFollowerView::for_community(conn, self.id)?
+ .into_iter()
+ .map(|c| get_shared_inbox(&c.user_actor_id))
+ .filter(|s| !s.is_empty())
+ .unique()
+ .collect(),
+ )
+ }
+
+ fn send_follow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> {
+ unimplemented!()
+ }
+
+ fn send_unfollow(&self, _follow_actor_id: &str, _conn: &PgConnection) -> Result<(), Error> {
+ unimplemented!()
+ }
}
-pub async fn get_apub_community(info: Path<CommunityQuery>) -> HttpResponse<Body> {
- let connection = establish_unpooled_connection();
+impl FromApub for CommunityForm {
+ type ApubType = GroupExt;
+
+ /// Parse an ActivityPub group received from another instance into a Lemmy community.
+ fn from_apub(group: &GroupExt, conn: &PgConnection) -> Result<Self, Error> {
+ let group_extensions: &GroupExtension = &group.ext_one;
+ let oprops = &group.inner.object_props;
+ let aprops = &group.ext_two;
+ let public_key: &PublicKey = &group.ext_three.public_key;
+
+ let mut creator_and_moderator_uris = oprops.get_many_attributed_to_xsd_any_uris().unwrap();
+ let creator = creator_and_moderator_uris
+ .next()
+ .map(|c| get_or_fetch_and_upsert_remote_user(&c.to_string(), &conn).unwrap())
+ .unwrap();
+
+ Ok(CommunityForm {
+ name: oprops.get_name_xsd_string().unwrap().to_string(),
+ title: aprops.get_preferred_username().unwrap().to_string(),
+ // TODO: should be parsed as html and tags like <script> removed (or use markdown source)
+ // -> same for post.content etc
+ description: oprops.get_content_xsd_string().map(|s| s.to_string()),
+ category_id: group_extensions.category.identifier.parse::<i32>()?,
+ creator_id: creator.id,
+ removed: None,
+ published: oprops
+ .get_published()
+ .map(|u| u.as_ref().to_owned().naive_local()),
+ updated: oprops
+ .get_updated()
+ .map(|u| u.as_ref().to_owned().naive_local()),
+ deleted: None,
+ nsfw: group_extensions.sensitive,
+ actor_id: oprops.get_id().unwrap().to_string(),
+ local: false,
+ private_key: None,
+ public_key: Some(public_key.to_owned().public_key_pem),
+ last_refreshed_at: Some(naive_now()),
+ })
+ }
+}
- if let Ok(community) = Community::read_from_name(&connection, info.community_name.to_owned()) {
- HttpResponse::Ok()
- .content_type("application/activity+json")
- .body(serde_json::to_string(&community.as_group()).unwrap())
+/// Return the community json over HTTP.
+pub async fn get_apub_community_http(
+ info: Path<CommunityQuery>,
+ db: DbPoolParam,
+) -> Result<HttpResponse<Body>, Error> {
+ let community = Community::read_from_name(&&db.get()?, &info.community_name)?;
+ if !community.deleted {
+ Ok(create_apub_response(
+ &community.to_apub(&db.get().unwrap())?,
+ ))
} else {
- HttpResponse::NotFound().finish()
+ Ok(create_apub_tombstone_response(&community.to_tombstone()?))
}
}
-pub async fn get_apub_community_followers(info: Path<CommunityQuery>) -> HttpResponse<Body> {
- let connection = establish_unpooled_connection();
+/// Returns an empty followers collection, only populating the size (for privacy).
+pub async fn get_apub_community_followers(
+ info: Path<CommunityQuery>,
+ db: DbPoolParam,
+) -> Result<HttpResponse<Body>, Error> {
+ let community = Community::read_from_name(&&db.get()?, &info.community_name)?;
- if let Ok(community) = Community::read_from_name(&connection, info.community_name.to_owned()) {
- HttpResponse::Ok()
- .content_type("application/activity+json")
- .body(serde_json::to_string(&community.followers_as_collection()).unwrap())
- } else {
- HttpResponse::NotFound().finish()
+ let conn = db.get()?;
+
+ //As we are an object, we validated that the community id was valid
+ let community_followers = CommunityFollowerView::for_community(&conn, community.id).unwrap();
+
+ let mut collection = UnorderedCollection::default();
+ let oprops: &mut ObjectProperties = collection.as_mut();
+ oprops
+ .set_context_xsd_any_uri(context())?
+ .set_id(community.actor_id)?;
+ collection
+ .collection_props
+ .set_total_items(community_followers.len() as u64)?;
+ Ok(create_apub_response(&collection))
+}
+
+impl Community {
+ pub fn do_announce<A>(
+ activity: A,
+ community: &Community,
+ sender: &dyn ActorType,
+ conn: &PgConnection,
+ ) -> Result<HttpResponse, Error>
+ where
+ A: Activity + Base + Serialize + Debug,
+ {
+ let mut announce = Announce::default();
+ populate_object_props(
+ &mut announce.object_props,
+ vec![community.get_followers_url()],
+ &format!("{}/announce/{}", community.actor_id, uuid::Uuid::new_v4()),
+ )?;
+ announce
+ .announce_props
+ .set_actor_xsd_any_uri(community.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(activity)?)?;
+
+ insert_activity(&conn, community.creator_id, &announce, true)?;
+
+ // dont send to the instance where the activity originally came from, because that would result
+ // in a database error (same data inserted twice)
+ let mut to = community.get_follower_inboxes(&conn)?;
+ // this seems to be the "easiest" stable alternative for remove_item()
+ to.retain(|x| *x != sender.get_shared_inbox_url());
+
+ send_activity(&announce, community, to)?;
+
+ Ok(HttpResponse::Ok().finish())
}
}
--- /dev/null
+use crate::{
+ apub::{
+ extensions::signatures::verify,
+ fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
+ ActorType,
+ },
+ db::{
+ activity::insert_activity,
+ community::{Community, CommunityFollower, CommunityFollowerForm},
+ user::User_,
+ Followable,
+ },
+ routes::{ChatServerParam, DbPoolParam},
+};
+use activitystreams::activity::Undo;
+use activitystreams_new::activity::Follow;
+use actix_web::{web, HttpRequest, HttpResponse, Result};
+use diesel::PgConnection;
+use failure::{Error, _core::fmt::Debug};
+use log::debug;
+use serde::Deserialize;
+
+#[serde(untagged)]
+#[derive(Deserialize, Debug)]
+pub enum CommunityAcceptedObjects {
+ Follow(Follow),
+ Undo(Undo),
+}
+
+impl CommunityAcceptedObjects {
+ fn follow(&self) -> Result<Follow, Error> {
+ match self {
+ CommunityAcceptedObjects::Follow(f) => Ok(f.to_owned()),
+ CommunityAcceptedObjects::Undo(u) => Ok(
+ u.undo_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Follow>()?,
+ ),
+ }
+ }
+}
+
+/// Handler for all incoming activities to community inboxes.
+pub async fn community_inbox(
+ request: HttpRequest,
+ input: web::Json<CommunityAcceptedObjects>,
+ path: web::Path<String>,
+ db: DbPoolParam,
+ _chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let input = input.into_inner();
+ let conn = db.get()?;
+ let community = Community::read_from_name(&conn, &path.into_inner())?;
+ if !community.local {
+ return Err(format_err!(
+ "Received activity is addressed to remote community {}",
+ &community.actor_id
+ ));
+ }
+ debug!(
+ "Community {} received activity {:?}",
+ &community.name, &input
+ );
+ let follow = input.follow()?;
+ let user_uri = follow.actor.as_single_xsd_any_uri().unwrap().to_string();
+ let community_uri = follow.object.as_single_xsd_any_uri().unwrap().to_string();
+
+ let conn = db.get()?;
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+ let community = get_or_fetch_and_upsert_remote_community(&community_uri, &conn)?;
+
+ verify(&request, &user)?;
+
+ match input {
+ CommunityAcceptedObjects::Follow(f) => handle_follow(&f, &user, &community, &conn),
+ CommunityAcceptedObjects::Undo(u) => handle_undo_follow(&u, &user, &community, &conn),
+ }
+}
+
+/// Handle a follow request from a remote user, adding it to the local database and returning an
+/// Accept activity.
+fn handle_follow(
+ follow: &Follow,
+ user: &User_,
+ community: &Community,
+ conn: &PgConnection,
+) -> Result<HttpResponse, Error> {
+ insert_activity(&conn, user.id, &follow, false)?;
+
+ let community_follower_form = CommunityFollowerForm {
+ community_id: community.id,
+ user_id: user.id,
+ };
+
+ // This will fail if they're already a follower, but ignore the error.
+ CommunityFollower::follow(&conn, &community_follower_form).ok();
+
+ community.send_accept_follow(&follow, &conn)?;
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn handle_undo_follow(
+ undo: &Undo,
+ user: &User_,
+ community: &Community,
+ conn: &PgConnection,
+) -> Result<HttpResponse, Error> {
+ insert_activity(&conn, user.id, &undo, false)?;
+
+ let community_follower_form = CommunityFollowerForm {
+ community_id: community.id,
+ user_id: user.id,
+ };
+
+ CommunityFollower::unfollow(&conn, &community_follower_form).ok();
+
+ Ok(HttpResponse::Ok().finish())
+}
--- /dev/null
+use crate::db::{category::Category, Crud};
+use activitystreams::{ext::Extension, Actor};
+use diesel::PgConnection;
+use failure::Error;
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct GroupExtension {
+ pub category: GroupCategory,
+ pub sensitive: bool,
+}
+
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct GroupCategory {
+ // Using a string because that's how Peertube does it.
+ pub identifier: String,
+ pub name: String,
+}
+
+impl GroupExtension {
+ pub fn new(
+ conn: &PgConnection,
+ category_id: i32,
+ sensitive: bool,
+ ) -> Result<GroupExtension, Error> {
+ let category = Category::read(conn, category_id)?;
+ let group_category = GroupCategory {
+ identifier: category_id.to_string(),
+ name: category.name,
+ };
+ Ok(GroupExtension {
+ category: group_category,
+ sensitive,
+ })
+ }
+}
+
+impl<T> Extension<T> for GroupExtension where T: Actor {}
--- /dev/null
+pub mod group_extensions;
+pub mod page_extension;
+pub mod signatures;
--- /dev/null
+use activitystreams::{ext::Extension, Base};
+use serde::{Deserialize, Serialize};
+
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PageExtension {
+ pub comments_enabled: bool,
+ pub sensitive: bool,
+}
+
+impl<T> Extension<T> for PageExtension where T: Base {}
--- /dev/null
+use crate::apub::ActorType;
+use activitystreams::ext::Extension;
+use actix_web::HttpRequest;
+use attohttpc::RequestBuilder;
+use failure::Error;
+use http_signature_normalization::Config;
+use log::debug;
+use openssl::{
+ hash::MessageDigest,
+ pkey::PKey,
+ rsa::Rsa,
+ sign::{Signer, Verifier},
+};
+use serde::{Deserialize, Serialize};
+use std::collections::BTreeMap;
+
+lazy_static! {
+ static ref HTTP_SIG_CONFIG: Config = Config::new();
+}
+
+pub struct Keypair {
+ pub private_key: String,
+ pub public_key: String,
+}
+
+/// Generate the asymmetric keypair for ActivityPub HTTP signatures.
+pub fn generate_actor_keypair() -> Result<Keypair, Error> {
+ let rsa = Rsa::generate(2048)?;
+ let pkey = PKey::from_rsa(rsa)?;
+ let public_key = pkey.public_key_to_pem()?;
+ let private_key = pkey.private_key_to_pem_pkcs8()?;
+ Ok(Keypair {
+ private_key: String::from_utf8(private_key)?,
+ public_key: String::from_utf8(public_key)?,
+ })
+}
+
+// TODO is it possible to create this signature, with just the url and actor?
+/// Signs request headers with the given keypair.
+pub fn sign(request: &mut RequestBuilder, actor: &dyn ActorType) -> Result<String, Error> {
+ let signing_key_id = format!("{}#main-key", actor.actor_id());
+
+ let headers = request
+ .inspect()
+ .headers()
+ .iter()
+ .map(|h| -> Result<(String, String), Error> {
+ Ok((h.0.as_str().to_owned(), h.1.to_str()?.to_owned()))
+ })
+ .collect::<Result<BTreeMap<String, String>, Error>>()?;
+
+ let mut path_and_query = request.inspect().url().path().to_owned();
+ if let Some(query) = request.inspect().url().query() {
+ path_and_query.push_str(query);
+ }
+
+ let signature_header_value = HTTP_SIG_CONFIG
+ .begin_sign(
+ request.inspect().method().as_str(),
+ &path_and_query,
+ headers,
+ )?
+ .sign(signing_key_id, |signing_string| {
+ let private_key = PKey::private_key_from_pem(actor.private_key().as_bytes())?;
+ let mut signer = Signer::new(MessageDigest::sha256(), &private_key).unwrap();
+ signer.update(signing_string.as_bytes()).unwrap();
+ Ok(base64::encode(signer.sign_to_vec()?)) as Result<_, Error>
+ })?
+ .signature_header();
+
+ Ok(signature_header_value)
+}
+
+pub fn verify(request: &HttpRequest, actor: &dyn ActorType) -> Result<(), Error> {
+ let headers = request
+ .headers()
+ .iter()
+ .map(|h| -> Result<(String, String), Error> {
+ Ok((h.0.as_str().to_owned(), h.1.to_str()?.to_owned()))
+ })
+ .collect::<Result<BTreeMap<String, String>, Error>>()?;
+
+ let verified = HTTP_SIG_CONFIG
+ .begin_verify(
+ request.method().as_str(),
+ request.uri().path_and_query().unwrap().as_str(),
+ headers,
+ )?
+ .verify(|signature, signing_string| -> Result<bool, Error> {
+ debug!(
+ "Verifying with key {}, message {}",
+ &actor.public_key(),
+ &signing_string
+ );
+ let public_key = PKey::public_key_from_pem(actor.public_key().as_bytes())?;
+ let mut verifier = Verifier::new(MessageDigest::sha256(), &public_key).unwrap();
+ verifier.update(&signing_string.as_bytes()).unwrap();
+ Ok(verifier.verify(&base64::decode(signature)?)?)
+ })?;
+
+ if verified {
+ debug!("verified signature for {}", &request.uri());
+ Ok(())
+ } else {
+ Err(format_err!(
+ "Invalid signature on request: {}",
+ &request.uri()
+ ))
+ }
+}
+
+// The following is taken from here:
+// https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html
+
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PublicKey {
+ pub id: String,
+ pub owner: String,
+ pub public_key_pem: String,
+}
+
+#[derive(Clone, Debug, Default, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PublicKeyExtension {
+ pub public_key: PublicKey,
+}
+
+impl PublicKey {
+ pub fn to_ext(&self) -> PublicKeyExtension {
+ PublicKeyExtension {
+ public_key: self.to_owned(),
+ }
+ }
+}
+
+impl<T> Extension<T> for PublicKeyExtension where T: activitystreams::Actor {}
--- /dev/null
+use activitystreams::object::Note;
+use actix_web::Result;
+use diesel::{result::Error::NotFound, PgConnection};
+use failure::{Error, _core::fmt::Debug};
+use log::debug;
+use serde::Deserialize;
+use std::time::Duration;
+use url::Url;
+
+use crate::{
+ api::site::SearchResponse,
+ db::{
+ comment::{Comment, CommentForm},
+ comment_view::CommentView,
+ community::{Community, CommunityForm, CommunityModerator, CommunityModeratorForm},
+ community_view::CommunityView,
+ post::{Post, PostForm},
+ post_view::PostView,
+ user::{UserForm, User_},
+ Crud,
+ Joinable,
+ SearchType,
+ },
+ naive_now,
+ routes::nodeinfo::{NodeInfo, NodeInfoWellKnown},
+};
+
+use crate::{
+ apub::{
+ get_apub_protocol_string,
+ is_apub_id_valid,
+ FromApub,
+ GroupExt,
+ PageExt,
+ PersonExt,
+ APUB_JSON_CONTENT_TYPE,
+ },
+ db::user_view::UserView,
+};
+use chrono::NaiveDateTime;
+
+static ACTOR_REFETCH_INTERVAL_SECONDS: i64 = 24 * 60 * 60;
+
+// Fetch nodeinfo metadata from a remote instance.
+fn _fetch_node_info(domain: &str) -> Result<NodeInfo, Error> {
+ let well_known_uri = Url::parse(&format!(
+ "{}://{}/.well-known/nodeinfo",
+ get_apub_protocol_string(),
+ domain
+ ))?;
+ let well_known = fetch_remote_object::<NodeInfoWellKnown>(&well_known_uri)?;
+ Ok(fetch_remote_object::<NodeInfo>(&well_known.links.href)?)
+}
+
+/// Fetch any type of ActivityPub object, handling things like HTTP headers, deserialisation,
+/// timeouts etc.
+pub fn fetch_remote_object<Response>(url: &Url) -> Result<Response, Error>
+where
+ Response: for<'de> Deserialize<'de>,
+{
+ if !is_apub_id_valid(&url) {
+ return Err(format_err!("Activitypub uri invalid or blocked: {}", url));
+ }
+ // TODO: this function should return a future
+ let timeout = Duration::from_secs(60);
+ let text: String = attohttpc::get(url.as_str())
+ .header("Accept", APUB_JSON_CONTENT_TYPE)
+ .connect_timeout(timeout)
+ .timeout(timeout)
+ // .body(())
+ .send()?
+ .text()?;
+ let res: Response = serde_json::from_str(&text)?;
+ Ok(res)
+}
+
+/// The types of ActivityPub objects that can be fetched directly by searching for their ID.
+#[serde(untagged)]
+#[derive(serde::Deserialize, Debug)]
+pub enum SearchAcceptedObjects {
+ Person(Box<PersonExt>),
+ Group(Box<GroupExt>),
+ Page(Box<PageExt>),
+ Comment(Box<Note>),
+}
+
+/// Attempt to parse the query as URL, and fetch an ActivityPub object from it.
+///
+/// Some working examples for use with the docker/federation/ setup:
+/// http://lemmy_alpha:8540/c/main, or !main@lemmy_alpha:8540
+/// http://lemmy_alpha:8540/u/lemmy_alpha, or @lemmy_alpha@lemmy_alpha:8540
+/// http://lemmy_alpha:8540/post/3
+/// http://lemmy_alpha:8540/comment/2
+pub fn search_by_apub_id(query: &str, conn: &PgConnection) -> Result<SearchResponse, Error> {
+ // Parse the shorthand query url
+ let query_url = if query.contains('@') {
+ debug!("{}", query);
+ let split = query.split('@').collect::<Vec<&str>>();
+
+ // User type will look like ['', username, instance]
+ // Community will look like [!community, instance]
+ let (name, instance) = if split.len() == 3 {
+ (format!("/u/{}", split[1]), split[2])
+ } else if split.len() == 2 {
+ if split[0].contains('!') {
+ let split2 = split[0].split('!').collect::<Vec<&str>>();
+ (format!("/c/{}", split2[1]), split[1])
+ } else {
+ return Err(format_err!("Invalid search query: {}", query));
+ }
+ } else {
+ return Err(format_err!("Invalid search query: {}", query));
+ };
+
+ let url = format!("{}://{}{}", get_apub_protocol_string(), instance, name);
+ Url::parse(&url)?
+ } else {
+ Url::parse(&query)?
+ };
+
+ let mut response = SearchResponse {
+ type_: SearchType::All.to_string(),
+ comments: vec![],
+ posts: vec![],
+ communities: vec![],
+ users: vec![],
+ };
+ match fetch_remote_object::<SearchAcceptedObjects>(&query_url)? {
+ SearchAcceptedObjects::Person(p) => {
+ let user_uri = p.inner.object_props.get_id().unwrap().to_string();
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+ response.users = vec![UserView::read(conn, user.id)?];
+ }
+ SearchAcceptedObjects::Group(g) => {
+ let community_uri = g.inner.object_props.get_id().unwrap().to_string();
+ let community = get_or_fetch_and_upsert_remote_community(&community_uri, &conn)?;
+ // TODO Maybe at some point in the future, fetch all the history of a community
+ // fetch_community_outbox(&c, conn)?;
+ response.communities = vec![CommunityView::read(conn, community.id, None)?];
+ }
+ SearchAcceptedObjects::Page(p) => {
+ let p = upsert_post(&PostForm::from_apub(&p, conn)?, conn)?;
+ response.posts = vec![PostView::read(conn, p.id, None)?];
+ }
+ SearchAcceptedObjects::Comment(c) => {
+ let post_url = c
+ .object_props
+ .get_many_in_reply_to_xsd_any_uris()
+ .unwrap()
+ .next()
+ .unwrap()
+ .to_string();
+ // TODO: also fetch parent comments if any
+ let post = fetch_remote_object(&Url::parse(&post_url)?)?;
+ upsert_post(&PostForm::from_apub(&post, conn)?, conn)?;
+ let c = upsert_comment(&CommentForm::from_apub(&c, conn)?, conn)?;
+ response.comments = vec![CommentView::read(conn, c.id, None)?];
+ }
+ }
+ Ok(response)
+}
+
+/// Check if a remote user exists, create if not found, if its too old update it.Fetch a user, insert/update it in the database and return the user.
+pub fn get_or_fetch_and_upsert_remote_user(
+ apub_id: &str,
+ conn: &PgConnection,
+) -> Result<User_, Error> {
+ match User_::read_from_actor_id(&conn, &apub_id) {
+ Ok(u) => {
+ // If its older than a day, re-fetch it
+ if !u.local && should_refetch_actor(u.last_refreshed_at) {
+ debug!("Fetching and updating from remote user: {}", apub_id);
+ let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?;
+ let mut uf = UserForm::from_apub(&person, &conn)?;
+ uf.last_refreshed_at = Some(naive_now());
+ Ok(User_::update(&conn, u.id, &uf)?)
+ } else {
+ Ok(u)
+ }
+ }
+ Err(NotFound {}) => {
+ debug!("Fetching and creating remote user: {}", apub_id);
+ let person = fetch_remote_object::<PersonExt>(&Url::parse(apub_id)?)?;
+ let uf = UserForm::from_apub(&person, &conn)?;
+ Ok(User_::create(conn, &uf)?)
+ }
+ Err(e) => Err(Error::from(e)),
+ }
+}
+
+/// Determines when a remote actor should be refetched from its instance. In release builds, this is
+/// ACTOR_REFETCH_INTERVAL_SECONDS after the last refetch, in debug builds always.
+///
+/// TODO it won't pick up new avatars, summaries etc until a day after.
+/// Actors need an "update" activity pushed to other servers to fix this.
+fn should_refetch_actor(last_refreshed: NaiveDateTime) -> bool {
+ if cfg!(debug_assertions) {
+ true
+ } else {
+ let update_interval = chrono::Duration::seconds(ACTOR_REFETCH_INTERVAL_SECONDS);
+ last_refreshed.lt(&(naive_now() - update_interval))
+ }
+}
+
+/// Check if a remote community exists, create if not found, if its too old update it.Fetch a community, insert/update it in the database and return the community.
+pub fn get_or_fetch_and_upsert_remote_community(
+ apub_id: &str,
+ conn: &PgConnection,
+) -> Result<Community, Error> {
+ match Community::read_from_actor_id(&conn, &apub_id) {
+ Ok(c) => {
+ if !c.local && should_refetch_actor(c.last_refreshed_at) {
+ debug!("Fetching and updating from remote community: {}", apub_id);
+ let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
+ let mut cf = CommunityForm::from_apub(&group, conn)?;
+ cf.last_refreshed_at = Some(naive_now());
+ Ok(Community::update(&conn, c.id, &cf)?)
+ } else {
+ Ok(c)
+ }
+ }
+ Err(NotFound {}) => {
+ debug!("Fetching and creating remote community: {}", apub_id);
+ let group = fetch_remote_object::<GroupExt>(&Url::parse(apub_id)?)?;
+ let cf = CommunityForm::from_apub(&group, conn)?;
+ let community = Community::create(conn, &cf)?;
+
+ // Also add the community moderators too
+ let creator_and_moderator_uris = group
+ .inner
+ .object_props
+ .get_many_attributed_to_xsd_any_uris()
+ .unwrap();
+ let creator_and_moderators = creator_and_moderator_uris
+ .map(|c| get_or_fetch_and_upsert_remote_user(&c.to_string(), &conn).unwrap())
+ .collect::<Vec<User_>>();
+
+ for mod_ in creator_and_moderators {
+ let community_moderator_form = CommunityModeratorForm {
+ community_id: community.id,
+ user_id: mod_.id,
+ };
+ CommunityModerator::join(&conn, &community_moderator_form)?;
+ }
+
+ Ok(community)
+ }
+ Err(e) => Err(Error::from(e)),
+ }
+}
+
+fn upsert_post(post_form: &PostForm, conn: &PgConnection) -> Result<Post, Error> {
+ let existing = Post::read_from_apub_id(conn, &post_form.ap_id);
+ match existing {
+ Err(NotFound {}) => Ok(Post::create(conn, &post_form)?),
+ Ok(p) => Ok(Post::update(conn, p.id, &post_form)?),
+ Err(e) => Err(Error::from(e)),
+ }
+}
+
+pub fn get_or_fetch_and_insert_remote_post(
+ post_ap_id: &str,
+ conn: &PgConnection,
+) -> Result<Post, Error> {
+ match Post::read_from_apub_id(conn, post_ap_id) {
+ Ok(p) => Ok(p),
+ Err(NotFound {}) => {
+ debug!("Fetching and creating remote post: {}", post_ap_id);
+ let post = fetch_remote_object::<PageExt>(&Url::parse(post_ap_id)?)?;
+ let post_form = PostForm::from_apub(&post, conn)?;
+ Ok(Post::create(conn, &post_form)?)
+ }
+ Err(e) => Err(Error::from(e)),
+ }
+}
+
+fn upsert_comment(comment_form: &CommentForm, conn: &PgConnection) -> Result<Comment, Error> {
+ let existing = Comment::read_from_apub_id(conn, &comment_form.ap_id);
+ match existing {
+ Err(NotFound {}) => Ok(Comment::create(conn, &comment_form)?),
+ Ok(p) => Ok(Comment::update(conn, p.id, &comment_form)?),
+ Err(e) => Err(Error::from(e)),
+ }
+}
+
+pub fn get_or_fetch_and_insert_remote_comment(
+ comment_ap_id: &str,
+ conn: &PgConnection,
+) -> Result<Comment, Error> {
+ match Comment::read_from_apub_id(conn, comment_ap_id) {
+ Ok(p) => Ok(p),
+ Err(NotFound {}) => {
+ debug!(
+ "Fetching and creating remote comment and its parents: {}",
+ comment_ap_id
+ );
+ let comment = fetch_remote_object::<Note>(&Url::parse(comment_ap_id)?)?;
+ let comment_form = CommentForm::from_apub(&comment, conn)?;
+ Ok(Comment::create(conn, &comment_form)?)
+ }
+ Err(e) => Err(Error::from(e)),
+ }
+}
+
+// TODO It should not be fetching data from a community outbox.
+// All posts, comments, comment likes, etc should be posts to our community_inbox
+// The only data we should be periodically fetching (if it hasn't been fetched in the last day
+// maybe), is community and user actors
+// and user actors
+// Fetch all posts in the outbox of the given user, and insert them into the database.
+// fn fetch_community_outbox(community: &Community, conn: &PgConnection) -> Result<Vec<Post>, Error> {
+// let outbox_url = Url::parse(&community.get_outbox_url())?;
+// let outbox = fetch_remote_object::<OrderedCollection>(&outbox_url)?;
+// let items = outbox.collection_props.get_many_items_base_boxes();
+
+// Ok(
+// items
+// .unwrap()
+// .map(|obox: &BaseBox| -> Result<PostForm, Error> {
+// let page = obox.clone().to_concrete::<Page>()?;
+// PostForm::from_page(&page, conn)
+// })
+// .map(|pf| upsert_post(&pf?, conn))
+// .collect::<Result<Vec<Post>, Error>>()?,
+// )
+// }
+pub mod activities;
+pub mod comment;
pub mod community;
+pub mod community_inbox;
+pub mod extensions;
+pub mod fetcher;
pub mod post;
+pub mod private_message;
+pub mod shared_inbox;
pub mod user;
-use crate::Settings;
-
-use std::fmt::Display;
-
-#[cfg(test)]
-mod tests {
- use crate::db::community::Community;
- use crate::db::post::Post;
- use crate::db::user::User_;
- use crate::db::{ListingType, SortType};
- use crate::{naive_now, Settings};
-
- #[test]
- fn test_person() {
- let user = User_ {
- id: 52,
- name: "thom".into(),
- fedi_name: "rrf".into(),
- preferred_username: None,
- password_encrypted: "here".into(),
- email: None,
- matrix_user_id: None,
- avatar: None,
- published: naive_now(),
- admin: false,
- banned: false,
- updated: None,
- show_nsfw: false,
- theme: "darkly".into(),
- default_sort_type: SortType::Hot as i16,
- default_listing_type: ListingType::Subscribed as i16,
- lang: "browser".into(),
- show_avatars: true,
- send_notifications_to_email: false,
- };
-
- let person = user.as_person();
- assert_eq!(
- format!("https://{}/federation/u/thom", Settings::get().hostname),
- person.object_props.id_string().unwrap()
- );
+pub mod user_inbox;
+
+use crate::{
+ apub::extensions::{
+ group_extensions::GroupExtension,
+ page_extension::PageExtension,
+ signatures::{PublicKey, PublicKeyExtension},
+ },
+ convert_datetime,
+ db::user::User_,
+ routes::webfinger::WebFingerResponse,
+ MentionData,
+ Settings,
+};
+use activitystreams::{
+ actor::{properties::ApActorProperties, Group, Person},
+ object::Page,
+};
+use activitystreams_ext::{Ext1, Ext2, Ext3};
+use activitystreams_new::{activity::Follow, object::Tombstone, prelude::*};
+use actix_web::{body::Body, HttpResponse, Result};
+use chrono::NaiveDateTime;
+use diesel::PgConnection;
+use failure::Error;
+use log::debug;
+use serde::Serialize;
+use url::Url;
+
+type GroupExt = Ext3<Group, GroupExtension, ApActorProperties, PublicKeyExtension>;
+type PersonExt = Ext2<Person, ApActorProperties, PublicKeyExtension>;
+type PageExt = Ext1<Page, PageExtension>;
+
+pub static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
+
+pub enum EndpointType {
+ Community,
+ User,
+ Post,
+ Comment,
+ PrivateMessage,
+}
+
+/// Convert the data to json and turn it into an HTTP Response with the correct ActivityPub
+/// headers.
+fn create_apub_response<T>(data: &T) -> HttpResponse<Body>
+where
+ T: Serialize,
+{
+ HttpResponse::Ok()
+ .content_type(APUB_JSON_CONTENT_TYPE)
+ .json(data)
+}
+
+fn create_apub_tombstone_response<T>(data: &T) -> HttpResponse<Body>
+where
+ T: Serialize,
+{
+ HttpResponse::Gone()
+ .content_type(APUB_JSON_CONTENT_TYPE)
+ .json(data)
+}
+
+/// Generates the ActivityPub ID for a given object type and ID.
+pub fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url {
+ let point = match endpoint_type {
+ EndpointType::Community => "c",
+ EndpointType::User => "u",
+ EndpointType::Post => "post",
+ EndpointType::Comment => "comment",
+ EndpointType::PrivateMessage => "private_message",
+ };
+
+ Url::parse(&format!(
+ "{}://{}/{}/{}",
+ get_apub_protocol_string(),
+ Settings::get().hostname,
+ point,
+ name
+ ))
+ .unwrap()
+}
+
+pub fn get_apub_protocol_string() -> &'static str {
+ if Settings::get().federation.tls_enabled {
+ "https"
+ } else {
+ "http"
+ }
+}
+
+// Checks if the ID has a valid format, correct scheme, and is in the allowed instance list.
+fn is_apub_id_valid(apub_id: &Url) -> bool {
+ if apub_id.scheme() != get_apub_protocol_string() {
+ return false;
}
- #[test]
- fn test_community() {
- let community = Community {
- id: 42,
- name: "Test".into(),
- title: "Test Title".into(),
- description: Some("Test community".into()),
- category_id: 32,
- creator_id: 52,
- removed: false,
- published: naive_now(),
- updated: Some(naive_now()),
- deleted: false,
- nsfw: false,
- };
-
- let group = community.as_group();
- assert_eq!(
- format!("https://{}/federation/c/Test", Settings::get().hostname),
- group.object_props.id_string().unwrap()
- );
+ let allowed_instances: Vec<String> = Settings::get()
+ .federation
+ .allowed_instances
+ .split(',')
+ .map(|d| d.to_string())
+ .collect();
+ match apub_id.domain() {
+ Some(d) => allowed_instances.contains(&d.to_owned()),
+ None => false,
}
+}
+
+pub trait ToApub {
+ type Response;
+ fn to_apub(&self, conn: &PgConnection) -> Result<Self::Response, Error>;
+ fn to_tombstone(&self) -> Result<Tombstone, Error>;
+}
- #[test]
- fn test_post() {
- let post = Post {
- id: 62,
- name: "A test post".into(),
- url: None,
- body: None,
- creator_id: 52,
- community_id: 42,
- published: naive_now(),
- removed: false,
- locked: false,
- stickied: false,
- nsfw: false,
- deleted: false,
- updated: None,
- embed_title: None,
- embed_description: None,
- embed_html: None,
- thumbnail_url: None,
- };
-
- let page = post.as_page();
- assert_eq!(
- format!("https://{}/federation/post/62", Settings::get().hostname),
- page.object_props.id_string().unwrap()
- );
+/// Updated is actually the deletion time
+fn create_tombstone(
+ deleted: bool,
+ object_id: &str,
+ updated: Option<NaiveDateTime>,
+ former_type: String,
+) -> Result<Tombstone, Error> {
+ if deleted {
+ if let Some(updated) = updated {
+ let mut tombstone = Tombstone::new();
+ tombstone.set_id(object_id.parse()?);
+ tombstone.set_former_type(former_type);
+ tombstone.set_deleted(convert_datetime(updated).into());
+ Ok(tombstone)
+ } else {
+ Err(format_err!(
+ "Cant convert to tombstone because updated time was None."
+ ))
+ }
+ } else {
+ Err(format_err!(
+ "Cant convert object to tombstone if it wasnt deleted"
+ ))
}
}
-pub fn make_apub_endpoint<S: Display, T: Display>(point: S, value: T) -> String {
+pub trait FromApub {
+ type ApubType;
+ fn from_apub(apub: &Self::ApubType, conn: &PgConnection) -> Result<Self, Error>
+ where
+ Self: Sized;
+}
+
+pub trait ApubObjectType {
+ fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
+}
+
+pub trait ApubLikeableType {
+ fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+}
+
+pub fn get_shared_inbox(actor_id: &str) -> String {
+ let url = Url::parse(actor_id).unwrap();
format!(
- "https://{}/federation/{}/{}",
- Settings::get().hostname,
- point,
- value
+ "{}://{}{}/inbox",
+ &url.scheme(),
+ &url.host_str().unwrap(),
+ if let Some(port) = url.port() {
+ format!(":{}", port)
+ } else {
+ "".to_string()
+ },
)
}
+
+pub trait ActorType {
+ fn actor_id(&self) -> String;
+
+ fn public_key(&self) -> String;
+ fn private_key(&self) -> String;
+
+ // These two have default impls, since currently a community can't follow anything,
+ // and a user can't be followed (yet)
+ #[allow(unused_variables)]
+ fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error>;
+ fn send_unfollow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error>;
+
+ #[allow(unused_variables)]
+ fn send_accept_follow(&self, follow: &Follow, conn: &PgConnection) -> Result<(), Error>;
+
+ fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error>;
+
+ fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
+ fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error>;
+
+ /// For a given community, returns the inboxes of all followers.
+ fn get_follower_inboxes(&self, conn: &PgConnection) -> Result<Vec<String>, Error>;
+
+ // TODO move these to the db rows
+ fn get_inbox_url(&self) -> String {
+ format!("{}/inbox", &self.actor_id())
+ }
+
+ fn get_shared_inbox_url(&self) -> String {
+ get_shared_inbox(&self.actor_id())
+ }
+
+ fn get_outbox_url(&self) -> String {
+ format!("{}/outbox", &self.actor_id())
+ }
+
+ fn get_followers_url(&self) -> String {
+ format!("{}/followers", &self.actor_id())
+ }
+
+ fn get_following_url(&self) -> String {
+ format!("{}/following", &self.actor_id())
+ }
+
+ fn get_liked_url(&self) -> String {
+ format!("{}/liked", &self.actor_id())
+ }
+
+ fn get_public_key_ext(&self) -> PublicKeyExtension {
+ PublicKey {
+ id: format!("{}#main-key", self.actor_id()),
+ owner: self.actor_id(),
+ public_key_pem: self.public_key(),
+ }
+ .to_ext()
+ }
+}
+
+pub fn fetch_webfinger_url(mention: &MentionData) -> Result<String, Error> {
+ let fetch_url = format!(
+ "{}://{}/.well-known/webfinger?resource=acct:{}@{}",
+ get_apub_protocol_string(),
+ mention.domain,
+ mention.name,
+ mention.domain
+ );
+ debug!("Fetching webfinger url: {}", &fetch_url);
+ let text: String = attohttpc::get(&fetch_url).send()?.text()?;
+ let res: WebFingerResponse = serde_json::from_str(&text)?;
+ let link = res
+ .links
+ .iter()
+ .find(|l| l.type_.eq(&Some("application/activity+json".to_string())))
+ .ok_or_else(|| format_err!("No application/activity+json link found."))?;
+ link
+ .href
+ .to_owned()
+ .ok_or_else(|| format_err!("No href found."))
+}
-use crate::apub::make_apub_endpoint;
-use crate::db::post::Post;
-use crate::to_datetime_utc;
-use activitypub::{context, object::Page};
-
-impl Post {
- pub fn as_page(&self) -> Page {
- let base_url = make_apub_endpoint("post", self.id);
+use crate::{
+ apub::{
+ activities::{populate_object_props, send_activity_to_community},
+ create_apub_response,
+ create_apub_tombstone_response,
+ create_tombstone,
+ extensions::page_extension::PageExtension,
+ fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
+ get_apub_protocol_string,
+ ActorType,
+ ApubLikeableType,
+ ApubObjectType,
+ FromApub,
+ PageExt,
+ ToApub,
+ },
+ convert_datetime,
+ db::{
+ community::Community,
+ post::{Post, PostForm},
+ user::User_,
+ Crud,
+ },
+ routes::DbPoolParam,
+ Settings,
+};
+use activitystreams::{
+ activity::{Create, Delete, Dislike, Like, Remove, Undo, Update},
+ context,
+ object::{kind::PageType, properties::ObjectProperties, AnyImage, Image, Page},
+ BaseBox,
+};
+use activitystreams_ext::Ext1;
+use activitystreams_new::object::Tombstone;
+use actix_web::{body::Body, web::Path, HttpResponse, Result};
+use diesel::PgConnection;
+use failure::Error;
+use serde::Deserialize;
+
+#[derive(Deserialize)]
+pub struct PostQuery {
+ post_id: String,
+}
+
+/// Return the post json over HTTP.
+pub async fn get_apub_post(
+ info: Path<PostQuery>,
+ db: DbPoolParam,
+) -> Result<HttpResponse<Body>, Error> {
+ let id = info.post_id.parse::<i32>()?;
+ let post = Post::read(&&db.get()?, id)?;
+ if !post.deleted {
+ Ok(create_apub_response(&post.to_apub(&db.get().unwrap())?))
+ } else {
+ Ok(create_apub_tombstone_response(&post.to_tombstone()?))
+ }
+}
+
+impl ToApub for Post {
+ type Response = PageExt;
+
+ // Turn a Lemmy post into an ActivityPub page that can be sent out over the network.
+ fn to_apub(&self, conn: &PgConnection) -> Result<PageExt, Error> {
let mut page = Page::default();
+ let oprops: &mut ObjectProperties = page.as_mut();
+ let creator = User_::read(conn, self.creator_id)?;
+ let community = Community::read(conn, self.community_id)?;
- page.object_props.set_context_object(context()).ok();
- page.object_props.set_id_string(base_url).ok();
- page.object_props.set_name_string(self.name.to_owned()).ok();
+ oprops
+ // Not needed when the Post is embedded in a collection (like for community outbox)
+ // TODO: need to set proper context defining sensitive/commentsEnabled fields
+ // https://git.asonix.dog/Aardwolf/activitystreams/issues/5
+ .set_context_xsd_any_uri(context())?
+ .set_id(self.ap_id.to_owned())?
+ // Use summary field to be consistent with mastodon content warning.
+ // https://mastodon.xyz/@Louisa/103987265222901387.json
+ .set_summary_xsd_string(self.name.to_owned())?
+ .set_published(convert_datetime(self.published))?
+ .set_to_xsd_any_uri(community.actor_id)?
+ .set_attributed_to_xsd_any_uri(creator.actor_id)?;
if let Some(body) = &self.body {
- page.object_props.set_content_string(body.to_owned()).ok();
+ oprops.set_content_xsd_string(body.to_owned())?;
}
- if let Some(url) = &self.url {
- page.object_props.set_url_string(url.to_owned()).ok();
+ // TODO: hacky code because we get self.url == Some("")
+ // https://github.com/LemmyNet/lemmy/issues/602
+ let url = self.url.as_ref().filter(|u| !u.is_empty());
+ if let Some(u) = url {
+ oprops.set_url_xsd_any_uri(u.to_owned())?;
+
+ // Embeds
+ let mut page_preview = Page::new();
+ page_preview
+ .object_props
+ .set_url_xsd_any_uri(u.to_owned())?;
+
+ if let Some(embed_title) = &self.embed_title {
+ page_preview
+ .object_props
+ .set_name_xsd_string(embed_title.to_owned())?;
+ }
+
+ if let Some(embed_description) = &self.embed_description {
+ page_preview
+ .object_props
+ .set_summary_xsd_string(embed_description.to_owned())?;
+ }
+
+ if let Some(embed_html) = &self.embed_html {
+ page_preview
+ .object_props
+ .set_content_xsd_string(embed_html.to_owned())?;
+ }
+
+ oprops.set_preview_base_box(page_preview)?;
}
- //page.object_props.set_attributed_to_string
+ if let Some(thumbnail_url) = &self.thumbnail_url {
+ let full_url = format!(
+ "{}://{}/pictshare/{}",
+ get_apub_protocol_string(),
+ Settings::get().hostname,
+ thumbnail_url
+ );
- page
- .object_props
- .set_published_utctime(to_datetime_utc(self.published))
- .ok();
- if let Some(updated) = self.updated {
- page
- .object_props
- .set_updated_utctime(to_datetime_utc(updated))
- .ok();
+ let mut image = Image::new();
+ image.object_props.set_url_xsd_any_uri(full_url)?;
+ let any_image = AnyImage::from_concrete(image)?;
+ oprops.set_image_any_image(any_image)?;
+ }
+
+ if let Some(u) = self.updated {
+ oprops.set_updated(convert_datetime(u))?;
}
- page
+ let ext = PageExtension {
+ comments_enabled: !self.locked,
+ sensitive: self.nsfw,
+ };
+ Ok(Ext1::new(page, ext))
+ }
+
+ fn to_tombstone(&self) -> Result<Tombstone, Error> {
+ create_tombstone(
+ self.deleted,
+ &self.ap_id,
+ self.updated,
+ PageType.to_string(),
+ )
+ }
+}
+
+impl FromApub for PostForm {
+ type ApubType = PageExt;
+
+ /// Parse an ActivityPub page received from another instance into a Lemmy post.
+ fn from_apub(page: &PageExt, conn: &PgConnection) -> Result<PostForm, Error> {
+ let ext = &page.ext_one;
+ let oprops = &page.inner.object_props;
+ let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
+ let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, &conn)?;
+ let community_actor_id = &oprops.get_to_xsd_any_uri().unwrap().to_string();
+ let community = get_or_fetch_and_upsert_remote_community(&community_actor_id, &conn)?;
+
+ let thumbnail_url = match oprops.get_image_any_image() {
+ Some(any_image) => any_image
+ .to_owned()
+ .into_concrete::<Image>()?
+ .object_props
+ .get_url_xsd_any_uri()
+ .map(|u| u.to_string()),
+ None => None,
+ };
+
+ let url = oprops.get_url_xsd_any_uri().map(|u| u.to_string());
+ let (embed_title, embed_description, embed_html) = match oprops.get_preview_base_box() {
+ Some(preview) => {
+ let preview_page = preview.to_owned().into_concrete::<Page>()?;
+ let name = preview_page
+ .object_props
+ .get_name_xsd_string()
+ .map(|n| n.to_string());
+ let summary = preview_page
+ .object_props
+ .get_summary_xsd_string()
+ .map(|s| s.to_string());
+ let content = preview_page
+ .object_props
+ .get_content_xsd_string()
+ .map(|c| c.to_string());
+ (name, summary, content)
+ }
+ None => (None, None, None),
+ };
+
+ Ok(PostForm {
+ name: oprops.get_summary_xsd_string().unwrap().to_string(),
+ url,
+ body: oprops.get_content_xsd_string().map(|c| c.to_string()),
+ creator_id: creator.id,
+ community_id: community.id,
+ removed: None,
+ locked: Some(!ext.comments_enabled),
+ published: oprops
+ .get_published()
+ .map(|u| u.as_ref().to_owned().naive_local()),
+ updated: oprops
+ .get_updated()
+ .map(|u| u.as_ref().to_owned().naive_local()),
+ deleted: None,
+ nsfw: ext.sensitive,
+ stickied: None, // -> put it in "featured" collection of the community
+ embed_title,
+ embed_description,
+ embed_html,
+ thumbnail_url,
+ ap_id: oprops.get_id().unwrap().to_string(),
+ local: false,
+ })
+ }
+}
+
+impl ApubObjectType for Post {
+ /// Send out information about a newly created post, to the followers of the community.
+ fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let page = self.to_apub(conn)?;
+ let community = Community::read(conn, self.community_id)?;
+ let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4());
+
+ let mut create = Create::new();
+ populate_object_props(
+ &mut create.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+ create
+ .create_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(page)?)?;
+
+ send_activity_to_community(
+ creator,
+ conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ create,
+ )?;
+ Ok(())
+ }
+
+ /// Send out information about an edited post, to the followers of the community.
+ fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let page = self.to_apub(conn)?;
+ let community = Community::read(conn, self.community_id)?;
+ let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4());
+
+ let mut update = Update::new();
+ populate_object_props(
+ &mut update.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+ update
+ .update_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(page)?)?;
+
+ send_activity_to_community(
+ creator,
+ conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ update,
+ )?;
+ Ok(())
+ }
+
+ fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let page = self.to_apub(conn)?;
+ let community = Community::read(conn, self.community_id)?;
+ let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut delete = Delete::default();
+
+ populate_object_props(
+ &mut delete.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(page)?)?;
+
+ let community = Community::read(conn, self.community_id)?;
+
+ send_activity_to_community(
+ creator,
+ conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ delete,
+ )?;
+ Ok(())
+ }
+
+ fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let page = self.to_apub(conn)?;
+ let community = Community::read(conn, self.community_id)?;
+ let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut delete = Delete::default();
+
+ populate_object_props(
+ &mut delete.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(page)?)?;
+
+ // TODO
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(
+ &mut undo.object_props,
+ vec![community.get_followers_url()],
+ &undo_id,
+ )?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(delete)?;
+
+ let community = Community::read(conn, self.community_id)?;
+ send_activity_to_community(
+ creator,
+ conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ undo,
+ )?;
+ Ok(())
+ }
+
+ fn send_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let page = self.to_apub(conn)?;
+ let community = Community::read(conn, self.community_id)?;
+ let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut remove = Remove::default();
+
+ populate_object_props(
+ &mut remove.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+
+ remove
+ .remove_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(page)?)?;
+
+ let community = Community::read(conn, self.community_id)?;
+
+ send_activity_to_community(
+ mod_,
+ conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ remove,
+ )?;
+ Ok(())
+ }
+ fn send_undo_remove(&self, mod_: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let page = self.to_apub(conn)?;
+ let community = Community::read(conn, self.community_id)?;
+ let id = format!("{}/remove/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut remove = Remove::default();
+
+ populate_object_props(
+ &mut remove.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+
+ remove
+ .remove_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(page)?)?;
+
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/remove/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(
+ &mut undo.object_props,
+ vec![community.get_followers_url()],
+ &undo_id,
+ )?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(mod_.actor_id.to_owned())?
+ .set_object_base_box(remove)?;
+
+ let community = Community::read(conn, self.community_id)?;
+ send_activity_to_community(
+ mod_,
+ conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ undo,
+ )?;
+ Ok(())
+ }
+}
+
+impl ApubLikeableType for Post {
+ fn send_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let page = self.to_apub(conn)?;
+ let community = Community::read(conn, self.community_id)?;
+ let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4());
+
+ let mut like = Like::new();
+ populate_object_props(
+ &mut like.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+ like
+ .like_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(page)?)?;
+
+ send_activity_to_community(
+ &creator,
+ &conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ like,
+ )?;
+ Ok(())
+ }
+
+ fn send_dislike(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let page = self.to_apub(conn)?;
+ let community = Community::read(conn, self.community_id)?;
+ let id = format!("{}/dislike/{}", self.ap_id, uuid::Uuid::new_v4());
+
+ let mut dislike = Dislike::new();
+ populate_object_props(
+ &mut dislike.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+ dislike
+ .dislike_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(page)?)?;
+
+ send_activity_to_community(
+ &creator,
+ &conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ dislike,
+ )?;
+ Ok(())
+ }
+
+ fn send_undo_like(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let page = self.to_apub(conn)?;
+ let community = Community::read(conn, self.community_id)?;
+ let id = format!("{}/like/{}", self.ap_id, uuid::Uuid::new_v4());
+
+ let mut like = Like::new();
+ populate_object_props(
+ &mut like.object_props,
+ vec![community.get_followers_url()],
+ &id,
+ )?;
+ like
+ .like_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(BaseBox::from_concrete(page)?)?;
+
+ // TODO
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/like/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ populate_object_props(
+ &mut undo.object_props,
+ vec![community.get_followers_url()],
+ &undo_id,
+ )?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(like)?;
+
+ send_activity_to_community(
+ &creator,
+ &conn,
+ &community,
+ vec![community.get_shared_inbox_url()],
+ undo,
+ )?;
+ Ok(())
}
}
--- /dev/null
+use crate::{
+ apub::{
+ activities::send_activity,
+ create_tombstone,
+ fetcher::get_or_fetch_and_upsert_remote_user,
+ ApubObjectType,
+ FromApub,
+ ToApub,
+ },
+ convert_datetime,
+ db::{
+ activity::insert_activity,
+ private_message::{PrivateMessage, PrivateMessageForm},
+ user::User_,
+ Crud,
+ },
+};
+use activitystreams::{
+ activity::{Create, Delete, Undo, Update},
+ context,
+ object::{kind::NoteType, properties::ObjectProperties, Note},
+};
+use activitystreams_new::object::Tombstone;
+use actix_web::Result;
+use diesel::PgConnection;
+use failure::Error;
+
+impl ToApub for PrivateMessage {
+ type Response = Note;
+
+ fn to_apub(&self, conn: &PgConnection) -> Result<Note, Error> {
+ let mut private_message = Note::default();
+ let oprops: &mut ObjectProperties = private_message.as_mut();
+ let creator = User_::read(&conn, self.creator_id)?;
+ let recipient = User_::read(&conn, self.recipient_id)?;
+
+ oprops
+ .set_context_xsd_any_uri(context())?
+ .set_id(self.ap_id.to_owned())?
+ .set_published(convert_datetime(self.published))?
+ .set_content_xsd_string(self.content.to_owned())?
+ .set_to_xsd_any_uri(recipient.actor_id)?
+ .set_attributed_to_xsd_any_uri(creator.actor_id)?;
+
+ if let Some(u) = self.updated {
+ oprops.set_updated(convert_datetime(u))?;
+ }
+
+ Ok(private_message)
+ }
+
+ fn to_tombstone(&self) -> Result<Tombstone, Error> {
+ create_tombstone(
+ self.deleted,
+ &self.ap_id,
+ self.updated,
+ NoteType.to_string(),
+ )
+ }
+}
+
+impl FromApub for PrivateMessageForm {
+ type ApubType = Note;
+
+ /// Parse an ActivityPub note received from another instance into a Lemmy Private message
+ fn from_apub(note: &Note, conn: &PgConnection) -> Result<PrivateMessageForm, Error> {
+ let oprops = ¬e.object_props;
+ let creator_actor_id = &oprops.get_attributed_to_xsd_any_uri().unwrap().to_string();
+ let creator = get_or_fetch_and_upsert_remote_user(&creator_actor_id, &conn)?;
+ let recipient_actor_id = &oprops.get_to_xsd_any_uri().unwrap().to_string();
+ let recipient = get_or_fetch_and_upsert_remote_user(&recipient_actor_id, &conn)?;
+
+ Ok(PrivateMessageForm {
+ creator_id: creator.id,
+ recipient_id: recipient.id,
+ content: oprops
+ .get_content_xsd_string()
+ .map(|c| c.to_string())
+ .unwrap(),
+ published: oprops
+ .get_published()
+ .map(|u| u.as_ref().to_owned().naive_local()),
+ updated: oprops
+ .get_updated()
+ .map(|u| u.as_ref().to_owned().naive_local()),
+ deleted: None,
+ read: None,
+ ap_id: oprops.get_id().unwrap().to_string(),
+ local: false,
+ })
+ }
+}
+
+impl ApubObjectType for PrivateMessage {
+ /// Send out information about a newly created private message
+ fn send_create(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(conn)?;
+ let id = format!("{}/create/{}", self.ap_id, uuid::Uuid::new_v4());
+ let recipient = User_::read(&conn, self.recipient_id)?;
+
+ let mut create = Create::new();
+ create
+ .object_props
+ .set_context_xsd_any_uri(context())?
+ .set_id(id)?;
+ let to = format!("{}/inbox", recipient.actor_id);
+
+ create
+ .create_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ insert_activity(&conn, creator.id, &create, true)?;
+
+ send_activity(&create, creator, vec![to])?;
+ Ok(())
+ }
+
+ /// Send out information about an edited post, to the followers of the community.
+ fn send_update(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(conn)?;
+ let id = format!("{}/update/{}", self.ap_id, uuid::Uuid::new_v4());
+ let recipient = User_::read(&conn, self.recipient_id)?;
+
+ let mut update = Update::new();
+ update
+ .object_props
+ .set_context_xsd_any_uri(context())?
+ .set_id(id)?;
+ let to = format!("{}/inbox", recipient.actor_id);
+
+ update
+ .update_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ insert_activity(&conn, creator.id, &update, true)?;
+
+ send_activity(&update, creator, vec![to])?;
+ Ok(())
+ }
+
+ fn send_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(conn)?;
+ let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let recipient = User_::read(&conn, self.recipient_id)?;
+
+ let mut delete = Delete::new();
+ delete
+ .object_props
+ .set_context_xsd_any_uri(context())?
+ .set_id(id)?;
+ let to = format!("{}/inbox", recipient.actor_id);
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ insert_activity(&conn, creator.id, &delete, true)?;
+
+ send_activity(&delete, creator, vec![to])?;
+ Ok(())
+ }
+
+ fn send_undo_delete(&self, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+ let note = self.to_apub(conn)?;
+ let id = format!("{}/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let recipient = User_::read(&conn, self.recipient_id)?;
+
+ let mut delete = Delete::new();
+ delete
+ .object_props
+ .set_context_xsd_any_uri(context())?
+ .set_id(id)?;
+ let to = format!("{}/inbox", recipient.actor_id);
+
+ delete
+ .delete_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(note)?;
+
+ // TODO
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/delete/{}", self.ap_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::default();
+
+ undo
+ .object_props
+ .set_context_xsd_any_uri(context())?
+ .set_id(undo_id)?;
+
+ undo
+ .undo_props
+ .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+ .set_object_base_box(delete)?;
+
+ insert_activity(&conn, creator.id, &undo, true)?;
+
+ send_activity(&undo, creator, vec![to])?;
+ Ok(())
+ }
+
+ fn send_remove(&self, _mod_: &User_, _conn: &PgConnection) -> Result<(), Error> {
+ unimplemented!()
+ }
+
+ fn send_undo_remove(&self, _mod_: &User_, _conn: &PgConnection) -> Result<(), Error> {
+ unimplemented!()
+ }
+}
--- /dev/null
+use crate::{
+ api::{
+ comment::{send_local_notifs, CommentResponse},
+ community::CommunityResponse,
+ post::PostResponse,
+ },
+ apub::{
+ extensions::signatures::verify,
+ fetcher::{
+ get_or_fetch_and_insert_remote_comment,
+ get_or_fetch_and_insert_remote_post,
+ get_or_fetch_and_upsert_remote_community,
+ get_or_fetch_and_upsert_remote_user,
+ },
+ FromApub,
+ GroupExt,
+ PageExt,
+ },
+ db::{
+ activity::insert_activity,
+ comment::{Comment, CommentForm, CommentLike, CommentLikeForm},
+ comment_view::CommentView,
+ community::{Community, CommunityForm},
+ community_view::CommunityView,
+ post::{Post, PostForm, PostLike, PostLikeForm},
+ post_view::PostView,
+ Crud,
+ Likeable,
+ },
+ naive_now,
+ routes::{ChatServerParam, DbPoolParam},
+ scrape_text_for_mentions,
+ websocket::{
+ server::{SendComment, SendCommunityRoomMessage, SendPost},
+ UserOperation,
+ },
+};
+use activitystreams::{
+ activity::{Announce, Create, Delete, Dislike, Like, Remove, Undo, Update},
+ object::Note,
+ Activity,
+ Base,
+ BaseBox,
+};
+use actix_web::{web, HttpRequest, HttpResponse, Result};
+use diesel::PgConnection;
+use failure::{Error, _core::fmt::Debug};
+use log::debug;
+use serde::{Deserialize, Serialize};
+
+#[serde(untagged)]
+#[derive(Serialize, Deserialize, Debug)]
+pub enum SharedAcceptedObjects {
+ Create(Box<Create>),
+ Update(Box<Update>),
+ Like(Box<Like>),
+ Dislike(Box<Dislike>),
+ Delete(Box<Delete>),
+ Undo(Box<Undo>),
+ Remove(Box<Remove>),
+ Announce(Box<Announce>),
+}
+
+impl SharedAcceptedObjects {
+ fn object(&self) -> Option<&BaseBox> {
+ match self {
+ SharedAcceptedObjects::Create(c) => c.create_props.get_object_base_box(),
+ SharedAcceptedObjects::Update(u) => u.update_props.get_object_base_box(),
+ SharedAcceptedObjects::Like(l) => l.like_props.get_object_base_box(),
+ SharedAcceptedObjects::Dislike(d) => d.dislike_props.get_object_base_box(),
+ SharedAcceptedObjects::Delete(d) => d.delete_props.get_object_base_box(),
+ SharedAcceptedObjects::Undo(d) => d.undo_props.get_object_base_box(),
+ SharedAcceptedObjects::Remove(r) => r.remove_props.get_object_base_box(),
+ SharedAcceptedObjects::Announce(a) => a.announce_props.get_object_base_box(),
+ }
+ }
+ fn sender(&self) -> String {
+ let uri = match self {
+ SharedAcceptedObjects::Create(c) => c.create_props.get_actor_xsd_any_uri(),
+ SharedAcceptedObjects::Update(u) => u.update_props.get_actor_xsd_any_uri(),
+ SharedAcceptedObjects::Like(l) => l.like_props.get_actor_xsd_any_uri(),
+ SharedAcceptedObjects::Dislike(d) => d.dislike_props.get_actor_xsd_any_uri(),
+ SharedAcceptedObjects::Delete(d) => d.delete_props.get_actor_xsd_any_uri(),
+ SharedAcceptedObjects::Undo(d) => d.undo_props.get_actor_xsd_any_uri(),
+ SharedAcceptedObjects::Remove(r) => r.remove_props.get_actor_xsd_any_uri(),
+ SharedAcceptedObjects::Announce(a) => a.announce_props.get_actor_xsd_any_uri(),
+ };
+ uri.unwrap().clone().to_string()
+ }
+ fn cc(&self) -> String {
+ // TODO: there is probably an easier way to do this
+ let oprops = match self {
+ SharedAcceptedObjects::Create(c) => &c.object_props,
+ SharedAcceptedObjects::Update(u) => &u.object_props,
+ SharedAcceptedObjects::Like(l) => &l.object_props,
+ SharedAcceptedObjects::Dislike(d) => &d.object_props,
+ SharedAcceptedObjects::Delete(d) => &d.object_props,
+ SharedAcceptedObjects::Undo(d) => &d.object_props,
+ SharedAcceptedObjects::Remove(r) => &r.object_props,
+ SharedAcceptedObjects::Announce(a) => &a.object_props,
+ };
+ oprops
+ .get_many_cc_xsd_any_uris()
+ .unwrap()
+ .next()
+ .unwrap()
+ .to_string()
+ }
+}
+
+/// Handler for all incoming activities to user inboxes.
+pub async fn shared_inbox(
+ request: HttpRequest,
+ input: web::Json<SharedAcceptedObjects>,
+ db: DbPoolParam,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let activity = input.into_inner();
+ let conn = &db.get().unwrap();
+
+ let json = serde_json::to_string(&activity)?;
+ debug!("Shared inbox received activity: {}", json);
+
+ let object = activity.object().cloned().unwrap();
+ let sender = &activity.sender();
+ let cc = &activity.cc();
+ // TODO: this is hacky, we should probably send the community id directly somehow
+ let to = cc.replace("/followers", "");
+
+ // TODO: this is ugly
+ match get_or_fetch_and_upsert_remote_user(&sender.to_string(), &conn) {
+ Ok(u) => verify(&request, &u),
+ Err(_) => {
+ let c = get_or_fetch_and_upsert_remote_community(&sender.to_string(), &conn)?;
+ verify(&request, &c)
+ }
+ }?;
+
+ match (activity, object.kind()) {
+ (SharedAcceptedObjects::Create(c), Some("Page")) => {
+ receive_create_post(&c, &conn, chat_server)?;
+ announce_activity_if_valid::<Create>(*c, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Update(u), Some("Page")) => {
+ receive_update_post(&u, &conn, chat_server)?;
+ announce_activity_if_valid::<Update>(*u, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Like(l), Some("Page")) => {
+ receive_like_post(&l, &conn, chat_server)?;
+ announce_activity_if_valid::<Like>(*l, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Dislike(d), Some("Page")) => {
+ receive_dislike_post(&d, &conn, chat_server)?;
+ announce_activity_if_valid::<Dislike>(*d, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Delete(d), Some("Page")) => {
+ receive_delete_post(&d, &conn, chat_server)?;
+ announce_activity_if_valid::<Delete>(*d, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Remove(r), Some("Page")) => {
+ receive_remove_post(&r, &conn, chat_server)?;
+ announce_activity_if_valid::<Remove>(*r, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Create(c), Some("Note")) => {
+ receive_create_comment(&c, &conn, chat_server)?;
+ announce_activity_if_valid::<Create>(*c, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Update(u), Some("Note")) => {
+ receive_update_comment(&u, &conn, chat_server)?;
+ announce_activity_if_valid::<Update>(*u, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Like(l), Some("Note")) => {
+ receive_like_comment(&l, &conn, chat_server)?;
+ announce_activity_if_valid::<Like>(*l, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Dislike(d), Some("Note")) => {
+ receive_dislike_comment(&d, &conn, chat_server)?;
+ announce_activity_if_valid::<Dislike>(*d, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Delete(d), Some("Note")) => {
+ receive_delete_comment(&d, &conn, chat_server)?;
+ announce_activity_if_valid::<Delete>(*d, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Remove(r), Some("Note")) => {
+ receive_remove_comment(&r, &conn, chat_server)?;
+ announce_activity_if_valid::<Remove>(*r, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Delete(d), Some("Group")) => {
+ receive_delete_community(&d, &conn, chat_server)?;
+ announce_activity_if_valid::<Delete>(*d, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Remove(r), Some("Group")) => {
+ receive_remove_community(&r, &conn, chat_server)?;
+ announce_activity_if_valid::<Remove>(*r, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Undo(u), Some("Delete")) => {
+ receive_undo_delete(&u, &conn, chat_server)?;
+ announce_activity_if_valid::<Undo>(*u, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Undo(u), Some("Remove")) => {
+ receive_undo_remove(&u, &conn, chat_server)?;
+ announce_activity_if_valid::<Undo>(*u, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Undo(u), Some("Like")) => {
+ receive_undo_like(&u, &conn, chat_server)?;
+ announce_activity_if_valid::<Undo>(*u, &to, sender, conn)
+ }
+ (SharedAcceptedObjects::Announce(a), _) => receive_announce(a, &conn, chat_server),
+ (a, _) => receive_unhandled_activity(a),
+ }
+}
+
+// TODO: should pass in sender as ActorType, but thats a bit tricky in shared_inbox()
+fn announce_activity_if_valid<A>(
+ activity: A,
+ community_uri: &str,
+ sender: &str,
+ conn: &PgConnection,
+) -> Result<HttpResponse, Error>
+where
+ A: Activity + Base + Serialize + Debug,
+{
+ let community = Community::read_from_actor_id(conn, &community_uri)?;
+ if community.local {
+ let sending_user = get_or_fetch_and_upsert_remote_user(&sender.to_string(), &conn)?;
+ Community::do_announce(activity, &community, &sending_user, conn)
+ } else {
+ Ok(HttpResponse::NotFound().finish())
+ }
+}
+
+fn receive_announce(
+ announce: Box<Announce>,
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let object = announce
+ .announce_props
+ .get_object_base_box()
+ .unwrap()
+ .to_owned();
+ // TODO: too much copy paste
+ match object.kind() {
+ Some("Create") => {
+ let create = object.into_concrete::<Create>()?;
+ let inner_object = create.create_props.get_object_base_box().unwrap();
+ match inner_object.kind() {
+ Some("Page") => receive_create_post(&create, &conn, chat_server),
+ Some("Note") => receive_create_comment(&create, &conn, chat_server),
+ _ => receive_unhandled_activity(announce),
+ }
+ }
+ Some("Update") => {
+ let update = object.into_concrete::<Update>()?;
+ let inner_object = update.update_props.get_object_base_box().unwrap();
+ match inner_object.kind() {
+ Some("Page") => receive_update_post(&update, &conn, chat_server),
+ Some("Note") => receive_update_comment(&update, &conn, chat_server),
+ _ => receive_unhandled_activity(announce),
+ }
+ }
+ Some("Like") => {
+ let like = object.into_concrete::<Like>()?;
+ let inner_object = like.like_props.get_object_base_box().unwrap();
+ match inner_object.kind() {
+ Some("Page") => receive_like_post(&like, &conn, chat_server),
+ Some("Note") => receive_like_comment(&like, &conn, chat_server),
+ _ => receive_unhandled_activity(announce),
+ }
+ }
+ Some("Dislike") => {
+ let dislike = object.into_concrete::<Dislike>()?;
+ let inner_object = dislike.dislike_props.get_object_base_box().unwrap();
+ match inner_object.kind() {
+ Some("Page") => receive_dislike_post(&dislike, &conn, chat_server),
+ Some("Note") => receive_dislike_comment(&dislike, &conn, chat_server),
+ _ => receive_unhandled_activity(announce),
+ }
+ }
+ Some("Delete") => {
+ let delete = object.into_concrete::<Delete>()?;
+ let inner_object = delete.delete_props.get_object_base_box().unwrap();
+ match inner_object.kind() {
+ Some("Page") => receive_delete_post(&delete, &conn, chat_server),
+ Some("Note") => receive_delete_comment(&delete, &conn, chat_server),
+ _ => receive_unhandled_activity(announce),
+ }
+ }
+ Some("Remove") => {
+ let remove = object.into_concrete::<Remove>()?;
+ let inner_object = remove.remove_props.get_object_base_box().unwrap();
+ match inner_object.kind() {
+ Some("Page") => receive_remove_post(&remove, &conn, chat_server),
+ Some("Note") => receive_remove_comment(&remove, &conn, chat_server),
+ _ => receive_unhandled_activity(announce),
+ }
+ }
+ Some("Undo") => {
+ let undo = object.into_concrete::<Undo>()?;
+ let inner_object = undo.undo_props.get_object_base_box().unwrap();
+ match inner_object.kind() {
+ Some("Delete") => receive_undo_delete(&undo, &conn, chat_server),
+ Some("Remove") => receive_undo_remove(&undo, &conn, chat_server),
+ Some("Like") => receive_undo_like(&undo, &conn, chat_server),
+ _ => receive_unhandled_activity(announce),
+ }
+ }
+ _ => receive_unhandled_activity(announce),
+ }
+}
+
+fn receive_unhandled_activity<A>(activity: A) -> Result<HttpResponse, Error>
+where
+ A: Debug,
+{
+ debug!("received unhandled activity type: {:?}", activity);
+ Ok(HttpResponse::NotImplemented().finish())
+}
+
+fn receive_create_post(
+ create: &Create,
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let page = create
+ .create_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<PageExt>()?;
+
+ let user_uri = create
+ .create_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &create, false)?;
+
+ let post = PostForm::from_apub(&page, &conn)?;
+ let inserted_post = Post::create(conn, &post)?;
+
+ // Refetch the view
+ let post_view = PostView::read(&conn, inserted_post.id, None)?;
+
+ let res = PostResponse { post: post_view };
+
+ chat_server.do_send(SendPost {
+ op: UserOperation::CreatePost,
+ post: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_create_comment(
+ create: &Create,
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let note = create
+ .create_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let user_uri = create
+ .create_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &create, false)?;
+
+ let comment = CommentForm::from_apub(¬e, &conn)?;
+ let inserted_comment = Comment::create(conn, &comment)?;
+ let post = Post::read(&conn, inserted_comment.post_id)?;
+
+ // Note:
+ // Although mentions could be gotten from the post tags (they are included there), or the ccs,
+ // Its much easier to scrape them from the comment body, since the API has to do that
+ // anyway.
+ let mentions = scrape_text_for_mentions(&inserted_comment.content);
+ let recipient_ids = send_local_notifs(&conn, &mentions, &inserted_comment, &user, &post);
+
+ // Refetch the view
+ let comment_view = CommentView::read(&conn, inserted_comment.id, None)?;
+
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ };
+
+ chat_server.do_send(SendComment {
+ op: UserOperation::CreateComment,
+ comment: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_update_post(
+ update: &Update,
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let page = update
+ .update_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<PageExt>()?;
+
+ let user_uri = update
+ .update_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &update, false)?;
+
+ let post = PostForm::from_apub(&page, conn)?;
+ let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, &conn)?.id;
+ Post::update(conn, post_id, &post)?;
+
+ // Refetch the view
+ let post_view = PostView::read(&conn, post_id, None)?;
+
+ let res = PostResponse { post: post_view };
+
+ chat_server.do_send(SendPost {
+ op: UserOperation::EditPost,
+ post: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_like_post(
+ like: &Like,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let page = like
+ .like_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<PageExt>()?;
+
+ let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &like, false)?;
+
+ let post = PostForm::from_apub(&page, conn)?;
+ let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, &conn)?.id;
+
+ let like_form = PostLikeForm {
+ post_id,
+ user_id: user.id,
+ score: 1,
+ };
+ PostLike::remove(&conn, &like_form)?;
+ PostLike::like(&conn, &like_form)?;
+
+ // Refetch the view
+ let post_view = PostView::read(&conn, post_id, None)?;
+
+ let res = PostResponse { post: post_view };
+
+ chat_server.do_send(SendPost {
+ op: UserOperation::CreatePostLike,
+ post: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_dislike_post(
+ dislike: &Dislike,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let page = dislike
+ .dislike_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<PageExt>()?;
+
+ let user_uri = dislike
+ .dislike_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &dislike, false)?;
+
+ let post = PostForm::from_apub(&page, conn)?;
+ let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, &conn)?.id;
+
+ let like_form = PostLikeForm {
+ post_id,
+ user_id: user.id,
+ score: -1,
+ };
+ PostLike::remove(&conn, &like_form)?;
+ PostLike::like(&conn, &like_form)?;
+
+ // Refetch the view
+ let post_view = PostView::read(&conn, post_id, None)?;
+
+ let res = PostResponse { post: post_view };
+
+ chat_server.do_send(SendPost {
+ op: UserOperation::CreatePostLike,
+ post: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_update_comment(
+ update: &Update,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let note = update
+ .update_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let user_uri = update
+ .update_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &update, false)?;
+
+ let comment = CommentForm::from_apub(¬e, &conn)?;
+ let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, &conn)?.id;
+ let updated_comment = Comment::update(conn, comment_id, &comment)?;
+ let post = Post::read(&conn, updated_comment.post_id)?;
+
+ let mentions = scrape_text_for_mentions(&updated_comment.content);
+ let recipient_ids = send_local_notifs(&conn, &mentions, &updated_comment, &user, &post);
+
+ // Refetch the view
+ let comment_view = CommentView::read(&conn, comment_id, None)?;
+
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ };
+
+ chat_server.do_send(SendComment {
+ op: UserOperation::EditComment,
+ comment: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_like_comment(
+ like: &Like,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let note = like
+ .like_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &like, false)?;
+
+ let comment = CommentForm::from_apub(¬e, &conn)?;
+ let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, &conn)?.id;
+ let like_form = CommentLikeForm {
+ comment_id,
+ post_id: comment.post_id,
+ user_id: user.id,
+ score: 1,
+ };
+ CommentLike::remove(&conn, &like_form)?;
+ CommentLike::like(&conn, &like_form)?;
+
+ // Refetch the view
+ let comment_view = CommentView::read(&conn, comment_id, None)?;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ };
+
+ chat_server.do_send(SendComment {
+ op: UserOperation::CreateCommentLike,
+ comment: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_dislike_comment(
+ dislike: &Dislike,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let note = dislike
+ .dislike_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let user_uri = dislike
+ .dislike_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &dislike, false)?;
+
+ let comment = CommentForm::from_apub(¬e, &conn)?;
+ let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, &conn)?.id;
+ let like_form = CommentLikeForm {
+ comment_id,
+ post_id: comment.post_id,
+ user_id: user.id,
+ score: -1,
+ };
+ CommentLike::remove(&conn, &like_form)?;
+ CommentLike::like(&conn, &like_form)?;
+
+ // Refetch the view
+ let comment_view = CommentView::read(&conn, comment_id, None)?;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ };
+
+ chat_server.do_send(SendComment {
+ op: UserOperation::CreateCommentLike,
+ comment: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_delete_community(
+ delete: &Delete,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let user_uri = delete
+ .delete_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let group = delete
+ .delete_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<GroupExt>()?;
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &delete, false)?;
+
+ let community_actor_id = CommunityForm::from_apub(&group, &conn)?.actor_id;
+ let community = Community::read_from_actor_id(conn, &community_actor_id)?;
+
+ let community_form = CommunityForm {
+ name: community.name.to_owned(),
+ title: community.title.to_owned(),
+ description: community.description.to_owned(),
+ category_id: community.category_id, // Note: need to keep this due to foreign key constraint
+ creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
+ removed: None,
+ published: None,
+ updated: Some(naive_now()),
+ deleted: Some(true),
+ nsfw: community.nsfw,
+ actor_id: community.actor_id,
+ local: community.local,
+ private_key: community.private_key,
+ public_key: community.public_key,
+ last_refreshed_at: None,
+ };
+
+ Community::update(&conn, community.id, &community_form)?;
+
+ let res = CommunityResponse {
+ community: CommunityView::read(&conn, community.id, None)?,
+ };
+
+ chat_server.do_send(SendCommunityRoomMessage {
+ op: UserOperation::EditCommunity,
+ response: res,
+ community_id: community.id,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_remove_community(
+ remove: &Remove,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let mod_uri = remove
+ .remove_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let group = remove
+ .remove_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<GroupExt>()?;
+
+ let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, &conn)?;
+
+ insert_activity(&conn, mod_.id, &remove, false)?;
+
+ let community_actor_id = CommunityForm::from_apub(&group, &conn)?.actor_id;
+ let community = Community::read_from_actor_id(conn, &community_actor_id)?;
+
+ let community_form = CommunityForm {
+ name: community.name.to_owned(),
+ title: community.title.to_owned(),
+ description: community.description.to_owned(),
+ category_id: community.category_id, // Note: need to keep this due to foreign key constraint
+ creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
+ removed: Some(true),
+ published: None,
+ updated: Some(naive_now()),
+ deleted: None,
+ nsfw: community.nsfw,
+ actor_id: community.actor_id,
+ local: community.local,
+ private_key: community.private_key,
+ public_key: community.public_key,
+ last_refreshed_at: None,
+ };
+
+ Community::update(&conn, community.id, &community_form)?;
+
+ let res = CommunityResponse {
+ community: CommunityView::read(&conn, community.id, None)?,
+ };
+
+ chat_server.do_send(SendCommunityRoomMessage {
+ op: UserOperation::EditCommunity,
+ response: res,
+ community_id: community.id,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_delete_post(
+ delete: &Delete,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let user_uri = delete
+ .delete_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let page = delete
+ .delete_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<PageExt>()?;
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &delete, false)?;
+
+ let post_ap_id = PostForm::from_apub(&page, conn)?.ap_id;
+ let post = get_or_fetch_and_insert_remote_post(&post_ap_id, &conn)?;
+
+ let post_form = PostForm {
+ name: post.name.to_owned(),
+ url: post.url.to_owned(),
+ body: post.body.to_owned(),
+ creator_id: post.creator_id.to_owned(),
+ community_id: post.community_id,
+ removed: None,
+ deleted: Some(true),
+ nsfw: post.nsfw,
+ locked: None,
+ stickied: None,
+ updated: Some(naive_now()),
+ embed_title: post.embed_title,
+ embed_description: post.embed_description,
+ embed_html: post.embed_html,
+ thumbnail_url: post.thumbnail_url,
+ ap_id: post.ap_id,
+ local: post.local,
+ published: None,
+ };
+ Post::update(&conn, post.id, &post_form)?;
+
+ // Refetch the view
+ let post_view = PostView::read(&conn, post.id, None)?;
+
+ let res = PostResponse { post: post_view };
+
+ chat_server.do_send(SendPost {
+ op: UserOperation::EditPost,
+ post: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_remove_post(
+ remove: &Remove,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let mod_uri = remove
+ .remove_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let page = remove
+ .remove_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<PageExt>()?;
+
+ let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, &conn)?;
+
+ insert_activity(&conn, mod_.id, &remove, false)?;
+
+ let post_ap_id = PostForm::from_apub(&page, conn)?.ap_id;
+ let post = get_or_fetch_and_insert_remote_post(&post_ap_id, &conn)?;
+
+ let post_form = PostForm {
+ name: post.name.to_owned(),
+ url: post.url.to_owned(),
+ body: post.body.to_owned(),
+ creator_id: post.creator_id.to_owned(),
+ community_id: post.community_id,
+ removed: Some(true),
+ deleted: None,
+ nsfw: post.nsfw,
+ locked: None,
+ stickied: None,
+ updated: Some(naive_now()),
+ embed_title: post.embed_title,
+ embed_description: post.embed_description,
+ embed_html: post.embed_html,
+ thumbnail_url: post.thumbnail_url,
+ ap_id: post.ap_id,
+ local: post.local,
+ published: None,
+ };
+ Post::update(&conn, post.id, &post_form)?;
+
+ // Refetch the view
+ let post_view = PostView::read(&conn, post.id, None)?;
+
+ let res = PostResponse { post: post_view };
+
+ chat_server.do_send(SendPost {
+ op: UserOperation::EditPost,
+ post: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_delete_comment(
+ delete: &Delete,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let user_uri = delete
+ .delete_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let note = delete
+ .delete_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &delete, false)?;
+
+ let comment_ap_id = CommentForm::from_apub(¬e, &conn)?.ap_id;
+ let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, &conn)?;
+ let comment_form = CommentForm {
+ content: comment.content.to_owned(),
+ parent_id: comment.parent_id,
+ post_id: comment.post_id,
+ creator_id: comment.creator_id,
+ removed: None,
+ deleted: Some(true),
+ read: None,
+ published: None,
+ updated: Some(naive_now()),
+ ap_id: comment.ap_id,
+ local: comment.local,
+ };
+ Comment::update(&conn, comment.id, &comment_form)?;
+
+ // Refetch the view
+ let comment_view = CommentView::read(&conn, comment.id, None)?;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ };
+
+ chat_server.do_send(SendComment {
+ op: UserOperation::EditComment,
+ comment: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_remove_comment(
+ remove: &Remove,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let mod_uri = remove
+ .remove_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let note = remove
+ .remove_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, &conn)?;
+
+ insert_activity(&conn, mod_.id, &remove, false)?;
+
+ let comment_ap_id = CommentForm::from_apub(¬e, &conn)?.ap_id;
+ let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, &conn)?;
+ let comment_form = CommentForm {
+ content: comment.content.to_owned(),
+ parent_id: comment.parent_id,
+ post_id: comment.post_id,
+ creator_id: comment.creator_id,
+ removed: Some(true),
+ deleted: None,
+ read: None,
+ published: None,
+ updated: Some(naive_now()),
+ ap_id: comment.ap_id,
+ local: comment.local,
+ };
+ Comment::update(&conn, comment.id, &comment_form)?;
+
+ // Refetch the view
+ let comment_view = CommentView::read(&conn, comment.id, None)?;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ };
+
+ chat_server.do_send(SendComment {
+ op: UserOperation::EditComment,
+ comment: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_undo_delete(
+ undo: &Undo,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let delete = undo
+ .undo_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Delete>()?;
+
+ let type_ = delete
+ .delete_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .kind()
+ .unwrap();
+
+ match type_ {
+ "Note" => receive_undo_delete_comment(&delete, &conn, chat_server),
+ "Page" => receive_undo_delete_post(&delete, &conn, chat_server),
+ "Group" => receive_undo_delete_community(&delete, &conn, chat_server),
+ d => Err(format_err!("Undo Delete type {} not supported", d)),
+ }
+}
+
+fn receive_undo_remove(
+ undo: &Undo,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let remove = undo
+ .undo_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Remove>()?;
+
+ let type_ = remove
+ .remove_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .kind()
+ .unwrap();
+
+ match type_ {
+ "Note" => receive_undo_remove_comment(&remove, &conn, chat_server),
+ "Page" => receive_undo_remove_post(&remove, &conn, chat_server),
+ "Group" => receive_undo_remove_community(&remove, &conn, chat_server),
+ d => Err(format_err!("Undo Delete type {} not supported", d)),
+ }
+}
+
+fn receive_undo_delete_comment(
+ delete: &Delete,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let user_uri = delete
+ .delete_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let note = delete
+ .delete_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &delete, false)?;
+
+ let comment_ap_id = CommentForm::from_apub(¬e, &conn)?.ap_id;
+ let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, &conn)?;
+ let comment_form = CommentForm {
+ content: comment.content.to_owned(),
+ parent_id: comment.parent_id,
+ post_id: comment.post_id,
+ creator_id: comment.creator_id,
+ removed: None,
+ deleted: Some(false),
+ read: None,
+ published: None,
+ updated: Some(naive_now()),
+ ap_id: comment.ap_id,
+ local: comment.local,
+ };
+ Comment::update(&conn, comment.id, &comment_form)?;
+
+ // Refetch the view
+ let comment_view = CommentView::read(&conn, comment.id, None)?;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ };
+
+ chat_server.do_send(SendComment {
+ op: UserOperation::EditComment,
+ comment: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_undo_remove_comment(
+ remove: &Remove,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let mod_uri = remove
+ .remove_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let note = remove
+ .remove_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, &conn)?;
+
+ insert_activity(&conn, mod_.id, &remove, false)?;
+
+ let comment_ap_id = CommentForm::from_apub(¬e, &conn)?.ap_id;
+ let comment = get_or_fetch_and_insert_remote_comment(&comment_ap_id, &conn)?;
+ let comment_form = CommentForm {
+ content: comment.content.to_owned(),
+ parent_id: comment.parent_id,
+ post_id: comment.post_id,
+ creator_id: comment.creator_id,
+ removed: Some(false),
+ deleted: None,
+ read: None,
+ published: None,
+ updated: Some(naive_now()),
+ ap_id: comment.ap_id,
+ local: comment.local,
+ };
+ Comment::update(&conn, comment.id, &comment_form)?;
+
+ // Refetch the view
+ let comment_view = CommentView::read(&conn, comment.id, None)?;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ };
+
+ chat_server.do_send(SendComment {
+ op: UserOperation::EditComment,
+ comment: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_undo_delete_post(
+ delete: &Delete,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let user_uri = delete
+ .delete_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let page = delete
+ .delete_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<PageExt>()?;
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &delete, false)?;
+
+ let post_ap_id = PostForm::from_apub(&page, conn)?.ap_id;
+ let post = get_or_fetch_and_insert_remote_post(&post_ap_id, &conn)?;
+
+ let post_form = PostForm {
+ name: post.name.to_owned(),
+ url: post.url.to_owned(),
+ body: post.body.to_owned(),
+ creator_id: post.creator_id.to_owned(),
+ community_id: post.community_id,
+ removed: None,
+ deleted: Some(false),
+ nsfw: post.nsfw,
+ locked: None,
+ stickied: None,
+ updated: Some(naive_now()),
+ embed_title: post.embed_title,
+ embed_description: post.embed_description,
+ embed_html: post.embed_html,
+ thumbnail_url: post.thumbnail_url,
+ ap_id: post.ap_id,
+ local: post.local,
+ published: None,
+ };
+ Post::update(&conn, post.id, &post_form)?;
+
+ // Refetch the view
+ let post_view = PostView::read(&conn, post.id, None)?;
+
+ let res = PostResponse { post: post_view };
+
+ chat_server.do_send(SendPost {
+ op: UserOperation::EditPost,
+ post: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_undo_remove_post(
+ remove: &Remove,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let mod_uri = remove
+ .remove_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let page = remove
+ .remove_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<PageExt>()?;
+
+ let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, &conn)?;
+
+ insert_activity(&conn, mod_.id, &remove, false)?;
+
+ let post_ap_id = PostForm::from_apub(&page, conn)?.ap_id;
+ let post = get_or_fetch_and_insert_remote_post(&post_ap_id, &conn)?;
+
+ let post_form = PostForm {
+ name: post.name.to_owned(),
+ url: post.url.to_owned(),
+ body: post.body.to_owned(),
+ creator_id: post.creator_id.to_owned(),
+ community_id: post.community_id,
+ removed: Some(false),
+ deleted: None,
+ nsfw: post.nsfw,
+ locked: None,
+ stickied: None,
+ updated: Some(naive_now()),
+ embed_title: post.embed_title,
+ embed_description: post.embed_description,
+ embed_html: post.embed_html,
+ thumbnail_url: post.thumbnail_url,
+ ap_id: post.ap_id,
+ local: post.local,
+ published: None,
+ };
+ Post::update(&conn, post.id, &post_form)?;
+
+ // Refetch the view
+ let post_view = PostView::read(&conn, post.id, None)?;
+
+ let res = PostResponse { post: post_view };
+
+ chat_server.do_send(SendPost {
+ op: UserOperation::EditPost,
+ post: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_undo_delete_community(
+ delete: &Delete,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let user_uri = delete
+ .delete_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let group = delete
+ .delete_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<GroupExt>()?;
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &delete, false)?;
+
+ let community_actor_id = CommunityForm::from_apub(&group, &conn)?.actor_id;
+ let community = Community::read_from_actor_id(conn, &community_actor_id)?;
+
+ let community_form = CommunityForm {
+ name: community.name.to_owned(),
+ title: community.title.to_owned(),
+ description: community.description.to_owned(),
+ category_id: community.category_id, // Note: need to keep this due to foreign key constraint
+ creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
+ removed: None,
+ published: None,
+ updated: Some(naive_now()),
+ deleted: Some(false),
+ nsfw: community.nsfw,
+ actor_id: community.actor_id,
+ local: community.local,
+ private_key: community.private_key,
+ public_key: community.public_key,
+ last_refreshed_at: None,
+ };
+
+ Community::update(&conn, community.id, &community_form)?;
+
+ let res = CommunityResponse {
+ community: CommunityView::read(&conn, community.id, None)?,
+ };
+
+ chat_server.do_send(SendCommunityRoomMessage {
+ op: UserOperation::EditCommunity,
+ response: res,
+ community_id: community.id,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_undo_remove_community(
+ remove: &Remove,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let mod_uri = remove
+ .remove_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let group = remove
+ .remove_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<GroupExt>()?;
+
+ let mod_ = get_or_fetch_and_upsert_remote_user(&mod_uri, &conn)?;
+
+ insert_activity(&conn, mod_.id, &remove, false)?;
+
+ let community_actor_id = CommunityForm::from_apub(&group, &conn)?.actor_id;
+ let community = Community::read_from_actor_id(conn, &community_actor_id)?;
+
+ let community_form = CommunityForm {
+ name: community.name.to_owned(),
+ title: community.title.to_owned(),
+ description: community.description.to_owned(),
+ category_id: community.category_id, // Note: need to keep this due to foreign key constraint
+ creator_id: community.creator_id, // Note: need to keep this due to foreign key constraint
+ removed: Some(false),
+ published: None,
+ updated: Some(naive_now()),
+ deleted: None,
+ nsfw: community.nsfw,
+ actor_id: community.actor_id,
+ local: community.local,
+ private_key: community.private_key,
+ public_key: community.public_key,
+ last_refreshed_at: None,
+ };
+
+ Community::update(&conn, community.id, &community_form)?;
+
+ let res = CommunityResponse {
+ community: CommunityView::read(&conn, community.id, None)?,
+ };
+
+ chat_server.do_send(SendCommunityRoomMessage {
+ op: UserOperation::EditCommunity,
+ response: res,
+ community_id: community.id,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_undo_like(
+ undo: &Undo,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let like = undo
+ .undo_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Like>()?;
+
+ let type_ = like
+ .like_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .kind()
+ .unwrap();
+
+ match type_ {
+ "Note" => receive_undo_like_comment(&like, &conn, chat_server),
+ "Page" => receive_undo_like_post(&like, &conn, chat_server),
+ d => Err(format_err!("Undo Delete type {} not supported", d)),
+ }
+}
+
+fn receive_undo_like_comment(
+ like: &Like,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let note = like
+ .like_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &like, false)?;
+
+ let comment = CommentForm::from_apub(¬e, &conn)?;
+ let comment_id = get_or_fetch_and_insert_remote_comment(&comment.ap_id, &conn)?.id;
+ let like_form = CommentLikeForm {
+ comment_id,
+ post_id: comment.post_id,
+ user_id: user.id,
+ score: 0,
+ };
+ CommentLike::remove(&conn, &like_form)?;
+
+ // Refetch the view
+ let comment_view = CommentView::read(&conn, comment_id, None)?;
+
+ // TODO get those recipient actor ids from somewhere
+ let recipient_ids = vec![];
+ let res = CommentResponse {
+ comment: comment_view,
+ recipient_ids,
+ };
+
+ chat_server.do_send(SendComment {
+ op: UserOperation::CreateCommentLike,
+ comment: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_undo_like_post(
+ like: &Like,
+
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let page = like
+ .like_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<PageExt>()?;
+
+ let user_uri = like.like_props.get_actor_xsd_any_uri().unwrap().to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+
+ insert_activity(&conn, user.id, &like, false)?;
+
+ let post = PostForm::from_apub(&page, conn)?;
+ let post_id = get_or_fetch_and_insert_remote_post(&post.ap_id, &conn)?.id;
+
+ let like_form = PostLikeForm {
+ post_id,
+ user_id: user.id,
+ score: 1,
+ };
+ PostLike::remove(&conn, &like_form)?;
+
+ // Refetch the view
+ let post_view = PostView::read(&conn, post_id, None)?;
+
+ let res = PostResponse { post: post_view };
+
+ chat_server.do_send(SendPost {
+ op: UserOperation::CreatePostLike,
+ post: res,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
-use crate::apub::make_apub_endpoint;
-use crate::db::establish_unpooled_connection;
-use crate::db::user::User_;
-use crate::to_datetime_utc;
-use activitypub::{actor::Person, context};
-use actix_web::body::Body;
-use actix_web::web::Path;
-use actix_web::HttpResponse;
+use crate::{
+ apub::{
+ activities::send_activity,
+ create_apub_response,
+ extensions::signatures::PublicKey,
+ ActorType,
+ FromApub,
+ PersonExt,
+ ToApub,
+ },
+ convert_datetime,
+ db::{
+ activity::insert_activity,
+ user::{UserForm, User_},
+ },
+ naive_now,
+ routes::DbPoolParam,
+};
+use activitystreams::{
+ actor::{properties::ApActorProperties, Person},
+ context,
+ endpoint::EndpointProperties,
+ object::{properties::ObjectProperties, AnyImage, Image},
+ primitives::XsdAnyUri,
+};
+use activitystreams_ext::Ext2;
+use activitystreams_new::{
+ activity::{Follow, Undo},
+ object::Tombstone,
+ prelude::*,
+};
+use actix_web::{body::Body, web::Path, HttpResponse, Result};
+use diesel::PgConnection;
+use failure::Error;
use serde::Deserialize;
-impl User_ {
- pub fn as_person(&self) -> Person {
- let base_url = make_apub_endpoint("u", &self.name);
+#[derive(Deserialize)]
+pub struct UserQuery {
+ user_name: String,
+}
+
+impl ToApub for User_ {
+ type Response = PersonExt;
+
+ // Turn a Lemmy Community into an ActivityPub group that can be sent out over the network.
+ fn to_apub(&self, _conn: &PgConnection) -> Result<PersonExt, Error> {
+ // TODO go through all these to_string and to_owned()
let mut person = Person::default();
- person.object_props.set_context_object(context()).ok();
- person.object_props.set_id_string(base_url.to_string()).ok();
- person
- .object_props
- .set_name_string(self.name.to_owned())
- .ok();
- person
- .object_props
- .set_published_utctime(to_datetime_utc(self.published))
- .ok();
- if let Some(updated) = self.updated {
- person
- .object_props
- .set_updated_utctime(to_datetime_utc(updated))
- .ok();
+ let oprops: &mut ObjectProperties = person.as_mut();
+ oprops
+ .set_context_xsd_any_uri(context())?
+ .set_id(self.actor_id.to_string())?
+ .set_name_xsd_string(self.name.to_owned())?
+ .set_published(convert_datetime(self.published))?;
+
+ if let Some(u) = self.updated {
+ oprops.set_updated(convert_datetime(u))?;
}
- person
- .ap_actor_props
- .set_inbox_string(format!("{}/inbox", &base_url))
- .ok();
- person
- .ap_actor_props
- .set_outbox_string(format!("{}/outbox", &base_url))
- .ok();
- person
- .ap_actor_props
- .set_following_string(format!("{}/following", &base_url))
- .ok();
- person
- .ap_actor_props
- .set_liked_string(format!("{}/liked", &base_url))
- .ok();
if let Some(i) = &self.preferred_username {
- person
- .ap_actor_props
- .set_preferred_username_string(i.to_string())
- .ok();
+ oprops.set_name_xsd_string(i.to_owned())?;
+ }
+
+ if let Some(avatar_url) = &self.avatar {
+ let mut image = Image::new();
+ image
+ .object_props
+ .set_url_xsd_any_uri(avatar_url.to_owned())?;
+ let any_image = AnyImage::from_concrete(image)?;
+ oprops.set_icon_any_image(any_image)?;
}
- person
+ let mut endpoint_props = EndpointProperties::default();
+
+ endpoint_props.set_shared_inbox(self.get_shared_inbox_url())?;
+
+ let mut actor_props = ApActorProperties::default();
+
+ actor_props
+ .set_inbox(self.get_inbox_url())?
+ .set_outbox(self.get_outbox_url())?
+ .set_endpoints(endpoint_props)?
+ .set_followers(self.get_followers_url())?
+ .set_following(self.get_following_url())?
+ .set_liked(self.get_liked_url())?;
+
+ Ok(Ext2::new(person, actor_props, self.get_public_key_ext()))
+ }
+ fn to_tombstone(&self) -> Result<Tombstone, Error> {
+ unimplemented!()
}
}
-#[derive(Deserialize)]
-pub struct UserQuery {
- user_name: String,
+impl ActorType for User_ {
+ fn actor_id(&self) -> String {
+ self.actor_id.to_owned()
+ }
+
+ fn public_key(&self) -> String {
+ self.public_key.to_owned().unwrap()
+ }
+
+ fn private_key(&self) -> String {
+ self.private_key.to_owned().unwrap()
+ }
+
+ /// As a given local user, send out a follow request to a remote community.
+ fn send_follow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> {
+ let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
+ let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
+ follow.set_context(context()).set_id(id.parse()?);
+ let to = format!("{}/inbox", follow_actor_id);
+
+ insert_activity(&conn, self.id, &follow, true)?;
+
+ send_activity(&follow, self, vec![to])?;
+ Ok(())
+ }
+
+ fn send_unfollow(&self, follow_actor_id: &str, conn: &PgConnection) -> Result<(), Error> {
+ let id = format!("{}/follow/{}", self.actor_id, uuid::Uuid::new_v4());
+ let mut follow = Follow::new(self.actor_id.to_owned(), follow_actor_id);
+ follow.set_context(context()).set_id(id.parse()?);
+
+ let to = format!("{}/inbox", follow_actor_id);
+
+ // TODO
+ // Undo that fake activity
+ let undo_id = format!("{}/undo/follow/{}", self.actor_id, uuid::Uuid::new_v4());
+ let mut undo = Undo::new(self.actor_id.parse::<XsdAnyUri>()?, follow.into_any_base()?);
+ undo.set_context(context()).set_id(undo_id.parse()?);
+
+ insert_activity(&conn, self.id, &undo, true)?;
+
+ send_activity(&undo, self, vec![to])?;
+ Ok(())
+ }
+
+ fn send_delete(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
+ unimplemented!()
+ }
+
+ fn send_undo_delete(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
+ unimplemented!()
+ }
+
+ fn send_remove(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
+ unimplemented!()
+ }
+
+ fn send_undo_remove(&self, _creator: &User_, _conn: &PgConnection) -> Result<(), Error> {
+ unimplemented!()
+ }
+
+ fn send_accept_follow(&self, _follow: &Follow, _conn: &PgConnection) -> Result<(), Error> {
+ unimplemented!()
+ }
+
+ fn get_follower_inboxes(&self, _conn: &PgConnection) -> Result<Vec<String>, Error> {
+ unimplemented!()
+ }
}
-pub async fn get_apub_user(info: Path<UserQuery>) -> HttpResponse<Body> {
- let connection = establish_unpooled_connection();
+impl FromApub for UserForm {
+ type ApubType = PersonExt;
+ /// Parse an ActivityPub person received from another instance into a Lemmy user.
+ fn from_apub(person: &PersonExt, _conn: &PgConnection) -> Result<Self, Error> {
+ let oprops = &person.inner.object_props;
+ let aprops = &person.ext_one;
+ let public_key: &PublicKey = &person.ext_two.public_key;
+
+ let avatar = match oprops.get_icon_any_image() {
+ Some(any_image) => any_image
+ .to_owned()
+ .into_concrete::<Image>()?
+ .object_props
+ .get_url_xsd_any_uri()
+ .map(|u| u.to_string()),
+ None => None,
+ };
- if let Ok(user) = User_::find_by_email_or_username(&connection, &info.user_name) {
- HttpResponse::Ok()
- .content_type("application/activity+json")
- .body(serde_json::to_string(&user.as_person()).unwrap())
- } else {
- HttpResponse::NotFound().finish()
+ Ok(UserForm {
+ name: oprops.get_name_xsd_string().unwrap().to_string(),
+ preferred_username: aprops.get_preferred_username().map(|u| u.to_string()),
+ password_encrypted: "".to_string(),
+ admin: false,
+ banned: false,
+ email: None,
+ avatar,
+ updated: oprops
+ .get_updated()
+ .map(|u| u.as_ref().to_owned().naive_local()),
+ show_nsfw: false,
+ theme: "".to_string(),
+ default_sort_type: 0,
+ default_listing_type: 0,
+ lang: "".to_string(),
+ show_avatars: false,
+ send_notifications_to_email: false,
+ matrix_user_id: None,
+ actor_id: oprops.get_id().unwrap().to_string(),
+ bio: oprops.get_summary_xsd_string().map(|s| s.to_string()),
+ local: false,
+ private_key: None,
+ public_key: Some(public_key.to_owned().public_key_pem),
+ last_refreshed_at: Some(naive_now()),
+ })
}
}
+
+/// Return the user json over HTTP.
+pub async fn get_apub_user_http(
+ info: Path<UserQuery>,
+ db: DbPoolParam,
+) -> Result<HttpResponse<Body>, Error> {
+ let user = User_::find_by_email_or_username(&&db.get()?, &info.user_name)?;
+ let u = user.to_apub(&db.get().unwrap())?;
+ Ok(create_apub_response(&u))
+}
--- /dev/null
+use crate::{
+ api::user::PrivateMessageResponse,
+ apub::{
+ extensions::signatures::verify,
+ fetcher::{get_or_fetch_and_upsert_remote_community, get_or_fetch_and_upsert_remote_user},
+ FromApub,
+ },
+ db::{
+ activity::insert_activity,
+ community::{CommunityFollower, CommunityFollowerForm},
+ private_message::{PrivateMessage, PrivateMessageForm},
+ private_message_view::PrivateMessageView,
+ user::User_,
+ Crud,
+ Followable,
+ },
+ naive_now,
+ routes::{ChatServerParam, DbPoolParam},
+ websocket::{server::SendUserRoomMessage, UserOperation},
+};
+use activitystreams::{
+ activity::{Accept, Create, Delete, Undo, Update},
+ object::Note,
+};
+use actix_web::{web, HttpRequest, HttpResponse, Result};
+use diesel::PgConnection;
+use failure::{Error, _core::fmt::Debug};
+use log::debug;
+use serde::Deserialize;
+
+#[serde(untagged)]
+#[derive(Deserialize, Debug)]
+pub enum UserAcceptedObjects {
+ Accept(Box<Accept>),
+ Create(Box<Create>),
+ Update(Box<Update>),
+ Delete(Box<Delete>),
+ Undo(Box<Undo>),
+}
+
+/// Handler for all incoming activities to user inboxes.
+pub async fn user_inbox(
+ request: HttpRequest,
+ input: web::Json<UserAcceptedObjects>,
+ path: web::Path<String>,
+ db: DbPoolParam,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ // TODO: would be nice if we could do the signature check here, but we cant access the actor property
+ let input = input.into_inner();
+ let conn = &db.get().unwrap();
+ let username = path.into_inner();
+ debug!("User {} received activity: {:?}", &username, &input);
+
+ match input {
+ UserAcceptedObjects::Accept(a) => receive_accept(&a, &request, &username, &conn),
+ UserAcceptedObjects::Create(c) => {
+ receive_create_private_message(&c, &request, &conn, chat_server)
+ }
+ UserAcceptedObjects::Update(u) => {
+ receive_update_private_message(&u, &request, &conn, chat_server)
+ }
+ UserAcceptedObjects::Delete(d) => {
+ receive_delete_private_message(&d, &request, &conn, chat_server)
+ }
+ UserAcceptedObjects::Undo(u) => {
+ receive_undo_delete_private_message(&u, &request, &conn, chat_server)
+ }
+ }
+}
+
+/// Handle accepted follows.
+fn receive_accept(
+ accept: &Accept,
+ request: &HttpRequest,
+ username: &str,
+ conn: &PgConnection,
+) -> Result<HttpResponse, Error> {
+ let community_uri = accept
+ .accept_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let community = get_or_fetch_and_upsert_remote_community(&community_uri, conn)?;
+ verify(request, &community)?;
+
+ let user = User_::read_from_name(&conn, username)?;
+
+ insert_activity(&conn, community.creator_id, &accept, false)?;
+
+ // Now you need to add this to the community follower
+ let community_follower_form = CommunityFollowerForm {
+ community_id: community.id,
+ user_id: user.id,
+ };
+
+ // This will fail if they're already a follower
+ CommunityFollower::follow(&conn, &community_follower_form)?;
+
+ // TODO: make sure that we actually requested a follow
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_create_private_message(
+ create: &Create,
+ request: &HttpRequest,
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let note = create
+ .create_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let user_uri = create
+ .create_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+ verify(request, &user)?;
+
+ insert_activity(&conn, user.id, &create, false)?;
+
+ let private_message = PrivateMessageForm::from_apub(¬e, &conn)?;
+ let inserted_private_message = PrivateMessage::create(&conn, &private_message)?;
+
+ let message = PrivateMessageView::read(&conn, inserted_private_message.id)?;
+
+ let res = PrivateMessageResponse {
+ message: message.to_owned(),
+ };
+
+ chat_server.do_send(SendUserRoomMessage {
+ op: UserOperation::CreatePrivateMessage,
+ response: res,
+ recipient_id: message.recipient_id,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_update_private_message(
+ update: &Update,
+ request: &HttpRequest,
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let note = update
+ .update_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let user_uri = update
+ .update_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+ verify(request, &user)?;
+
+ insert_activity(&conn, user.id, &update, false)?;
+
+ let private_message = PrivateMessageForm::from_apub(¬e, &conn)?;
+ let private_message_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id;
+ PrivateMessage::update(conn, private_message_id, &private_message)?;
+
+ let message = PrivateMessageView::read(&conn, private_message_id)?;
+
+ let res = PrivateMessageResponse {
+ message: message.to_owned(),
+ };
+
+ chat_server.do_send(SendUserRoomMessage {
+ op: UserOperation::EditPrivateMessage,
+ response: res,
+ recipient_id: message.recipient_id,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_delete_private_message(
+ delete: &Delete,
+ request: &HttpRequest,
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let note = delete
+ .delete_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let user_uri = delete
+ .delete_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+ verify(request, &user)?;
+
+ insert_activity(&conn, user.id, &delete, false)?;
+
+ let private_message = PrivateMessageForm::from_apub(¬e, &conn)?;
+ let private_message_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id;
+ let private_message_form = PrivateMessageForm {
+ content: private_message.content,
+ recipient_id: private_message.recipient_id,
+ creator_id: private_message.creator_id,
+ deleted: Some(true),
+ read: None,
+ ap_id: private_message.ap_id,
+ local: private_message.local,
+ published: None,
+ updated: Some(naive_now()),
+ };
+ PrivateMessage::update(conn, private_message_id, &private_message_form)?;
+
+ let message = PrivateMessageView::read(&conn, private_message_id)?;
+
+ let res = PrivateMessageResponse {
+ message: message.to_owned(),
+ };
+
+ chat_server.do_send(SendUserRoomMessage {
+ op: UserOperation::EditPrivateMessage,
+ response: res,
+ recipient_id: message.recipient_id,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
+
+fn receive_undo_delete_private_message(
+ undo: &Undo,
+ request: &HttpRequest,
+ conn: &PgConnection,
+ chat_server: ChatServerParam,
+) -> Result<HttpResponse, Error> {
+ let delete = undo
+ .undo_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Delete>()?;
+
+ let note = delete
+ .delete_props
+ .get_object_base_box()
+ .to_owned()
+ .unwrap()
+ .to_owned()
+ .into_concrete::<Note>()?;
+
+ let user_uri = delete
+ .delete_props
+ .get_actor_xsd_any_uri()
+ .unwrap()
+ .to_string();
+
+ let user = get_or_fetch_and_upsert_remote_user(&user_uri, &conn)?;
+ verify(request, &user)?;
+
+ insert_activity(&conn, user.id, &delete, false)?;
+
+ let private_message = PrivateMessageForm::from_apub(¬e, &conn)?;
+ let private_message_id = PrivateMessage::read_from_apub_id(&conn, &private_message.ap_id)?.id;
+ let private_message_form = PrivateMessageForm {
+ content: private_message.content,
+ recipient_id: private_message.recipient_id,
+ creator_id: private_message.creator_id,
+ deleted: Some(false),
+ read: None,
+ ap_id: private_message.ap_id,
+ local: private_message.local,
+ published: None,
+ updated: Some(naive_now()),
+ };
+ PrivateMessage::update(conn, private_message_id, &private_message_form)?;
+
+ let message = PrivateMessageView::read(&conn, private_message_id)?;
+
+ let res = PrivateMessageResponse {
+ message: message.to_owned(),
+ };
+
+ chat_server.do_send(SendUserRoomMessage {
+ op: UserOperation::EditPrivateMessage,
+ response: res,
+ recipient_id: message.recipient_id,
+ my_id: None,
+ });
+
+ Ok(HttpResponse::Ok().finish())
+}
--- /dev/null
+use crate::{db::Crud, schema::activity};
+use diesel::{dsl::*, result::Error, *};
+use failure::_core::fmt::Debug;
+use log::debug;
+use serde::{Deserialize, Serialize};
+use serde_json::Value;
+
+#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
+#[table_name = "activity"]
+pub struct Activity {
+ pub id: i32,
+ pub user_id: i32,
+ pub data: Value,
+ pub local: bool,
+ pub published: chrono::NaiveDateTime,
+ pub updated: Option<chrono::NaiveDateTime>,
+}
+
+#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
+#[table_name = "activity"]
+pub struct ActivityForm {
+ pub user_id: i32,
+ pub data: Value,
+ pub local: bool,
+ pub updated: Option<chrono::NaiveDateTime>,
+}
+
+impl Crud<ActivityForm> for Activity {
+ fn read(conn: &PgConnection, activity_id: i32) -> Result<Self, Error> {
+ use crate::schema::activity::dsl::*;
+ activity.find(activity_id).first::<Self>(conn)
+ }
+
+ fn delete(conn: &PgConnection, activity_id: i32) -> Result<usize, Error> {
+ use crate::schema::activity::dsl::*;
+ diesel::delete(activity.find(activity_id)).execute(conn)
+ }
+
+ fn create(conn: &PgConnection, new_activity: &ActivityForm) -> Result<Self, Error> {
+ use crate::schema::activity::dsl::*;
+ insert_into(activity)
+ .values(new_activity)
+ .get_result::<Self>(conn)
+ }
+
+ fn update(
+ conn: &PgConnection,
+ activity_id: i32,
+ new_activity: &ActivityForm,
+ ) -> Result<Self, Error> {
+ use crate::schema::activity::dsl::*;
+ diesel::update(activity.find(activity_id))
+ .set(new_activity)
+ .get_result::<Self>(conn)
+ }
+}
+
+pub fn insert_activity<T>(
+ conn: &PgConnection,
+ user_id: i32,
+ data: &T,
+ local: bool,
+) -> Result<(), failure::Error>
+where
+ T: Serialize + Debug,
+{
+ let activity_form = ActivityForm {
+ user_id,
+ data: serde_json::to_value(&data)?,
+ local,
+ updated: None,
+ };
+ debug!("inserting activity for user {}, data {:?}", user_id, data);
+ Activity::create(&conn, &activity_form)?;
+ Ok(())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::{super::user::*, *};
+ use crate::db::{establish_unpooled_connection, Crud, ListingType, SortType};
+
+ #[test]
+ fn test_crud() {
+ let conn = establish_unpooled_connection();
+
+ let creator_form = UserForm {
+ name: "activity_creator_pm".into(),
+ preferred_username: None,
+ password_encrypted: "nope".into(),
+ email: None,
+ matrix_user_id: None,
+ avatar: None,
+ admin: false,
+ banned: false,
+ updated: None,
+ show_nsfw: false,
+ theme: "darkly".into(),
+ default_sort_type: SortType::Hot as i16,
+ default_listing_type: ListingType::Subscribed as i16,
+ lang: "browser".into(),
+ show_avatars: true,
+ send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ };
+
+ let inserted_creator = User_::create(&conn, &creator_form).unwrap();
+
+ let test_json: Value = serde_json::from_str(
+ r#"{
+ "street": "Article Circle Expressway 1",
+ "city": "North Pole",
+ "postcode": "99705",
+ "state": "Alaska"
+}"#,
+ )
+ .unwrap();
+ let activity_form = ActivityForm {
+ user_id: inserted_creator.id,
+ data: test_json.to_owned(),
+ local: true,
+ updated: None,
+ };
+
+ let inserted_activity = Activity::create(&conn, &activity_form).unwrap();
+
+ let expected_activity = Activity {
+ id: inserted_activity.id,
+ user_id: inserted_creator.id,
+ data: test_json,
+ local: true,
+ published: inserted_activity.published,
+ updated: None,
+ };
+
+ let read_activity = Activity::read(&conn, inserted_activity.id).unwrap();
+ let num_deleted = Activity::delete(&conn, inserted_activity.id).unwrap();
+ User_::delete(&conn, inserted_creator.id).unwrap();
+
+ assert_eq!(expected_activity, read_activity);
+ assert_eq!(expected_activity, inserted_activity);
+ assert_eq!(1, num_deleted);
+ }
+}
-use super::*;
-use crate::schema::category;
-use crate::schema::category::dsl::*;
+use crate::{
+ db::Crud,
+ schema::{category, category::dsl::*},
+};
+use diesel::{dsl::*, result::Error, *};
+use serde::{Deserialize, Serialize};
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "category"]
#[cfg(test)]
mod tests {
use super::*;
+ use crate::db::establish_unpooled_connection;
+
#[test]
fn test_crud() {
let conn = establish_unpooled_connection();
--- /dev/null
+// This is for db migrations that require code
+use super::{
+ comment::Comment,
+ community::{Community, CommunityForm},
+ post::Post,
+ private_message::PrivateMessage,
+ user::{UserForm, User_},
+};
+use crate::{
+ apub::{extensions::signatures::generate_actor_keypair, make_apub_endpoint, EndpointType},
+ db::Crud,
+ naive_now,
+};
+use diesel::*;
+use failure::Error;
+use log::info;
+
+pub fn run_advanced_migrations(conn: &PgConnection) -> Result<(), Error> {
+ user_updates_2020_04_02(conn)?;
+ community_updates_2020_04_02(conn)?;
+ post_updates_2020_04_03(conn)?;
+ comment_updates_2020_04_03(conn)?;
+ private_message_updates_2020_05_05(conn)?;
+
+ Ok(())
+}
+
+fn user_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> {
+ use crate::schema::user_::dsl::*;
+
+ info!("Running user_updates_2020_04_02");
+
+ // Update the actor_id, private_key, and public_key, last_refreshed_at
+ let incorrect_users = user_
+ .filter(actor_id.eq("changeme"))
+ .filter(local.eq(true))
+ .load::<User_>(conn)?;
+
+ for cuser in &incorrect_users {
+ let keypair = generate_actor_keypair()?;
+
+ let form = UserForm {
+ name: cuser.name.to_owned(),
+ email: cuser.email.to_owned(),
+ matrix_user_id: cuser.matrix_user_id.to_owned(),
+ avatar: cuser.avatar.to_owned(),
+ password_encrypted: cuser.password_encrypted.to_owned(),
+ preferred_username: cuser.preferred_username.to_owned(),
+ updated: None,
+ admin: cuser.admin,
+ banned: cuser.banned,
+ show_nsfw: cuser.show_nsfw,
+ theme: cuser.theme.to_owned(),
+ default_sort_type: cuser.default_sort_type,
+ default_listing_type: cuser.default_listing_type,
+ lang: cuser.lang.to_owned(),
+ show_avatars: cuser.show_avatars,
+ send_notifications_to_email: cuser.send_notifications_to_email,
+ actor_id: make_apub_endpoint(EndpointType::User, &cuser.name).to_string(),
+ bio: cuser.bio.to_owned(),
+ local: cuser.local,
+ private_key: Some(keypair.private_key),
+ public_key: Some(keypair.public_key),
+ last_refreshed_at: Some(naive_now()),
+ };
+
+ User_::update(&conn, cuser.id, &form)?;
+ }
+
+ info!("{} user rows updated.", incorrect_users.len());
+
+ Ok(())
+}
+
+fn community_updates_2020_04_02(conn: &PgConnection) -> Result<(), Error> {
+ use crate::schema::community::dsl::*;
+
+ info!("Running community_updates_2020_04_02");
+
+ // Update the actor_id, private_key, and public_key, last_refreshed_at
+ let incorrect_communities = community
+ .filter(actor_id.eq("changeme"))
+ .filter(local.eq(true))
+ .load::<Community>(conn)?;
+
+ for ccommunity in &incorrect_communities {
+ let keypair = generate_actor_keypair()?;
+
+ let form = CommunityForm {
+ name: ccommunity.name.to_owned(),
+ title: ccommunity.title.to_owned(),
+ description: ccommunity.description.to_owned(),
+ category_id: ccommunity.category_id,
+ creator_id: ccommunity.creator_id,
+ removed: None,
+ deleted: None,
+ nsfw: ccommunity.nsfw,
+ updated: None,
+ actor_id: make_apub_endpoint(EndpointType::Community, &ccommunity.name).to_string(),
+ local: ccommunity.local,
+ private_key: Some(keypair.private_key),
+ public_key: Some(keypair.public_key),
+ last_refreshed_at: Some(naive_now()),
+ published: None,
+ };
+
+ Community::update(&conn, ccommunity.id, &form)?;
+ }
+
+ info!("{} community rows updated.", incorrect_communities.len());
+
+ Ok(())
+}
+
+fn post_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> {
+ use crate::schema::post::dsl::*;
+
+ info!("Running post_updates_2020_04_03");
+
+ // Update the ap_id
+ let incorrect_posts = post
+ .filter(ap_id.eq("changeme"))
+ .filter(local.eq(true))
+ .load::<Post>(conn)?;
+
+ for cpost in &incorrect_posts {
+ Post::update_ap_id(&conn, cpost.id)?;
+ }
+
+ info!("{} post rows updated.", incorrect_posts.len());
+
+ Ok(())
+}
+
+fn comment_updates_2020_04_03(conn: &PgConnection) -> Result<(), Error> {
+ use crate::schema::comment::dsl::*;
+
+ info!("Running comment_updates_2020_04_03");
+
+ // Update the ap_id
+ let incorrect_comments = comment
+ .filter(ap_id.eq("changeme"))
+ .filter(local.eq(true))
+ .load::<Comment>(conn)?;
+
+ for ccomment in &incorrect_comments {
+ Comment::update_ap_id(&conn, ccomment.id)?;
+ }
+
+ info!("{} comment rows updated.", incorrect_comments.len());
+
+ Ok(())
+}
+
+fn private_message_updates_2020_05_05(conn: &PgConnection) -> Result<(), Error> {
+ use crate::schema::private_message::dsl::*;
+
+ info!("Running private_message_updates_2020_05_05");
+
+ // Update the ap_id
+ let incorrect_pms = private_message
+ .filter(ap_id.eq("changeme"))
+ .filter(local.eq(true))
+ .load::<PrivateMessage>(conn)?;
+
+ for cpm in &incorrect_pms {
+ PrivateMessage::update_ap_id(&conn, cpm.id)?;
+ }
+
+ info!("{} private message rows updated.", incorrect_pms.len());
+
+ Ok(())
+}
-use super::post::Post;
-use super::*;
-use crate::schema::{comment, comment_like, comment_saved};
+use super::{post::Post, *};
+use crate::{
+ apub::{make_apub_endpoint, EndpointType},
+ naive_now,
+ schema::{comment, comment_like, comment_saved},
+};
// WITH RECURSIVE MyTree AS (
// SELECT * FROM comment WHERE parent_id IS NULL
pub parent_id: Option<i32>,
pub content: String,
pub removed: bool,
- pub read: bool,
+ pub read: bool, // Whether the recipient has read the comment or not
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
+ pub ap_id: String,
+ pub local: bool,
}
#[derive(Insertable, AsChangeset, Clone)]
pub content: String,
pub removed: Option<bool>,
pub read: Option<bool>,
+ pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: Option<bool>,
+ pub ap_id: String,
+ pub local: bool,
}
impl Crud<CommentForm> for Comment {
}
}
+impl Comment {
+ pub fn update_ap_id(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
+ use crate::schema::comment::dsl::*;
+
+ let apid = make_apub_endpoint(EndpointType::Comment, &comment_id.to_string()).to_string();
+ diesel::update(comment.find(comment_id))
+ .set(ap_id.eq(apid))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
+ use crate::schema::comment::dsl::*;
+ comment.filter(ap_id.eq(object_id)).first::<Self>(conn)
+ }
+
+ pub fn mark_as_read(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
+ use crate::schema::comment::dsl::*;
+
+ diesel::update(comment.find(comment_id))
+ .set(read.eq(true))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn permadelete(conn: &PgConnection, comment_id: i32) -> Result<Self, Error> {
+ use crate::schema::comment::dsl::*;
+
+ diesel::update(comment.find(comment_id))
+ .set((
+ content.eq("*Permananently Deleted*"),
+ deleted.eq(true),
+ updated.eq(naive_now()),
+ ))
+ .get_result::<Self>(conn)
+ }
+}
+
#[derive(Identifiable, Queryable, Associations, PartialEq, Debug, Clone)]
#[belongs_to(Comment)]
#[table_name = "comment_like"]
#[cfg(test)]
mod tests {
- use super::super::community::*;
- use super::super::post::*;
- use super::super::user::*;
- use super::*;
+ use super::{
+ super::{community::*, post::*, user::*},
+ *,
+ };
#[test]
fn test_crud() {
let conn = establish_unpooled_connection();
let new_user = UserForm {
name: "terry".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
deleted: None,
updated: None,
nsfw: false,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
deleted: None,
read: None,
parent_id: None,
+ published: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
parent_id: None,
published: inserted_comment.published,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let child_comment_form = CommentForm {
removed: None,
deleted: None,
read: None,
+ published: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
-use super::*;
-use diesel::pg::Pg;
+use crate::db::{fuzzy_search, limit_and_offset, ListingType, MaybeOptional, SortType};
+use diesel::{dsl::*, pg::Pg, result::Error, *};
+use serde::{Deserialize, Serialize};
// The faked schema since diesel doesn't do views
table! {
published -> Timestamp,
updated -> Nullable<Timestamp>,
deleted -> Bool,
+ ap_id -> Text,
+ local -> Bool,
community_id -> Int4,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
banned -> Bool,
banned_from_community -> Bool,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
score -> BigInt,
published -> Timestamp,
updated -> Nullable<Timestamp>,
deleted -> Bool,
+ ap_id -> Text,
+ local -> Bool,
community_id -> Int4,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
banned -> Bool,
banned_from_community -> Bool,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
score -> BigInt,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
+ pub ap_id: String,
+ pub local: bool,
pub community_id: i32,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
pub banned: bool,
pub banned_from_community: bool,
+ pub creator_actor_id: String,
+ pub creator_local: bool,
pub creator_name: String,
pub creator_avatar: Option<String>,
pub score: i64,
published -> Timestamp,
updated -> Nullable<Timestamp>,
deleted -> Bool,
+ ap_id -> Text,
+ local -> Bool,
community_id -> Int4,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
banned -> Bool,
banned_from_community -> Bool,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
score -> BigInt,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
+ pub ap_id: String,
+ pub local: bool,
pub community_id: i32,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
pub banned: bool,
pub banned_from_community: bool,
+ pub creator_actor_id: String,
+ pub creator_local: bool,
pub creator_name: String,
pub creator_avatar: Option<String>,
pub score: i64,
#[cfg(test)]
mod tests {
- use super::super::comment::*;
- use super::super::community::*;
- use super::super::post::*;
- use super::super::user::*;
- use super::*;
+ use super::{
+ super::{comment::*, community::*, post::*, user::*},
+ *,
+ };
+ use crate::db::{establish_unpooled_connection, Crud, Likeable};
+
#[test]
fn test_crud() {
let conn = establish_unpooled_connection();
let new_user = UserForm {
name: "timmy".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
deleted: None,
updated: None,
nsfw: false,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
removed: None,
deleted: None,
read: None,
+ published: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
my_vote: None,
subscribed: None,
saved: None,
+ ap_id: "changeme".to_string(),
+ local: true,
+ community_actor_id: inserted_community.actor_id.to_owned(),
+ community_local: true,
+ creator_actor_id: inserted_user.actor_id.to_owned(),
+ creator_local: true,
};
let expected_comment_view_with_user = CommentView {
my_vote: Some(1),
subscribed: None,
saved: None,
+ ap_id: "changeme".to_string(),
+ local: true,
+ community_actor_id: inserted_community.actor_id.to_owned(),
+ community_local: true,
+ creator_actor_id: inserted_user.actor_id.to_owned(),
+ creator_local: true,
};
let mut read_comment_views_no_user = CommentQueryBuilder::create(&conn)
-use super::*;
-use crate::schema::{community, community_follower, community_moderator, community_user_ban};
+use crate::{
+ db::{Bannable, Crud, Followable, Joinable},
+ schema::{community, community_follower, community_moderator, community_user_ban},
+};
+use diesel::{dsl::*, result::Error, *};
+use serde::{Deserialize, Serialize};
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "community"]
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
pub nsfw: bool,
+ pub actor_id: String,
+ pub local: bool,
+ pub private_key: Option<String>,
+ pub public_key: Option<String>,
+ pub last_refreshed_at: chrono::NaiveDateTime,
}
-#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize)]
+// TODO add better delete, remove, lock actions here.
+#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize, Debug)]
#[table_name = "community"]
pub struct CommunityForm {
pub name: String,
pub category_id: i32,
pub creator_id: i32,
pub removed: Option<bool>,
+ pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: Option<bool>,
pub nsfw: bool,
+ pub actor_id: String,
+ pub local: bool,
+ pub private_key: Option<String>,
+ pub public_key: Option<String>,
+ pub last_refreshed_at: Option<chrono::NaiveDateTime>,
}
impl Crud<CommunityForm> for Community {
}
impl Community {
- pub fn read_from_name(conn: &PgConnection, community_name: String) -> Result<Self, Error> {
+ pub fn read_from_name(conn: &PgConnection, community_name: &str) -> Result<Self, Error> {
use crate::schema::community::dsl::*;
community
.filter(name.eq(community_name))
.first::<Self>(conn)
}
- pub fn get_url(&self) -> String {
- format!("https://{}/c/{}", Settings::get().hostname, self.name)
+ pub fn read_from_actor_id(conn: &PgConnection, community_id: &str) -> Result<Self, Error> {
+ use crate::schema::community::dsl::*;
+ community
+ .filter(actor_id.eq(community_id))
+ .first::<Self>(conn)
+ }
+
+ pub fn list_local(conn: &PgConnection) -> Result<Vec<Self>, Error> {
+ use crate::schema::community::dsl::*;
+ community.filter(local.eq(true)).load::<Community>(conn)
}
}
.values(community_follower_form)
.get_result::<Self>(conn)
}
- fn ignore(
+ fn unfollow(
conn: &PgConnection,
community_follower_form: &CommunityFollowerForm,
) -> Result<usize, Error> {
#[cfg(test)]
mod tests {
- use super::super::user::*;
- use super::*;
+ use super::{super::user::*, *};
+ use crate::db::{establish_unpooled_connection, ListingType, SortType};
+
#[test]
fn test_crud() {
let conn = establish_unpooled_connection();
let new_user = UserForm {
name: "bobbee".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
removed: None,
deleted: None,
updated: None,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
deleted: false,
published: inserted_community.published,
updated: None,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: inserted_community.published,
};
let community_follower_form = CommunityFollowerForm {
let read_community = Community::read(&conn, inserted_community.id).unwrap();
let updated_community =
Community::update(&conn, inserted_community.id, &new_community).unwrap();
- let ignored_community = CommunityFollower::ignore(&conn, &community_follower_form).unwrap();
+ let ignored_community = CommunityFollower::unfollow(&conn, &community_follower_form).unwrap();
let left_community = CommunityModerator::leave(&conn, &community_user_form).unwrap();
let unban = CommunityUserBan::unban(&conn, &community_user_ban_form).unwrap();
let num_deleted = Community::delete(&conn, inserted_community.id).unwrap();
use super::community_view::community_mview::BoxedQuery;
-use super::*;
-use diesel::pg::Pg;
+use crate::db::{fuzzy_search, limit_and_offset, MaybeOptional, SortType};
+use diesel::{pg::Pg, result::Error, *};
+use serde::{Deserialize, Serialize};
table! {
community_view (id) {
updated -> Nullable<Timestamp>,
deleted -> Bool,
nsfw -> Bool,
+ actor_id -> Text,
+ local -> Bool,
+ last_refreshed_at -> Timestamp,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
category_name -> Varchar,
updated -> Nullable<Timestamp>,
deleted -> Bool,
nsfw -> Bool,
+ actor_id -> Text,
+ local -> Bool,
+ last_refreshed_at -> Timestamp,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
category_name -> Varchar,
community_id -> Int4,
user_id -> Int4,
published -> Timestamp,
+ user_actor_id -> Text,
+ user_local -> Bool,
user_name -> Varchar,
avatar -> Nullable<Text>,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
}
}
community_id -> Int4,
user_id -> Int4,
published -> Timestamp,
+ user_actor_id -> Text,
+ user_local -> Bool,
user_name -> Varchar,
avatar -> Nullable<Text>,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
}
}
community_id -> Int4,
user_id -> Int4,
published -> Timestamp,
+ user_actor_id -> Text,
+ user_local -> Bool,
user_name -> Varchar,
avatar -> Nullable<Text>,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
}
}
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
pub nsfw: bool,
+ pub actor_id: String,
+ pub local: bool,
+ pub last_refreshed_at: chrono::NaiveDateTime,
+ pub creator_actor_id: String,
+ pub creator_local: bool,
pub creator_name: String,
pub creator_avatar: Option<String>,
pub category_name: String,
pub community_id: i32,
pub user_id: i32,
pub published: chrono::NaiveDateTime,
+ pub user_actor_id: String,
+ pub user_local: bool,
pub user_name: String,
pub avatar: Option<String>,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
}
pub community_id: i32,
pub user_id: i32,
pub published: chrono::NaiveDateTime,
+ pub user_actor_id: String,
+ pub user_local: bool,
pub user_name: String,
pub avatar: Option<String>,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
}
pub community_id: i32,
pub user_id: i32,
pub published: chrono::NaiveDateTime,
+ pub user_actor_id: String,
+ pub user_local: bool,
pub user_name: String,
pub avatar: Option<String>,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
}
use crate::settings::Settings;
-use diesel::dsl::*;
-use diesel::result::Error;
-use diesel::*;
+use diesel::{dsl::*, result::Error, *};
use serde::{Deserialize, Serialize};
+pub mod activity;
pub mod category;
+pub mod code_migrations;
pub mod comment;
pub mod comment_view;
pub mod community;
fn follow(conn: &PgConnection, form: &T) -> Result<Self, Error>
where
Self: Sized;
- fn ignore(conn: &PgConnection, form: &T) -> Result<usize, Error>
+ fn unfollow(conn: &PgConnection, form: &T) -> Result<usize, Error>
where
Self: Sized;
}
-use super::*;
-use crate::schema::{
- mod_add, mod_add_community, mod_ban, mod_ban_from_community, mod_lock_post, mod_remove_comment,
- mod_remove_community, mod_remove_post, mod_sticky_post,
+use crate::{
+ db::Crud,
+ schema::{
+ mod_add,
+ mod_add_community,
+ mod_ban,
+ mod_ban_from_community,
+ mod_lock_post,
+ mod_remove_comment,
+ mod_remove_community,
+ mod_remove_post,
+ mod_sticky_post,
+ },
};
+use diesel::{dsl::*, result::Error, *};
+use serde::{Deserialize, Serialize};
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "mod_remove_post"]
#[cfg(test)]
mod tests {
- use super::super::comment::*;
- use super::super::community::*;
- use super::super::post::*;
- use super::super::user::*;
- use super::*;
+ use super::{
+ super::{comment::*, community::*, post::*, user::*},
+ *,
+ };
+ use crate::db::{establish_unpooled_connection, ListingType, SortType};
+
// use Crud;
#[test]
fn test_crud() {
let new_mod = UserForm {
name: "the mod".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_mod = User_::create(&conn, &new_mod).unwrap();
let new_user = UserForm {
name: "jim2".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
deleted: None,
updated: None,
nsfw: false,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
deleted: None,
read: None,
parent_id: None,
+ published: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
-use super::*;
+use crate::db::limit_and_offset;
+use diesel::{result::Error, *};
+use serde::{Deserialize, Serialize};
table! {
mod_remove_post_view (id) {
-use super::*;
-use crate::schema::password_reset_request;
-use crate::schema::password_reset_request::dsl::*;
+use crate::{
+ db::Crud,
+ schema::{password_reset_request, password_reset_request::dsl::*},
+};
+use diesel::{dsl::*, result::Error, *};
use sha2::{Digest, Sha256};
#[derive(Queryable, Identifiable, PartialEq, Debug)]
#[cfg(test)]
mod tests {
- use super::super::user::*;
- use super::*;
+ use super::{super::user::*, *};
+ use crate::db::{establish_unpooled_connection, ListingType, SortType};
#[test]
fn test_crud() {
let new_user = UserForm {
name: "thommy prw".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
-use super::*;
-use crate::schema::{post, post_like, post_read, post_saved};
+use crate::{
+ apub::{make_apub_endpoint, EndpointType},
+ db::{Crud, Likeable, Readable, Saveable},
+ naive_now,
+ schema::{post, post_like, post_read, post_saved},
+};
+use diesel::{dsl::*, result::Error, *};
+use serde::{Deserialize, Serialize};
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "post"]
pub embed_description: Option<String>,
pub embed_html: Option<String>,
pub thumbnail_url: Option<String>,
+ pub ap_id: String,
+ pub local: bool,
}
-#[derive(Insertable, AsChangeset, Clone)]
+#[derive(Insertable, AsChangeset, Clone, Debug)]
#[table_name = "post"]
pub struct PostForm {
pub name: String,
pub community_id: i32,
pub removed: Option<bool>,
pub locked: Option<bool>,
+ pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: Option<bool>,
pub nsfw: bool,
pub embed_description: Option<String>,
pub embed_html: Option<String>,
pub thumbnail_url: Option<String>,
+ pub ap_id: String,
+ pub local: bool,
+}
+
+impl Post {
+ pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
+ use crate::schema::post::dsl::*;
+ post.filter(id.eq(post_id)).first::<Self>(conn)
+ }
+
+ pub fn list_for_community(
+ conn: &PgConnection,
+ the_community_id: i32,
+ ) -> Result<Vec<Self>, Error> {
+ use crate::schema::post::dsl::*;
+ post
+ .filter(community_id.eq(the_community_id))
+ .load::<Self>(conn)
+ }
+
+ pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
+ use crate::schema::post::dsl::*;
+ post.filter(ap_id.eq(object_id)).first::<Self>(conn)
+ }
+
+ pub fn update_ap_id(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
+ use crate::schema::post::dsl::*;
+
+ let apid = make_apub_endpoint(EndpointType::Post, &post_id.to_string()).to_string();
+ diesel::update(post.find(post_id))
+ .set(ap_id.eq(apid))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn permadelete(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
+ use crate::schema::post::dsl::*;
+
+ let perma_deleted = "*Permananently Deleted*";
+ let perma_deleted_url = "https://deleted.com";
+
+ diesel::update(post.find(post_id))
+ .set((
+ name.eq(perma_deleted),
+ url.eq(perma_deleted_url),
+ body.eq(perma_deleted),
+ deleted.eq(true),
+ updated.eq(naive_now()),
+ ))
+ .get_result::<Self>(conn)
+ }
}
impl Crud<PostForm> for Post {
#[cfg(test)]
mod tests {
- use super::super::community::*;
- use super::super::user::*;
- use super::*;
+ use super::{
+ super::{community::*, user::*},
+ *,
+ };
+ use crate::db::{establish_unpooled_connection, ListingType, SortType};
+
#[test]
fn test_crud() {
let conn = establish_unpooled_connection();
let new_user = UserForm {
name: "jim".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
deleted: None,
updated: None,
nsfw: false,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
};
// Post Like
use super::post_view::post_mview::BoxedQuery;
-use super::*;
-use diesel::pg::Pg;
+use crate::db::{fuzzy_search, limit_and_offset, ListingType, MaybeOptional, SortType};
+use diesel::{dsl::*, pg::Pg, result::Error, *};
+use serde::{Deserialize, Serialize};
// The faked schema since diesel doesn't do views
table! {
embed_description -> Nullable<Text>,
embed_html -> Nullable<Text>,
thumbnail_url -> Nullable<Text>,
+ ap_id -> Text,
+ local -> Bool,
banned -> Bool,
banned_from_community -> Bool,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
community_removed -> Bool,
community_deleted -> Bool,
embed_description -> Nullable<Text>,
embed_html -> Nullable<Text>,
thumbnail_url -> Nullable<Text>,
+ ap_id -> Text,
+ local -> Bool,
banned -> Bool,
banned_from_community -> Bool,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
community_removed -> Bool,
community_deleted -> Bool,
pub embed_description: Option<String>,
pub embed_html: Option<String>,
pub thumbnail_url: Option<String>,
+ pub ap_id: String,
+ pub local: bool,
pub banned: bool,
pub banned_from_community: bool,
+ pub creator_actor_id: String,
+ pub creator_local: bool,
pub creator_name: String,
pub creator_avatar: Option<String>,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
pub community_removed: bool,
pub community_deleted: bool,
#[cfg(test)]
mod tests {
- use super::super::community::*;
- use super::super::post::*;
- use super::super::user::*;
- use super::*;
+ use super::{
+ super::{community::*, post::*, user::*},
+ *,
+ };
+ use crate::db::{establish_unpooled_connection, Crud, Likeable};
+
#[test]
fn test_crud() {
let conn = establish_unpooled_connection();
let new_user = UserForm {
name: user_name.to_owned(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
deleted: None,
updated: None,
nsfw: false,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".to_string(),
+ local: true,
+ creator_actor_id: inserted_user.actor_id.to_owned(),
+ creator_local: true,
+ community_actor_id: inserted_community.actor_id.to_owned(),
+ community_local: true,
};
let expected_post_listing_with_user = PostView {
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".to_string(),
+ local: true,
+ creator_actor_id: inserted_user.actor_id.to_owned(),
+ creator_local: true,
+ community_actor_id: inserted_community.actor_id.to_owned(),
+ community_local: true,
};
let read_post_listings_with_user = PostQueryBuilder::create(&conn)
-use super::*;
-use crate::schema::private_message;
+use crate::{
+ apub::{make_apub_endpoint, EndpointType},
+ db::Crud,
+ schema::private_message,
+};
+use diesel::{dsl::*, result::Error, *};
+use serde::{Deserialize, Serialize};
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "private_message"]
pub read: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
+ pub ap_id: String,
+ pub local: bool,
}
#[derive(Insertable, AsChangeset, Clone)]
pub struct PrivateMessageForm {
pub creator_id: i32,
pub recipient_id: i32,
- pub content: Option<String>,
+ pub content: String,
pub deleted: Option<bool>,
pub read: Option<bool>,
+ pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<chrono::NaiveDateTime>,
+ pub ap_id: String,
+ pub local: bool,
}
impl Crud<PrivateMessageForm> for PrivateMessage {
}
}
+impl PrivateMessage {
+ pub fn update_ap_id(conn: &PgConnection, private_message_id: i32) -> Result<Self, Error> {
+ use crate::schema::private_message::dsl::*;
+
+ let apid = make_apub_endpoint(
+ EndpointType::PrivateMessage,
+ &private_message_id.to_string(),
+ )
+ .to_string();
+ diesel::update(private_message.find(private_message_id))
+ .set(ap_id.eq(apid))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
+ use crate::schema::private_message::dsl::*;
+ private_message
+ .filter(ap_id.eq(object_id))
+ .first::<Self>(conn)
+ }
+}
+
#[cfg(test)]
mod tests {
- use super::super::user::*;
- use super::*;
+ use super::{super::user::*, *};
+ use crate::db::{establish_unpooled_connection, ListingType, SortType};
+
#[test]
fn test_crud() {
let conn = establish_unpooled_connection();
let creator_form = UserForm {
name: "creator_pm".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_creator = User_::create(&conn, &creator_form).unwrap();
let recipient_form = UserForm {
name: "recipient_pm".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();
let private_message_form = PrivateMessageForm {
- content: Some("A test private message".into()),
+ content: "A test private message".into(),
creator_id: inserted_creator.id,
recipient_id: inserted_recipient.id,
deleted: None,
read: None,
+ published: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_private_message = PrivateMessage::create(&conn, &private_message_form).unwrap();
read: false,
updated: None,
published: inserted_private_message.published,
+ ap_id: "changeme".into(),
+ local: true,
};
let read_private_message = PrivateMessage::read(&conn, inserted_private_message.id).unwrap();
-use super::*;
-use diesel::pg::Pg;
+use crate::db::{limit_and_offset, MaybeOptional};
+use diesel::{pg::Pg, result::Error, *};
+use serde::{Deserialize, Serialize};
// The faked schema since diesel doesn't do views
table! {
read -> Bool,
published -> Timestamp,
updated -> Nullable<Timestamp>,
+ ap_id -> Text,
+ local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
recipient_name -> Varchar,
recipient_avatar -> Nullable<Text>,
+ recipient_actor_id -> Text,
+ recipient_local -> Bool,
}
}
read -> Bool,
published -> Timestamp,
updated -> Nullable<Timestamp>,
+ ap_id -> Text,
+ local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
recipient_name -> Varchar,
recipient_avatar -> Nullable<Text>,
+ recipient_actor_id -> Text,
+ recipient_local -> Bool,
}
}
pub read: bool,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
+ pub ap_id: String,
+ pub local: bool,
pub creator_name: String,
pub creator_avatar: Option<String>,
+ pub creator_actor_id: String,
+ pub creator_local: bool,
pub recipient_name: String,
pub recipient_avatar: Option<String>,
+ pub recipient_actor_id: String,
+ pub recipient_local: bool,
}
pub struct PrivateMessageQueryBuilder<'a> {
-use super::*;
-use crate::schema::site;
+use crate::{db::Crud, schema::site};
+use diesel::{dsl::*, result::Error, *};
+use serde::{Deserialize, Serialize};
#[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[table_name = "site"]
-use super::*;
+use diesel::{result::Error, *};
+use serde::{Deserialize, Serialize};
table! {
site_view (id) {
-use super::*;
-use crate::schema::user_;
-use crate::schema::user_::dsl::*;
-use crate::{is_email_regex, Settings};
+use crate::{
+ db::Crud,
+ is_email_regex,
+ naive_now,
+ schema::{user_, user_::dsl::*},
+ settings::Settings,
+};
use bcrypt::{hash, DEFAULT_COST};
+use diesel::{dsl::*, result::Error, *};
use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
+use serde::{Deserialize, Serialize};
#[derive(Queryable, Identifiable, PartialEq, Debug)]
#[table_name = "user_"]
pub struct User_ {
pub id: i32,
pub name: String,
- pub fedi_name: String,
pub preferred_username: Option<String>,
pub password_encrypted: String,
pub email: Option<String>,
pub show_avatars: bool,
pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>,
+ pub actor_id: String,
+ pub bio: Option<String>,
+ pub local: bool,
+ pub private_key: Option<String>,
+ pub public_key: Option<String>,
+ pub last_refreshed_at: chrono::NaiveDateTime,
}
-#[derive(Insertable, AsChangeset, Clone)]
+#[derive(Insertable, AsChangeset, Clone, Debug)]
#[table_name = "user_"]
pub struct UserForm {
pub name: String,
- pub fedi_name: String,
pub preferred_username: Option<String>,
pub password_encrypted: String,
pub admin: bool,
pub show_avatars: bool,
pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>,
+ pub actor_id: String,
+ pub bio: Option<String>,
+ pub local: bool,
+ pub private_key: Option<String>,
+ pub public_key: Option<String>,
+ pub last_refreshed_at: Option<chrono::NaiveDateTime>,
}
impl Crud<UserForm> for User_ {
Self::create(&conn, &edited_user)
}
+ // TODO do more individual updates like these
pub fn update_password(
conn: &PgConnection,
user_id: i32,
let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
diesel::update(user_.find(user_id))
- .set(password_encrypted.eq(password_hash))
+ .set((
+ password_encrypted.eq(password_hash),
+ updated.eq(naive_now()),
+ ))
.get_result::<Self>(conn)
}
- pub fn read_from_name(conn: &PgConnection, from_user_name: String) -> Result<Self, Error> {
+ pub fn read_from_name(conn: &PgConnection, from_user_name: &str) -> Result<Self, Error> {
user_.filter(name.eq(from_user_name)).first::<Self>(conn)
}
+
+ pub fn add_admin(conn: &PgConnection, user_id: i32, added: bool) -> Result<Self, Error> {
+ diesel::update(user_.find(user_id))
+ .set(admin.eq(added))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn ban_user(conn: &PgConnection, user_id: i32, ban: bool) -> Result<Self, Error> {
+ diesel::update(user_.find(user_id))
+ .set(banned.eq(ban))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn read_from_actor_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
+ use crate::schema::user_::dsl::*;
+ user_.filter(actor_id.eq(object_id)).first::<Self>(conn)
+ }
}
#[derive(Debug, Serialize, Deserialize)]
let my_claims = Claims {
id: self.id,
username: self.name.to_owned(),
- iss: self.fedi_name.to_owned(),
+ iss: Settings::get().hostname,
show_nsfw: self.show_nsfw,
theme: self.theme.to_owned(),
default_sort_type: self.default_sort_type,
#[cfg(test)]
mod tests {
- use super::User_;
- use super::*;
+ use super::{User_, *};
+ use crate::db::{establish_unpooled_connection, ListingType, SortType};
#[test]
fn test_crud() {
let new_user = UserForm {
name: "thommy".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
let expected_user = User_ {
id: inserted_user.id,
name: "thommy".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: inserted_user.published,
};
let read_user = User_::read(&conn, inserted_user.id).unwrap();
use super::comment::Comment;
-use super::*;
-use crate::schema::user_mention;
+use crate::{db::Crud, schema::user_mention};
+use diesel::{dsl::*, result::Error, *};
+use serde::{Deserialize, Serialize};
#[derive(Queryable, Associations, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
#[belongs_to(Comment)]
#[cfg(test)]
mod tests {
- use super::super::comment::*;
- use super::super::community::*;
- use super::super::post::*;
- use super::super::user::*;
- use super::*;
+ use super::{
+ super::{comment::*, community::*, post::*, user::*},
+ *,
+ };
+ use crate::db::{establish_unpooled_connection, ListingType, SortType};
+
#[test]
fn test_crud() {
let conn = establish_unpooled_connection();
let new_user = UserForm {
name: "terrylake".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
let recipient_form = UserForm {
name: "terrylakes recipient".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();
deleted: None,
updated: None,
nsfw: false,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
deleted: None,
read: None,
parent_id: None,
+ published: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
-use super::*;
-use diesel::pg::Pg;
+use crate::db::{limit_and_offset, MaybeOptional, SortType};
+use diesel::{dsl::*, pg::Pg, result::Error, *};
+use serde::{Deserialize, Serialize};
// The faked schema since diesel doesn't do views
table! {
id -> Int4,
user_mention_id -> Int4,
creator_id -> Int4,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
post_id -> Int4,
parent_id -> Nullable<Int4>,
content -> Text,
updated -> Nullable<Timestamp>,
deleted -> Bool,
community_id -> Int4,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
banned -> Bool,
banned_from_community -> Bool,
my_vote -> Nullable<Int4>,
saved -> Nullable<Bool>,
recipient_id -> Int4,
+ recipient_actor_id -> Text,
+ recipient_local -> Bool,
}
}
id -> Int4,
user_mention_id -> Int4,
creator_id -> Int4,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
post_id -> Int4,
parent_id -> Nullable<Int4>,
content -> Text,
updated -> Nullable<Timestamp>,
deleted -> Bool,
community_id -> Int4,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
banned -> Bool,
banned_from_community -> Bool,
my_vote -> Nullable<Int4>,
saved -> Nullable<Bool>,
recipient_id -> Int4,
+ recipient_actor_id -> Text,
+ recipient_local -> Bool,
}
}
pub id: i32,
pub user_mention_id: i32,
pub creator_id: i32,
+ pub creator_actor_id: String,
+ pub creator_local: bool,
pub post_id: i32,
pub parent_id: Option<i32>,
pub content: String,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
pub community_id: i32,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
pub banned: bool,
pub banned_from_community: bool,
pub my_vote: Option<i32>,
pub saved: Option<bool>,
pub recipient_id: i32,
+ pub recipient_actor_id: String,
+ pub recipient_local: bool,
}
pub struct UserMentionQueryBuilder<'a> {
use super::user_view::user_mview::BoxedQuery;
-use super::*;
-use diesel::pg::Pg;
+use crate::db::{fuzzy_search, limit_and_offset, MaybeOptional, SortType};
+use diesel::{dsl::*, pg::Pg, result::Error, *};
+use serde::{Deserialize, Serialize};
table! {
user_view (id) {
id -> Int4,
+ actor_id -> Text,
name -> Varchar,
avatar -> Nullable<Text>,
email -> Nullable<Text>,
matrix_user_id -> Nullable<Text>,
- fedi_name -> Varchar,
+ bio -> Nullable<Text>,
+ local -> Bool,
admin -> Bool,
banned -> Bool,
show_avatars -> Bool,
table! {
user_mview (id) {
id -> Int4,
+ actor_id -> Text,
name -> Varchar,
avatar -> Nullable<Text>,
email -> Nullable<Text>,
matrix_user_id -> Nullable<Text>,
- fedi_name -> Varchar,
+ bio -> Nullable<Text>,
+ local -> Bool,
admin -> Bool,
banned -> Bool,
show_avatars -> Bool,
#[table_name = "user_view"]
pub struct UserView {
pub id: i32,
+ pub actor_id: String,
pub name: String,
pub avatar: Option<String>,
pub email: Option<String>,
pub matrix_user_id: Option<String>,
- pub fedi_name: String,
+ pub bio: Option<String>,
+ pub local: bool,
pub admin: bool,
pub banned: bool,
pub show_avatars: bool,
pub extern crate jsonwebtoken;
pub extern crate lettre;
pub extern crate lettre_email;
+extern crate log;
+pub extern crate openssl;
pub extern crate rand;
pub extern crate regex;
pub extern crate rss;
pub mod version;
pub mod websocket;
+use crate::settings::Settings;
use actix_web::dev::ConnectionInfo;
-use chrono::{DateTime, NaiveDateTime, Utc};
-use lettre::smtp::authentication::{Credentials, Mechanism};
-use lettre::smtp::extension::ClientId;
-use lettre::smtp::ConnectionReuseParameters;
-use lettre::{ClientSecurity, SmtpClient, Transport};
+use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, Utc};
+use itertools::Itertools;
+use lettre::{
+ smtp::{
+ authentication::{Credentials, Mechanism},
+ extension::ClientId,
+ ConnectionReuseParameters,
+ },
+ ClientSecurity,
+ SmtpClient,
+ Transport,
+};
use lettre_email::Email;
use log::error;
use percent_encoding::{utf8_percent_encode, NON_ALPHANUMERIC};
-use rand::distributions::Alphanumeric;
-use rand::{thread_rng, Rng};
+use rand::{distributions::Alphanumeric, thread_rng, Rng};
use regex::{Regex, RegexBuilder};
use serde::Deserialize;
-use crate::settings::Settings;
-
pub type ConnectionId = usize;
pub type PostId = i32;
pub type CommunityId = i32;
NaiveDateTime::from_timestamp(time, 0)
}
+pub fn convert_datetime(datetime: NaiveDateTime) -> DateTime<FixedOffset> {
+ let now = Local::now();
+ DateTime::<FixedOffset>::from_utc(datetime, *now.offset())
+}
+
pub fn is_email_regex(test: &str) -> bool {
EMAIL_REGEX.is_match(test)
}
[start, combined].concat()
}
-pub fn extract_usernames(test: &str) -> Vec<&str> {
- let mut matches: Vec<&str> = USERNAME_MATCHES_REGEX
- .find_iter(test)
- .map(|mat| mat.as_str())
- .collect();
-
- // Unique
- matches.sort_unstable();
- matches.dedup();
-
- // Remove /u/
- matches.iter().map(|t| &t[3..]).collect()
-}
-
pub fn generate_random_string() -> String {
thread_rng().sample_iter(&Alphanumeric).take(30).collect()
}
.to_string()
}
+// TODO nothing is done with community / group webfingers yet, so just ignore those for now
+#[derive(Clone, PartialEq, Eq, Hash)]
+pub struct MentionData {
+ pub name: String,
+ pub domain: String,
+}
+
+impl MentionData {
+ pub fn is_local(&self) -> bool {
+ Settings::get().hostname.eq(&self.domain)
+ }
+ pub fn full_name(&self) -> String {
+ format!("@{}@{}", &self.name, &self.domain)
+ }
+}
+
+pub fn scrape_text_for_mentions(text: &str) -> Vec<MentionData> {
+ let mut out: Vec<MentionData> = Vec::new();
+ for caps in WEBFINGER_USER_REGEX.captures_iter(text) {
+ out.push(MentionData {
+ name: caps["name"].to_string(),
+ domain: caps["domain"].to_string(),
+ });
+ }
+ out.into_iter().unique().collect()
+}
+
pub fn is_valid_username(name: &str) -> bool {
VALID_USERNAME_REGEX.is_match(name)
}
#[cfg(test)]
mod tests {
use crate::{
- extract_usernames, is_email_regex, is_image_content_type, is_valid_community_name,
- is_valid_username, remove_slurs, slur_check, slurs_vec_to_str,
+ is_email_regex,
+ is_image_content_type,
+ is_valid_community_name,
+ is_valid_username,
+ remove_slurs,
+ scrape_text_for_mentions,
+ slur_check,
+ slurs_vec_to_str,
};
+ #[test]
+ fn test_mentions_regex() {
+ let text = "Just read a great blog post by [@tedu@honk.teduangst.com](/u/test). And another by !test_community@fish.teduangst.com . Another [@lemmy@lemmy_alpha:8540](/u/fish)";
+ let mentions = scrape_text_for_mentions(text);
+
+ assert_eq!(mentions[0].name, "tedu".to_string());
+ assert_eq!(mentions[0].domain, "honk.teduangst.com".to_string());
+ assert_eq!(mentions[1].domain, "lemmy_alpha:8540".to_string());
+ }
+
#[test]
fn test_image() {
assert!(is_image_content_type("https://1734811051.rsc.cdn77.org/data/images/full/365645/as-virus-kills-navajos-in-their-homes-tribal-women-provide-lifeline.jpg?w=600?w=650").is_ok());
}
}
- #[test]
- fn test_extract_usernames() {
- let usernames = extract_usernames("this is a user mention for [/u/testme](/u/testme) and thats all. Oh [/u/another](/u/another) user. And the first again [/u/testme](/u/testme) okay");
- let expected = vec!["another", "testme"];
- assert_eq!(usernames, expected);
- }
-
// These helped with testing
// #[test]
// fn test_iframely() {
static ref EMAIL_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9.!#$%&’*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\.[a-zA-Z0-9-]+)*$").unwrap();
static ref SLUR_REGEX: Regex = RegexBuilder::new(r"(fag(g|got|tard)?|maricos?|cock\s?sucker(s|ing)?|nig(\b|g?(a|er)?(s|z)?)\b|dindu(s?)|mudslime?s?|kikes?|mongoloids?|towel\s*heads?|\bspi(c|k)s?\b|\bchinks?|niglets?|beaners?|\bnips?\b|\bcoons?\b|jungle\s*bunn(y|ies?)|jigg?aboo?s?|\bpakis?\b|rag\s*heads?|gooks?|cunts?|bitch(es|ing|y)?|puss(y|ies?)|twats?|feminazis?|whor(es?|ing)|\bslut(s|t?y)?|\btrann?(y|ies?)|ladyboy(s?)|\b(b|re|r)tard(ed)?s?)").case_insensitive(true).build().unwrap();
static ref USERNAME_MATCHES_REGEX: Regex = Regex::new(r"/u/[a-zA-Z][0-9a-zA-Z_]*").unwrap();
+ // TODO keep this old one, it didn't work with port well tho
+ // static ref WEBFINGER_USER_REGEX: Regex = Regex::new(r"@(?P<name>[\w.]+)@(?P<domain>[a-zA-Z0-9._-]+\.[a-zA-Z0-9_-]+)").unwrap();
+ static ref WEBFINGER_USER_REGEX: Regex = Regex::new(r"@(?P<name>[\w.]+)@(?P<domain>[a-zA-Z0-9._:-]+)").unwrap();
static ref VALID_USERNAME_REGEX: Regex = Regex::new(r"^[a-zA-Z0-9_]{3,20}$").unwrap();
static ref VALID_COMMUNITY_NAME_REGEX: Regex = Regex::new(r"^[a-z0-9_]{3,20}$").unwrap();
}
use crate::lemmy_server::actix_web::dev::Service;
use actix::prelude::*;
-use actix_web::body::Body;
-use actix_web::dev::{ServiceRequest, ServiceResponse};
-use actix_web::http::header::CONTENT_TYPE;
-use actix_web::http::{header::CACHE_CONTROL, HeaderValue};
-use actix_web::*;
-use diesel::r2d2::{ConnectionManager, Pool};
-use diesel::PgConnection;
+use actix_web::{
+ body::Body,
+ dev::{ServiceRequest, ServiceResponse},
+ http::{
+ header::{CACHE_CONTROL, CONTENT_TYPE},
+ HeaderValue,
+ },
+ *,
+};
+use diesel::{
+ r2d2::{ConnectionManager, Pool},
+ PgConnection,
+};
use lemmy_server::{
+ db::code_migrations::run_advanced_migrations,
rate_limit::{rate_limiter::RateLimiter, RateLimit},
routes::{api, federation, feeds, index, nodeinfo, webfinger},
settings::Settings,
// Run the migrations from code
let conn = pool.get().unwrap();
embedded_migrations::run(&conn).unwrap();
+ run_advanced_migrations(&conn).unwrap();
// Set up the rate limiter
let rate_limiter = RateLimit {
-pub mod rate_limiter;
-
use super::{IPAddr, Settings};
-use crate::api::APIError;
-use crate::get_ip;
-use crate::settings::RateLimitConfig;
+use crate::{api::APIError, get_ip, settings::RateLimitConfig};
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
-use failure::Error;
use futures::future::{ok, Ready};
-use log::debug;
use rate_limiter::{RateLimitType, RateLimiter};
-use std::collections::HashMap;
-use std::future::Future;
-use std::pin::Pin;
-use std::sync::Arc;
-use std::task::{Context, Poll};
-use std::time::SystemTime;
-use strum::IntoEnumIterator;
+use std::{
+ future::Future,
+ pin::Pin,
+ sync::Arc,
+ task::{Context, Poll},
+};
use tokio::sync::Mutex;
+pub mod rate_limiter;
+
#[derive(Debug, Clone)]
pub struct RateLimit {
pub rate_limiter: Arc<Mutex<RateLimiter>>,
-use super::*;
+use super::IPAddr;
+use crate::api::APIError;
+use failure::Error;
+use log::debug;
+use std::{collections::HashMap, time::SystemTime};
+use strum::IntoEnumIterator;
#[derive(Debug, Clone)]
pub struct RateLimitBucket {
-use super::*;
-use crate::api::comment::*;
-use crate::api::community::*;
-use crate::api::post::*;
-use crate::api::site::*;
-use crate::api::user::*;
-use crate::rate_limit::RateLimit;
+use crate::{
+ api::{comment::*, community::*, post::*, site::*, user::*, Oper, Perform},
+ rate_limit::RateLimit,
+ routes::{ChatServerParam, DbPoolParam},
+ websocket::WebsocketInfo,
+};
+use actix_web::{error::ErrorBadRequest, *};
+use serde::Serialize;
pub fn config(cfg: &mut web::ServiceConfig, rate_limit: &RateLimit) {
cfg.service(
.route("/like", web::post().to(route_post::<CreateCommentLike>))
.route("/save", web::put().to(route_post::<SaveComment>)),
)
+ // Private Message
+ .service(
+ web::scope("/private_message")
+ .wrap(rate_limit.message())
+ .route("/list", web::get().to(route_get::<GetPrivateMessages>))
+ .route("", web::post().to(route_post::<CreatePrivateMessage>))
+ .route("", web::put().to(route_post::<EditPrivateMessage>)),
+ )
// User
.service(
// Account action, I don't like that it's in /user maybe /accounts
-use super::*;
-use crate::apub;
+use crate::{
+ apub::{
+ comment::get_apub_comment,
+ community::*,
+ community_inbox::community_inbox,
+ post::get_apub_post,
+ shared_inbox::shared_inbox,
+ user::*,
+ user_inbox::user_inbox,
+ APUB_JSON_CONTENT_TYPE,
+ },
+ settings::Settings,
+};
+use actix_web::*;
pub fn config(cfg: &mut web::ServiceConfig) {
- cfg
- .route(
- "/federation/c/{community_name}",
- web::get().to(apub::community::get_apub_community),
- )
- .route(
- "/federation/c/{community_name}/followers",
- web::get().to(apub::community::get_apub_community_followers),
- )
- .route(
- "/federation/u/{user_name}",
- web::get().to(apub::user::get_apub_user),
- );
+ if Settings::get().federation.enabled {
+ println!("federation enabled, host is {}", Settings::get().hostname);
+ cfg
+ .service(
+ web::scope("/")
+ .guard(guard::Header("Accept", APUB_JSON_CONTENT_TYPE))
+ .route(
+ "/c/{community_name}",
+ web::get().to(get_apub_community_http),
+ )
+ .route(
+ "/c/{community_name}/followers",
+ web::get().to(get_apub_community_followers),
+ )
+ // TODO This is only useful for history which we aren't doing right now
+ // .route(
+ // "/c/{community_name}/outbox",
+ // web::get().to(get_apub_community_outbox),
+ // )
+ .route("/u/{user_name}", web::get().to(get_apub_user_http))
+ .route("/post/{post_id}", web::get().to(get_apub_post))
+ .route("/comment/{comment_id}", web::get().to(get_apub_comment)),
+ )
+ // Inboxes dont work with the header guard for some reason.
+ .route("/c/{community_name}/inbox", web::post().to(community_inbox))
+ .route("/u/{user_name}/inbox", web::post().to(user_inbox))
+ .route("/inbox", web::post().to(shared_inbox));
+ }
}
-use super::*;
-use crate::db::comment_view::{ReplyQueryBuilder, ReplyView};
-use crate::db::community::Community;
-use crate::db::post_view::{PostQueryBuilder, PostView};
-use crate::db::site_view::SiteView;
-use crate::db::user::{Claims, User_};
-use crate::db::user_mention_view::{UserMentionQueryBuilder, UserMentionView};
-use crate::db::{ListingType, SortType};
+use crate::{
+ db::{
+ comment_view::{ReplyQueryBuilder, ReplyView},
+ community::Community,
+ post_view::{PostQueryBuilder, PostView},
+ site_view::SiteView,
+ user::{Claims, User_},
+ user_mention_view::{UserMentionQueryBuilder, UserMentionView},
+ ListingType,
+ SortType,
+ },
+ markdown_to_html,
+ routes::DbPoolParam,
+ settings::Settings,
+};
+use actix_web::{error::ErrorBadRequest, *};
+use chrono::{DateTime, NaiveDateTime, Utc};
+use diesel::{
+ r2d2::{ConnectionManager, Pool},
+ PgConnection,
+};
+use rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder};
+use serde::Deserialize;
+use std::str::FromStr;
+use strum::ParseError;
#[derive(Deserialize)]
pub struct Params {
pub fn config(cfg: &mut web::ServiceConfig) {
cfg
- .route("/feeds/{type}/{name}.xml", web::get().to(feeds::get_feed))
- .route("/feeds/all.xml", web::get().to(feeds::get_all_feed));
+ .route("/feeds/{type}/{name}.xml", web::get().to(get_feed))
+ .route("/feeds/all.xml", web::get().to(get_all_feed));
}
-async fn get_all_feed(
- info: web::Query<Params>,
- db: web::Data<Pool<ConnectionManager<PgConnection>>>,
-) -> Result<HttpResponse, Error> {
+async fn get_all_feed(info: web::Query<Params>, db: DbPoolParam) -> Result<HttpResponse, Error> {
let res = web::block(move || {
let conn = db.get()?;
get_feed_all_data(&conn, &get_sort_type(info)?)
community_name: String,
) -> Result<ChannelBuilder, failure::Error> {
let site_view = SiteView::read(&conn)?;
- let community = Community::read_from_name(&conn, community_name)?;
- let community_url = community.get_url();
+ let community = Community::read_from_name(&conn, &community_name)?;
let posts = PostQueryBuilder::create(&conn)
.listing_type(ListingType::All)
let mut channel_builder = ChannelBuilder::default();
channel_builder
.title(&format!("{} - {}", site_view.name, community.name))
- .link(community_url)
+ .link(community.actor_id)
.items(items);
if let Some(community_desc) = community.description {
-use super::*;
+use crate::settings::Settings;
+use actix_files::NamedFile;
+use actix_web::*;
pub fn config(cfg: &mut web::ServiceConfig) {
cfg
-use crate::api::{Oper, Perform};
-use crate::db::site_view::SiteView;
-use crate::rate_limit::rate_limiter::RateLimiter;
-use crate::websocket::{server::ChatServer, WebsocketInfo};
-use crate::{get_ip, markdown_to_html, version, Settings};
+pub mod api;
+pub mod federation;
+pub mod feeds;
+pub mod index;
+pub mod nodeinfo;
+pub mod webfinger;
+pub mod websocket;
+
+use crate::{rate_limit::rate_limiter::RateLimiter, websocket::server::ChatServer};
use actix::prelude::*;
-use actix_files::NamedFile;
-use actix_web::{body::Body, error::ErrorBadRequest, web::Query, *};
-use actix_web_actors::ws;
-use chrono::{DateTime, NaiveDateTime, Utc};
+use actix_web::*;
use diesel::{
r2d2::{ConnectionManager, Pool},
PgConnection,
};
-use log::{error, info};
-use regex::Regex;
-use rss::{CategoryBuilder, ChannelBuilder, GuidBuilder, Item, ItemBuilder};
-use serde::{Deserialize, Serialize};
-use serde_json::json;
-use std::str::FromStr;
use std::sync::{Arc, Mutex};
-use std::time::{Duration, Instant};
-use strum::ParseError;
pub type DbPoolParam = web::Data<Pool<ConnectionManager<PgConnection>>>;
pub type RateLimitParam = web::Data<Arc<Mutex<RateLimiter>>>;
pub type ChatServerParam = web::Data<Addr<ChatServer>>;
-
-pub mod api;
-pub mod federation;
-pub mod feeds;
-pub mod index;
-pub mod nodeinfo;
-pub mod webfinger;
-pub mod websocket;
-use super::*;
+use crate::{
+ apub::get_apub_protocol_string,
+ db::site_view::SiteView,
+ routes::DbPoolParam,
+ version,
+ Settings,
+};
+use actix_web::{body::Body, error::ErrorBadRequest, *};
+use serde::{Deserialize, Serialize};
+use url::Url;
pub fn config(cfg: &mut web::ServiceConfig) {
cfg
.route("/.well-known/nodeinfo", web::get().to(node_info_well_known));
}
-async fn node_info_well_known() -> HttpResponse<Body> {
+async fn node_info_well_known() -> Result<HttpResponse<Body>, failure::Error> {
let node_info = NodeInfoWellKnown {
links: NodeInfoWellKnownLinks {
- rel: "http://nodeinfo.diaspora.software/ns/schema/2.0".to_string(),
- href: format!("https://{}/nodeinfo/2.0.json", Settings::get().hostname),
+ rel: Url::parse("http://nodeinfo.diaspora.software/ns/schema/2.0")?,
+ href: Url::parse(&format!(
+ "{}://{}/nodeinfo/2.0.json",
+ get_apub_protocol_string(),
+ Settings::get().hostname
+ ))?,
},
};
- HttpResponse::Ok().json(node_info)
+ Ok(HttpResponse::Ok().json(node_info))
}
-async fn node_info(
- db: web::Data<Pool<ConnectionManager<PgConnection>>>,
-) -> Result<HttpResponse, Error> {
+async fn node_info(db: DbPoolParam) -> Result<HttpResponse, Error> {
let res = web::block(move || {
let conn = db.get()?;
let site_view = match SiteView::read(&conn) {
Ok(site_view) => site_view,
Err(_) => return Err(format_err!("not_found")),
};
- let protocols = vec![];
+ let protocols = if Settings::get().federation.enabled {
+ vec!["activitypub".to_string()]
+ } else {
+ vec![]
+ };
Ok(NodeInfo {
version: "2.0".to_string(),
software: NodeInfoSoftware {
Ok(res)
}
-#[derive(Serialize)]
-struct NodeInfoWellKnown {
- links: NodeInfoWellKnownLinks,
+#[derive(Serialize, Deserialize, Debug)]
+pub struct NodeInfoWellKnown {
+ pub links: NodeInfoWellKnownLinks,
}
-#[derive(Serialize)]
-struct NodeInfoWellKnownLinks {
- rel: String,
- href: String,
+#[derive(Serialize, Deserialize, Debug)]
+pub struct NodeInfoWellKnownLinks {
+ pub rel: Url,
+ pub href: Url,
}
-#[derive(Serialize)]
-struct NodeInfo {
- version: String,
- software: NodeInfoSoftware,
- protocols: Vec<String>,
- usage: NodeInfoUsage,
+#[derive(Serialize, Deserialize, Debug)]
+pub struct NodeInfo {
+ pub version: String,
+ pub software: NodeInfoSoftware,
+ pub protocols: Vec<String>,
+ pub usage: NodeInfoUsage,
}
-#[derive(Serialize)]
-struct NodeInfoSoftware {
- name: String,
- version: String,
+#[derive(Serialize, Deserialize, Debug)]
+pub struct NodeInfoSoftware {
+ pub name: String,
+ pub version: String,
}
-#[derive(Serialize)]
+#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
-struct NodeInfoUsage {
- users: NodeInfoUsers,
- local_posts: i64,
- local_comments: i64,
- open_registrations: bool,
+pub struct NodeInfoUsage {
+ pub users: NodeInfoUsers,
+ pub local_posts: i64,
+ pub local_comments: i64,
+ pub open_registrations: bool,
}
-#[derive(Serialize)]
-struct NodeInfoUsers {
- total: i64,
+#[derive(Serialize, Deserialize, Debug)]
+pub struct NodeInfoUsers {
+ pub total: i64,
}
-use super::*;
-use crate::db::community::Community;
+use crate::{
+ db::{community::Community, user::User_},
+ routes::DbPoolParam,
+ Settings,
+};
+use actix_web::{error::ErrorBadRequest, web::Query, *};
+use regex::Regex;
+use serde::{Deserialize, Serialize};
#[derive(Deserialize)]
pub struct Params {
resource: String,
}
+#[derive(Serialize, Deserialize, Debug)]
+pub struct WebFingerResponse {
+ pub subject: String,
+ pub aliases: Vec<String>,
+ pub links: Vec<WebFingerLink>,
+}
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct WebFingerLink {
+ pub rel: Option<String>,
+ #[serde(rename(serialize = "type", deserialize = "type"))]
+ pub type_: Option<String>,
+ pub href: Option<String>,
+ #[serde(skip_serializing_if = "Option::is_none")]
+ pub template: Option<String>,
+}
+
pub fn config(cfg: &mut web::ServiceConfig) {
- cfg.route(
- ".well-known/webfinger",
- web::get().to(get_webfinger_response),
- );
+ if Settings::get().federation.enabled {
+ cfg.route(
+ ".well-known/webfinger",
+ web::get().to(get_webfinger_response),
+ );
+ }
}
lazy_static! {
Settings::get().hostname
))
.unwrap();
+ static ref WEBFINGER_USER_REGEX: Regex = Regex::new(&format!(
+ "^acct:([a-z0-9_]{{3, 20}})@{}$",
+ Settings::get().hostname
+ ))
+ .unwrap();
}
/// Responds to webfinger requests of the following format. There isn't any real documentation for
/// https://radical.town/.well-known/webfinger?resource=acct:felix@radical.town
async fn get_webfinger_response(
info: Query<Params>,
- db: web::Data<Pool<ConnectionManager<PgConnection>>>,
+ db: DbPoolParam,
) -> Result<HttpResponse, Error> {
let res = web::block(move || {
let conn = db.get()?;
- let regex_parsed = WEBFINGER_COMMUNITY_REGEX
+ let community_regex_parsed = WEBFINGER_COMMUNITY_REGEX
.captures(&info.resource)
- .map(|c| c.get(1));
- // TODO: replace this with .flatten() once we are running rust 1.40
- let regex_parsed_flattened = match regex_parsed {
- Some(s) => s,
- None => None,
- };
- let community_name = match regex_parsed_flattened {
- Some(c) => c.as_str(),
- None => return Err(format_err!("not_found")),
- };
+ .map(|c| c.get(1))
+ .flatten();
- // Make sure the requested community exists.
- let community = match Community::read_from_name(&conn, community_name.to_string()) {
- Ok(o) => o,
- Err(_) => return Err(format_err!("not_found")),
+ let user_regex_parsed = WEBFINGER_USER_REGEX
+ .captures(&info.resource)
+ .map(|c| c.get(1))
+ .flatten();
+
+ let url = if let Some(community_name) = community_regex_parsed {
+ // Make sure the requested community exists.
+ let community = match Community::read_from_name(&conn, &community_name.as_str()) {
+ Ok(o) => o,
+ Err(_) => return Err(format_err!("not_found")),
+ };
+ community.actor_id
+ } else if let Some(user_name) = user_regex_parsed {
+ // Make sure the requested user exists.
+ let user = match User_::read_from_name(&conn, &user_name.as_str()) {
+ Ok(o) => o,
+ Err(_) => return Err(format_err!("not_found")),
+ };
+ user.actor_id
+ } else {
+ return Err(format_err!("not_found"));
};
- let community_url = community.get_url();
+ let wf_res = WebFingerResponse {
+ subject: info.resource.to_owned(),
+ aliases: vec![url.to_owned()],
+ links: vec![
+ WebFingerLink {
+ rel: Some("http://webfinger.net/rel/profile-page".to_string()),
+ type_: Some("text/html".to_string()),
+ href: Some(url.to_owned()),
+ template: None,
+ },
+ WebFingerLink {
+ rel: Some("self".to_string()),
+ type_: Some("application/activity+json".to_string()),
+ href: Some(url),
+ template: None,
+ }, // TODO: this also needs to return the subscribe link once that's implemented
+ //{
+ // "rel": "http://ostatus.org/schema/1.0/subscribe",
+ // "template": "https://my_instance.com/authorize_interaction?uri={uri}"
+ //}
+ ],
+ };
- Ok(json!({
- "subject": info.resource,
- "aliases": [
- community_url,
- ],
- "links": [
- {
- "rel": "http://webfinger.net/rel/profile-page",
- "type": "text/html",
- "href": community_url
- },
- {
- "rel": "self",
- "type": "application/activity+json",
- // Yes this is correct, this link doesn't include the `.json` extension
- "href": community_url
- }
- // TODO: this also needs to return the subscribe link once that's implemented
- //{
- // "rel": "http://ostatus.org/schema/1.0/subscribe",
- // "template": "https://my_instance.com/authorize_interaction?uri={uri}"
- //}
- ]
- }))
+ Ok(wf_res)
})
.await
.map(|json| HttpResponse::Ok().json(json))
-use super::*;
-use crate::websocket::server::*;
+use crate::{
+ get_ip,
+ websocket::server::{ChatServer, *},
+};
+use actix::prelude::*;
+use actix_web::*;
+use actix_web_actors::ws;
+use log::{debug, error, info};
+use std::time::{Duration, Instant};
/// How often heartbeat pings are sent
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
/// Client must send ping at least once per 10 seconds (CLIENT_TIMEOUT),
/// otherwise we drop connection.
hb: Instant,
- // db: Pool<ConnectionManager<PgConnection>>,
}
impl Actor for WSSession {
// check client heartbeats
if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
// heartbeat timed out
- error!("Websocket Client heartbeat failed, disconnecting!");
+ debug!("Websocket Client heartbeat failed, disconnecting!");
// notify chat server
act.cs_addr.do_send(Disconnect {
+table! {
+ activity (id) {
+ id -> Int4,
+ user_id -> Int4,
+ data -> Jsonb,
+ local -> Bool,
+ published -> Timestamp,
+ updated -> Nullable<Timestamp>,
+ }
+}
+
table! {
category (id) {
id -> Int4,
published -> Timestamp,
updated -> Nullable<Timestamp>,
deleted -> Bool,
+ ap_id -> Varchar,
+ local -> Bool,
}
}
updated -> Nullable<Timestamp>,
deleted -> Bool,
nsfw -> Bool,
+ actor_id -> Varchar,
+ local -> Bool,
+ private_key -> Nullable<Text>,
+ public_key -> Nullable<Text>,
+ last_refreshed_at -> Timestamp,
}
}
embed_description -> Nullable<Text>,
embed_html -> Nullable<Text>,
thumbnail_url -> Nullable<Text>,
+ ap_id -> Varchar,
+ local -> Bool,
}
}
read -> Bool,
published -> Timestamp,
updated -> Nullable<Timestamp>,
+ ap_id -> Varchar,
+ local -> Bool,
}
}
user_ (id) {
id -> Int4,
name -> Varchar,
- fedi_name -> Varchar,
preferred_username -> Nullable<Varchar>,
password_encrypted -> Text,
email -> Nullable<Text>,
show_avatars -> Bool,
send_notifications_to_email -> Bool,
matrix_user_id -> Nullable<Text>,
+ actor_id -> Varchar,
+ bio -> Nullable<Text>,
+ local -> Bool,
+ private_key -> Nullable<Text>,
+ public_key -> Nullable<Text>,
+ last_refreshed_at -> Timestamp,
}
}
}
}
+joinable!(activity -> user_ (user_id));
joinable!(comment -> post (post_id));
joinable!(comment -> user_ (creator_id));
joinable!(comment_like -> comment (comment_id));
joinable!(user_mention -> user_ (recipient_id));
allow_tables_to_appear_in_same_query!(
+ activity,
category,
comment,
comment_like,
use config::{Config, ConfigError, Environment, File};
use failure::Error;
use serde::Deserialize;
-use std::env;
-use std::fs;
-use std::net::IpAddr;
-use std::sync::RwLock;
+use std::{env, fs, net::IpAddr, sync::RwLock};
static CONFIG_FILE_DEFAULTS: &str = "config/defaults.hjson";
static CONFIG_FILE: &str = "config/config.hjson";
pub front_end_dir: String,
pub rate_limit: RateLimitConfig,
pub email: Option<EmailConfig>,
+ pub federation: Federation,
}
#[derive(Debug, Deserialize, Clone)]
pub pool_size: u32,
}
+#[derive(Debug, Deserialize, Clone)]
+pub struct Federation {
+ pub enabled: bool,
+ pub tls_enabled: bool,
+ pub allowed_instances: String,
+}
+
lazy_static! {
static ref SETTINGS: RwLock<Settings> = RwLock::new(match Settings::init() {
Ok(c) => c,
use crate::ConnectionId;
use actix::prelude::*;
-use diesel::r2d2::{ConnectionManager, Pool};
-use diesel::PgConnection;
+use diesel::{
+ r2d2::{ConnectionManager, Pool},
+ PgConnection,
+};
use failure::Error;
use log::{error, info};
use rand::{rngs::ThreadRng, Rng};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use server::ChatServer;
-use std::collections::{HashMap, HashSet};
-use std::str::FromStr;
+use std::{
+ collections::{HashMap, HashSet},
+ str::FromStr,
+};
#[derive(EnumString, ToString, Debug, Clone)]
pub enum UserOperation {
//! room through `ChatServer`.
use super::*;
-use crate::api::comment::*;
-use crate::api::community::*;
-use crate::api::post::*;
-use crate::api::site::*;
-use crate::api::user::*;
-use crate::api::*;
-use crate::rate_limit::RateLimit;
-use crate::websocket::UserOperation;
-use crate::{CommunityId, ConnectionId, IPAddr, PostId, UserId};
+use crate::{
+ api::{comment::*, community::*, post::*, site::*, user::*, *},
+ rate_limit::RateLimit,
+ websocket::UserOperation,
+ CommunityId,
+ ConnectionId,
+ IPAddr,
+ PostId,
+ UserId,
+};
/// Chat server sends this messages to session
#[derive(Message)]
fuse.js
translation_report.ts
+src/api_tests
.git
build
.build
-.git
-.history
.idea
.jshintrc
.nyc_output
.sass-cache
.vscode
-build
coverage
jsconfig.json
Gemfile.lock
--- /dev/null
+module.exports = {
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ testTimeout: 30000,
+ globals: {
+ 'ts-jest': {
+ diagnostics: false,
+ },
+ },
+};
"license": "AGPL-3.0-or-later",
"main": "index.js",
"scripts": {
+ "api-test": "jest src/api_tests/api.spec.ts",
"build": "node fuse prod",
"lint": "tsc --noEmit && eslint --report-unused-disable-directives --ext .js,.ts,.tsx src",
"prebuild": "node generate_translations.js",
"@types/autosize": "^3.0.6",
"@types/js-cookie": "^2.2.6",
"@types/jwt-decode": "^2.2.1",
- "@types/markdown-it": "^10.0.0",
+ "@types/markdown-it": "^0.0.9",
"@types/markdown-it-container": "^2.0.2",
"@types/node": "^13.11.1",
"autosize": "^4.0.2",
"markdown-it-emoji": "^1.4.0",
"mobius1-selectr": "^2.4.13",
"moment": "^2.24.0",
+ "node-fetch": "^2.6.0",
"prettier": "^2.0.4",
"reconnecting-websocket": "^4.4.0",
"rxjs": "^6.5.5",
"ws": "^7.2.3"
},
"devDependencies": {
+ "@types/jest": "^25.2.1",
+ "@types/node-fetch": "^2.5.6",
"eslint": "^6.5.1",
"eslint-plugin-inferno": "^7.14.3",
"eslint-plugin-jane": "^7.2.1",
"fuse-box": "^3.1.3",
+ "jest": "^25.4.0",
"lint-staged": "^10.1.3",
"sortpack": "^2.1.4",
+ "ts-jest": "^25.4.0",
"ts-node": "^8.8.2",
"ts-transform-classcat": "^1.0.0",
"ts-transform-inferno": "^4.0.3",
--- /dev/null
+import fetch from 'node-fetch';
+
+import {
+ LoginForm,
+ LoginResponse,
+ PostForm,
+ PostResponse,
+ SearchResponse,
+ FollowCommunityForm,
+ CommunityResponse,
+ GetFollowedCommunitiesResponse,
+ GetPostForm,
+ GetPostResponse,
+ CommentForm,
+ CommentResponse,
+ CommunityForm,
+ GetCommunityForm,
+ GetCommunityResponse,
+ CommentLikeForm,
+ CreatePostLikeForm,
+ PrivateMessageForm,
+ EditPrivateMessageForm,
+ PrivateMessageResponse,
+ PrivateMessagesResponse,
+ GetUserMentionsResponse,
+} from '../interfaces';
+
+let lemmyAlphaUrl = 'http://localhost:8540';
+let lemmyAlphaApiUrl = `${lemmyAlphaUrl}/api/v1`;
+let lemmyAlphaAuth: string;
+
+let lemmyBetaUrl = 'http://localhost:8550';
+let lemmyBetaApiUrl = `${lemmyBetaUrl}/api/v1`;
+let lemmyBetaAuth: string;
+
+let lemmyGammaUrl = 'http://localhost:8560';
+let lemmyGammaApiUrl = `${lemmyGammaUrl}/api/v1`;
+let lemmyGammaAuth: string;
+
+// Workaround for tests being run before beforeAll() is finished
+// https://github.com/facebook/jest/issues/9527#issuecomment-592406108
+describe('main', () => {
+ beforeAll(async () => {
+ console.log('Logging in as lemmy_alpha');
+ let form: LoginForm = {
+ username_or_email: 'lemmy_alpha',
+ password: 'lemmy',
+ };
+
+ let res: LoginResponse = await fetch(`${lemmyAlphaApiUrl}/user/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(form),
+ }).then(d => d.json());
+
+ lemmyAlphaAuth = res.jwt;
+
+ console.log('Logging in as lemmy_beta');
+ let formB = {
+ username_or_email: 'lemmy_beta',
+ password: 'lemmy',
+ };
+
+ let resB: LoginResponse = await fetch(`${lemmyBetaApiUrl}/user/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(formB),
+ }).then(d => d.json());
+
+ lemmyBetaAuth = resB.jwt;
+
+ console.log('Logging in as lemmy_gamma');
+ let formC = {
+ username_or_email: 'lemmy_gamma',
+ password: 'lemmy',
+ };
+
+ let resG: LoginResponse = await fetch(`${lemmyGammaApiUrl}/user/login`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(formC),
+ }).then(d => d.json());
+
+ lemmyGammaAuth = resG.jwt;
+ });
+
+ describe('post_search', () => {
+ test('Create test post on alpha and fetch it on beta', async () => {
+ let name = 'A jest test post';
+ let postForm: PostForm = {
+ name,
+ auth: lemmyAlphaAuth,
+ community_id: 2,
+ creator_id: 2,
+ nsfw: false,
+ };
+
+ let createPostRes: PostResponse = await fetch(
+ `${lemmyAlphaApiUrl}/post`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(postForm),
+ }
+ ).then(d => d.json());
+ expect(createPostRes.post.name).toBe(name);
+
+ let searchUrl = `${lemmyBetaApiUrl}/search?q=${createPostRes.post.ap_id}&type_=All&sort=TopAll`;
+ let searchResponse: SearchResponse = await fetch(searchUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ // TODO: check more fields
+ expect(searchResponse.posts[0].name).toBe(name);
+ });
+ });
+
+ describe('follow_accept', () => {
+ test('/u/lemmy_alpha follows and accepts lemmy_beta/c/main', async () => {
+ // Make sure lemmy_beta/c/main is cached on lemmy_alpha
+ // Use short-hand search url
+ let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy_beta:8550&type_=All&sort=TopAll`;
+
+ let searchResponse: SearchResponse = await fetch(searchUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ expect(searchResponse.communities[0].name).toBe('main');
+
+ let followForm: FollowCommunityForm = {
+ community_id: searchResponse.communities[0].id,
+ follow: true,
+ auth: lemmyAlphaAuth,
+ };
+
+ let followRes: CommunityResponse = await fetch(
+ `${lemmyAlphaApiUrl}/community/follow`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(followForm),
+ }
+ ).then(d => d.json());
+
+ // Make sure the follow response went through
+ expect(followRes.community.local).toBe(false);
+ expect(followRes.community.name).toBe('main');
+
+ // Check that you are subscribed to it locally
+ let followedCommunitiesUrl = `${lemmyAlphaApiUrl}/user/followed_communities?&auth=${lemmyAlphaAuth}`;
+ let followedCommunitiesRes: GetFollowedCommunitiesResponse = await fetch(
+ followedCommunitiesUrl,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+
+ expect(followedCommunitiesRes.communities[1].community_local).toBe(false);
+
+ // Test out unfollowing
+ let unfollowForm: FollowCommunityForm = {
+ community_id: searchResponse.communities[0].id,
+ follow: false,
+ auth: lemmyAlphaAuth,
+ };
+
+ let unfollowRes: CommunityResponse = await fetch(
+ `${lemmyAlphaApiUrl}/community/follow`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(unfollowForm),
+ }
+ ).then(d => d.json());
+ expect(unfollowRes.community.local).toBe(false);
+
+ // Check that you are unsubscribed to it locally
+ let followedCommunitiesResAgain: GetFollowedCommunitiesResponse = await fetch(
+ followedCommunitiesUrl,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+
+ expect(followedCommunitiesResAgain.communities.length).toBe(1);
+
+ // Follow again, for other tests
+ let followResAgain: CommunityResponse = await fetch(
+ `${lemmyAlphaApiUrl}/community/follow`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(followForm),
+ }
+ ).then(d => d.json());
+
+ // Make sure the follow response went through
+ expect(followResAgain.community.local).toBe(false);
+ expect(followResAgain.community.name).toBe('main');
+
+ // Also make G follow B
+
+ // Use short-hand search url
+ let searchUrlG = `${lemmyGammaApiUrl}/search?q=!main@lemmy_beta:8550&type_=All&sort=TopAll`;
+
+ let searchResponseG: SearchResponse = await fetch(searchUrlG, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ expect(searchResponseG.communities[0].name).toBe('main');
+
+ let followFormG: FollowCommunityForm = {
+ community_id: searchResponseG.communities[0].id,
+ follow: true,
+ auth: lemmyGammaAuth,
+ };
+
+ let followResG: CommunityResponse = await fetch(
+ `${lemmyGammaApiUrl}/community/follow`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(followFormG),
+ }
+ ).then(d => d.json());
+
+ // Make sure the follow response went through
+ expect(followResG.community.local).toBe(false);
+ expect(followResG.community.name).toBe('main');
+
+ // Check that you are subscribed to it locally
+ let followedCommunitiesUrlG = `${lemmyGammaApiUrl}/user/followed_communities?&auth=${lemmyGammaAuth}`;
+ let followedCommunitiesResG: GetFollowedCommunitiesResponse = await fetch(
+ followedCommunitiesUrlG,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+
+ expect(followedCommunitiesResG.communities[1].community_local).toBe(
+ false
+ );
+ });
+ });
+
+ describe('create test post', () => {
+ test('/u/lemmy_alpha creates a post on /c/lemmy_beta/main, its on both instances', async () => {
+ let name = 'A jest test federated post';
+ let postForm: PostForm = {
+ name,
+ auth: lemmyAlphaAuth,
+ community_id: 3,
+ creator_id: 2,
+ nsfw: false,
+ };
+
+ let createResponse: PostResponse = await fetch(
+ `${lemmyAlphaApiUrl}/post`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(postForm),
+ }
+ ).then(d => d.json());
+
+ let unlikePostForm: CreatePostLikeForm = {
+ post_id: createResponse.post.id,
+ score: 0,
+ auth: lemmyAlphaAuth,
+ };
+ expect(createResponse.post.name).toBe(name);
+ expect(createResponse.post.community_local).toBe(false);
+ expect(createResponse.post.creator_local).toBe(true);
+ expect(createResponse.post.score).toBe(1);
+
+ let unlikePostRes: PostResponse = await fetch(
+ `${lemmyAlphaApiUrl}/post/like`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(unlikePostForm),
+ }
+ ).then(d => d.json());
+ expect(unlikePostRes.post.score).toBe(0);
+
+ let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
+ let getPostRes: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ expect(getPostRes.post.name).toBe(name);
+ expect(getPostRes.post.community_local).toBe(true);
+ expect(getPostRes.post.creator_local).toBe(false);
+ expect(getPostRes.post.score).toBe(0);
+ });
+ });
+
+ describe('update test post', () => {
+ test('/u/lemmy_alpha updates a post on /c/lemmy_beta/main, the update is on both', async () => {
+ let name = 'A jest test federated post, updated';
+ let postForm: PostForm = {
+ name,
+ edit_id: 2,
+ auth: lemmyAlphaAuth,
+ community_id: 3,
+ creator_id: 2,
+ nsfw: false,
+ };
+
+ let updateResponse: PostResponse = await fetch(
+ `${lemmyAlphaApiUrl}/post`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(postForm),
+ }
+ ).then(d => d.json());
+
+ expect(updateResponse.post.name).toBe(name);
+ expect(updateResponse.post.community_local).toBe(false);
+ expect(updateResponse.post.creator_local).toBe(true);
+
+ let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
+ let getPostRes: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ expect(getPostRes.post.name).toBe(name);
+ expect(getPostRes.post.community_local).toBe(true);
+ expect(getPostRes.post.creator_local).toBe(false);
+ });
+ });
+
+ describe('create test comment', () => {
+ test('/u/lemmy_alpha creates a comment on /c/lemmy_beta/main, its on both instances', async () => {
+ let content = 'A jest test federated comment';
+ let commentForm: CommentForm = {
+ content,
+ post_id: 2,
+ auth: lemmyAlphaAuth,
+ };
+
+ let createResponse: CommentResponse = await fetch(
+ `${lemmyAlphaApiUrl}/comment`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(commentForm),
+ }
+ ).then(d => d.json());
+
+ expect(createResponse.comment.content).toBe(content);
+ expect(createResponse.comment.community_local).toBe(false);
+ expect(createResponse.comment.creator_local).toBe(true);
+ expect(createResponse.comment.score).toBe(1);
+
+ // Do an unlike, to test it
+ let unlikeCommentForm: CommentLikeForm = {
+ comment_id: createResponse.comment.id,
+ score: 0,
+ post_id: 2,
+ auth: lemmyAlphaAuth,
+ };
+
+ let unlikeCommentRes: CommentResponse = await fetch(
+ `${lemmyAlphaApiUrl}/comment/like`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(unlikeCommentForm),
+ }
+ ).then(d => d.json());
+
+ expect(unlikeCommentRes.comment.score).toBe(0);
+
+ let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
+ let getPostRes: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ expect(getPostRes.comments[0].content).toBe(content);
+ expect(getPostRes.comments[0].community_local).toBe(true);
+ expect(getPostRes.comments[0].creator_local).toBe(false);
+ expect(getPostRes.comments[0].score).toBe(0);
+
+ // Now do beta replying to that comment, as a child comment
+ let contentBeta = 'A child federated comment from beta';
+ let commentFormBeta: CommentForm = {
+ content: contentBeta,
+ post_id: getPostRes.post.id,
+ parent_id: getPostRes.comments[0].id,
+ auth: lemmyBetaAuth,
+ };
+
+ let createResponseBeta: CommentResponse = await fetch(
+ `${lemmyBetaApiUrl}/comment`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(commentFormBeta),
+ }
+ ).then(d => d.json());
+
+ expect(createResponseBeta.comment.content).toBe(contentBeta);
+ expect(createResponseBeta.comment.community_local).toBe(true);
+ expect(createResponseBeta.comment.creator_local).toBe(true);
+ expect(createResponseBeta.comment.parent_id).toBe(1);
+ expect(createResponseBeta.comment.score).toBe(1);
+
+ // Make sure lemmy alpha sees that new child comment from beta
+ let getPostUrlAlpha = `${lemmyAlphaApiUrl}/post?id=2`;
+ let getPostResAlpha: GetPostResponse = await fetch(getPostUrlAlpha, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ // The newest show up first
+ expect(getPostResAlpha.comments[0].content).toBe(contentBeta);
+ expect(getPostResAlpha.comments[0].community_local).toBe(false);
+ expect(getPostResAlpha.comments[0].creator_local).toBe(false);
+ expect(getPostResAlpha.comments[0].score).toBe(1);
+
+ // Lemmy alpha responds to their own comment, but mentions lemmy beta.
+ // Make sure lemmy beta gets that in their inbox.
+ let mentionContent = 'A test mention of @lemmy_beta@lemmy_beta:8550';
+ let mentionCommentForm: CommentForm = {
+ content: mentionContent,
+ post_id: 2,
+ parent_id: createResponse.comment.id,
+ auth: lemmyAlphaAuth,
+ };
+
+ let createMentionRes: CommentResponse = await fetch(
+ `${lemmyAlphaApiUrl}/comment`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(mentionCommentForm),
+ }
+ ).then(d => d.json());
+
+ expect(createMentionRes.comment.content).toBe(mentionContent);
+ expect(createMentionRes.comment.community_local).toBe(false);
+ expect(createMentionRes.comment.creator_local).toBe(true);
+ expect(createMentionRes.comment.score).toBe(1);
+
+ // Make sure lemmy beta sees that new mention
+ let getMentionUrl = `${lemmyBetaApiUrl}/user/mention?sort=New&unread_only=false&auth=${lemmyBetaAuth}`;
+ let getMentionsRes: GetUserMentionsResponse = await fetch(getMentionUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ // The newest show up first
+ expect(getMentionsRes.mentions[0].content).toBe(mentionContent);
+ expect(getMentionsRes.mentions[0].community_local).toBe(true);
+ expect(getMentionsRes.mentions[0].creator_local).toBe(false);
+ expect(getMentionsRes.mentions[0].score).toBe(1);
+ });
+ });
+
+ describe('update test comment', () => {
+ test('/u/lemmy_alpha updates a comment on /c/lemmy_beta/main, its on both instances', async () => {
+ let content = 'A jest test federated comment update';
+ let commentForm: CommentForm = {
+ content,
+ post_id: 2,
+ edit_id: 1,
+ auth: lemmyAlphaAuth,
+ creator_id: 2,
+ };
+
+ let updateResponse: CommentResponse = await fetch(
+ `${lemmyAlphaApiUrl}/comment`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(commentForm),
+ }
+ ).then(d => d.json());
+
+ expect(updateResponse.comment.content).toBe(content);
+ expect(updateResponse.comment.community_local).toBe(false);
+ expect(updateResponse.comment.creator_local).toBe(true);
+
+ let getPostUrl = `${lemmyBetaApiUrl}/post?id=2`;
+ let getPostRes: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ expect(getPostRes.comments[2].content).toBe(content);
+ expect(getPostRes.comments[2].community_local).toBe(true);
+ expect(getPostRes.comments[2].creator_local).toBe(false);
+ });
+ });
+
+ describe('delete things', () => {
+ test('/u/lemmy_beta deletes and undeletes a federated comment, post, and community, lemmy_alpha sees its deleted.', async () => {
+ // Create a test community
+ let communityName = 'test_community';
+ let communityForm: CommunityForm = {
+ name: communityName,
+ title: communityName,
+ category_id: 1,
+ nsfw: false,
+ auth: lemmyBetaAuth,
+ };
+
+ let createCommunityRes: CommunityResponse = await fetch(
+ `${lemmyBetaApiUrl}/community`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(communityForm),
+ }
+ ).then(d => d.json());
+
+ expect(createCommunityRes.community.name).toBe(communityName);
+
+ // Cache it on lemmy_alpha
+ let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy_beta:8550/c/${communityName}&type_=All&sort=TopAll`;
+ let searchResponse: SearchResponse = await fetch(searchUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ let communityOnAlphaId = searchResponse.communities[0].id;
+
+ // Follow it
+ let followForm: FollowCommunityForm = {
+ community_id: communityOnAlphaId,
+ follow: true,
+ auth: lemmyAlphaAuth,
+ };
+
+ let followRes: CommunityResponse = await fetch(
+ `${lemmyAlphaApiUrl}/community/follow`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(followForm),
+ }
+ ).then(d => d.json());
+
+ // Make sure the follow response went through
+ expect(followRes.community.local).toBe(false);
+ expect(followRes.community.name).toBe(communityName);
+
+ // Lemmy beta creates a test post
+ let postName = 'A jest test post with delete';
+ let createPostForm: PostForm = {
+ name: postName,
+ auth: lemmyBetaAuth,
+ community_id: createCommunityRes.community.id,
+ creator_id: 2,
+ nsfw: false,
+ };
+
+ let createPostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(createPostForm),
+ }).then(d => d.json());
+ expect(createPostRes.post.name).toBe(postName);
+
+ // Lemmy beta creates a test comment
+ let commentContent = 'A jest test federated comment with delete';
+ let createCommentForm: CommentForm = {
+ content: commentContent,
+ post_id: createPostRes.post.id,
+ auth: lemmyBetaAuth,
+ };
+
+ let createCommentRes: CommentResponse = await fetch(
+ `${lemmyBetaApiUrl}/comment`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(createCommentForm),
+ }
+ ).then(d => d.json());
+
+ expect(createCommentRes.comment.content).toBe(commentContent);
+
+ // lemmy_beta deletes the comment
+ let deleteCommentForm: CommentForm = {
+ content: commentContent,
+ edit_id: createCommentRes.comment.id,
+ post_id: createPostRes.post.id,
+ deleted: true,
+ auth: lemmyBetaAuth,
+ creator_id: createCommentRes.comment.creator_id,
+ };
+
+ let deleteCommentRes: CommentResponse = await fetch(
+ `${lemmyBetaApiUrl}/comment`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(deleteCommentForm),
+ }
+ ).then(d => d.json());
+ expect(deleteCommentRes.comment.deleted).toBe(true);
+
+ // lemmy_alpha sees that the comment is deleted
+ let getPostUrl = `${lemmyAlphaApiUrl}/post?id=3`;
+ let getPostRes: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ expect(getPostRes.comments[0].deleted).toBe(true);
+
+ // lemmy_beta undeletes the comment
+ let undeleteCommentForm: CommentForm = {
+ content: commentContent,
+ edit_id: createCommentRes.comment.id,
+ post_id: createPostRes.post.id,
+ deleted: false,
+ auth: lemmyBetaAuth,
+ creator_id: createCommentRes.comment.creator_id,
+ };
+
+ let undeleteCommentRes: CommentResponse = await fetch(
+ `${lemmyBetaApiUrl}/comment`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(undeleteCommentForm),
+ }
+ ).then(d => d.json());
+ expect(undeleteCommentRes.comment.deleted).toBe(false);
+
+ // lemmy_alpha sees that the comment is undeleted
+ let getPostUndeleteRes: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ expect(getPostUndeleteRes.comments[0].deleted).toBe(false);
+
+ // lemmy_beta deletes the post
+ let deletePostForm: PostForm = {
+ name: postName,
+ edit_id: createPostRes.post.id,
+ auth: lemmyBetaAuth,
+ community_id: createPostRes.post.community_id,
+ creator_id: createPostRes.post.creator_id,
+ nsfw: false,
+ deleted: true,
+ };
+
+ let deletePostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(deletePostForm),
+ }).then(d => d.json());
+ expect(deletePostRes.post.deleted).toBe(true);
+
+ // Make sure lemmy_alpha sees the post is deleted
+ let getPostResAgain: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ expect(getPostResAgain.post.deleted).toBe(true);
+
+ // lemmy_beta undeletes the post
+ let undeletePostForm: PostForm = {
+ name: postName,
+ edit_id: createPostRes.post.id,
+ auth: lemmyBetaAuth,
+ community_id: createPostRes.post.community_id,
+ creator_id: createPostRes.post.creator_id,
+ nsfw: false,
+ deleted: false,
+ };
+
+ let undeletePostRes: PostResponse = await fetch(
+ `${lemmyBetaApiUrl}/post`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(undeletePostForm),
+ }
+ ).then(d => d.json());
+ expect(undeletePostRes.post.deleted).toBe(false);
+
+ // Make sure lemmy_alpha sees the post is undeleted
+ let getPostResAgainTwo: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ expect(getPostResAgainTwo.post.deleted).toBe(false);
+
+ // lemmy_beta deletes the community
+ let deleteCommunityForm: CommunityForm = {
+ name: communityName,
+ title: communityName,
+ category_id: 1,
+ edit_id: createCommunityRes.community.id,
+ nsfw: false,
+ deleted: true,
+ auth: lemmyBetaAuth,
+ };
+
+ let deleteResponse: CommunityResponse = await fetch(
+ `${lemmyBetaApiUrl}/community`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(deleteCommunityForm),
+ }
+ ).then(d => d.json());
+
+ // Make sure the delete went through
+ expect(deleteResponse.community.deleted).toBe(true);
+
+ // Re-get it from alpha, make sure its deleted there too
+ let getCommunityUrl = `${lemmyAlphaApiUrl}/community?id=${communityOnAlphaId}&auth=${lemmyAlphaAuth}`;
+ let getCommunityRes: GetCommunityResponse = await fetch(getCommunityUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ expect(getCommunityRes.community.deleted).toBe(true);
+
+ // lemmy_beta undeletes the community
+ let undeleteCommunityForm: CommunityForm = {
+ name: communityName,
+ title: communityName,
+ category_id: 1,
+ edit_id: createCommunityRes.community.id,
+ nsfw: false,
+ deleted: false,
+ auth: lemmyBetaAuth,
+ };
+
+ let undeleteCommunityRes: CommunityResponse = await fetch(
+ `${lemmyBetaApiUrl}/community`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(undeleteCommunityForm),
+ }
+ ).then(d => d.json());
+
+ // Make sure the delete went through
+ expect(undeleteCommunityRes.community.deleted).toBe(false);
+
+ // Re-get it from alpha, make sure its deleted there too
+ let getCommunityResAgain: GetCommunityResponse = await fetch(
+ getCommunityUrl,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+ expect(getCommunityResAgain.community.deleted).toBe(false);
+ });
+ });
+
+ describe('remove things', () => {
+ test('/u/lemmy_beta removes and unremoves a federated comment, post, and community, lemmy_alpha sees its removed.', async () => {
+ // Create a test community
+ let communityName = 'test_community_rem';
+ let communityForm: CommunityForm = {
+ name: communityName,
+ title: communityName,
+ category_id: 1,
+ nsfw: false,
+ auth: lemmyBetaAuth,
+ };
+
+ let createCommunityRes: CommunityResponse = await fetch(
+ `${lemmyBetaApiUrl}/community`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(communityForm),
+ }
+ ).then(d => d.json());
+
+ expect(createCommunityRes.community.name).toBe(communityName);
+
+ // Cache it on lemmy_alpha
+ let searchUrl = `${lemmyAlphaApiUrl}/search?q=http://lemmy_beta:8550/c/${communityName}&type_=All&sort=TopAll`;
+ let searchResponse: SearchResponse = await fetch(searchUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ let communityOnAlphaId = searchResponse.communities[0].id;
+
+ // Follow it
+ let followForm: FollowCommunityForm = {
+ community_id: communityOnAlphaId,
+ follow: true,
+ auth: lemmyAlphaAuth,
+ };
+
+ let followRes: CommunityResponse = await fetch(
+ `${lemmyAlphaApiUrl}/community/follow`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(followForm),
+ }
+ ).then(d => d.json());
+
+ // Make sure the follow response went through
+ expect(followRes.community.local).toBe(false);
+ expect(followRes.community.name).toBe(communityName);
+
+ // Lemmy beta creates a test post
+ let postName = 'A jest test post with remove';
+ let createPostForm: PostForm = {
+ name: postName,
+ auth: lemmyBetaAuth,
+ community_id: createCommunityRes.community.id,
+ creator_id: 2,
+ nsfw: false,
+ };
+
+ let createPostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(createPostForm),
+ }).then(d => d.json());
+ expect(createPostRes.post.name).toBe(postName);
+
+ // Lemmy beta creates a test comment
+ let commentContent = 'A jest test federated comment with remove';
+ let createCommentForm: CommentForm = {
+ content: commentContent,
+ post_id: createPostRes.post.id,
+ auth: lemmyBetaAuth,
+ };
+
+ let createCommentRes: CommentResponse = await fetch(
+ `${lemmyBetaApiUrl}/comment`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(createCommentForm),
+ }
+ ).then(d => d.json());
+
+ expect(createCommentRes.comment.content).toBe(commentContent);
+
+ // lemmy_beta removes the comment
+ let removeCommentForm: CommentForm = {
+ content: commentContent,
+ edit_id: createCommentRes.comment.id,
+ post_id: createPostRes.post.id,
+ removed: true,
+ auth: lemmyBetaAuth,
+ creator_id: createCommentRes.comment.creator_id,
+ };
+
+ let removeCommentRes: CommentResponse = await fetch(
+ `${lemmyBetaApiUrl}/comment`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(removeCommentForm),
+ }
+ ).then(d => d.json());
+ expect(removeCommentRes.comment.removed).toBe(true);
+
+ // lemmy_alpha sees that the comment is removed
+ let getPostUrl = `${lemmyAlphaApiUrl}/post?id=4`;
+ let getPostRes: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ expect(getPostRes.comments[0].removed).toBe(true);
+
+ // lemmy_beta undeletes the comment
+ let unremoveCommentForm: CommentForm = {
+ content: commentContent,
+ edit_id: createCommentRes.comment.id,
+ post_id: createPostRes.post.id,
+ removed: false,
+ auth: lemmyBetaAuth,
+ creator_id: createCommentRes.comment.creator_id,
+ };
+
+ let unremoveCommentRes: CommentResponse = await fetch(
+ `${lemmyBetaApiUrl}/comment`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(unremoveCommentForm),
+ }
+ ).then(d => d.json());
+ expect(unremoveCommentRes.comment.removed).toBe(false);
+
+ // lemmy_alpha sees that the comment is undeleted
+ let getPostUnremoveRes: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ expect(getPostUnremoveRes.comments[0].removed).toBe(false);
+
+ // lemmy_beta deletes the post
+ let removePostForm: PostForm = {
+ name: postName,
+ edit_id: createPostRes.post.id,
+ auth: lemmyBetaAuth,
+ community_id: createPostRes.post.community_id,
+ creator_id: createPostRes.post.creator_id,
+ nsfw: false,
+ removed: true,
+ };
+
+ let removePostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(removePostForm),
+ }).then(d => d.json());
+ expect(removePostRes.post.removed).toBe(true);
+
+ // Make sure lemmy_alpha sees the post is deleted
+ let getPostResAgain: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ expect(getPostResAgain.post.removed).toBe(true);
+
+ // lemmy_beta unremoves the post
+ let unremovePostForm: PostForm = {
+ name: postName,
+ edit_id: createPostRes.post.id,
+ auth: lemmyBetaAuth,
+ community_id: createPostRes.post.community_id,
+ creator_id: createPostRes.post.creator_id,
+ nsfw: false,
+ removed: false,
+ };
+
+ let unremovePostRes: PostResponse = await fetch(
+ `${lemmyBetaApiUrl}/post`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(unremovePostForm),
+ }
+ ).then(d => d.json());
+ expect(unremovePostRes.post.removed).toBe(false);
+
+ // Make sure lemmy_alpha sees the post is unremoved
+ let getPostResAgainTwo: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ expect(getPostResAgainTwo.post.removed).toBe(false);
+
+ // lemmy_beta deletes the community
+ let removeCommunityForm: CommunityForm = {
+ name: communityName,
+ title: communityName,
+ category_id: 1,
+ edit_id: createCommunityRes.community.id,
+ nsfw: false,
+ removed: true,
+ auth: lemmyBetaAuth,
+ };
+
+ let removeCommunityRes: CommunityResponse = await fetch(
+ `${lemmyBetaApiUrl}/community`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(removeCommunityForm),
+ }
+ ).then(d => d.json());
+
+ // Make sure the delete went through
+ expect(removeCommunityRes.community.removed).toBe(true);
+
+ // Re-get it from alpha, make sure its removed there too
+ let getCommunityUrl = `${lemmyAlphaApiUrl}/community?id=${communityOnAlphaId}&auth=${lemmyAlphaAuth}`;
+ let getCommunityRes: GetCommunityResponse = await fetch(getCommunityUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ expect(getCommunityRes.community.removed).toBe(true);
+
+ // lemmy_beta unremoves the community
+ let unremoveCommunityForm: CommunityForm = {
+ name: communityName,
+ title: communityName,
+ category_id: 1,
+ edit_id: createCommunityRes.community.id,
+ nsfw: false,
+ removed: false,
+ auth: lemmyBetaAuth,
+ };
+
+ let unremoveCommunityRes: CommunityResponse = await fetch(
+ `${lemmyBetaApiUrl}/community`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(unremoveCommunityForm),
+ }
+ ).then(d => d.json());
+
+ // Make sure the delete went through
+ expect(unremoveCommunityRes.community.removed).toBe(false);
+
+ // Re-get it from alpha, make sure its deleted there too
+ let getCommunityResAgain: GetCommunityResponse = await fetch(
+ getCommunityUrl,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+ expect(getCommunityResAgain.community.removed).toBe(false);
+ });
+ });
+
+ describe('private message', () => {
+ test('/u/lemmy_alpha creates/updates/deletes/undeletes a private_message to /u/lemmy_beta, its on both instances', async () => {
+ let content = 'A jest test federated private message';
+ let privateMessageForm: PrivateMessageForm = {
+ content,
+ recipient_id: 3,
+ auth: lemmyAlphaAuth,
+ };
+
+ let createRes: PrivateMessageResponse = await fetch(
+ `${lemmyAlphaApiUrl}/private_message`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(privateMessageForm),
+ }
+ ).then(d => d.json());
+ expect(createRes.message.content).toBe(content);
+ expect(createRes.message.local).toBe(true);
+ expect(createRes.message.creator_local).toBe(true);
+ expect(createRes.message.recipient_local).toBe(false);
+
+ // Get it from beta
+ let getPrivateMessagesUrl = `${lemmyBetaApiUrl}/private_message/list?auth=${lemmyBetaAuth}&unread_only=false`;
+
+ let getPrivateMessagesRes: PrivateMessagesResponse = await fetch(
+ getPrivateMessagesUrl,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+
+ expect(getPrivateMessagesRes.messages[0].content).toBe(content);
+ expect(getPrivateMessagesRes.messages[0].local).toBe(false);
+ expect(getPrivateMessagesRes.messages[0].creator_local).toBe(false);
+ expect(getPrivateMessagesRes.messages[0].recipient_local).toBe(true);
+
+ // lemmy alpha updates the private message
+ let updatedContent = 'A jest test federated private message edited';
+ let updatePrivateMessageForm: EditPrivateMessageForm = {
+ content: updatedContent,
+ edit_id: createRes.message.id,
+ auth: lemmyAlphaAuth,
+ };
+
+ let updateRes: PrivateMessageResponse = await fetch(
+ `${lemmyAlphaApiUrl}/private_message`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(updatePrivateMessageForm),
+ }
+ ).then(d => d.json());
+
+ expect(updateRes.message.content).toBe(updatedContent);
+
+ // Fetch from beta again
+ let getPrivateMessagesUpdatedRes: PrivateMessagesResponse = await fetch(
+ getPrivateMessagesUrl,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+
+ expect(getPrivateMessagesUpdatedRes.messages[0].content).toBe(
+ updatedContent
+ );
+
+ // lemmy alpha deletes the private message
+ let deletePrivateMessageForm: EditPrivateMessageForm = {
+ deleted: true,
+ edit_id: createRes.message.id,
+ auth: lemmyAlphaAuth,
+ };
+
+ let deleteRes: PrivateMessageResponse = await fetch(
+ `${lemmyAlphaApiUrl}/private_message`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(deletePrivateMessageForm),
+ }
+ ).then(d => d.json());
+
+ expect(deleteRes.message.deleted).toBe(true);
+
+ // Fetch from beta again
+ let getPrivateMessagesDeletedRes: PrivateMessagesResponse = await fetch(
+ getPrivateMessagesUrl,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+
+ // The GetPrivateMessages filters out deleted,
+ // even though they are in the actual database.
+ // no reason to show them
+ expect(getPrivateMessagesDeletedRes.messages.length).toBe(0);
+
+ // lemmy alpha undeletes the private message
+ let undeletePrivateMessageForm: EditPrivateMessageForm = {
+ deleted: false,
+ edit_id: createRes.message.id,
+ auth: lemmyAlphaAuth,
+ };
+
+ let undeleteRes: PrivateMessageResponse = await fetch(
+ `${lemmyAlphaApiUrl}/private_message`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(undeletePrivateMessageForm),
+ }
+ ).then(d => d.json());
+
+ expect(undeleteRes.message.deleted).toBe(false);
+
+ // Fetch from beta again
+ let getPrivateMessagesUnDeletedRes: PrivateMessagesResponse = await fetch(
+ getPrivateMessagesUrl,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+
+ expect(getPrivateMessagesUnDeletedRes.messages[0].deleted).toBe(false);
+ });
+ });
+
+ describe('comment_search', () => {
+ test('Create comment on alpha and search it', async () => {
+ let content = 'A jest test federated comment for search';
+ let commentForm: CommentForm = {
+ content,
+ post_id: 1,
+ auth: lemmyAlphaAuth,
+ };
+
+ let createResponse: CommentResponse = await fetch(
+ `${lemmyAlphaApiUrl}/comment`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(commentForm),
+ }
+ ).then(d => d.json());
+
+ let searchUrl = `${lemmyBetaApiUrl}/search?q=${createResponse.comment.ap_id}&type_=All&sort=TopAll`;
+ let searchResponse: SearchResponse = await fetch(searchUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ // TODO: check more fields
+ expect(searchResponse.comments[0].content).toBe(content);
+ });
+ });
+
+ describe('announce', () => {
+ test('A and G subscribe to B (center) A does action, it gets announced to G', async () => {
+ // A and G are already subscribed to B earlier.
+ //
+ let postName = 'A jest test post for announce';
+ let createPostForm: PostForm = {
+ name: postName,
+ auth: lemmyAlphaAuth,
+ community_id: 2,
+ creator_id: 2,
+ nsfw: false,
+ };
+
+ let createPostRes: PostResponse = await fetch(
+ `${lemmyAlphaApiUrl}/post`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(createPostForm),
+ }
+ ).then(d => d.json());
+ expect(createPostRes.post.name).toBe(postName);
+
+ // Make sure that post got announced to Gamma
+ let searchUrl = `${lemmyGammaApiUrl}/search?q=${createPostRes.post.ap_id}&type_=All&sort=TopAll`;
+ let searchResponse: SearchResponse = await fetch(searchUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+ let postId = searchResponse.posts[0].id;
+ expect(searchResponse.posts[0].name).toBe(postName);
+
+ // Create a test comment on Gamma, make sure it gets announced to alpha
+ let commentContent =
+ 'A jest test federated comment announce, lets mention @lemmy_beta@lemmy_beta:8550';
+
+ let commentForm: CommentForm = {
+ content: commentContent,
+ post_id: postId,
+ auth: lemmyGammaAuth,
+ };
+
+ let createCommentRes: CommentResponse = await fetch(
+ `${lemmyGammaApiUrl}/comment`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(commentForm),
+ }
+ ).then(d => d.json());
+
+ expect(createCommentRes.comment.content).toBe(commentContent);
+ expect(createCommentRes.comment.community_local).toBe(false);
+ expect(createCommentRes.comment.creator_local).toBe(true);
+ expect(createCommentRes.comment.score).toBe(1);
+
+ // Get the post from alpha, make sure it has gamma's comment
+ let getPostUrl = `${lemmyAlphaApiUrl}/post?id=5`;
+ let getPostRes: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ expect(getPostRes.comments[0].content).toBe(commentContent);
+ expect(getPostRes.comments[0].community_local).toBe(true);
+ expect(getPostRes.comments[0].creator_local).toBe(false);
+ expect(getPostRes.comments[0].score).toBe(1);
+ });
+ });
+
+ describe('fetch inreplytos', () => {
+ test('A is unsubbed from B, B makes a post, and some embedded comments, A subs to B, B updates the lowest level comment, A fetches both the post and all the inreplyto comments for that post.', async () => {
+ // Check that A is subscribed to B
+ let followedCommunitiesUrl = `${lemmyAlphaApiUrl}/user/followed_communities?&auth=${lemmyAlphaAuth}`;
+ let followedCommunitiesRes: GetFollowedCommunitiesResponse = await fetch(
+ followedCommunitiesUrl,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+ expect(followedCommunitiesRes.communities[1].community_local).toBe(false);
+
+ // A unsubs from B (communities ids 3-5)
+ for (let i = 3; i <= 5; i++) {
+ let unfollowForm: FollowCommunityForm = {
+ community_id: i,
+ follow: false,
+ auth: lemmyAlphaAuth,
+ };
+
+ let unfollowRes: CommunityResponse = await fetch(
+ `${lemmyAlphaApiUrl}/community/follow`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(unfollowForm),
+ }
+ ).then(d => d.json());
+ expect(unfollowRes.community.local).toBe(false);
+ }
+
+ // Check that you are unsubscribed from all of them locally
+ let followedCommunitiesResAgain: GetFollowedCommunitiesResponse = await fetch(
+ followedCommunitiesUrl,
+ {
+ method: 'GET',
+ }
+ ).then(d => d.json());
+ expect(followedCommunitiesResAgain.communities.length).toBe(1);
+
+ // B creates a post, and two comments, should be invisible to A
+ let betaPostName = 'Test post on B, invisible to A at first';
+ let postForm: PostForm = {
+ name: betaPostName,
+ auth: lemmyBetaAuth,
+ community_id: 2,
+ creator_id: 2,
+ nsfw: false,
+ };
+
+ let createPostRes: PostResponse = await fetch(`${lemmyBetaApiUrl}/post`, {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(postForm),
+ }).then(d => d.json());
+ expect(createPostRes.post.name).toBe(betaPostName);
+
+ // B creates a comment, then a child one of that.
+ let parentCommentContent = 'An invisible top level comment from beta';
+ let createParentCommentForm: CommentForm = {
+ content: parentCommentContent,
+ post_id: createPostRes.post.id,
+ auth: lemmyBetaAuth,
+ };
+
+ let createParentCommentRes: CommentResponse = await fetch(
+ `${lemmyBetaApiUrl}/comment`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(createParentCommentForm),
+ }
+ ).then(d => d.json());
+ expect(createParentCommentRes.comment.content).toBe(parentCommentContent);
+
+ let childCommentContent = 'An invisible child comment from beta';
+ let createChildCommentForm: CommentForm = {
+ content: childCommentContent,
+ parent_id: createParentCommentRes.comment.id,
+ post_id: createPostRes.post.id,
+ auth: lemmyBetaAuth,
+ };
+
+ let createChildCommentRes: CommentResponse = await fetch(
+ `${lemmyBetaApiUrl}/comment`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(createChildCommentForm),
+ }
+ ).then(d => d.json());
+ expect(createChildCommentRes.comment.content).toBe(childCommentContent);
+
+ // Follow again, for other tests
+ let searchUrl = `${lemmyAlphaApiUrl}/search?q=!main@lemmy_beta:8550&type_=All&sort=TopAll`;
+
+ let searchResponse: SearchResponse = await fetch(searchUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ expect(searchResponse.communities[0].name).toBe('main');
+
+ let followForm: FollowCommunityForm = {
+ community_id: searchResponse.communities[0].id,
+ follow: true,
+ auth: lemmyAlphaAuth,
+ };
+
+ let followResAgain: CommunityResponse = await fetch(
+ `${lemmyAlphaApiUrl}/community/follow`,
+ {
+ method: 'POST',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(followForm),
+ }
+ ).then(d => d.json());
+
+ // Make sure the follow response went through
+ expect(followResAgain.community.local).toBe(false);
+ expect(followResAgain.community.name).toBe('main');
+
+ let updatedCommentContent = 'An update child comment from beta';
+ let updatedCommentForm: CommentForm = {
+ content: updatedCommentContent,
+ post_id: createPostRes.post.id,
+ edit_id: createChildCommentRes.comment.id,
+ auth: lemmyBetaAuth,
+ creator_id: 2,
+ };
+
+ let updateResponse: CommentResponse = await fetch(
+ `${lemmyBetaApiUrl}/comment`,
+ {
+ method: 'PUT',
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ body: wrapper(updatedCommentForm),
+ }
+ ).then(d => d.json());
+ expect(updateResponse.comment.content).toBe(updatedCommentContent);
+
+ // Make sure that A picked up the post, parent comment, and child comment
+ let getPostUrl = `${lemmyAlphaApiUrl}/post?id=6`;
+ let getPostRes: GetPostResponse = await fetch(getPostUrl, {
+ method: 'GET',
+ }).then(d => d.json());
+
+ expect(getPostRes.post.name).toBe(betaPostName);
+ expect(getPostRes.comments[1].content).toBe(parentCommentContent);
+ expect(getPostRes.comments[0].content).toBe(updatedCommentContent);
+ expect(getPostRes.post.community_local).toBe(false);
+ expect(getPostRes.post.creator_local).toBe(false);
+ });
+ });
+});
+
+function wrapper(form: any): string {
+ return JSON.stringify(form);
+}
user={{
name: admin.name,
avatar: admin.avatar,
+ id: admin.id,
+ local: admin.local,
+ actor_id: admin.actor_id,
}}
/>
</li>
user={{
name: banned.name,
avatar: banned.avatar,
+ id: banned.id,
+ local: banned.local,
+ actor_id: banned.actor_id,
}}
/>
</li>
user={{
name: node.comment.creator_name,
avatar: node.comment.creator_avatar,
+ id: node.comment.creator_id,
+ local: node.comment.creator_local,
+ actor_id: node.comment.creator_actor_id,
}}
/>
</span>
} from '../interfaces';
import { WebSocketService } from '../services';
import { wsJsonToRes, toast } from '../utils';
+import { CommunityLink } from './community-link';
import { i18n } from '../i18next';
declare const Sortable: any;
{this.state.communities.map(community => (
<tr>
<td>
- <Link to={`/c/${community.name}`}>
- {community.name}
- </Link>
+ <CommunityLink community={community} />
</td>
<td class="d-none d-lg-table-cell">{community.title}</td>
<td>{community.category_name}</td>
--- /dev/null
+import { Component } from 'inferno';
+import { Link } from 'inferno-router';
+import { Community } from '../interfaces';
+import { hostname } from '../utils';
+
+interface CommunityOther {
+ name: string;
+ id?: number; // Necessary if its federated
+ local?: boolean;
+ actor_id?: string;
+}
+
+interface CommunityLinkProps {
+ community: Community | CommunityOther;
+ realLink?: boolean;
+}
+
+export class CommunityLink extends Component<CommunityLinkProps, any> {
+ constructor(props: any, context: any) {
+ super(props, context);
+ }
+
+ render() {
+ let community = this.props.community;
+ let name_: string, link: string;
+ let local = community.local == null ? true : community.local;
+ if (local) {
+ name_ = community.name;
+ link = `/c/${community.name}`;
+ } else {
+ name_ = `${community.name}@${hostname(community.actor_id)}`;
+ link = !this.props.realLink
+ ? `/community/${community.id}`
+ : community.actor_id;
+ }
+ return <Link to={link}>{name_}</Link>;
+ }
+}
removed: null,
nsfw: false,
deleted: null,
+ local: null,
+ actor_id: null,
+ last_refreshed_at: null,
+ creator_actor_id: null,
+ creator_local: null,
},
moderators: [],
admins: [],
import { DataTypeSelect } from './data-type-select';
import { SiteForm } from './site-form';
import { UserListing } from './user-listing';
+import { CommunityLink } from './community-link';
import {
wsJsonToRes,
repoUrl,
<ul class="list-inline">
{this.state.subscribedCommunities.map(community => (
<li class="list-inline-item">
- <Link to={`/c/${community.community_name}`}>
- {community.community_name}
- </Link>
+ <CommunityLink
+ community={{
+ name: community.community_name,
+ id: community.community_id,
+ local: community.community_local,
+ actor_id: community.community_actor_id,
+ }}
+ />
</li>
))}
</ul>
<ul class="list-inline">
{this.state.trendingCommunities.map(community => (
<li class="list-inline-item">
- <Link to={`/c/${community.name}`}>{community.name}</Link>
+ <CommunityLink community={community} />
</li>
))}
</ul>
user={{
name: admin.name,
avatar: admin.avatar,
+ local: admin.local,
+ actor_id: admin.actor_id,
+ id: admin.id,
}}
/>
</li>
setupTribute,
setupTippy,
emojiPicker,
+ hostname,
pictrsDeleteToast,
} from '../utils';
import autosize from 'autosize';
>
<option>{i18n.t('select_a_community')}</option>
{this.state.communities.map(community => (
- <option value={community.id}>{community.name}</option>
+ <option value={community.id}>
+ {community.local
+ ? community.name
+ : `${hostname(community.actor_id)}/${community.name}`}
+ </option>
))}
</select>
</div>
import { PostForm } from './post-form';
import { IFramelyCard } from './iframely-card';
import { UserListing } from './user-listing';
+import { CommunityLink } from './community-link';
import {
md,
mdToHtml,
getUnixTime,
pictrsImage,
setupTippy,
+ hostname,
previewLines,
} from '../utils';
import { i18n } from '../i18next';
</Link>
)}
</h5>
- {post.url &&
- !(new URL(post.url).hostname == window.location.hostname) && (
- <small class="d-inline-block">
- <a
- className="ml-2 text-muted font-italic"
- href={post.url}
- target="_blank"
- title={post.url}
- >
- {new URL(post.url).hostname}
- <svg class="ml-1 icon icon-inline">
- <use xlinkHref="#icon-external-link"></use>
- </svg>
- </a>
- </small>
- )}
+ {post.url && !(hostname(post.url) == window.location.hostname) && (
+ <small class="d-inline-block">
+ <a
+ className="ml-2 text-muted font-italic"
+ href={post.url}
+ target="_blank"
+ title={post.url}
+ >
+ {hostname(post.url)}
+ <svg class="ml-1 icon icon-inline">
+ <use xlinkHref="#icon-external-link"></use>
+ </svg>
+ </a>
+ </small>
+ )}
{(isImage(post.url) || this.props.post.thumbnail_url) && (
<>
{!this.state.imageExpanded ? (
user={{
name: post.creator_name,
avatar: post.creator_avatar,
+ id: post.creator_id,
+ local: post.creator_local,
+ actor_id: post.creator_actor_id,
}}
/>
{this.isMod && (
{this.props.showCommunity && (
<span>
<span> {i18n.t('to')} </span>
- <Link to={`/c/${post.community_name}`}>
- {post.community_name}
- </Link>
+ <CommunityLink
+ community={{
+ name: post.community_name,
+ id: post.community_id,
+ local: post.community_local,
+ actor_id: post.community_actor_id,
+ }}
+ />
</span>
)}
</li>
user={{
name: this.state.recipient.name,
avatar: this.state.recipient.avatar,
+ id: this.state.recipient.id,
+ local: this.state.recipient.local,
+ actor_id: this.state.recipient.actor_id,
}}
/>
</div>
fetchLimit,
routeSearchTypeToEnum,
routeSortTypeToEnum,
- pictrsAvatarThumbnail,
- showAvatars,
toast,
createCommentLikeRes,
createPostLikeFindRes,
} from '../utils';
import { PostListing } from './post-listing';
import { UserListing } from './user-listing';
+import { CommunityLink } from './community-link';
import { SortSelect } from './sort-select';
import { CommentNodes } from './comment-nodes';
import { i18n } from '../i18next';
nextProps.history.action == 'POP' ||
nextProps.history.action == 'PUSH'
) {
- this.state = this.emptyState;
this.state.q = this.getSearchQueryFromProps(nextProps);
this.state.type_ = this.getSearchTypeFromProps(nextProps);
this.state.sort = this.getSortTypeFromProps(nextProps);
/>
)}
{i.type_ == 'communities' && (
- <div>
- <span>
- <Link to={`/c/${(i.data as Community).name}`}>{`/c/${
- (i.data as Community).name
- }`}</Link>
- </span>
- <span>{` - ${(i.data as Community).title} - ${
- (i.data as Community).number_of_subscribers
- } subscribers`}</span>
- </div>
+ <div>{this.communityListing(i.data as Community)}</div>
)}
{i.type_ == 'users' && (
<div>
<>
{this.state.searchResponse.communities.map(community => (
<div class="row">
- <div class="col-12">
- <span>
- <Link
- to={`/c/${community.name}`}
- >{`/c/${community.name}`}</Link>
- </span>
- <span>{` - ${community.title} - ${community.number_of_subscribers} subscribers`}</span>
- </div>
+ <div class="col-12">{this.communityListing(community)}</div>
</div>
))}
</>
);
}
+ communityListing(community: Community) {
+ return (
+ <>
+ <span>
+ <CommunityLink community={community} />
+ </span>
+ <span>{` - ${community.title} -
+ ${i18n.t('number_of_subscribers', {
+ count: community.number_of_subscribers,
+ })}
+ `}</span>
+ </>
+ );
+ }
+
users() {
return (
<>
UserView,
} from '../interfaces';
import { WebSocketService, UserService } from '../services';
-import {
- mdToHtml,
- getUnixTime,
- pictrsAvatarThumbnail,
- showAvatars,
-} from '../utils';
+import { mdToHtml, getUnixTime, hostname } from '../utils';
import { CommunityForm } from './community-form';
import { UserListing } from './user-listing';
+import { CommunityLink } from './community-link';
import { i18n } from '../i18next';
interface SidebarProps {
sidebar() {
let community = this.props.community;
+ let name_: string, link: string;
+
+ if (community.local) {
+ name_ = community.name;
+ link = `/c/${community.name}`;
+ } else {
+ name_ = `${community.name}@${hostname(community.actor_id)}`;
+ link = community.actor_id;
+ }
return (
<div>
<div class="card border-secondary mb-3">
</small>
)}
</h5>
- <Link className="text-muted" to={`/c/${community.name}`}>
- /c/{community.name}
- </Link>
+ <CommunityLink community={community} realLink />
<ul class="list-inline mb-1 text-muted font-weight-bold">
{this.canMod && (
<>
user={{
name: mod.user_name,
avatar: mod.avatar,
+ id: mod.user_id,
+ local: mod.user_local,
+ actor_id: mod.user_actor_id,
}}
/>
</li>
))}
</ul>
+ {/* TODO the to= needs to be able to handle community_ids as well, since they're federated */}
<Link
class={`btn btn-sm btn-secondary btn-block mb-3 ${
(community.deleted || community.removed) && 'no-click'
import { Component } from 'inferno';
import { Link } from 'inferno-router';
import { UserView } from '../interfaces';
-import { pictrsAvatarThumbnail, showAvatars } from '../utils';
+import { pictrsAvatarThumbnail, showAvatars, hostname } from '../utils';
interface UserOther {
name: string;
+ id?: number; // Necessary if its federated
avatar?: string;
+ local?: boolean;
+ actor_id?: string;
}
interface UserListingProps {
user: UserView | UserOther;
+ realLink?: boolean;
}
export class UserListing extends Component<UserListingProps, any> {
render() {
let user = this.props.user;
+ let local = user.local == null ? true : user.local;
+ let name_: string, link: string;
+
+ if (local) {
+ name_ = user.name;
+ link = `/u/${user.name}`;
+ } else {
+ name_ = `${user.name}@${hostname(user.actor_id)}`;
+ link = !this.props.realLink ? `/user/${user.id}` : user.actor_id;
+ }
+
return (
- <Link className="text-body font-weight-bold" to={`/u/${user.name}`}>
+ <Link className="text-body font-weight-bold" to={link}>
{user.avatar && showAvatars() && (
<img
height="32"
class="rounded-circle mr-2"
/>
)}
- <span>{user.name}</span>
+ <span>{name_}</span>
</Link>
);
}
setupTippy,
} from '../utils';
import { PostListing } from './post-listing';
+import { UserListing } from './user-listing';
import { SortSelect } from './sort-select';
import { ListingTypeSelect } from './listing-type-select';
import { CommentNodes } from './comment-nodes';
avatar: null,
show_avatars: null,
send_notifications_to_email: null,
+ actor_id: null,
+ local: null,
},
user_id: null,
username: null,
<div class="card-body">
<h5>
<ul class="list-inline mb-0">
- <li className="list-inline-item">{user.name}</li>
+ <li className="list-inline-item">
+ <UserListing user={user} realLink />
+ </li>
{user.banned && (
<li className="list-inline-item badge badge-danger">
{i18n.t('banned')}
const host = `${window.location.hostname}`;
const port = `${
- window.location.port == '4444' ? '8536' : window.location.port
+ window.location.port == '4444' ? '8540' : window.location.port
}`;
const endpoint = `${host}:${port}`;
export interface UserView {
id: number;
+ actor_id: string;
name: string;
avatar?: string;
email?: string;
matrix_user_id?: string;
+ bio?: string;
+ local: boolean;
published: string;
number_of_posts: number;
post_score: number;
export interface CommunityUser {
id: number;
user_id: number;
+ user_actor_id: string;
+ user_local: boolean;
user_name: string;
avatar?: string;
community_id: number;
+ community_actor_id: string;
+ community_local: boolean;
community_name: string;
published: string;
}
export interface Community {
id: number;
+ actor_id: string;
+ local: boolean;
name: string;
title: string;
description?: string;
nsfw: boolean;
published: string;
updated?: string;
+ creator_actor_id: string;
+ creator_local: boolean;
+ last_refreshed_at: string;
creator_name: string;
creator_avatar?: string;
category_name: string;
embed_description?: string;
embed_html?: string;
thumbnail_url?: string;
+ ap_id: string;
+ local: boolean;
nsfw: boolean;
banned: boolean;
banned_from_community: boolean;
published: string;
updated?: string;
+ creator_actor_id: string;
+ creator_local: boolean;
creator_name: string;
creator_avatar?: string;
+ community_actor_id: string;
+ community_local: boolean;
community_name: string;
community_removed: boolean;
community_deleted: boolean;
export interface Comment {
id: number;
+ ap_id: string;
+ local: boolean;
creator_id: number;
post_id: number;
parent_id?: number;
published: string;
updated?: string;
community_id: number;
+ community_actor_id: string;
+ community_local: boolean;
community_name: string;
banned: boolean;
banned_from_community: boolean;
+ creator_actor_id: string;
+ creator_local: boolean;
creator_name: string;
creator_avatar?: string;
score: number;
saved?: boolean;
user_mention_id?: number; // For mention type
recipient_id?: number;
+ recipient_actor_id?: string;
+ recipient_local?: boolean;
depth?: number;
}
read: boolean;
published: string;
updated?: string;
+ ap_id: string;
+ local: boolean;
creator_name: string;
creator_avatar?: string;
+ creator_actor_id: string;
+ creator_local: boolean;
recipient_name: string;
recipient_avatar?: string;
+ recipient_actor_id: string;
+ recipient_local: boolean;
}
export enum BanType {
post_id: number;
parent_id?: number;
edit_id?: number;
- creator_id: number;
+ creator_id?: number;
removed?: boolean;
deleted?: boolean;
reason?: string;
{
trigger: '@',
selectTemplate: (item: any) => {
- return `[/u/${item.original.key}](/u/${item.original.key})`;
+ let link = item.original.local
+ ? `[${item.original.key}](/u/${item.original.name})`
+ : `[${item.original.key}](/user/${item.original.id})`;
+ return link;
},
values: (text: string, cb: any) => {
userSearch(text, (users: any) => cb(users));
// Communities
{
- trigger: '#',
+ trigger: '!',
selectTemplate: (item: any) => {
- return `[/c/${item.original.key}](/c/${item.original.key})`;
+ let link = item.original.local
+ ? `[${item.original.key}](/c/${item.original.name})`
+ : `[${item.original.key}](/community/${item.original.id})`;
+ return link;
},
values: (text: string, cb: any) => {
communitySearch(text, (communities: any) => cb(communities));
if (res.op == UserOperation.Search) {
let data = res.data as SearchResponse;
let users = data.users.map(u => {
- return { key: u.name };
+ return {
+ key: `@${u.name}@${hostname(u.actor_id)}`,
+ name: u.name,
+ local: u.local,
+ id: u.id,
+ };
});
cb(users);
this.userSub.unsubscribe();
let res = wsJsonToRes(msg);
if (res.op == UserOperation.Search) {
let data = res.data as SearchResponse;
- let communities = data.communities.map(u => {
- return { key: u.name };
+ let communities = data.communities.map(c => {
+ return {
+ key: `!${c.name}@${hostname(c.actor_id)}`,
+ name: c.name,
+ local: c.local,
+ id: c.id,
+ };
});
cb(communities);
this.communitySub.unsubscribe();
.join('\n');
}
+export function hostname(url: string): string {
+ let cUrl = new URL(url);
+ return window.location.port
+ ? `${cUrl.hostname}:${cUrl.port}`
+ : `${cUrl.hostname}`;
+}
+
function canUseWebP() {
// TODO pictshare might have a webp conversion bug, try disabling this
return false;
dependencies:
"@babel/highlight" "^7.8.3"
+"@babel/core@^7.1.0", "@babel/core@^7.7.5":
+ version "7.9.0"
+ resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.9.0.tgz#ac977b538b77e132ff706f3b8a4dbad09c03c56e"
+ integrity sha512-kWc7L0fw1xwvI0zi8OKVBuxRVefwGOrKSQMvrQ3dW+bIIavBY3/NpXmpjMy7bQnLgwgzWQZ8TlM57YHpHNHz4w==
+ dependencies:
+ "@babel/code-frame" "^7.8.3"
+ "@babel/generator" "^7.9.0"
+ "@babel/helper-module-transforms" "^7.9.0"
+ "@babel/helpers" "^7.9.0"
+ "@babel/parser" "^7.9.0"
+ "@babel/template" "^7.8.6"
+ "@babel/traverse" "^7.9.0"
+ "@babel/types" "^7.9.0"
+ convert-source-map "^1.7.0"
+ debug "^4.1.0"
+ gensync "^1.0.0-beta.1"
+ json5 "^2.1.2"
+ lodash "^4.17.13"
+ resolve "^1.3.2"
+ semver "^5.4.1"
+ source-map "^0.5.0"
+
"@babel/generator@^7.8.6":
version "7.8.8"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.8.8.tgz#cdcd58caab730834cee9eeadb729e833b625da3e"
lodash "^4.17.13"
source-map "^0.5.0"
+"@babel/generator@^7.9.0", "@babel/generator@^7.9.5":
+ version "7.9.5"
+ resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.9.5.tgz#27f0917741acc41e6eaaced6d68f96c3fa9afaf9"
+ integrity sha512-GbNIxVB3ZJe3tLeDm1HSn2AhuD/mVcyLDpgtLXa5tplmWrJdF/elxB56XNqCuD6szyNkDi6wuoKXln3QeBmCHQ==
+ dependencies:
+ "@babel/types" "^7.9.5"
+ jsesc "^2.5.1"
+ lodash "^4.17.13"
+ source-map "^0.5.0"
+
"@babel/helper-function-name@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.8.3.tgz#eeeb665a01b1f11068e9fb86ad56a1cb1a824cca"
"@babel/template" "^7.8.3"
"@babel/types" "^7.8.3"
+"@babel/helper-function-name@^7.9.5":
+ version "7.9.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.9.5.tgz#2b53820d35275120e1874a82e5aabe1376920a5c"
+ integrity sha512-JVcQZeXM59Cd1qanDUxv9fgJpt3NeKUaqBqUEvfmQ+BCOKq2xUgaWZW2hr0dkbyJgezYuplEoh5knmrnS68efw==
+ dependencies:
+ "@babel/helper-get-function-arity" "^7.8.3"
+ "@babel/template" "^7.8.3"
+ "@babel/types" "^7.9.5"
+
"@babel/helper-get-function-arity@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.8.3.tgz#b894b947bd004381ce63ea1db9f08547e920abd5"
dependencies:
"@babel/types" "^7.8.3"
+"@babel/helper-member-expression-to-functions@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.8.3.tgz#659b710498ea6c1d9907e0c73f206eee7dadc24c"
+ integrity sha512-fO4Egq88utkQFjbPrSHGmGLFqmrshs11d46WI+WZDESt7Wu7wN2G2Iu+NMMZJFDOVRHAMIkB5SNh30NtwCA7RA==
+ dependencies:
+ "@babel/types" "^7.8.3"
+
+"@babel/helper-module-imports@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.8.3.tgz#7fe39589b39c016331b6b8c3f441e8f0b1419498"
+ integrity sha512-R0Bx3jippsbAEtzkpZ/6FIiuzOURPcMjHp+Z6xPe6DtApDJx+w7UYyOLanZqO8+wKR9G10s/FmHXvxaMd9s6Kg==
+ dependencies:
+ "@babel/types" "^7.8.3"
+
+"@babel/helper-module-transforms@^7.9.0":
+ version "7.9.0"
+ resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.9.0.tgz#43b34dfe15961918707d247327431388e9fe96e5"
+ integrity sha512-0FvKyu0gpPfIQ8EkxlrAydOWROdHpBmiCiRwLkUiBGhCUPRRbVD2/tm3sFr/c/GWFrQ/ffutGUAnx7V0FzT2wA==
+ dependencies:
+ "@babel/helper-module-imports" "^7.8.3"
+ "@babel/helper-replace-supers" "^7.8.6"
+ "@babel/helper-simple-access" "^7.8.3"
+ "@babel/helper-split-export-declaration" "^7.8.3"
+ "@babel/template" "^7.8.6"
+ "@babel/types" "^7.9.0"
+ lodash "^4.17.13"
+
+"@babel/helper-optimise-call-expression@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.8.3.tgz#7ed071813d09c75298ef4f208956006b6111ecb9"
+ integrity sha512-Kag20n86cbO2AvHca6EJsvqAd82gc6VMGule4HwebwMlwkpXuVqrNRj6CkCV2sKxgi9MyAUnZVnZ6lJ1/vKhHQ==
+ dependencies:
+ "@babel/types" "^7.8.3"
+
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.8.3.tgz#9ea293be19babc0f52ff8ca88b34c3611b208670"
+ integrity sha512-j+fq49Xds2smCUNYmEHF9kGNkhbet6yVIBp4e6oeQpH1RUs/Ir06xUKzDjDkGcaaokPiTNs2JBWHjaE4csUkZQ==
+
+"@babel/helper-replace-supers@^7.8.6":
+ version "7.8.6"
+ resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz#5ada744fd5ad73203bf1d67459a27dcba67effc8"
+ integrity sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==
+ dependencies:
+ "@babel/helper-member-expression-to-functions" "^7.8.3"
+ "@babel/helper-optimise-call-expression" "^7.8.3"
+ "@babel/traverse" "^7.8.6"
+ "@babel/types" "^7.8.6"
+
+"@babel/helper-simple-access@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.8.3.tgz#7f8109928b4dab4654076986af575231deb639ae"
+ integrity sha512-VNGUDjx5cCWg4vvCTR8qQ7YJYZ+HBjxOgXEl7ounz+4Sn7+LMD3CFrCTEU6/qXKbA2nKg21CwhhBzO0RpRbdCw==
+ dependencies:
+ "@babel/template" "^7.8.3"
+ "@babel/types" "^7.8.3"
+
"@babel/helper-split-export-declaration@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.8.3.tgz#31a9f30070f91368a7182cf05f831781065fc7a9"
dependencies:
"@babel/types" "^7.8.3"
+"@babel/helper-validator-identifier@^7.9.5":
+ version "7.9.5"
+ resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.9.5.tgz#90977a8e6fbf6b431a7dc31752eee233bf052d80"
+ integrity sha512-/8arLKUFq882w4tWGj9JYzRpAlZgiWUJ+dtteNTDqrRBz9Iguck9Rn3ykuBDoUwh2TO4tSAJlrxDUOXWklJe4g==
+
+"@babel/helpers@^7.9.0":
+ version "7.9.2"
+ resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.9.2.tgz#b42a81a811f1e7313b88cba8adc66b3d9ae6c09f"
+ integrity sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==
+ dependencies:
+ "@babel/template" "^7.8.3"
+ "@babel/traverse" "^7.9.0"
+ "@babel/types" "^7.9.0"
+
"@babel/highlight@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.8.3.tgz#28f173d04223eaaa59bc1d439a3836e6d1265797"
esutils "^2.0.2"
js-tokens "^4.0.0"
+"@babel/parser@^7.1.0", "@babel/parser@^7.7.5", "@babel/parser@^7.9.0":
+ version "7.9.4"
+ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.9.4.tgz#68a35e6b0319bbc014465be43828300113f2f2e8"
+ integrity sha512-bC49otXX6N0/VYhgOMh4gnP26E9xnDZK3TmbNpxYzzz9BQLBosQwfyOe9/cXUU3txYhTzLCbcqd5c8y/OmCjHA==
+
"@babel/parser@^7.7.0", "@babel/parser@^7.8.6":
version "7.8.8"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.8.tgz#4c3b7ce36db37e0629be1f0d50a571d2f86f6cd4"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.8.4.tgz#d1dbe64691d60358a974295fa53da074dd2ce8e8"
integrity sha512-0fKu/QqildpXmPVaRBoXOlyBb3MC+J0A66x97qEfLOMkn3u6nfY5esWogQwi/K0BjASYy4DbnsEWnpNL6qT5Mw==
+"@babel/plugin-syntax-async-generators@^7.8.4":
+ version "7.8.4"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
+ integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-bigint@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea"
+ integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-class-properties@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.8.3.tgz#6cb933a8872c8d359bfde69bbeaae5162fd1e8f7"
+ integrity sha512-UcAyQWg2bAN647Q+O811tG9MrJ38Z10jjhQdKNAL8fsyPzE3cCN/uT+f55cFVY4aGO4jqJAvmqsuY3GQDwAoXg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.3"
+
+"@babel/plugin-syntax-json-strings@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
+ integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-logical-assignment-operators@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.8.3.tgz#3995d7d7ffff432f6ddc742b47e730c054599897"
+ integrity sha512-Zpg2Sgc++37kuFl6ppq2Q7Awc6E6AIW671x5PY8E/f7MCIyPPGK/EoeZXvvY3P42exZ3Q4/t3YOzP/HiN79jDg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.3"
+
+"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
+ integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-numeric-separator@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.8.3.tgz#0e3fb63e09bea1b11e96467271c8308007e7c41f"
+ integrity sha512-H7dCMAdN83PcCmqmkHB5dtp+Xa9a6LKSvA2hiFBC/5alSHxM5VgWZXFqDi0YFe8XNGT6iCa+z4V4zSt/PdZ7Dw==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.3"
+
+"@babel/plugin-syntax-object-rest-spread@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
+ integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-catch-binding@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
+ integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-chaining@^7.8.3":
+ version "7.8.3"
+ resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
+ integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.8.0"
+
"@babel/runtime-corejs3@^7.7.4":
version "7.8.4"
resolved "https://registry.yarnpkg.com/@babel/runtime-corejs3/-/runtime-corejs3-7.8.4.tgz#ccc4e042e2fae419c67fa709567e5d2179ed3940"
dependencies:
regenerator-runtime "^0.13.2"
+"@babel/template@^7.7.4", "@babel/template@^7.8.6":
+ version "7.8.6"
+ resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.6.tgz#86b22af15f828dfb086474f964dcc3e39c43ce2b"
+ integrity sha512-zbMsPMy/v0PWFZEhQJ66bqjhH+z0JgMoBWuikXybgG3Gkd/3t5oQ1Rw2WQhnSrsOmsKXnZOx15tkC4qON/+JPg==
+ dependencies:
+ "@babel/code-frame" "^7.8.3"
+ "@babel/parser" "^7.8.6"
+ "@babel/types" "^7.8.6"
+
"@babel/template@^7.8.3":
version "7.8.3"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.8.3.tgz#e02ad04fe262a657809327f578056ca15fd4d1b8"
"@babel/parser" "^7.8.3"
"@babel/types" "^7.8.3"
+"@babel/traverse@^7.1.0", "@babel/traverse@^7.7.4", "@babel/traverse@^7.8.6", "@babel/traverse@^7.9.0":
+ version "7.9.5"
+ resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.9.5.tgz#6e7c56b44e2ac7011a948c21e283ddd9d9db97a2"
+ integrity sha512-c4gH3jsvSuGUezlP6rzSJ6jf8fYjLj3hsMZRx/nX0h+fmHN0w+ekubRrHPqnMec0meycA2nwCsJ7dC8IPem2FQ==
+ dependencies:
+ "@babel/code-frame" "^7.8.3"
+ "@babel/generator" "^7.9.5"
+ "@babel/helper-function-name" "^7.9.5"
+ "@babel/helper-split-export-declaration" "^7.8.3"
+ "@babel/parser" "^7.9.0"
+ "@babel/types" "^7.9.5"
+ debug "^4.1.0"
+ globals "^11.1.0"
+ lodash "^4.17.13"
+
"@babel/traverse@^7.7.0":
version "7.8.6"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.8.6.tgz#acfe0c64e1cd991b3e32eae813a6eb564954b5ff"
globals "^11.1.0"
lodash "^4.17.13"
+"@babel/types@^7.0.0", "@babel/types@^7.3.0", "@babel/types@^7.9.0", "@babel/types@^7.9.5":
+ version "7.9.5"
+ resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.9.5.tgz#89231f82915a8a566a703b3b20133f73da6b9444"
+ integrity sha512-XjnvNqenk818r5zMaba+sLQjnbda31UfUURv3ei0qPQw4u+j2jMyJ5b11y8ZHYTRSI3NnInQkkkRT4fLqqPdHg==
+ dependencies:
+ "@babel/helper-validator-identifier" "^7.9.5"
+ lodash "^4.17.13"
+ to-fast-properties "^2.0.0"
+
"@babel/types@^7.7.0", "@babel/types@^7.8.6", "@babel/types@^7.8.7":
version "7.8.7"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.8.7.tgz#1fc9729e1acbb2337d5b6977a63979b4819f5d1d"
lodash "^4.17.13"
to-fast-properties "^2.0.0"
+"@bcoe/v8-coverage@^0.2.3":
+ version "0.2.3"
+ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
+ integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
+
+"@cnakazawa/watch@^1.0.3":
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
+ integrity sha512-v9kIhKwjeZThiWrLmj0y17CWoyddASLj9O2yvbZkbvw/N3rWOYy9zkV66ursAoVr0mV15bL8g0c4QZUE6cdDoQ==
+ dependencies:
+ exec-sh "^0.3.2"
+ minimist "^1.2.0"
+
"@fortawesome/fontawesome-common-types@^0.2.28":
version "0.2.28"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-0.2.28.tgz#1091bdfe63b3f139441e9cba27aa022bff97d8b2"
dependencies:
"@fortawesome/fontawesome-common-types" "^0.2.28"
+"@istanbuljs/load-nyc-config@^1.0.0":
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.0.0.tgz#10602de5570baea82f8afbfa2630b24e7a8cfe5b"
+ integrity sha512-ZR0rq/f/E4f4XcgnDvtMWXCUJpi8eO0rssVhmztsZqLIEFA9UUP9zmpE0VxlM+kv/E1ul2I876Fwil2ayptDVg==
+ dependencies:
+ camelcase "^5.3.1"
+ find-up "^4.1.0"
+ js-yaml "^3.13.1"
+ resolve-from "^5.0.0"
+
+"@istanbuljs/schema@^0.1.2":
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd"
+ integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw==
+
+"@jest/console@^25.4.0":
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/@jest/console/-/console-25.4.0.tgz#e2760b532701137801ba824dcff6bc822c961bac"
+ integrity sha512-CfE0erx4hdJ6t7RzAcE1wLG6ZzsHSmybvIBQDoCkDM1QaSeWL9wJMzID/2BbHHa7ll9SsbbK43HjbERbBaFX2A==
+ dependencies:
+ "@jest/types" "^25.4.0"
+ chalk "^3.0.0"
+ jest-message-util "^25.4.0"
+ jest-util "^25.4.0"
+ slash "^3.0.0"
+
+"@jest/core@^25.4.0":
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/@jest/core/-/core-25.4.0.tgz#cc1fe078df69b8f0fbb023bb0bcee23ef3b89411"
+ integrity sha512-h1x9WSVV0+TKVtATGjyQIMJENs8aF6eUjnCoi4jyRemYZmekLr8EJOGQqTWEX8W6SbZ6Skesy9pGXrKeAolUJw==
+ dependencies:
+ "@jest/console" "^25.4.0"
+ "@jest/reporters" "^25.4.0"
+ "@jest/test-result" "^25.4.0"
+ "@jest/transform" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ ansi-escapes "^4.2.1"
+ chalk "^3.0.0"
+ exit "^0.1.2"
+ graceful-fs "^4.2.3"
+ jest-changed-files "^25.4.0"
+ jest-config "^25.4.0"
+ jest-haste-map "^25.4.0"
+ jest-message-util "^25.4.0"
+ jest-regex-util "^25.2.6"
+ jest-resolve "^25.4.0"
+ jest-resolve-dependencies "^25.4.0"
+ jest-runner "^25.4.0"
+ jest-runtime "^25.4.0"
+ jest-snapshot "^25.4.0"
+ jest-util "^25.4.0"
+ jest-validate "^25.4.0"
+ jest-watcher "^25.4.0"
+ micromatch "^4.0.2"
+ p-each-series "^2.1.0"
+ realpath-native "^2.0.0"
+ rimraf "^3.0.0"
+ slash "^3.0.0"
+ strip-ansi "^6.0.0"
+
+"@jest/environment@^25.4.0":
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-25.4.0.tgz#45071f525f0d8c5a51ed2b04fd42b55a8f0c7cb3"
+ integrity sha512-KDctiak4mu7b4J6BIoN/+LUL3pscBzoUCP+EtSPd2tK9fqyDY5OF+CmkBywkFWezS9tyH5ACOQNtpjtueEDH6Q==
+ dependencies:
+ "@jest/fake-timers" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ jest-mock "^25.4.0"
+
+"@jest/fake-timers@^25.4.0":
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-25.4.0.tgz#3a9a4289ba836abd084953dca406389a57e00fbd"
+ integrity sha512-lI9z+VOmVX4dPPFzyj0vm+UtaB8dCJJ852lcDnY0uCPRvZAaVGnMwBBc1wxtf+h7Vz6KszoOvKAt4QijDnHDkg==
+ dependencies:
+ "@jest/types" "^25.4.0"
+ jest-message-util "^25.4.0"
+ jest-mock "^25.4.0"
+ jest-util "^25.4.0"
+ lolex "^5.0.0"
+
+"@jest/reporters@^25.4.0":
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-25.4.0.tgz#836093433b32ce4e866298af2d6fcf6ed351b0b0"
+ integrity sha512-bhx/buYbZgLZm4JWLcRJ/q9Gvmd3oUh7k2V7gA4ZYBx6J28pIuykIouclRdiAC6eGVX1uRZT+GK4CQJLd/PwPg==
+ dependencies:
+ "@bcoe/v8-coverage" "^0.2.3"
+ "@jest/console" "^25.4.0"
+ "@jest/test-result" "^25.4.0"
+ "@jest/transform" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ chalk "^3.0.0"
+ collect-v8-coverage "^1.0.0"
+ exit "^0.1.2"
+ glob "^7.1.2"
+ istanbul-lib-coverage "^3.0.0"
+ istanbul-lib-instrument "^4.0.0"
+ istanbul-lib-report "^3.0.0"
+ istanbul-lib-source-maps "^4.0.0"
+ istanbul-reports "^3.0.2"
+ jest-haste-map "^25.4.0"
+ jest-resolve "^25.4.0"
+ jest-util "^25.4.0"
+ jest-worker "^25.4.0"
+ slash "^3.0.0"
+ source-map "^0.6.0"
+ string-length "^3.1.0"
+ terminal-link "^2.0.0"
+ v8-to-istanbul "^4.1.3"
+ optionalDependencies:
+ node-notifier "^6.0.0"
+
+"@jest/source-map@^25.2.6":
+ version "25.2.6"
+ resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-25.2.6.tgz#0ef2209514c6d445ebccea1438c55647f22abb4c"
+ integrity sha512-VuIRZF8M2zxYFGTEhkNSvQkUKafQro4y+mwUxy5ewRqs5N/ynSFUODYp3fy1zCnbCMy1pz3k+u57uCqx8QRSQQ==
+ dependencies:
+ callsites "^3.0.0"
+ graceful-fs "^4.2.3"
+ source-map "^0.6.0"
+
+"@jest/test-result@^25.4.0":
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-25.4.0.tgz#6f2ec2c8da9981ef013ad8651c1c6f0cb20c6324"
+ integrity sha512-8BAKPaMCHlL941eyfqhWbmp3MebtzywlxzV+qtngQ3FH+RBqnoSAhNEPj4MG7d2NVUrMOVfrwuzGpVIK+QnMAA==
+ dependencies:
+ "@jest/console" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ collect-v8-coverage "^1.0.0"
+
+"@jest/test-sequencer@^25.4.0":
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-25.4.0.tgz#2b96f9d37f18dc3336b28e3c8070f97f9f55f43b"
+ integrity sha512-240cI+nsM3attx2bMp9uGjjHrwrpvxxrZi8Tyqp/cfOzl98oZXVakXBgxODGyBYAy/UGXPKXLvNc2GaqItrsJg==
+ dependencies:
+ "@jest/test-result" "^25.4.0"
+ jest-haste-map "^25.4.0"
+ jest-runner "^25.4.0"
+ jest-runtime "^25.4.0"
+
+"@jest/transform@^25.4.0":
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-25.4.0.tgz#eef36f0367d639e2fd93dccd758550377fbb9962"
+ integrity sha512-t1w2S6V1sk++1HHsxboWxPEuSpN8pxEvNrZN+Ud/knkROWtf8LeUmz73A4ezE8476a5AM00IZr9a8FO9x1+j3g==
+ dependencies:
+ "@babel/core" "^7.1.0"
+ "@jest/types" "^25.4.0"
+ babel-plugin-istanbul "^6.0.0"
+ chalk "^3.0.0"
+ convert-source-map "^1.4.0"
+ fast-json-stable-stringify "^2.0.0"
+ graceful-fs "^4.2.3"
+ jest-haste-map "^25.4.0"
+ jest-regex-util "^25.2.6"
+ jest-util "^25.4.0"
+ micromatch "^4.0.2"
+ pirates "^4.0.1"
+ realpath-native "^2.0.0"
+ slash "^3.0.0"
+ source-map "^0.6.1"
+ write-file-atomic "^3.0.0"
+
+"@jest/types@^25.4.0":
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-25.4.0.tgz#5afeb8f7e1cba153a28e5ac3c9fe3eede7206d59"
+ integrity sha512-XBeaWNzw2PPnGW5aXvZt3+VO60M+34RY3XDsCK5tW7kyj3RK0XClRutCfjqcBuaR2aBQTbluEDME9b5MB9UAPw==
+ dependencies:
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^1.1.1"
+ "@types/yargs" "^15.0.0"
+ chalk "^3.0.0"
+
"@joeattardi/emoji-button@^2.12.1":
version "2.12.1"
resolved "https://registry.yarnpkg.com/@joeattardi/emoji-button/-/emoji-button-2.12.1.tgz#190df7c00721e04742ed6f8852db828798a4cf98"
dependencies:
any-observable "^0.3.0"
+"@sinonjs/commons@^1.7.0":
+ version "1.7.2"
+ resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.7.2.tgz#505f55c74e0272b43f6c52d81946bed7058fc0e2"
+ integrity sha512-+DUO6pnp3udV/v2VfUWgaY5BIE1IfT7lLfeDzPVeMT1XKkaAp9LgSI9x5RtrFQoZ9Oi0PgXQQHPaoKu7dCjVxw==
+ dependencies:
+ type-detect "4.0.8"
+
"@types/autosize@^3.0.6":
version "3.0.7"
resolved "https://registry.yarnpkg.com/@types/autosize/-/autosize-3.0.7.tgz#f5da28d7ea4532c8b60573d67ec04fc866fa13db"
dependencies:
"@types/jquery" "*"
+"@types/babel__core@^7.1.7":
+ version "7.1.7"
+ resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89"
+ integrity sha512-RL62NqSFPCDK2FM1pSDH0scHpJvsXtZNiYlMB73DgPBaG1E38ZYVL+ei5EkWRbr+KC4YNiAUNBnRj+bgwpgjMw==
+ dependencies:
+ "@babel/parser" "^7.1.0"
+ "@babel/types" "^7.0.0"
+ "@types/babel__generator" "*"
+ "@types/babel__template" "*"
+ "@types/babel__traverse" "*"
+
+"@types/babel__generator@*":
+ version "7.6.1"
+ resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.1.tgz#4901767b397e8711aeb99df8d396d7ba7b7f0e04"
+ integrity sha512-bBKm+2VPJcMRVwNhxKu8W+5/zT7pwNEqeokFOmbvVSqGzFneNxYcEBro9Ac7/N9tlsaPYnZLK8J1LWKkMsLAew==
+ dependencies:
+ "@babel/types" "^7.0.0"
+
+"@types/babel__template@*":
+ version "7.0.2"
+ resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.0.2.tgz#4ff63d6b52eddac1de7b975a5223ed32ecea9307"
+ integrity sha512-/K6zCpeW7Imzgab2bLkLEbz0+1JlFSrUMdw7KoIIu+IUdu51GWaBZpd3y1VXGVXzynvGa4DaIaxNZHiON3GXUg==
+ dependencies:
+ "@babel/parser" "^7.1.0"
+ "@babel/types" "^7.0.0"
+
+"@types/babel__traverse@*", "@types/babel__traverse@^7.0.6":
+ version "7.0.10"
+ resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.0.10.tgz#d9a99f017317d9b3d1abc2ced45d3bca68df0daf"
+ integrity sha512-74fNdUGrWsgIB/V9kTO5FGHPWYY6Eqn+3Z7L6Hc4e/BxjYV7puvBqp5HwsVYYfLm6iURYBNCx4Ut37OF9yitCw==
+ dependencies:
+ "@babel/types" "^7.3.0"
+
"@types/color-name@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0"
resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d"
integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag==
+"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz#42995b446db9a48a11a07ec083499a860e9138ff"
+ integrity sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==
+
+"@types/istanbul-lib-report@*":
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686"
+ integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+
+"@types/istanbul-reports@^1.1.1":
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-1.1.1.tgz#7a8cbf6a406f36c8add871625b278eaf0b0d255a"
+ integrity sha512-UpYjBi8xefVChsCoBpKShdxTllC9pwISirfoZsUa2AAdQg/Jd2KQGtSbw+ya7GPo7x/wAPlH6JBhKhAsXUEZNA==
+ dependencies:
+ "@types/istanbul-lib-coverage" "*"
+ "@types/istanbul-lib-report" "*"
+
+"@types/jest@^25.2.1":
+ version "25.2.1"
+ resolved "https://registry.yarnpkg.com/@types/jest/-/jest-25.2.1.tgz#9544cd438607955381c1bdbdb97767a249297db5"
+ integrity sha512-msra1bCaAeEdkSyA0CZ6gW1ukMIvZ5YoJkdXw/qhQdsuuDlFTcEUrUw8CLCPt2rVRUfXlClVvK2gvPs9IokZaA==
+ dependencies:
+ jest-diff "^25.2.1"
+ pretty-format "^25.2.1"
+
"@types/jquery@*":
version "3.3.31"
resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.31.tgz#27c706e4bf488474e1cb54a71d8303f37c93451b"
dependencies:
"@types/markdown-it" "*"
-"@types/markdown-it@*":
+"@types/markdown-it@*", "@types/markdown-it@^0.0.9":
version "0.0.9"
resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-0.0.9.tgz#a5d552f95216c478e0a27a5acc1b28dcffd989ce"
integrity sha512-IFSepyZXbF4dgSvsk8EsgaQ/8Msv1I5eTL0BZ0X3iGO9jw6tCVtPG8HchIPm3wrkmGdqZOD42kE0zplVi1gYDA==
dependencies:
"@types/linkify-it" "*"
-"@types/markdown-it@^10.0.0":
- version "10.0.0"
- resolved "https://registry.yarnpkg.com/@types/markdown-it/-/markdown-it-10.0.0.tgz#a2b5f9fb444bb27c1e0c4a0116fea09b3c6ebc1e"
- integrity sha512-7UPBg1W0rfsqQ1JwNFfhxibKO0t7Q0scNt96XcFIFLGE/vhZamzZayaFS2LKha/26Pz7b/2GgiaxQZ1GUwW0dA==
+"@types/node-fetch@^2.5.6":
+ version "2.5.6"
+ resolved "https://registry.yarnpkg.com/@types/node-fetch/-/node-fetch-2.5.6.tgz#df8377a66e64ddf75b65b072e37b3c5c5425a96f"
+ integrity sha512-2w0NTwMWF1d3NJMK0Uiq2UNN8htVCyOWOD0jIPjPgC5Ph/YP4dVhs9YxxcMcuLuwAslz0dVEcZQUaqkLs3IzOQ==
dependencies:
- "@types/linkify-it" "*"
- "@types/mdurl" "*"
+ "@types/node" "*"
+ form-data "^3.0.0"
-"@types/mdurl@*":
- version "1.0.2"
- resolved "https://registry.yarnpkg.com/@types/mdurl/-/mdurl-1.0.2.tgz#e2ce9d83a613bacf284c7be7d491945e39e1f8e9"
- integrity sha512-eC4U9MlIcu2q0KQmXszyn5Akca/0jrQmwDRgpAMJai7qBWq4amIQhZyNau4VYGtCeALvW1/NtjzJJ567aZxfKA==
+"@types/node@*":
+ version "13.13.2"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-13.13.2.tgz#160d82623610db590a64e8ca81784e11117e5a54"
+ integrity sha512-LB2R1Oyhpg8gu4SON/mfforE525+Hi/M1ineICEDftqNVTyFg1aRIeGuTvXAoWHc4nbrFncWtJgMmoyRvuGh7A==
"@types/node@^13.11.1":
version "13.11.1"
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
integrity sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==
+"@types/prettier@^1.19.0":
+ version "1.19.1"
+ resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f"
+ integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ==
+
"@types/sizzle@*":
version "2.3.2"
resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47"
integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg==
+"@types/stack-utils@^1.0.1":
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-1.0.1.tgz#0a851d3bd96498fa25c33ab7278ed3bd65f06c3e"
+ integrity sha512-l42BggppR6zLmpfU6fq9HEa2oGPEI8yrSPL3GITjfRInppYFahObbIQOQK3UGxEnyQpltZLaPe75046NOZQikw==
+
+"@types/yargs-parser@*":
+ version "15.0.0"
+ resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-15.0.0.tgz#cb3f9f741869e20cce330ffbeb9271590483882d"
+ integrity sha512-FA/BWv8t8ZWJ+gEOnLLd8ygxH/2UFbAvgEonyfN6yWGLKc7zVjbpl2Y4CTjid9h2RfgPP6SEt6uHwEOply00yw==
+
+"@types/yargs@^15.0.0":
+ version "15.0.4"
+ resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-15.0.4.tgz#7e5d0f8ca25e9d5849f2ea443cf7c402decd8299"
+ integrity sha512-9T1auFmbPZoxHz0enUFlUuKRy3it01R+hlggyVUMtnCTQRunsQYifnSGb8hET4Xo8yiC0o0r1paW3ud5+rbURg==
+ dependencies:
+ "@types/yargs-parser" "*"
+
"@typescript-eslint/eslint-plugin@2.24.0":
version "2.24.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.24.0.tgz#a86cf618c965a462cddf3601f594544b134d6d68"
semver "^6.3.0"
tsutils "^3.17.1"
+abab@^2.0.0:
+ version "2.0.3"
+ resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.3.tgz#623e2075e02eb2d3f2475e49f99c91846467907a"
+ integrity sha512-tsFzPpcttalNjFBCFMqsKYQcWxxen1pgJR56by//QwvJc4/OUS3kPOOttx2tSIfjsylB0pYu7f5D3K1RCxUnUg==
+
accepts@~1.3.7:
version "1.3.7"
resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.7.tgz#531bc726517a3b2b41f850021c6cc15eaab507cd"
mime-types "~2.1.24"
negotiator "0.6.2"
+acorn-globals@^4.3.2:
+ version "4.3.4"
+ resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-4.3.4.tgz#9fa1926addc11c97308c4e66d7add0d40c3272e7"
+ integrity sha512-clfQEh21R+D0leSbUdWf3OcfqyaCSAQ8Ryq00bofSekfr9W8u1jyYZo6ir0xu9Gtcf7BjcHJpnbZH7JOCpP60A==
+ dependencies:
+ acorn "^6.0.1"
+ acorn-walk "^6.0.1"
+
acorn-jsx@^4.0.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-4.1.1.tgz#e8e41e48ea2fe0c896740610ab6a4ffd8add225e"
resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384"
integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==
+acorn-walk@^6.0.1:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-6.2.0.tgz#123cb8f3b84c2171f1f7fb252615b1c78a6b1a8c"
+ integrity sha512-7evsyfH1cLOCdAzZAd43Cic04yKydNx0cF+7tiA19p1XnLLPU4dpCQOqpjqwokFe//vS0QqfqqjCS2JkiIs0cA==
+
acorn@^5.0.3, acorn@^5.7.3:
version "5.7.4"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.4.tgz#3e8d8a9947d0599a1796d10225d7432f4a4acf5e"
integrity sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==
+acorn@^6.0.1:
+ version "6.4.1"
+ resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.1.tgz#531e58ba3f51b9dacb9a6646ca4debf5b14ca474"
+ integrity sha512-ZVA9k326Nwrj3Cj9jlh3wGFutC2ZornPNARZwsNYqQYgN0EsV2d53w5RN/co65Ohn4sUAUtb1rSUAOD6XN9idA==
+
acorn@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.1.0.tgz#949d36f2c292535da602283586c2477c57eb2d6c"
dependencies:
color-convert "^1.9.0"
-ansi-styles@^4.1.0:
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
version "4.2.1"
resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359"
integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==
micromatch "^2.1.5"
normalize-path "^2.0.0"
+anymatch@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb"
+ integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==
+ dependencies:
+ micromatch "^3.1.4"
+ normalize-path "^2.1.1"
+
+anymatch@^3.0.3:
+ version "3.1.1"
+ resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.1.tgz#c55ecf02185e2469259399310c173ce31233b142"
+ integrity sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==
+ dependencies:
+ normalize-path "^3.0.0"
+ picomatch "^2.0.4"
+
app-root-path@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/app-root-path/-/app-root-path-1.4.0.tgz#6335d865c9640d0fad99004e5a79232238e92dfa"
resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4"
integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=
+array-equal@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93"
+ integrity sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=
+
array-flatten@1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2"
eslint-visitor-keys "^1.0.0"
resolve "^1.12.0"
+babel-jest@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-25.4.0.tgz#409eb3e2ddc2ad9a92afdbb00991f1633f8018d0"
+ integrity sha512-p+epx4K0ypmHuCnd8BapfyOwWwosNCYhedetQey1awddtfmEX0MmdxctGl956uwUmjwXR5VSS5xJcGX9DvdIog==
+ dependencies:
+ "@jest/transform" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ "@types/babel__core" "^7.1.7"
+ babel-plugin-istanbul "^6.0.0"
+ babel-preset-jest "^25.4.0"
+ chalk "^3.0.0"
+ slash "^3.0.0"
+
+babel-plugin-istanbul@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.0.0.tgz#e159ccdc9af95e0b570c75b4573b7c34d671d765"
+ integrity sha512-AF55rZXpe7trmEylbaE1Gv54wn6rwU03aptvRoVIGP8YykoSxqdVLV1TfwflBCE/QtHmqtP8SWlTENqbK8GCSQ==
+ dependencies:
+ "@babel/helper-plugin-utils" "^7.0.0"
+ "@istanbuljs/load-nyc-config" "^1.0.0"
+ "@istanbuljs/schema" "^0.1.2"
+ istanbul-lib-instrument "^4.0.0"
+ test-exclude "^6.0.0"
+
+babel-plugin-jest-hoist@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-25.4.0.tgz#0c122c1b93fb76f52d2465be2e8069e798e9d442"
+ integrity sha512-M3a10JCtTyKevb0MjuH6tU+cP/NVQZ82QPADqI1RQYY1OphztsCeIeQmTsHmF/NS6m0E51Zl4QNsI3odXSQF5w==
+ dependencies:
+ "@types/babel__traverse" "^7.0.6"
+
+babel-preset-current-node-syntax@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.2.tgz#fb4a4c51fe38ca60fede1dc74ab35eb843cb41d6"
+ integrity sha512-u/8cS+dEiK1SFILbOC8/rUI3ml9lboKuuMvZ/4aQnQmhecQAgPw5ew066C1ObnEAUmlx7dv/s2z52psWEtLNiw==
+ dependencies:
+ "@babel/plugin-syntax-async-generators" "^7.8.4"
+ "@babel/plugin-syntax-bigint" "^7.8.3"
+ "@babel/plugin-syntax-class-properties" "^7.8.3"
+ "@babel/plugin-syntax-json-strings" "^7.8.3"
+ "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3"
+ "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+ "@babel/plugin-syntax-numeric-separator" "^7.8.3"
+ "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+ "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+ "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+
+babel-preset-jest@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-25.4.0.tgz#10037cc32b751b994b260964629e49dc479abf4c"
+ integrity sha512-PwFiEWflHdu3JCeTr0Pb9NcHHE34qWFnPQRVPvqQITx4CsDCzs6o05923I10XvLvn9nNsRHuiVgB72wG/90ZHQ==
+ dependencies:
+ babel-plugin-jest-hoist "^25.4.0"
+ babel-preset-current-node-syntax "^0.1.2"
+
balanced-match@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
dependencies:
fill-range "^7.0.1"
-buffer-from@^1.0.0:
+browser-process-hrtime@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
+ integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
+
+browser-resolve@^1.11.3:
+ version "1.11.3"
+ resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.3.tgz#9b7cbb3d0f510e4cb86bdbd796124d28b5890af6"
+ integrity sha512-exDi1BYWB/6raKHmDTCicQfTkqwN5fioMFV4j8BsfMU4R2DK/QfZfK7kOVkmWCNANf0snkBzqGqAJBao9gZMdQ==
+ dependencies:
+ resolve "1.1.7"
+
+bs-logger@0.x:
+ version "0.2.6"
+ resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
+ integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==
+ dependencies:
+ fast-json-stable-stringify "2.x"
+
+bser@2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05"
+ integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==
+ dependencies:
+ node-int64 "^0.4.0"
+
+buffer-from@1.x, buffer-from@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+camelcase@^5.0.0, camelcase@^5.3.1:
+ version "5.3.1"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+ integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+
+capture-exit@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/capture-exit/-/capture-exit-2.0.0.tgz#fb953bfaebeb781f62898239dabb426d08a509a4"
+ integrity sha512-PiT/hQmTonHhl/HFGN+Lx3JJUznrVYJ3+AQsnthneZbvW7x+f08Tk7yLJTLEOUvBTbduLeeBkxEaYXUOUrRq6g==
+ dependencies:
+ rsvp "^4.8.4"
+
caseless@~0.12.0:
version "0.12.0"
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.2.0.tgz#ff19ede8a9a5e579324147b0c11f0fbcbabed639"
integrity sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=
+cliui@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1"
+ integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==
+ dependencies:
+ string-width "^4.2.0"
+ strip-ansi "^6.0.0"
+ wrap-ansi "^6.2.0"
+
+co@^4.6.0:
+ version "4.6.0"
+ resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+ integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=
+
code-point-at@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77"
integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=
+collect-v8-coverage@^1.0.0:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59"
+ integrity sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==
+
collection-visit@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0"
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-combined-stream@^1.0.6, combined-stream@~1.0.6:
+combined-stream@^1.0.6, combined-stream@^1.0.8, combined-stream@~1.0.6:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
+convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
+ version "1.7.0"
+ resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.7.0.tgz#17a2cb882d7f77d3490585e2ce6c524424a3a442"
+ integrity sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==
+ dependencies:
+ safe-buffer "~5.1.1"
+
cookie-signature@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c"
path-type "^4.0.0"
yaml "^1.7.2"
-cross-spawn@^6.0.5:
+cross-spawn@^6.0.0, cross-spawn@^6.0.5:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
shebang-command "^2.0.0"
which "^2.0.1"
+cssom@^0.4.1:
+ version "0.4.4"
+ resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
+ integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==
+
+cssom@~0.3.6:
+ version "0.3.8"
+ resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
+ integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
+
+cssstyle@^2.0.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.2.0.tgz#e4c44debccd6b7911ed617a4395e5754bba59992"
+ integrity sha512-sEb3XFPx3jNnCAMtqrXPDeSgQr+jojtCeNf8cvMNMh1cG970+lljssvQDzPq6lmmJu2Vhqood/gtEomBiHOGnA==
+ dependencies:
+ cssom "~0.3.6"
+
damerau-levenshtein@^1.0.4:
version "1.0.6"
resolved "https://registry.yarnpkg.com/damerau-levenshtein/-/damerau-levenshtein-1.0.6.tgz#143c1641cb3d85c60c32329e26899adea8701791"
dependencies:
assert-plus "^1.0.0"
+data-urls@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-1.1.0.tgz#15ee0582baa5e22bb59c77140da8f9c76963bbfe"
+ integrity sha512-YTWYI9se1P55u58gL5GkQHW4P6VJBJ5iBT+B5a7i2Tjadhv52paJG0qHX4A0OR6/t52odI64KP2YvFpkDOi3eQ==
+ dependencies:
+ abab "^2.0.0"
+ whatwg-mimetype "^2.2.0"
+ whatwg-url "^7.0.0"
+
date-fns@^1.27.2:
version "1.30.1"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c"
dependencies:
ms "^2.1.1"
+decamelize@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
+ integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
+
decode-uri-component@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
+deepmerge@^4.2.2:
+ version "4.2.2"
+ resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
+ integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg==
+
define-properties@^1.1.2, define-properties@^1.1.3:
version "1.1.3"
resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1"
resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80"
integrity sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=
+detect-newline@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
+ integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
+
+diff-sequences@^25.2.6:
+ version "25.2.6"
+ resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd"
+ integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg==
+
diff@^4.0.1:
version "4.0.2"
resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d"
dependencies:
esutils "^2.0.2"
+domexception@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/domexception/-/domexception-1.0.1.tgz#937442644ca6a31261ef36e3ec677fe805582c90"
+ integrity sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==
+ dependencies:
+ webidl-conversions "^4.0.2"
+
dotenv@^8.2.0:
version "8.2.0"
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=
+escodegen@^1.11.1:
+ version "1.14.1"
+ resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.1.tgz#ba01d0c8278b5e95a9a45350142026659027a457"
+ integrity sha512-Bmt7NcRySdIfNPfU2ZoXDrrXsG9ZjvDxcAlMfDUgRBjLOWTuIACXPBFJH7Z+cLb40JeQco5toikyc9t9P8E9SQ==
+ dependencies:
+ esprima "^4.0.1"
+ estraverse "^4.2.0"
+ esutils "^2.0.2"
+ optionator "^0.8.1"
+ optionalDependencies:
+ source-map "~0.6.1"
+
escodegen@^1.8.1:
version "1.13.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.13.0.tgz#c7adf9bd3f3cc675bb752f202f79a720189cab29"
dependencies:
merge "^1.2.0"
-execa@^3.4.0:
+exec-sh@^0.3.2:
+ version "0.3.4"
+ resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5"
+ integrity sha512-sEFIkc61v75sWeOe72qyrqg2Qg0OuLESziUDk/O/z2qgS15y2gWVFrI6f2Qn/qw/0/NCfCEsmNA4zOjkwEZT1A==
+
+execa@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
+ integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==
+ dependencies:
+ cross-spawn "^6.0.0"
+ get-stream "^4.0.0"
+ is-stream "^1.1.0"
+ npm-run-path "^2.0.0"
+ p-finally "^1.0.0"
+ signal-exit "^3.0.0"
+ strip-eof "^1.0.0"
+
+execa@^3.2.0, execa@^3.4.0:
version "3.4.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-3.4.0.tgz#c08ed4550ef65d858fac269ffc8572446f37eb89"
integrity sha512-r9vdGQk4bmCuK1yKQu1KTwcT2zwfWdbdaXfCtAh+5nU/4fSX+JAb7vZGvI5naJrQlvONrEB20jeruESI69530g==
signal-exit "^3.0.2"
strip-final-newline "^2.0.0"
+exit@^0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
+ integrity sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=
+
expand-brackets@^0.1.4:
version "0.1.5"
resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b"
dependencies:
fill-range "^2.1.0"
+expect@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/expect/-/expect-25.4.0.tgz#0b16c17401906d1679d173e59f0d4580b22f8dc8"
+ integrity sha512-7BDIX99BTi12/sNGJXA9KMRcby4iAmu1xccBOhyKCyEhjcVKS3hPmHdA/4nSI9QGIOkUropKqr3vv7WMDM5lvQ==
+ dependencies:
+ "@jest/types" "^25.4.0"
+ ansi-styles "^4.0.0"
+ jest-get-type "^25.2.6"
+ jest-matcher-utils "^25.4.0"
+ jest-message-util "^25.4.0"
+ jest-regex-util "^25.2.6"
+
express@^4.14.0:
version "4.17.1"
resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134"
resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.2.0.tgz#73ee11982d86caaf7959828d519cfe927fac5f03"
integrity sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==
-fast-json-stable-stringify@^2.0.0:
+fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
+fb-watchman@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.1.tgz#fc84fb39d2709cf3ff6d743706157bb5708a8a85"
+ integrity sha512-DkPJKQeY6kKwmuMretBhr7G6Vodr7bFwDYTXIkfG1gjvNpaxBTQV3PbXg6bR1c1UP4jPOX0jHUbbHANL9vRjVg==
+ dependencies:
+ bser "2.1.1"
+
figures@^1.7.0:
version "1.7.0"
resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e"
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
+form-data@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.0.tgz#31b7e39c85f1355b7139ee0c647cf0de7f83c682"
+ integrity sha512-CKMFDglpbMi6PyN+brwB9Q/GOw0eAnsrEZDgcsH5Krhz5Od/haKHAX0NmQfha2zPPz0JpWzA7GJHGSnvCRLWsg==
+ dependencies:
+ asynckit "^0.4.0"
+ combined-stream "^1.0.8"
+ mime-types "^2.1.12"
+
form-data@~2.3.2:
version "2.3.3"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6"
bindings "^1.5.0"
nan "^2.12.1"
+fsevents@^2.1.2:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.1.3.tgz#fb738703ae8d2f9fe900c33836ddebee8b97f23e"
+ integrity sha512-Auw9a4AxqWpa9GUfj370BMPzzyncfBABW8Mab7BGWBYDj4Isgq+cDKtx0i6u9jcX9pQDnswsaaOTgTmA5pEjuQ==
+
function-bind@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
dependencies:
source-map "^0.6.1"
+gensync@^1.0.0-beta.1:
+ version "1.0.0-beta.1"
+ resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.1.tgz#58f4361ff987e5ff6e1e7a210827aa371eaac269"
+ integrity sha512-r8EC6NO1sngH/zdD9fiRDLdcgnbayXah+mLgManTaIZJqEC1MZstmnox8KpnI2/fxQwrp5OpCOYWLp4rBl4Jcg==
+
+get-caller-file@^2.0.1:
+ version "2.0.5"
+ resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+ integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
get-own-enumerable-property-symbols@^3.0.0:
version "3.0.2"
resolved "https://registry.yarnpkg.com/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz#b5fde77f22cbe35f390b4e089922c50bce6ef664"
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
+get-stream@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
+ integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==
+ dependencies:
+ pump "^3.0.0"
+
get-stream@^5.0.0:
version "5.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-5.1.0.tgz#01203cdc92597f9b909067c3e656cc1f4d3c4dc9"
dependencies:
is-glob "^4.0.1"
-glob@^7.1.1, glob@^7.1.3, glob@^7.1.6:
+glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
dependencies:
type-fest "^0.8.1"
-graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0:
+graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
+growly@^1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081"
+ integrity sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=
+
har-schema@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
-har-validator@~5.1.0:
+har-validator@~5.1.0, har-validator@~5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.3.tgz#1ef89ebd3e4996557675eed9893110dc350fa080"
integrity sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.5.tgz#759cfcf2c4d156ade59b0b2dfabddc42a6b9c70c"
integrity sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==
+html-encoding-sniffer@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz#e70d84b94da53aa375e11fe3a351be6642ca46f8"
+ integrity sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==
+ dependencies:
+ whatwg-encoding "^1.0.1"
+
+html-escaper@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
+ integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
+
html-parse-stringify2@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/html-parse-stringify2/-/html-parse-stringify2-2.0.1.tgz#dc5670b7292ca158b7bc916c9a6735ac8872834a"
parent-module "^1.0.0"
resolve-from "^4.0.0"
+import-local@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.0.2.tgz#a8cfd0431d1de4a2199703d003e3e62364fa6db6"
+ integrity sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==
+ dependencies:
+ pkg-dir "^4.2.0"
+ resolve-cwd "^3.0.0"
+
import-modules@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/import-modules/-/import-modules-2.0.0.tgz#9c1e13b4e7a15682f70a6e3fa29534e4540cfc5d"
has "^1.0.3"
side-channel "^1.0.2"
+ip-regex@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
+ integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
+
ipaddr.js@1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.5.tgz#f7e46b596890456db74e7f6e976cb3273d06faab"
integrity sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==
+is-ci@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-2.0.0.tgz#6bc6334181810e04b5c22b3d589fdca55026404c"
+ integrity sha512-YfJT7rkpQB0updsdHLGWrvhBJfcfzNNawYDNIyQXJz0IViGf75O8EBPKSdvw2rF+LGCsX4FZ8tcr3b19LcZq4w==
+ dependencies:
+ ci-info "^2.0.0"
+
is-data-descriptor@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56"
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+is-generator-fn@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
+ integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
+
is-glob@^2.0.0, is-glob@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863"
dependencies:
has-symbols "^1.0.1"
-is-typedarray@~1.0.0:
+is-typedarray@^1.0.0, is-typedarray@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=
resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d"
integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==
+is-wsl@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d"
+ integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==
+
isarray@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf"
integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=
-isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
- version "1.0.0"
- resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
- integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11"
+ integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=
+
+isexe@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+ integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
+
+isobject@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
+ integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=
+ dependencies:
+ isarray "1.0.0"
+
+isobject@^3.0.0, isobject@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
+ integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
+
+isstream@~0.1.2:
+ version "0.1.2"
+ resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
+ integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
+
+istanbul-lib-coverage@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.0.0.tgz#f5944a37c70b550b02a78a5c3b2055b280cec8ec"
+ integrity sha512-UiUIqxMgRDET6eR+o5HbfRYP1l0hqkWOs7vNxC/mggutCMUIhWMm8gAHb8tHlyfD3/l6rlgNA5cKdDzEAf6hEg==
+
+istanbul-lib-instrument@^4.0.0:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.1.tgz#61f13ac2c96cfefb076fe7131156cc05907874e6"
+ integrity sha512-imIchxnodll7pvQBYOqUu88EufLCU56LMeFPZZM/fJZ1irYcYdqroaV+ACK1Ila8ls09iEYArp+nqyC6lW1Vfg==
+ dependencies:
+ "@babel/core" "^7.7.5"
+ "@babel/parser" "^7.7.5"
+ "@babel/template" "^7.7.4"
+ "@babel/traverse" "^7.7.4"
+ "@istanbuljs/schema" "^0.1.2"
+ istanbul-lib-coverage "^3.0.0"
+ semver "^6.3.0"
+
+istanbul-lib-report@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6"
+ integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==
+ dependencies:
+ istanbul-lib-coverage "^3.0.0"
+ make-dir "^3.0.0"
+ supports-color "^7.1.0"
+
+istanbul-lib-source-maps@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.0.tgz#75743ce6d96bb86dc7ee4352cf6366a23f0b1ad9"
+ integrity sha512-c16LpFRkR8vQXyHZ5nLpY35JZtzj1PQY1iZmesUbf1FZHbIupcWfjgOXBY9YHkLEQ6puz1u4Dgj6qmU/DisrZg==
+ dependencies:
+ debug "^4.1.1"
+ istanbul-lib-coverage "^3.0.0"
+ source-map "^0.6.1"
+
+istanbul-reports@^3.0.2:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.0.2.tgz#d593210e5000683750cb09fc0644e4b6e27fd53b"
+ integrity sha512-9tZvz7AiR3PEDNGiV9vIouQ/EAcqMXFmkcA1CDFTwOB98OZVDL0PH9glHotf5Ugp6GCOTypfzGWI/OqjWNCRUw==
+ dependencies:
+ html-escaper "^2.0.0"
+ istanbul-lib-report "^3.0.0"
+
+jest-changed-files@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-25.4.0.tgz#e573db32c2fd47d2b90357ea2eda0622c5c5cbd6"
+ integrity sha512-VR/rfJsEs4BVMkwOTuStRyS630fidFVekdw/lBaBQjx9KK3VZFOZ2c0fsom2fRp8pMCrCTP6LGna00o/DXGlqA==
+ dependencies:
+ "@jest/types" "^25.4.0"
+ execa "^3.2.0"
+ throat "^5.0.0"
+
+jest-cli@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-25.4.0.tgz#5dac8be0fece6ce39f0d671395a61d1357322bab"
+ integrity sha512-usyrj1lzCJZMRN1r3QEdnn8e6E6yCx/QN7+B1sLoA68V7f3WlsxSSQfy0+BAwRiF4Hz2eHauf11GZG3PIfWTXQ==
+ dependencies:
+ "@jest/core" "^25.4.0"
+ "@jest/test-result" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ chalk "^3.0.0"
+ exit "^0.1.2"
+ import-local "^3.0.2"
+ is-ci "^2.0.0"
+ jest-config "^25.4.0"
+ jest-util "^25.4.0"
+ jest-validate "^25.4.0"
+ prompts "^2.0.1"
+ realpath-native "^2.0.0"
+ yargs "^15.3.1"
+
+jest-config@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-25.4.0.tgz#56e5df3679a96ff132114b44fb147389c8c0a774"
+ integrity sha512-egT9aKYxMyMSQV1aqTgam0SkI5/I2P9qrKexN5r2uuM2+68ypnc+zPGmfUxK7p1UhE7dYH9SLBS7yb+TtmT1AA==
+ dependencies:
+ "@babel/core" "^7.1.0"
+ "@jest/test-sequencer" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ babel-jest "^25.4.0"
+ chalk "^3.0.0"
+ deepmerge "^4.2.2"
+ glob "^7.1.1"
+ jest-environment-jsdom "^25.4.0"
+ jest-environment-node "^25.4.0"
+ jest-get-type "^25.2.6"
+ jest-jasmine2 "^25.4.0"
+ jest-regex-util "^25.2.6"
+ jest-resolve "^25.4.0"
+ jest-util "^25.4.0"
+ jest-validate "^25.4.0"
+ micromatch "^4.0.2"
+ pretty-format "^25.4.0"
+ realpath-native "^2.0.0"
+
+jest-diff@^25.2.1, jest-diff@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-25.4.0.tgz#260b70f19a46c283adcad7f081cae71eb784a634"
+ integrity sha512-kklLbJVXW0y8UKOWOdYhI6TH5MG6QAxrWiBMgQaPIuhj3dNFGirKCd+/xfplBXICQ7fI+3QcqHm9p9lWu1N6ug==
+ dependencies:
+ chalk "^3.0.0"
+ diff-sequences "^25.2.6"
+ jest-get-type "^25.2.6"
+ pretty-format "^25.4.0"
+
+jest-docblock@^25.3.0:
+ version "25.3.0"
+ resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-25.3.0.tgz#8b777a27e3477cd77a168c05290c471a575623ef"
+ integrity sha512-aktF0kCar8+zxRHxQZwxMy70stc9R1mOmrLsT5VO3pIT0uzGRSDAXxSlz4NqQWpuLjPpuMhPRl7H+5FRsvIQAg==
+ dependencies:
+ detect-newline "^3.0.0"
+
+jest-each@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-25.4.0.tgz#ad4e46164764e8e77058f169a0076a7f86f6b7d4"
+ integrity sha512-lwRIJ8/vQU/6vq3nnSSUw1Y3nz5tkYSFIywGCZpUBd6WcRgpn8NmJoQICojbpZmsJOJNHm0BKdyuJ6Xdx+eDQQ==
+ dependencies:
+ "@jest/types" "^25.4.0"
+ chalk "^3.0.0"
+ jest-get-type "^25.2.6"
+ jest-util "^25.4.0"
+ pretty-format "^25.4.0"
+
+jest-environment-jsdom@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-25.4.0.tgz#bbfc7f85bb6ade99089062a830c79cb454565cf0"
+ integrity sha512-KTitVGMDrn2+pt7aZ8/yUTuS333w3pWt1Mf88vMntw7ZSBNDkRS6/4XLbFpWXYfWfp1FjcjQTOKzbK20oIehWQ==
+ dependencies:
+ "@jest/environment" "^25.4.0"
+ "@jest/fake-timers" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ jest-mock "^25.4.0"
+ jest-util "^25.4.0"
+ jsdom "^15.2.1"
+
+jest-environment-node@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-25.4.0.tgz#188aef01ae6418e001c03fdd1c299961e1439082"
+ integrity sha512-wryZ18vsxEAKFH7Z74zi/y/SyI1j6UkVZ6QsllBuT/bWlahNfQjLNwFsgh/5u7O957dYFoXj4yfma4n4X6kU9A==
+ dependencies:
+ "@jest/environment" "^25.4.0"
+ "@jest/fake-timers" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ jest-mock "^25.4.0"
+ jest-util "^25.4.0"
+ semver "^6.3.0"
+
+jest-get-type@^25.2.6:
+ version "25.2.6"
+ resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877"
+ integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig==
+
+jest-haste-map@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-25.4.0.tgz#da7c309dd7071e0a80c953ba10a0ec397efb1ae2"
+ integrity sha512-5EoCe1gXfGC7jmXbKzqxESrgRcaO3SzWXGCnvp9BcT0CFMyrB1Q6LIsjl9RmvmJGQgW297TCfrdgiy574Rl9HQ==
+ dependencies:
+ "@jest/types" "^25.4.0"
+ anymatch "^3.0.3"
+ fb-watchman "^2.0.0"
+ graceful-fs "^4.2.3"
+ jest-serializer "^25.2.6"
+ jest-util "^25.4.0"
+ jest-worker "^25.4.0"
+ micromatch "^4.0.2"
+ sane "^4.0.3"
+ walker "^1.0.7"
+ which "^2.0.2"
+ optionalDependencies:
+ fsevents "^2.1.2"
+
+jest-jasmine2@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-25.4.0.tgz#3d3d19514022e2326e836c2b66d68b4cb63c5861"
+ integrity sha512-QccxnozujVKYNEhMQ1vREiz859fPN/XklOzfQjm2j9IGytAkUbSwjFRBtQbHaNZ88cItMpw02JnHGsIdfdpwxQ==
+ dependencies:
+ "@babel/traverse" "^7.1.0"
+ "@jest/environment" "^25.4.0"
+ "@jest/source-map" "^25.2.6"
+ "@jest/test-result" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ chalk "^3.0.0"
+ co "^4.6.0"
+ expect "^25.4.0"
+ is-generator-fn "^2.0.0"
+ jest-each "^25.4.0"
+ jest-matcher-utils "^25.4.0"
+ jest-message-util "^25.4.0"
+ jest-runtime "^25.4.0"
+ jest-snapshot "^25.4.0"
+ jest-util "^25.4.0"
+ pretty-format "^25.4.0"
+ throat "^5.0.0"
+
+jest-leak-detector@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-25.4.0.tgz#cf94a160c78e53d810e7b2f40b5fd7ee263375b3"
+ integrity sha512-7Y6Bqfv2xWsB+7w44dvZuLs5SQ//fzhETgOGG7Gq3TTGFdYvAgXGwV8z159RFZ6fXiCPm/szQ90CyfVos9JIFQ==
+ dependencies:
+ jest-get-type "^25.2.6"
+ pretty-format "^25.4.0"
+
+jest-matcher-utils@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-25.4.0.tgz#dc3e7aec402a1e567ed80b572b9ad285878895e6"
+ integrity sha512-yPMdtj7YDgXhnGbc66bowk8AkQ0YwClbbwk3Kzhn5GVDrciiCr27U4NJRbrqXbTdtxjImONITg2LiRIw650k5A==
+ dependencies:
+ chalk "^3.0.0"
+ jest-diff "^25.4.0"
+ jest-get-type "^25.2.6"
+ pretty-format "^25.4.0"
+
+jest-message-util@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-25.4.0.tgz#2899e8bc43f5317acf8dfdfe89ea237d354fcdab"
+ integrity sha512-LYY9hRcVGgMeMwmdfh9tTjeux1OjZHMusq/E5f3tJN+dAoVVkJtq5ZUEPIcB7bpxDUt2zjUsrwg0EGgPQ+OhXQ==
+ dependencies:
+ "@babel/code-frame" "^7.0.0"
+ "@jest/types" "^25.4.0"
+ "@types/stack-utils" "^1.0.1"
+ chalk "^3.0.0"
+ micromatch "^4.0.2"
+ slash "^3.0.0"
+ stack-utils "^1.0.1"
+
+jest-mock@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-25.4.0.tgz#ded7d64b5328d81d78d2138c825d3a45e30ec8ca"
+ integrity sha512-MdazSfcYAUjJjuVTTnusLPzE0pE4VXpOUzWdj8sbM+q6abUjm3bATVPXFqTXrxSieR8ocpvQ9v/QaQCftioQFg==
+ dependencies:
+ "@jest/types" "^25.4.0"
+
+jest-pnp-resolver@^1.2.1:
+ version "1.2.1"
+ resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a"
+ integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ==
+
+jest-regex-util@^25.2.6:
+ version "25.2.6"
+ resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-25.2.6.tgz#d847d38ba15d2118d3b06390056028d0f2fd3964"
+ integrity sha512-KQqf7a0NrtCkYmZZzodPftn7fL1cq3GQAFVMn5Hg8uKx/fIenLEobNanUxb7abQ1sjADHBseG/2FGpsv/wr+Qw==
+
+jest-resolve-dependencies@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-25.4.0.tgz#783937544cfc40afcc7c569aa54748c4b3f83f5a"
+ integrity sha512-A0eoZXx6kLiuG1Ui7wITQPl04HwjLErKIJTt8GR3c7UoDAtzW84JtCrgrJ6Tkw6c6MwHEyAaLk7dEPml5pf48A==
+ dependencies:
+ "@jest/types" "^25.4.0"
+ jest-regex-util "^25.2.6"
+ jest-snapshot "^25.4.0"
+
+jest-resolve@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-25.4.0.tgz#6f4540ce0d419c4c720e791e871da32ba4da7a60"
+ integrity sha512-wOsKqVDFWUiv8BtLMCC6uAJ/pHZkfFgoBTgPtmYlsprAjkxrr2U++ZnB3l5ykBMd2O24lXvf30SMAjJIW6k2aA==
+ dependencies:
+ "@jest/types" "^25.4.0"
+ browser-resolve "^1.11.3"
+ chalk "^3.0.0"
+ jest-pnp-resolver "^1.2.1"
+ read-pkg-up "^7.0.1"
+ realpath-native "^2.0.0"
+ resolve "^1.15.1"
+ slash "^3.0.0"
+
+jest-runner@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-25.4.0.tgz#6ca4a3d52e692bbc081228fa68f750012f1f29e5"
+ integrity sha512-wWQSbVgj2e/1chFdMRKZdvlmA6p1IPujhpLT7TKNtCSl1B0PGBGvJjCaiBal/twaU2yfk8VKezHWexM8IliBfA==
+ dependencies:
+ "@jest/console" "^25.4.0"
+ "@jest/environment" "^25.4.0"
+ "@jest/test-result" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ chalk "^3.0.0"
+ exit "^0.1.2"
+ graceful-fs "^4.2.3"
+ jest-config "^25.4.0"
+ jest-docblock "^25.3.0"
+ jest-haste-map "^25.4.0"
+ jest-jasmine2 "^25.4.0"
+ jest-leak-detector "^25.4.0"
+ jest-message-util "^25.4.0"
+ jest-resolve "^25.4.0"
+ jest-runtime "^25.4.0"
+ jest-util "^25.4.0"
+ jest-worker "^25.4.0"
+ source-map-support "^0.5.6"
+ throat "^5.0.0"
+
+jest-runtime@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-25.4.0.tgz#1e5227a9e2159d26ae27dcd426ca6bc041983439"
+ integrity sha512-lgNJlCDULtXu9FumnwCyWlOub8iytijwsPNa30BKrSNtgoT6NUMXOPrZvsH06U6v0wgD/Igwz13nKA2wEKU2VA==
+ dependencies:
+ "@jest/console" "^25.4.0"
+ "@jest/environment" "^25.4.0"
+ "@jest/source-map" "^25.2.6"
+ "@jest/test-result" "^25.4.0"
+ "@jest/transform" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ "@types/yargs" "^15.0.0"
+ chalk "^3.0.0"
+ collect-v8-coverage "^1.0.0"
+ exit "^0.1.2"
+ glob "^7.1.3"
+ graceful-fs "^4.2.3"
+ jest-config "^25.4.0"
+ jest-haste-map "^25.4.0"
+ jest-message-util "^25.4.0"
+ jest-mock "^25.4.0"
+ jest-regex-util "^25.2.6"
+ jest-resolve "^25.4.0"
+ jest-snapshot "^25.4.0"
+ jest-util "^25.4.0"
+ jest-validate "^25.4.0"
+ realpath-native "^2.0.0"
+ slash "^3.0.0"
+ strip-bom "^4.0.0"
+ yargs "^15.3.1"
+
+jest-serializer@^25.2.6:
+ version "25.2.6"
+ resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-25.2.6.tgz#3bb4cc14fe0d8358489dbbefbb8a4e708ce039b7"
+ integrity sha512-RMVCfZsezQS2Ww4kB5HJTMaMJ0asmC0BHlnobQC6yEtxiFKIxohFA4QSXSabKwSggaNkqxn6Z2VwdFCjhUWuiQ==
+
+jest-snapshot@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-25.4.0.tgz#e0b26375e2101413fd2ccb4278a5711b1922545c"
+ integrity sha512-J4CJ0X2SaGheYRZdLz9CRHn9jUknVmlks4UBeu270hPAvdsauFXOhx9SQP2JtRzhnR3cvro/9N9KP83/uvFfRg==
+ dependencies:
+ "@babel/types" "^7.0.0"
+ "@jest/types" "^25.4.0"
+ "@types/prettier" "^1.19.0"
+ chalk "^3.0.0"
+ expect "^25.4.0"
+ jest-diff "^25.4.0"
+ jest-get-type "^25.2.6"
+ jest-matcher-utils "^25.4.0"
+ jest-message-util "^25.4.0"
+ jest-resolve "^25.4.0"
+ make-dir "^3.0.0"
+ natural-compare "^1.4.0"
+ pretty-format "^25.4.0"
+ semver "^6.3.0"
-isexe@^2.0.0:
- version "2.0.0"
- resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
- integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=
+jest-util@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-25.4.0.tgz#6a093d09d86d2b41ef583e5fe7dd3976346e1acd"
+ integrity sha512-WSZD59sBtAUjLv1hMeKbNZXmMcrLRWcYqpO8Dz8b4CeCTZpfNQw2q9uwrYAD+BbJoLJlu4ezVPwtAmM/9/SlZA==
+ dependencies:
+ "@jest/types" "^25.4.0"
+ chalk "^3.0.0"
+ is-ci "^2.0.0"
+ make-dir "^3.0.0"
-isobject@^2.0.0:
- version "2.1.0"
- resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89"
- integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=
+jest-validate@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-25.4.0.tgz#2e177a93b716a137110eaf2768f3d9095abd3f38"
+ integrity sha512-hvjmes/EFVJSoeP1yOl8qR8mAtMR3ToBkZeXrD/ZS9VxRyWDqQ/E1C5ucMTeSmEOGLipvdlyipiGbHJ+R1MQ0g==
dependencies:
- isarray "1.0.0"
+ "@jest/types" "^25.4.0"
+ camelcase "^5.3.1"
+ chalk "^3.0.0"
+ jest-get-type "^25.2.6"
+ leven "^3.1.0"
+ pretty-format "^25.4.0"
-isobject@^3.0.0, isobject@^3.0.1:
- version "3.0.1"
- resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
- integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8=
+jest-watcher@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-25.4.0.tgz#63ec0cd5c83bb9c9d1ac95be7558dd61c995ff05"
+ integrity sha512-36IUfOSRELsKLB7k25j/wutx0aVuHFN6wO94gPNjQtQqFPa2rkOymmx9rM5EzbF3XBZZ2oqD9xbRVoYa2w86gw==
+ dependencies:
+ "@jest/test-result" "^25.4.0"
+ "@jest/types" "^25.4.0"
+ ansi-escapes "^4.2.1"
+ chalk "^3.0.0"
+ jest-util "^25.4.0"
+ string-length "^3.1.0"
-isstream@~0.1.2:
- version "0.1.2"
- resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a"
- integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=
+jest-worker@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-25.4.0.tgz#ee0e2ceee5a36ecddf5172d6d7e0ab00df157384"
+ integrity sha512-ghAs/1FtfYpMmYQ0AHqxV62XPvKdUDIBBApMZfly+E9JEmYh2K45G0R5dWxx986RN12pRCxsViwQVtGl+N4whw==
+ dependencies:
+ merge-stream "^2.0.0"
+ supports-color "^7.0.0"
+
+jest@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/jest/-/jest-25.4.0.tgz#fb96892c5c4e4a6b9bcb12068849cddf4c5f8cc7"
+ integrity sha512-XWipOheGB4wai5JfCYXd6vwsWNwM/dirjRoZgAa7H2wd8ODWbli2AiKjqG8AYhyx+8+5FBEdpO92VhGlBydzbw==
+ dependencies:
+ "@jest/core" "^25.4.0"
+ import-local "^3.0.2"
+ jest-cli "^25.4.0"
js-cookie@^2.2.0:
version "2.2.1"
resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513"
integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM=
+jsdom@^15.2.1:
+ version "15.2.1"
+ resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-15.2.1.tgz#d2feb1aef7183f86be521b8c6833ff5296d07ec5"
+ integrity sha512-fAl1W0/7T2G5vURSyxBzrJ1LSdQn6Tr5UX/xD4PXDx/PDgwygedfW6El/KIj3xJ7FU61TTYnc/l/B7P49Eqt6g==
+ dependencies:
+ abab "^2.0.0"
+ acorn "^7.1.0"
+ acorn-globals "^4.3.2"
+ array-equal "^1.0.0"
+ cssom "^0.4.1"
+ cssstyle "^2.0.0"
+ data-urls "^1.1.0"
+ domexception "^1.0.1"
+ escodegen "^1.11.1"
+ html-encoding-sniffer "^1.0.2"
+ nwsapi "^2.2.0"
+ parse5 "5.1.0"
+ pn "^1.1.0"
+ request "^2.88.0"
+ request-promise-native "^1.0.7"
+ saxes "^3.1.9"
+ symbol-tree "^3.2.2"
+ tough-cookie "^3.0.1"
+ w3c-hr-time "^1.0.1"
+ w3c-xmlserializer "^1.1.2"
+ webidl-conversions "^4.0.2"
+ whatwg-encoding "^1.0.5"
+ whatwg-mimetype "^2.3.0"
+ whatwg-url "^7.0.0"
+ ws "^7.0.0"
+ xml-name-validator "^3.0.0"
+
jsesc@^2.5.1:
version "2.5.2"
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=
+json5@2.x, json5@^2.1.2:
+ version "2.1.3"
+ resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43"
+ integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA==
+ dependencies:
+ minimist "^1.2.5"
+
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd"
integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==
+kleur@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
+ integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
+
lego-api@^1.0.7:
version "1.0.8"
resolved "https://registry.yarnpkg.com/lego-api/-/lego-api-1.0.8.tgz#5e26be726c5e11d540f89e7c6b1abf8c5834bd01"
dependencies:
chain-able "^3.0.0"
+leven@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
+ integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
+
levn@^0.3.0, levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
+lodash.memoize@4.x:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+ integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
+
+lodash.sortby@^4.7.0:
+ version "4.7.0"
+ resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
+ integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=
+
lodash.zip@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/lodash.zip/-/lodash.zip-4.2.0.tgz#ec6662e4896408ed4ab6c542a3990b72cc080020"
cli-cursor "^2.0.0"
wrap-ansi "^3.0.1"
+lolex@^5.0.0:
+ version "5.1.2"
+ resolved "https://registry.yarnpkg.com/lolex/-/lolex-5.1.2.tgz#953694d098ce7c07bc5ed6d0e42bc6c0c6d5a367"
+ integrity sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==
+ dependencies:
+ "@sinonjs/commons" "^1.7.0"
+
loose-envify@^1.2.0, loose-envify@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
dependencies:
js-tokens "^3.0.0 || ^4.0.0"
+make-dir@^3.0.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
+ integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
+ dependencies:
+ semver "^6.0.0"
+
+make-error@1.x:
+ version "1.3.6"
+ resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
+ integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
+
make-error@^1.1.1:
version "1.3.5"
resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.5.tgz#efe4e81f6db28cadd605c70f29c831b58ef776c8"
integrity sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==
+makeerror@1.0.x:
+ version "1.0.11"
+ resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c"
+ integrity sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=
+ dependencies:
+ tmpl "1.0.x"
+
map-cache@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf"
resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee"
integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=
+micromatch@4.x, micromatch@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
+ integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
+ dependencies:
+ braces "^3.0.1"
+ picomatch "^2.0.5"
+
micromatch@^2.1.5:
version "2.3.11"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565"
parse-glob "^3.0.4"
regex-cache "^0.4.2"
-micromatch@^3.1.10:
+micromatch@^3.1.10, micromatch@^3.1.4:
version "3.1.10"
resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23"
integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==
snapdragon "^0.8.1"
to-regex "^3.0.2"
-micromatch@^4.0.2:
- version "4.0.2"
- resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.2.tgz#4fcb0999bf9fbc2fcbdd212f6d629b9a56c39259"
- integrity sha512-y7FpHSbMUMoyPbYUSzO6PaZ6FyRnQOpHuKwbo1G+Knck95XVU4QAiKdGEnj5wwoS7PlOgthX/09u5iFJ+aYf5Q==
- dependencies:
- braces "^3.0.1"
- picomatch "^2.0.5"
-
mime-db@1.43.0:
version "1.43.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.43.0.tgz#0a12e0502650e473d735535050e7c8f4eb4fae58"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d"
integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=
+minimist@^1.1.1, minimist@^1.2.5:
+ version "1.2.5"
+ resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602"
+ integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
+
minimist@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284"
for-in "^1.0.2"
is-extendable "^1.0.1"
+mkdirp@1.x:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
+ integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+
mkdirp@^0.5.1:
version "0.5.1"
resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903"
resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
+node-fetch@^2.6.0:
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
+ integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
+
+node-int64@^0.4.0:
+ version "0.4.0"
+ resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
+ integrity sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=
+
+node-modules-regexp@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40"
+ integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA=
+
+node-notifier@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-6.0.0.tgz#cea319e06baa16deec8ce5cd7f133c4a46b68e12"
+ integrity sha512-SVfQ/wMw+DesunOm5cKqr6yDcvUTDl/yc97ybGHMrteNEY6oekXpNpS3lZwgLlwz0FLgHoiW28ZpmBHUDg37cw==
+ dependencies:
+ growly "^1.3.0"
+ is-wsl "^2.1.1"
+ semver "^6.3.0"
+ shellwords "^0.1.1"
+ which "^1.3.1"
+
normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
semver "2 || 3 || 4 || 5"
validate-npm-package-license "^3.0.1"
-normalize-path@^2.0.0, normalize-path@^2.0.1:
+normalize-path@^2.0.0, normalize-path@^2.0.1, normalize-path@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9"
integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=
resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+npm-run-path@^2.0.0:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f"
+ integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=
+ dependencies:
+ path-key "^2.0.0"
+
npm-run-path@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d"
integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=
+nwsapi@^2.2.0:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
+ integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
+
oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274"
integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=
+p-each-series@^2.1.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/p-each-series/-/p-each-series-2.1.0.tgz#961c8dd3f195ea96c747e636b262b800a6b1af48"
+ integrity sha512-ZuRs1miPT4HrjFa+9fRfOFXxGJfORgelKV9f9nNOWw2gl6gVsRaVDOQP0+MI0G0wGKns1Yacsu0GjOFbTK0JFQ==
+
+p-finally@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
+ integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
+
p-finally@^2.0.0:
version "2.0.1"
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-2.0.1.tgz#bd6fcaa9c559a096b680806f4d657b3f0f240561"
json-parse-better-errors "^1.0.1"
lines-and-columns "^1.1.6"
+parse5@5.1.0:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.0.tgz#c59341c9723f414c452975564c7c00a68d58acd2"
+ integrity sha512-fxNG2sQjHvlVAYmzBZS9YlDp6PTSSDwa98vkD4QgVDDCAo84z5X1t5XyJQ62ImdLXx5NdIIfihey6xpum9/gRQ==
+
parseurl@~1.3.3:
version "1.3.3"
resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
-path-key@^2.0.1:
+path-key@^2.0.0, path-key@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=
resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b"
integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=
+picomatch@^2.0.4:
+ version "2.2.2"
+ resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.2.tgz#21f333e9b6b8eaff02468f5146ea406d345f4dad"
+ integrity sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==
+
picomatch@^2.0.5:
version "2.2.1"
resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.2.1.tgz#21bac888b6ed8601f831ce7816e335bc779f0a4a"
resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c"
integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw=
+pirates@^4.0.1:
+ version "4.0.1"
+ resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.1.tgz#643a92caf894566f91b2b986d2c66950a8e2fb87"
+ integrity sha512-WuNqLTbMI3tmfef2TKxlQmAiLHKtFhlsCZnPIpuv2Ow0RDVO8lfy1Opf4NUzlMXLjPl+Men7AuVdX6TA+s+uGA==
+ dependencies:
+ node-modules-regexp "^1.0.0"
+
pkg-dir@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b"
dependencies:
semver-compare "^1.0.0"
+pn@^1.1.0:
+ version "1.1.0"
+ resolved "https://registry.yarnpkg.com/pn/-/pn-1.1.0.tgz#e2f4cef0e219f463c179ab37463e4e1ecdccbafb"
+ integrity sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==
+
posix-character-classes@^0.1.0:
version "0.1.1"
resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.0.4.tgz#2d1bae173e355996ee355ec9830a7a1ee05457ef"
integrity sha512-SVJIQ51spzFDvh4fIbCLvciiDMCrRhlN3mbZvv/+ycjvmF5E73bKdGfU8QDLNmjYJf+lsGnDBC4UUnvTe5OO0w==
+pretty-format@^25.2.1, pretty-format@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-25.4.0.tgz#c58801bb5c4926ff4a677fe43f9b8b99812c7830"
+ integrity sha512-PI/2dpGjXK5HyXexLPZU/jw5T9Q6S1YVXxxVxco+LIqzUFHXIbKZKdUVt7GcX7QUCr31+3fzhi4gN4/wUYPVxQ==
+ dependencies:
+ "@jest/types" "^25.4.0"
+ ansi-regex "^5.0.0"
+ ansi-styles "^4.0.0"
+ react-is "^16.12.0"
+
pretty-time@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/pretty-time/-/pretty-time-0.2.0.tgz#7a3bdec4049c620cd7c42b7f342b74d56e73d74e"
resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8"
integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==
+prompts@^2.0.1:
+ version "2.3.2"
+ resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.3.2.tgz#480572d89ecf39566d2bd3fe2c9fccb7c4c0b068"
+ integrity sha512-Q06uKs2CkNYVID0VqwfAl9mipo99zkBv/n2JtWY89Yxa3ZabWSrs0e2KTudKVa3peLUvYXMefDqIleLPVUBZMA==
+ dependencies:
+ kleur "^3.0.3"
+ sisteransi "^1.0.4"
+
prop-types@^15.7.2:
version "15.7.2"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.7.0.tgz#f1c4c47a8ef97167dea5d6bbf4816d736e884a3c"
integrity sha512-5NsSEDv8zY70ScRnOTn7bK7eanl2MvFrOrS/R6x+dBt5g1ghnj9Zv90kO8GwT8gxcu2ANyFprnFYB85IogIJOQ==
+psl@^1.1.28:
+ version "1.8.0"
+ resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
+ integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
+
pump@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
-punycode@^2.1.0:
+punycode@^2.1.0, punycode@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec"
integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==
iconv-lite "0.4.24"
unpipe "1.0.0"
+react-is@^16.12.0:
+ version "16.13.1"
+ resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
+ integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==
+
react-is@^16.8.1:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.12.0.tgz#2cc0fe0fba742d97fd527c42a13bec4eeb06241c"
app-root-path "^1.3.0"
mkdirp "^0.5.1"
+realpath-native@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/realpath-native/-/realpath-native-2.0.0.tgz#7377ac429b6e1fd599dc38d08ed942d0d7beb866"
+ integrity sha512-v1SEYUOXXdbBZK8ZuNgO4TBjamPsiSgcFr0aP+tEKpQZK8vooEUqV6nm6Cv502mX4NF2EfsnVqtNAHG+/6Ur1Q==
+
reconnecting-websocket@^4.4.0:
version "4.4.0"
resolved "https://registry.yarnpkg.com/reconnecting-websocket/-/reconnecting-websocket-4.4.0.tgz#3b0e5b96ef119e78a03135865b8bb0af1b948783"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
+request-promise-core@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.3.tgz#e9a3c081b51380dfea677336061fea879a829ee9"
+ integrity sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==
+ dependencies:
+ lodash "^4.17.15"
+
+request-promise-native@^1.0.7:
+ version "1.0.8"
+ resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.8.tgz#a455b960b826e44e2bf8999af64dff2bfe58cb36"
+ integrity sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==
+ dependencies:
+ request-promise-core "1.1.3"
+ stealthy-require "^1.1.1"
+ tough-cookie "^2.3.3"
+
request@^2.79.0:
version "2.88.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef"
tunnel-agent "^0.6.0"
uuid "^3.3.2"
+request@^2.88.0:
+ version "2.88.2"
+ resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
+ integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
+ 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"
+
+require-directory@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+ integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I=
+
+require-main-filename@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b"
+ integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==
+
reserved-words@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/reserved-words/-/reserved-words-0.1.2.tgz#00a0940f98cd501aeaaac316411d9adc52b31ab1"
integrity sha1-AKCUD5jNUBrqqsMWQR2a3FKzGrE=
+resolve-cwd@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
+ integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==
+ dependencies:
+ resolve-from "^5.0.0"
+
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+resolve-from@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
+ integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
+
resolve-pathname@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-3.0.0.tgz#99d02224d3cf263689becbb393bc560313025dcd"
resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a"
integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
+resolve@1.1.7:
+ version "1.1.7"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b"
+ integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=
+
+resolve@1.x, resolve@^1.3.2:
+ version "1.17.0"
+ resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
+ integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
+ dependencies:
+ path-parse "^1.0.6"
+
resolve@^1.10.0, resolve@^1.10.1, resolve@^1.12.0, resolve@^1.13.1:
version "1.15.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.15.0.tgz#1b7ca96073ebb52e741ffd799f6b39ea462c67f5"
dependencies:
glob "^7.1.3"
+rimraf@^3.0.0:
+ version "3.0.2"
+ resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+ integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+ dependencies:
+ glob "^7.1.3"
+
+rsvp@^4.8.4:
+ version "4.8.5"
+ resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
+ integrity sha512-nfMOlASu9OnRJo1mbEk2cz0D56a1MBNrJ7orjRZQG10XDyuvwksKbuXNp6qa+kbn839HwjwhBzhFmdsaEAfauA==
+
run-async@^2.2.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0"
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+sane@^4.0.3:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded"
+ integrity sha512-hhbzAgTIX8O7SHfp2c8/kREfEn4qO/9q8C9beyY6+tvZ87EpoZ3i1RIEvp27YBswnNbY9mWd6paKVmKbAgLfZA==
+ dependencies:
+ "@cnakazawa/watch" "^1.0.3"
+ anymatch "^2.0.0"
+ capture-exit "^2.0.0"
+ exec-sh "^0.3.2"
+ execa "^1.0.0"
+ fb-watchman "^2.0.0"
+ micromatch "^3.1.4"
+ minimist "^1.1.1"
+ walker "~1.0.5"
+
+saxes@^3.1.9:
+ version "3.1.11"
+ resolved "https://registry.yarnpkg.com/saxes/-/saxes-3.1.11.tgz#d59d1fd332ec92ad98a2e0b2ee644702384b1c5b"
+ integrity sha512-Ydydq3zC+WYDJK1+gRxRapLIED9PWeSuuS41wqyoRmzvhhh9nc+QQrVMKJYzJFULazeGhzSV0QleN2wD3boh2g==
+ dependencies:
+ xmlchars "^2.1.1"
+
semver-compare@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc"
resolved "https://registry.yarnpkg.com/semver-regex/-/semver-regex-2.0.0.tgz#a93c2c5844539a770233379107b38c7b4ac9d338"
integrity sha512-mUdIBBvdn0PLOeP3TEkMH7HHeUP3GjsXCwKarjv/kGmUFOYg1VqEemKhoQpWMu6X2I8kHeuVdGibLGkVK+/5Qw==
-"semver@2 || 3 || 4 || 5", semver@^5.5.0:
+"semver@2 || 3 || 4 || 5", semver@^5.4.1, semver@^5.5.0:
version "5.7.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
-semver@^6.1.0, semver@^6.1.2, semver@^6.3.0:
+semver@6.x, semver@^6.0.0, semver@^6.1.0, semver@^6.1.2, semver@^6.3.0:
version "6.3.0"
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
parseurl "~1.3.3"
send "0.17.1"
+set-blocking@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
+ integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc=
+
set-value@^2.0.0, set-value@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b"
resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+shellwords@^0.1.1:
+ version "0.1.1"
+ resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.1.tgz#d6b9181c1a48d397324c84871efbcfc73fc0654b"
+ integrity sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==
+
shorthash@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/shorthash/-/shorthash-0.0.2.tgz#59b268eecbde59038b30da202bcfbddeb2c4a4eb"
es-abstract "^1.17.0-next.1"
object-inspect "^1.7.0"
+signal-exit@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c"
+ integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==
+
signal-exit@^3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d"
integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=
+sisteransi@^1.0.4:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
+ integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
+
slash@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
safer-buffer "^2.0.2"
tweetnacl "~0.14.0"
+stack-utils@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-1.0.2.tgz#33eba3897788558bebfc2db059dc158ec36cebb8"
+ integrity sha512-MTX+MeG5U994cazkjd/9KNAapsHnibjMLnfXodlkXw76JEea0UiNzrqidzo1emMwk7w5Qhc9jd4Bn9TBb1MFwA==
+
static-extend@^0.1.1:
version "0.1.2"
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
+stealthy-require@^1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
+ integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
+
stream-browserify@^2.0.1:
version "2.0.2"
resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
+string-length@^3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/string-length/-/string-length-3.1.0.tgz#107ef8c23456e187a8abd4a61162ff4ac6e25837"
+ integrity sha512-Ttp5YvkGm5v9Ijagtaz1BnN+k9ObpvS0eIBblPMp2YWL8FBmi9qblQ9fexc2k/CXFgrTIteU3jAw3payCnwSTA==
+ dependencies:
+ astral-regex "^1.0.0"
+ strip-ansi "^5.2.0"
+
string-width@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3"
is-fullwidth-code-point "^2.0.0"
strip-ansi "^5.1.0"
-string-width@^4.1.0:
+string-width@^4.1.0, string-width@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5"
integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=
+strip-bom@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
+ integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==
+
+strip-eof@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf"
+ integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=
+
strip-final-newline@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
dependencies:
has-flag "^3.0.0"
-supports-color@^7.1.0:
+supports-color@^7.0.0, supports-color@^7.1.0:
version "7.1.0"
resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.1.0.tgz#68e32591df73e25ad1c4b49108a2ec507962bfd1"
integrity sha512-oRSIpR8pxT1Wr2FquTNnGet79b3BWljqOuoW/h4oBhxJ/HUbX5nX6JSruTkvXDCFMwDPvsaTTbvMLKZWSy0R5g==
dependencies:
has-flag "^4.0.0"
+supports-hyperlinks@^2.0.0:
+ version "2.1.0"
+ resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.1.0.tgz#f663df252af5f37c5d49bbd7eeefa9e0b9e59e47"
+ integrity sha512-zoE5/e+dnEijk6ASB6/qrK+oYdm2do1hjoLWrqUC/8WEIW1gbxFcKuBof7sW8ArN6e+AYvsE8HBGiVRWL/F5CA==
+ dependencies:
+ has-flag "^4.0.0"
+ supports-color "^7.0.0"
+
symbol-observable@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.2.0.tgz#c22688aed4eab3cdc2dfeacbb561660560a00804"
integrity sha512-e900nM8RRtGhlV36KGEU9k65K3mPb1WV70OdjfxlG2EAuM1noi/E/BaW/uMhL7bPEssK8QV57vN3esixjUvcXQ==
+symbol-tree@^3.2.2:
+ version "3.2.4"
+ resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
+ integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
+
tabbable@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/tabbable/-/tabbable-4.0.0.tgz#5bff1d1135df1482cf0f0206434f15eadbeb9261"
slice-ansi "^2.1.0"
string-width "^3.0.0"
+terminal-link@^2.0.0:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
+ integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==
+ dependencies:
+ ansi-escapes "^4.2.1"
+ supports-hyperlinks "^2.0.0"
+
terser@^4.6.11:
version "4.6.11"
resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.11.tgz#12ff99fdd62a26de2a82f508515407eb6ccd8a9f"
source-map "~0.6.1"
source-map-support "~0.5.12"
+test-exclude@^6.0.0:
+ version "6.0.0"
+ resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
+ integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==
+ dependencies:
+ "@istanbuljs/schema" "^0.1.2"
+ glob "^7.1.4"
+ minimatch "^3.0.4"
+
text-table@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=
+throat@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.yarnpkg.com/throat/-/throat-5.0.0.tgz#c5199235803aad18754a667d659b5e72ce16764b"
+ integrity sha512-fcwX4mndzpLQKBS1DVYhGAcYaYt7vsHNIvQV+WXMvnow5cgjPphq5CaayLaGsjRdSCKZFNGt7/GYAuXaNOiYCA==
+
through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
dependencies:
os-tmpdir "~1.0.2"
+tmpl@1.0.x:
+ version "1.0.4"
+ resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1"
+ integrity sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=
+
to-fast-properties@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553"
integrity sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==
+tough-cookie@^2.3.3, tough-cookie@~2.5.0:
+ version "2.5.0"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
+ integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
+ dependencies:
+ psl "^1.1.28"
+ punycode "^2.1.1"
+
+tough-cookie@^3.0.1:
+ version "3.0.1"
+ resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2"
+ integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==
+ dependencies:
+ ip-regex "^2.1.0"
+ psl "^1.1.28"
+ punycode "^2.1.1"
+
tough-cookie@~2.4.3:
version "2.4.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781"
psl "^1.1.24"
punycode "^1.4.1"
+tr46@^1.0.1:
+ version "1.0.1"
+ resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
+ integrity sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=
+ dependencies:
+ punycode "^2.1.0"
+
tributejs@^5.1.3:
version "5.1.3"
resolved "https://registry.yarnpkg.com/tributejs/-/tributejs-5.1.3.tgz#980600fc72865be5868893078b4bfde721129eae"
integrity sha512-B5CXihaVzXw+1UHhNFyAwUTMDk1EfoLP5Tj1VhD9yybZ1I8DZJEv8tZ1l0RJo0t0tk9ZhR8eG5tEsaCvRigmdQ==
+ts-jest@^25.4.0:
+ version "25.4.0"
+ resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.4.0.tgz#5ad504299f8541d463a52e93e5e9d76876be0ba4"
+ integrity sha512-+0ZrksdaquxGUBwSdTIcdX7VXdwLIlSRsyjivVA9gcO+Cvr6ByqDhu/mi5+HCcb6cMkiQp5xZ8qRO7/eCqLeyw==
+ dependencies:
+ bs-logger "0.x"
+ buffer-from "1.x"
+ fast-json-stable-stringify "2.x"
+ json5 "2.x"
+ lodash.memoize "4.x"
+ make-error "1.x"
+ micromatch "4.x"
+ mkdirp "1.x"
+ resolve "1.x"
+ semver "6.x"
+ yargs-parser "18.x"
+
ts-node@^8.8.2:
version "8.8.2"
resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.8.2.tgz#0b39e690bee39ea5111513a9d2bcdc0bc121755f"
dependencies:
prelude-ls "~1.1.2"
+type-detect@4.0.8:
+ version "4.0.8"
+ resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
+ integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
+
type-fest@^0.6.0:
version "0.6.0"
resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
media-typer "0.3.0"
mime-types "~2.1.24"
+typedarray-to-buffer@^3.1.5:
+ version "3.1.5"
+ resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
+ integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
+ dependencies:
+ is-typedarray "^1.0.0"
+
typescript@^3.8.3:
version "3.8.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.8.3.tgz#409eb8544ea0335711205869ec458ab109ee1061"
resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e"
integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g==
+v8-to-istanbul@^4.1.3:
+ version "4.1.3"
+ resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-4.1.3.tgz#22fe35709a64955f49a08a7c7c959f6520ad6f20"
+ integrity sha512-sAjOC+Kki6aJVbUOXJbcR0MnbfjvBzwKZazEJymA2IX49uoOdEdk+4fBq5cXgYgiyKtAyrrJNtBZdOeDIF+Fng==
+ dependencies:
+ "@types/istanbul-lib-coverage" "^2.0.1"
+ convert-source-map "^1.6.0"
+ source-map "^0.7.3"
+
validate-npm-package-license@^3.0.1:
version "3.0.4"
resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-2.0.1.tgz#c066afb582bb1cb4128d60ea92392e94d5e9dbec"
integrity sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=
+w3c-hr-time@^1.0.1:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
+ integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==
+ dependencies:
+ browser-process-hrtime "^1.0.0"
+
+w3c-xmlserializer@^1.1.2:
+ version "1.1.2"
+ resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-1.1.2.tgz#30485ca7d70a6fd052420a3d12fd90e6339ce794"
+ integrity sha512-p10l/ayESzrBMYWRID6xbuCKh2Fp77+sA0doRuGn4tTIMrrZVeqfpKjXHY+oDh3K4nLdPgNwMTVP6Vp4pvqbNg==
+ dependencies:
+ domexception "^1.0.1"
+ webidl-conversions "^4.0.2"
+ xml-name-validator "^3.0.0"
+
+walker@^1.0.7, walker@~1.0.5:
+ version "1.0.7"
+ resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb"
+ integrity sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=
+ dependencies:
+ makeerror "1.0.x"
+
watch@^1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/watch/-/watch-1.0.2.tgz#340a717bde765726fa0aa07d721e0147a551df0c"
exec-sh "^0.2.0"
minimist "^1.2.0"
+webidl-conversions@^4.0.2:
+ version "4.0.2"
+ resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.2.tgz#a855980b1f0b6b359ba1d5d9fb39ae941faa63ad"
+ integrity sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==
+
+whatwg-encoding@^1.0.1, whatwg-encoding@^1.0.5:
+ version "1.0.5"
+ resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
+ integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==
+ dependencies:
+ iconv-lite "0.4.24"
+
+whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
+ integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
+
+whatwg-url@^7.0.0:
+ version "7.1.0"
+ resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-7.1.0.tgz#c2c492f1eca612988efd3d2266be1b9fc6170d06"
+ integrity sha512-WUu7Rg1DroM7oQvGWfOiAK21n74Gg+T4elXEQYkOhtyLeWiJFoOGLXPKI/9gzIie9CtwVLm8wtw6YJdKyxSjeg==
+ dependencies:
+ lodash.sortby "^4.7.0"
+ tr46 "^1.0.1"
+ webidl-conversions "^4.0.2"
+
+which-module@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a"
+ integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=
+
which-pm-runs@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/which-pm-runs/-/which-pm-runs-1.0.0.tgz#670b3afbc552e0b55df6b7780ca74615f23ad1cb"
integrity sha1-Zws6+8VS4LVd9rd4DKdGFfI60cs=
-which@^1.2.9:
+which@^1.2.9, which@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
dependencies:
isexe "^2.0.0"
-which@^2.0.1:
+which@^2.0.1, which@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
string-width "^2.1.1"
strip-ansi "^4.0.0"
+wrap-ansi@^6.2.0:
+ version "6.2.0"
+ resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53"
+ integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==
+ dependencies:
+ ansi-styles "^4.0.0"
+ string-width "^4.1.0"
+ strip-ansi "^6.0.0"
+
wrappy@1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=
+write-file-atomic@^3.0.0:
+ version "3.0.3"
+ resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
+ integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
+ dependencies:
+ imurmurhash "^0.1.4"
+ is-typedarray "^1.0.0"
+ signal-exit "^3.0.2"
+ typedarray-to-buffer "^3.1.5"
+
write@1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/write/-/write-1.0.3.tgz#0800e14523b923a387e415123c865616aae0f5c3"
options ">=0.0.5"
ultron "1.0.x"
-ws@^7.2.3:
+ws@^7.0.0, ws@^7.2.3:
version "7.2.3"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.2.3.tgz#a5411e1fb04d5ed0efee76d26d5c46d830c39b46"
integrity sha512-HTDl9G9hbkNDk98naoR/cHDws7+EyYMOdL1BmjsZXRUjf7d+MficC4B7HLUPlSiho0vg+CWKrGIt/VJBd1xunQ==
+xml-name-validator@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
+ integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
+
+xmlchars@^2.1.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
+ integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
+
xregexp@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.3.0.tgz#7e92e73d9174a99a59743f67a4ce879a04b5ae50"
resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54"
integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==
+y18n@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b"
+ integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==
+
yaml@^1.7.2:
version "1.7.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.7.2.tgz#f26aabf738590ab61efaca502358e48dc9f348b2"
dependencies:
"@babel/runtime" "^7.6.3"
+yargs-parser@18.x, yargs-parser@^18.1.1:
+ version "18.1.3"
+ resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0"
+ integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==
+ dependencies:
+ camelcase "^5.0.0"
+ decamelize "^1.2.0"
+
+yargs@^15.3.1:
+ version "15.3.1"
+ resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.3.1.tgz#9505b472763963e54afe60148ad27a330818e98b"
+ integrity sha512-92O1HWEjw27sBfgmXiixJWT5hRBp2eobqXicLtPBIDBhYB+1HpwZlXmbW2luivBJHBzki+7VyCLRtAkScbTBQA==
+ dependencies:
+ cliui "^6.0.0"
+ decamelize "^1.2.0"
+ find-up "^4.1.0"
+ get-caller-file "^2.0.1"
+ require-directory "^2.1.1"
+ require-main-filename "^2.0.0"
+ set-blocking "^2.0.0"
+ string-width "^4.2.0"
+ which-module "^2.0.0"
+ y18n "^4.0.0"
+ yargs-parser "^18.1.1"
+
yn@3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50"