]> Untitled Git - lemmy.git/commitdiff
Merge branch 'dev' into federation
authorDessalines <tyhou13@gmx.com>
Tue, 14 Apr 2020 20:07:20 +0000 (16:07 -0400)
committerDessalines <tyhou13@gmx.com>
Tue, 14 Apr 2020 20:07:20 +0000 (16:07 -0400)
68 files changed:
.dockerignore
.gitignore
docker/federation/Dockerfile [new file with mode: 0644]
docker/federation/docker-compose.yml [new file with mode: 0644]
docker/federation/nginx.conf [new file with mode: 0644]
docker/federation/run-federation-test.bash [new file with mode: 0755]
server/Cargo.lock
server/Cargo.toml
server/config/config.hjson [new file with mode: 0644]
server/config/defaults.hjson
server/migrations/2020-03-26-192410_add_activitypub_tables/down.sql [new file with mode: 0644]
server/migrations/2020-03-26-192410_add_activitypub_tables/up.sql [new file with mode: 0644]
server/migrations/2020-04-03-194936_add_activitypub_for_posts_and_comments/down.sql [new file with mode: 0644]
server/migrations/2020-04-03-194936_add_activitypub_for_posts_and_comments/up.sql [new file with mode: 0644]
server/migrations/2020-04-07-135912_add_user_community_apub_constraints/down.sql [new file with mode: 0644]
server/migrations/2020-04-07-135912_add_user_community_apub_constraints/up.sql [new file with mode: 0644]
server/migrations/2020-04-14-163701_update_views_for_activitypub/down.sql [new file with mode: 0644]
server/migrations/2020-04-14-163701_update_views_for_activitypub/up.sql [new file with mode: 0644]
server/src/api/comment.rs
server/src/api/community.rs
server/src/api/post.rs
server/src/api/site.rs
server/src/api/user.rs
server/src/apub/activities.rs [new file with mode: 0644]
server/src/apub/community.rs
server/src/apub/fetcher.rs [new file with mode: 0644]
server/src/apub/inbox.rs [new file with mode: 0644]
server/src/apub/mod.rs
server/src/apub/post.rs
server/src/apub/signatures.rs [new file with mode: 0644]
server/src/apub/user.rs
server/src/db/code_migrations.rs [new file with mode: 0644]
server/src/db/comment.rs
server/src/db/comment_view.rs
server/src/db/community.rs
server/src/db/community_view.rs
server/src/db/mod.rs
server/src/db/moderator.rs
server/src/db/password_reset_request.rs
server/src/db/post.rs
server/src/db/post_view.rs
server/src/db/private_message.rs
server/src/db/user.rs
server/src/db/user_mention.rs
server/src/db/user_mention_view.rs
server/src/db/user_view.rs
server/src/lib.rs
server/src/main.rs
server/src/routes/federation.rs
server/src/routes/nodeinfo.rs
server/src/routes/webfinger.rs
server/src/schema.rs
server/src/settings.rs
server/src/websocket/server.rs
ui/.gitignore
ui/package.json
ui/src/components/comment-form.tsx
ui/src/components/comment-node.tsx
ui/src/components/navbar.tsx
ui/src/components/post-form.tsx
ui/src/components/post-listing.tsx
ui/src/components/post.tsx
ui/src/components/private-message-form.tsx
ui/src/components/private-message.tsx
ui/src/components/sidebar.tsx
ui/src/interfaces.ts
ui/src/utils.ts
ui/yarn.lock

index a29cd2ec2f54f23b14ae40c0e9d6318e18a2c44a..4f186bcde356ed7b50b039337906614751102883 100644 (file)
@@ -1,6 +1,11 @@
+# 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
 .git
+ansible
+
+# exceptions, needed for federation-test build
+
+!server/target/debug/lemmy_server
index 9f7fa1e3c6930299976d9bab212b53ec509d4d9c..5e9fd40d6d156b03ec276d5f84fdefd186399b8e 100644 (file)
@@ -1,10 +1,17 @@
+# 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/dev/volumes
+
+# local build files
 build/
-.idea/
 ui/src/translations
-docker/dev/volumes
-docker/federation-test/volumes
+
+# ide config
+.idea/
diff --git a/docker/federation/Dockerfile b/docker/federation/Dockerfile
new file mode 100644 (file)
index 0000000..d8302ea
--- /dev/null
@@ -0,0 +1,17 @@
+FROM ekidd/rust-musl-builder:1.38.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"]
diff --git a/docker/federation/docker-compose.yml b/docker/federation/docker-compose.yml
new file mode 100644 (file)
index 0000000..216ac9a
--- /dev/null
@@ -0,0 +1,91 @@
+version: '3.3'
+
+services:
+  nginx:
+    image: nginx:1.17-alpine
+    ports:
+      - "8540:8540"
+      - "8550:8550"
+    volumes:
+      - ./nginx.conf:/etc/nginx/nginx.conf
+    depends_on:
+      - lemmy_alpha
+      - pictshare_alpha
+      - lemmy_beta
+      - pictshare_beta
+      - 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__FOLLOWED_INSTANCES=lemmy_beta:8550
+      - LEMMY_FEDERATION__TLS_ENABLED=false
+      - 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=actix_web=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
+  pictshare_alpha:
+    image: shtripok/pictshare:latest
+    volumes:
+      - ./volumes/pictshare_alpha:/usr/share/nginx/html/data
+    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__FOLLOWED_INSTANCES=lemmy_alpha:8540
+      - LEMMY_FEDERATION__TLS_ENABLED=false
+      - 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=actix_web=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
+  pictshare_beta:
+    image: shtripok/pictshare:latest
+    volumes:
+      - ./volumes/pictshare_beta:/usr/share/nginx/html/data
+    restart: always
+
+  iframely:
+    image: dogbin/iframely:latest
+    volumes:
+      - ../iframely.config.local.js:/iframely/config.local.js:ro
+    restart: always
diff --git a/docker/federation/nginx.conf b/docker/federation/nginx.conf
new file mode 100644 (file)
index 0000000..c0633ea
--- /dev/null
@@ -0,0 +1,75 @@
+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";
+        }
+
+        location /pictshare/ {
+            proxy_pass http://pictshare_alpha: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;
+        }
+
+        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";
+        }
+
+        location /pictshare/ {
+            proxy_pass http://pictshare_beta: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;
+        }
+
+        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;
+        }
+    }
+}
diff --git a/docker/federation/run-federation-test.bash b/docker/federation/run-federation-test.bash
new file mode 100755 (executable)
index 0000000..62bc1e8
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+set -e
+
+if [ "$1" = "-yarn" ]; then
+  pushd ../../ui/ || exit
+  yarn build
+  popd || exit
+fi
+
+pushd ../../server/ || exit
+cargo build
+popd || exit
+
+sudo docker build ../../ -f Dockerfile -t lemmy-federation:latest
+
+sudo docker-compose up
\ No newline at end of file
index 2a3bc03302b064db867ab4f09f6b8ffd832c1896..b17f4d60b3959af625743d8ffe7e81e493d5a58c 100644 (file)
@@ -1,21 +1,22 @@
 # 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.5.0-alpha.16"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 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)",
+ "activitystreams-derive 0.5.0-alpha.8 (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.105 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
+ "thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "activitystreams-derive"
-version = "0.2.0"
+version = "0.5.0-alpha.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -23,30 +24,6 @@ dependencies = [
  "syn 1.0.16 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
-[[package]]
-name = "activitystreams-traits"
-version = "0.2.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
- "thiserror 1.0.11 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
-[[package]]
-name = "activitystreams-types"
-version = "0.3.0"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-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.105 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_derive 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)",
- "serde_json 1.0.48 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
 [[package]]
 name = "actix"
 version = "0.9.0"
@@ -143,7 +120,7 @@ dependencies = [
  "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.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "flate2 1.0.13 (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)",
@@ -372,7 +349,7 @@ dependencies = [
 
 [[package]]
 name = "aho-corasick"
-version = "0.7.9"
+version = "0.7.10"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "memchr 2.3.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -388,7 +365,7 @@ dependencies = [
 
 [[package]]
 name = "arc-swap"
-version = "0.4.4"
+version = "0.4.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -420,7 +397,7 @@ version = "0.2.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -461,19 +438,19 @@ name = "backtrace"
 version = "0.3.45"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "backtrace-sys 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)",
+ "backtrace-sys 0.1.34 (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.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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.33"
+version = "0.1.34"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -563,7 +540,7 @@ version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -572,7 +549,7 @@ version = "0.3.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "brotli-sys 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -608,14 +585,6 @@ dependencies = [
  "bytes 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
-[[package]]
-name = "c2-chacha"
-version = "0.2.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-dependencies = [
- "ppv-lite86 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
-]
-
 [[package]]
 name = "cc"
 version = "1.0.50"
@@ -701,7 +670,7 @@ version = "0.7.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -738,11 +707,11 @@ dependencies = [
 
 [[package]]
 name = "curl"
-version = "0.4.26"
+version = "0.4.28"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "curl-sys 0.4.28+curl-7.69.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "curl-sys 0.4.30+curl-7.69.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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.54 (registry+https://github.com/rust-lang/crates.io-index)",
  "schannel 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -752,11 +721,11 @@ dependencies = [
 
 [[package]]
 name = "curl-sys"
-version = "0.4.28+curl-7.69.0"
+version = "0.4.30+curl-7.69.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
  "libnghttp2-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
  "libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)",
  "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1043,12 +1012,12 @@ dependencies = [
 
 [[package]]
 name = "flate2"
-version = "1.0.13"
+version = "1.0.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 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.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
  "miniz_oxide 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1137,7 +1106,7 @@ name = "futures-macro"
 version = "0.3.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "proc-macro-hack 0.5.11 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro-hack 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)",
  "proc-macro2 1.0.9 (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.16 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1166,8 +1135,8 @@ dependencies = [
  "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.11 (registry+https://github.com/rust-lang/crates.io-index)",
- "proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "proc-macro-hack 0.5.12 (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)",
 ]
 
@@ -1193,7 +1162,7 @@ version = "0.1.14"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
 ]
 
@@ -1228,7 +1197,7 @@ name = "hermit-abi"
 version = "0.1.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1247,7 +1216,7 @@ name = "hostname"
 version = "0.1.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
  "winutil 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1256,7 +1225,7 @@ name = "hostname"
 version = "0.3.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
 ]
@@ -1317,7 +1286,7 @@ name = "iovec"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1339,8 +1308,8 @@ dependencies = [
  "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)",
  "crossbeam-utils 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "curl 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)",
- "curl-sys 0.4.28+curl-7.69.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "curl 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)",
+ "curl-sys 0.4.30+curl-7.69.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "encoding_rs 0.8.22 (registry+https://github.com/rust-lang/crates.io-index)",
  "futures-channel 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)",
@@ -1412,7 +1381,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 name = "lemmy_server"
 version = "0.0.1"
 dependencies = [
- "activitypub 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "activitystreams 0.5.0-alpha.16 (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.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1435,6 +1404,7 @@ dependencies = [
  "lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "lettre_email 0.9.2 (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.28 (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.5 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1444,6 +1414,7 @@ dependencies = [
  "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)",
+ "url 2.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1484,13 +1455,13 @@ 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.2 (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)",
 ]
 
 [[package]]
 name = "libc"
-version = "0.2.67"
+version = "0.2.68"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -1499,7 +1470,7 @@ version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1508,7 +1479,7 @@ version = "1.0.25"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cc 1.0.50 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
 ]
@@ -1576,7 +1547,7 @@ name = "memchr"
 version = "0.1.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1635,7 +1606,7 @@ dependencies = [
  "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.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
@@ -1649,7 +1620,7 @@ version = "0.6.7"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "iovec 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
  "mio 0.6.21 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1670,7 +1641,7 @@ version = "0.2.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "lazy_static 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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.28 (registry+https://github.com/rust-lang/crates.io-index)",
  "openssl-probe 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1687,7 +1658,7 @@ version = "0.2.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1756,7 +1727,7 @@ version = "1.12.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "hermit-abi 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -1773,7 +1744,7 @@ dependencies = [
  "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.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
  "openssl-sys 0.9.54 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -1789,7 +1760,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 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.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
 ]
@@ -1810,7 +1781,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 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.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "winapi 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1918,7 +1889,7 @@ dependencies = [
 
 [[package]]
 name = "proc-macro-hack"
-version = "0.5.11"
+version = "0.5.12"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1928,7 +1899,7 @@ dependencies = [
 
 [[package]]
 name = "proc-macro-nested"
-version = "0.1.3"
+version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -1977,7 +1948,7 @@ version = "0.4.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "fuchsia-cprng 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
@@ -1989,7 +1960,7 @@ version = "0.6.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "autocfg 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
@@ -2007,8 +1978,8 @@ version = "0.7.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "getrandom 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
- "rand_chacha 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
 ]
@@ -2024,10 +1995,10 @@ dependencies = [
 
 [[package]]
 name = "rand_chacha"
-version = "0.2.1"
+version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "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)",
 ]
 
@@ -2081,7 +2052,7 @@ name = "rand_jitter"
 version = "0.1.4"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
 ]
@@ -2093,7 +2064,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 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.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
@@ -2146,7 +2117,7 @@ name = "regex"
 version = "1.3.5"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)",
+ "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)",
@@ -2186,7 +2157,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 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.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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.36 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -2227,7 +2198,7 @@ dependencies = [
 
 [[package]]
 name = "ryu"
-version = "1.0.2"
+version = "1.0.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -2274,7 +2245,7 @@ version = "0.4.1"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "core-foundation-sys 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2355,7 +2326,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "indexmap 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "itoa 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
- "ryu 1.0.2 (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.105 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
@@ -2410,8 +2381,8 @@ name = "signal-hook-registry"
 version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "arc-swap 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "arc-swap 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2451,7 +2422,7 @@ version = "0.3.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
 ]
@@ -2524,7 +2495,7 @@ version = "3.1.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
@@ -2571,7 +2542,7 @@ version = "2.0.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2603,7 +2574,7 @@ name = "time"
 version = "0.1.42"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
- "libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
 ]
@@ -2618,7 +2589,7 @@ dependencies = [
  "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.67 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.68 (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)",
@@ -2772,6 +2743,7 @@ 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)",
+ "serde 1.0.105 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
@@ -2974,10 +2946,8 @@ dependencies = [
 ]
 
 [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 activitystreams 0.5.0-alpha.16 (registry+https://github.com/rust-lang/crates.io-index)" = "e7173513c9d586a1157f375835777e3b50498b6b7aab4411a7098b455ba995f0"
+"checksum activitystreams-derive 0.5.0-alpha.8 (registry+https://github.com/rust-lang/crates.io-index)" = "c7ff4a2be3b67d763e78794f622ef2d53da077521229774837f61963c4067b36"
 "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"
@@ -2998,9 +2968,9 @@ dependencies = [
 "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.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66"
-"checksum aho-corasick 0.7.9 (registry+https://github.com/rust-lang/crates.io-index)" = "d5e63fd144e18ba274ae7095c0197a870a7b9468abc801dd62f190d80817d2ec"
+"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.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d7b8a9123b8027467bce0099fe556c628a53c8d83df0507084c31e9ba2e39aff"
+"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.24 (registry+https://github.com/rust-lang/crates.io-index)" = "750b1c38a1dfadd108da0f01c08f4cdc7ff1bb39b325f9c82cc972361780a6e1"
@@ -3009,7 +2979,7 @@ dependencies = [
 "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.45 (registry+https://github.com/rust-lang/crates.io-index)" = "ad235dabf00f36301792cfe82499880ba54c6486be094d1047b02bacb67c14e8"
-"checksum backtrace-sys 0.1.33 (registry+https://github.com/rust-lang/crates.io-index)" = "e17b52e737c40a7d75abca20b29a19a0eb7ba9fc72c5a72dd282a0a3c2c0dc35"
+"checksum backtrace-sys 0.1.34 (registry+https://github.com/rust-lang/crates.io-index)" = "ca797db0057bae1a7aa2eef3283a874695455cecf08a43bfb8507ee0ebc1ed69"
 "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"
@@ -3028,7 +2998,6 @@ dependencies = [
 "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.4 (registry+https://github.com/rust-lang/crates.io-index)" = "fc267467f58ef6cc8874064c62a0423eb0d099362c8a23edd1c6d044f46eead4"
-"checksum c2-chacha 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "214238caa1bf3a496ec3392968969cab8549f96ff30652c9e56885329315f6bb"
 "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"
@@ -3042,8 +3011,8 @@ dependencies = [
 "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 curl 0.4.26 (registry+https://github.com/rust-lang/crates.io-index)" = "ecb534fed9060d04bccaa8b8e1e2d3d5a0d7a9ec6d9c667691c80a3c6b7d19ef"
-"checksum curl-sys 0.4.28+curl-7.69.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e2c6b7fa5d36aa192e410788b77af65f339af24c8786419e8b48173689a484bf"
+"checksum curl 0.4.28 (registry+https://github.com/rust-lang/crates.io-index)" = "eda1c0c03cacf3365d84818a40293f0e3f3953db8759c9c565a3b434edf0b52e"
+"checksum curl-sys 0.4.30+curl-7.69.1 (registry+https://github.com/rust-lang/crates.io-index)" = "923b38e423a8f47a4058e96f2a1fa2865a6231097ee860debd678d244277d50c"
 "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"
@@ -3075,7 +3044,7 @@ dependencies = [
 "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.13 (registry+https://github.com/rust-lang/crates.io-index)" = "6bd6d6f4752952feb71363cffc9ebac9411b75b87c6ab6058c40c8900cf43c0f"
+"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"
@@ -3121,7 +3090,7 @@ dependencies = [
 "checksum lettre 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "c66afaa5dfadbb81d4e00fd1d1ab057c7cd4c799c5a44e0009386d553587e728"
 "checksum lettre_email 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bbb68ca999042d965476e47bbdbacd52db0927348b6f8062c44dd04a3b1fd43b"
 "checksum lexical-core 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d7043aa5c05dd34fb73b47acb8c3708eac428de4545ea3682ed2f11293ebd890"
-"checksum libc 0.2.67 (registry+https://github.com/rust-lang/crates.io-index)" = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
+"checksum libc 0.2.68 (registry+https://github.com/rust-lang/crates.io-index)" = "dea0c0405123bba743ee3f91f49b1c7cfb684eef0da0a50110f758ccf24cdff0"
 "checksum libnghttp2-sys 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b359f5ec8106bc297694c9a562ace312be2cfd17a5fc68dc12249845aa144b11"
 "checksum libz-sys 1.0.25 (registry+https://github.com/rust-lang/crates.io-index)" = "2eb5e43362e38e2bca2fd5f5134c4d4564a23a5c28e9b95411652021a8675ebe"
 "checksum linked-hash-map 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "6d262045c5b87c0861b3f004610afd0e2c851e2908d08b6c870cbb9d5f494ecd"
@@ -3172,8 +3141,8 @@ dependencies = [
 "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.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ecd45702f76d6d3c75a80564378ae228a85f0b59d2f3ed43c91b4a69eb2ebfc5"
-"checksum proc-macro-nested 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "369a6ed065f249a159e06c45752c780bda2fb53c995718f9e484d08daa9eb42e"
+"checksum proc-macro-hack 0.5.12 (registry+https://github.com/rust-lang/crates.io-index)" = "f918f2b601f93baa836c1c2945faef682ba5b6d4828ecb45eeb7cc3c71b811b4"
+"checksum proc-macro-nested 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "8e946095f9d3ed29ec38de908c22f95d9ac008e424c7bcae54c75a79c527c694"
 "checksum proc-macro2 1.0.9 (registry+https://github.com/rust-lang/crates.io-index)" = "6c09721c6781493a2a492a96b5a5bf19b65917fe6728884e7c44dd0c60ca3435"
 "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"
@@ -3183,7 +3152,7 @@ dependencies = [
 "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.1 (registry+https://github.com/rust-lang/crates.io-index)" = "03a2a90da8c7523f554344f921aa97283eadf6ac484a6d2a7d0212fa7f8d6853"
+"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"
@@ -3208,7 +3177,7 @@ dependencies = [
 "checksum rustc-demangle 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "4c691c0e608126e00913e33f0ccf3727d5fc84573623b8d65b2df340b5201783"
 "checksum rustc-serialize 0.3.24 (registry+https://github.com/rust-lang/crates.io-index)" = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
 "checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
-"checksum ryu 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "bfa8506c1de11c9c4e4c38863ccbe02a305c8188e85a05a784c9e11e1c3910c8"
+"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.17 (registry+https://github.com/rust-lang/crates.io-index)" = "507a9e6e8ffe0a4e0ebb9a10293e62fdf7657c06f1b8bb07a8fcf697d2abf295"
 "checksum scheduled-thread-pool 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "f5de7bc31f28f8e6c28df5e1bf3d10610f5fdc14cc95f272853512c70a2bd779"
index 5a4fdcece840fbddb6817e819c3c61037a09133f..03bbfbee547bc1dc9862635a6027d593c796b387 100644 (file)
@@ -8,8 +8,8 @@ edition = "2018"
 diesel = { version = "1.4.2", features = ["postgres","chrono", "r2d2", "64-column-tables"] }
 diesel_migrations = "1.4.0"
 dotenv = "0.15.0"
+activitystreams = "0.5.0-alpha.16"
 bcrypt = "0.6.2"
-activitypub = "0.2.0"
 chrono = { version = "0.4.7", features = ["serde"] }
 failure = "0.1.5"
 serde_json = { version = "1.0.48", features = ["preserve_order"]}
@@ -34,6 +34,8 @@ rss = "1.9.0"
 htmlescape = "0.3.1"
 config = "0.10.1"
 hjson = "0.8.2"
+url = { version = "2.1.1", features = ["serde"] }
 percent-encoding = "2.1.0"
 isahc = "0.9"
 comrak = "0.7"
+openssl = "0.10"
diff --git a/server/config/config.hjson b/server/config/config.hjson
new file mode 100644 (file)
index 0000000..96eff45
--- /dev/null
@@ -0,0 +1,4 @@
+{
+  hostname: "localhost:8536"
+  federation_enabled: true
+}
\ No newline at end of file
index 97b9429c16812ba43f607a6daf720b5a16b1e721..8603e49a8b3e5edd4788b013c989e02cf82e370a 100644 (file)
@@ -26,7 +26,7 @@
     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
@@ -35,9 +35,6 @@
   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
+    # comma seperated list of instances to follow
+    followed_instances: ""
+    # whether tls is required for activitypub. only disable this for debugging, never for producion.
+    tls_enabled: true
+  }
 #  # email sending configuration
 #  email: {
 #    # hostname of the smtp server
diff --git a/server/migrations/2020-03-26-192410_add_activitypub_tables/down.sql b/server/migrations/2020-03-26-192410_add_activitypub_tables/down.sql
new file mode 100644 (file)
index 0000000..b171062
--- /dev/null
@@ -0,0 +1,16 @@
+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;
diff --git a/server/migrations/2020-03-26-192410_add_activitypub_tables/up.sql b/server/migrations/2020-03-26-192410_add_activitypub_tables/up.sql
new file mode 100644 (file)
index 0000000..8fe3b8e
--- /dev/null
@@ -0,0 +1,36 @@
+-- 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.
+
diff --git a/server/migrations/2020-04-03-194936_add_activitypub_for_posts_and_comments/down.sql b/server/migrations/2020-04-03-194936_add_activitypub_for_posts_and_comments/down.sql
new file mode 100644 (file)
index 0000000..50c95bb
--- /dev/null
@@ -0,0 +1,7 @@
+alter table post 
+drop column ap_id, 
+drop column local;
+
+alter table comment 
+drop column ap_id, 
+drop column local;
diff --git a/server/migrations/2020-04-03-194936_add_activitypub_for_posts_and_comments/up.sql b/server/migrations/2020-04-03-194936_add_activitypub_for_posts_and_comments/up.sql
new file mode 100644 (file)
index 0000000..a3fb956
--- /dev/null
@@ -0,0 +1,14 @@
+-- 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
+;
+
diff --git a/server/migrations/2020-04-07-135912_add_user_community_apub_constraints/down.sql b/server/migrations/2020-04-07-135912_add_user_community_apub_constraints/down.sql
new file mode 100644 (file)
index 0000000..faf24fd
--- /dev/null
@@ -0,0 +1,36 @@
+-- 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);
diff --git a/server/migrations/2020-04-07-135912_add_user_community_apub_constraints/up.sql b/server/migrations/2020-04-07-135912_add_user_community_apub_constraints/up.sql
new file mode 100644 (file)
index 0000000..de65191
--- /dev/null
@@ -0,0 +1,38 @@
+-- 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);
+
diff --git a/server/migrations/2020-04-14-163701_update_views_for_activitypub/down.sql b/server/migrations/2020-04-14-163701_update_views_for_activitypub/down.sql
new file mode 100644 (file)
index 0000000..ce2dde3
--- /dev/null
@@ -0,0 +1,440 @@
+-- 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
+;
+
diff --git a/server/migrations/2020-04-14-163701_update_views_for_activitypub/up.sql b/server/migrations/2020-04-14-163701_update_views_for_activitypub/up.sql
new file mode 100644 (file)
index 0000000..02499fc
--- /dev/null
@@ -0,0 +1,497 @@
+-- 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
+;
+
index 8373a338beded5b61ad156e20d16217dc1a8be73..1528f509b74090b0d32d29fafac0da55c067846d 100644 (file)
@@ -99,6 +99,8 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
       deleted: None,
       read: None,
       updated: None,
+      ap_id: "changeme".into(),
+      local: true,
     };
 
     let inserted_comment = match Comment::create(&conn, &comment_form) {
@@ -106,6 +108,11 @@ impl Perform<CommentResponse> for Oper<CreateComment> {
       Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
     };
 
+    match Comment::update_ap_id(&conn, inserted_comment.id) {
+      Ok(comment) => comment,
+      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
@@ -272,6 +279,8 @@ impl Perform<CommentResponse> for Oper<EditComment> {
 
     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,
@@ -285,6 +294,8 @@ impl Perform<CommentResponse> for Oper<EditComment> {
       } 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) {
index 936e54cda33212c9fbb7de0cdf6f20fc7db0caaf..35ca1d260dac8ca1a4028339d2f420bc0496b16c 100644 (file)
@@ -1,19 +1,22 @@
 use super::*;
+use crate::apub::activities::follow_community;
+use crate::apub::{format_community_name, gen_keypair_str, make_apub_endpoint, EndpointType};
 use diesel::PgConnection;
 use std::str::FromStr;
+use url::Url;
 
 #[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,
 }
 
@@ -32,17 +35,17 @@ pub struct CommunityResponse {
   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)]
@@ -128,25 +131,25 @@ impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
       None => None,
     };
 
-    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()),
         ) {
-          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 mut 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()),
     };
@@ -157,6 +160,12 @@ impl Perform<GetCommunityResponse> for Oper<GetCommunity> {
     let creator_user = admins.remove(creator_index);
     admins.insert(0, creator_user);
 
+    if !community.local {
+      let domain = Url::parse(&community.actor_id)?;
+      community_view.name =
+        format_community_name(&community_view.name.to_string(), domain.host_str().unwrap());
+    }
+
     // Return the jwt
     Ok(GetCommunityResponse {
       community: community_view,
@@ -198,6 +207,8 @@ impl Perform<CommunityResponse> for Oper<CreateCommunity> {
     }
 
     // When you create a community, make sure the user becomes a moderator and a follower
+    let (community_public_key, community_private_key) = gen_keypair_str();
+
     let community_form = CommunityForm {
       name: data.name.to_owned(),
       title: data.title.to_owned(),
@@ -208,6 +219,12 @@ impl Perform<CommunityResponse> for Oper<CreateCommunity> {
       deleted: None,
       nsfw: data.nsfw,
       updated: None,
+      actor_id: make_apub_endpoint(EndpointType::Community, &data.name).to_string(),
+      local: true,
+      private_key: Some(community_private_key),
+      public_key: Some(community_public_key),
+      last_refreshed_at: None,
+      published: None,
     };
 
     let inserted_community = match Community::create(&conn, &community_form) {
@@ -288,6 +305,8 @@ impl Perform<CommunityResponse> for Oper<EditCommunity> {
       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(),
@@ -298,6 +317,12 @@ impl Perform<CommunityResponse> for Oper<EditCommunity> {
       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) {
@@ -377,21 +402,29 @@ impl Perform<CommunityResponse> for Oper<FollowCommunity> {
 
     let user_id = claims.id;
 
-    let community_follower_form = CommunityFollowerForm {
-      community_id: data.community_id,
-      user_id,
-    };
-
-    if data.follow {
-      match CommunityFollower::follow(&conn, &community_follower_form) {
-        Ok(user) => user,
-        Err(_e) => return Err(APIError::err("community_follower_already_exists").into()),
+    let community = Community::read(conn, data.community_id)?;
+    if community.local {
+      let community_follower_form = CommunityFollowerForm {
+        community_id: data.community_id,
+        user_id,
       };
+
+      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::ignore(&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()),
-      };
+      // TODO: still have to implement unfollow
+      let user = User_::read(conn, user_id)?;
+      follow_community(&community, &user, conn)?;
+      // 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))?;
@@ -554,6 +587,12 @@ impl Perform<GetCommunityResponse> for Oper<TransferCommunity> {
       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) {
index fb022589195e2d9bcaf1f2085d83d512c5e9ae1e..eb8909b2d290ffac582e10a750febaa9cc4574e7 100644 (file)
@@ -1,8 +1,9 @@
 use super::*;
+use crate::apub::activities::{post_create, post_update};
 use diesel::PgConnection;
 use std::str::FromStr;
 
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
 pub struct CreatePost {
   name: String,
   url: Option<String>,
@@ -33,7 +34,7 @@ pub struct GetPostResponse {
   pub online: usize,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
 pub struct GetPosts {
   type_: String,
   sort: String,
@@ -43,9 +44,9 @@ pub struct GetPosts {
   auth: Option<String>,
 }
 
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
 pub struct GetPostsResponse {
-  posts: Vec<PostView>,
+  pub posts: Vec<PostView>,
 }
 
 #[derive(Serialize, Deserialize)]
@@ -106,7 +107,8 @@ impl Perform<PostResponse> for Oper<CreatePost> {
     }
 
     // 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());
     }
 
@@ -130,6 +132,9 @@ impl Perform<PostResponse> for Oper<CreatePost> {
       embed_description: iframely_description,
       embed_html: iframely_html,
       thumbnail_url: pictshare_thumbnail,
+      ap_id: "changeme".into(),
+      local: true,
+      published: None,
     };
 
     let inserted_post = match Post::create(&conn, &post_form) {
@@ -145,6 +150,13 @@ impl Perform<PostResponse> for Oper<CreatePost> {
       }
     };
 
+    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()),
+    };
+
+    post_create(&updated_post, &user, conn)?;
+
     // They like their own post by default
     let like_form = PostLikeForm {
       post_id: inserted_post.id,
@@ -357,7 +369,8 @@ impl Perform<PostResponse> for Oper<EditPost> {
     }
 
     // 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());
     }
 
@@ -365,6 +378,8 @@ impl Perform<PostResponse> for Oper<EditPost> {
     let (iframely_title, iframely_description, iframely_html, pictshare_thumbnail) =
       fetch_iframely_and_pictshare_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(),
@@ -381,9 +396,12 @@ impl Perform<PostResponse> for Oper<EditPost> {
       embed_description: iframely_description,
       embed_html: iframely_html,
       thumbnail_url: pictshare_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)" {
@@ -425,6 +443,8 @@ impl Perform<PostResponse> for Oper<EditPost> {
       ModStickyPost::create(&conn, &form)?;
     }
 
+    post_update(&updated_post, &user, conn)?;
+
     let post_view = PostView::read(&conn, data.edit_id, Some(user_id))?;
 
     Ok(PostResponse { post: post_view })
index 3720a2c4c1c8a7cf612aa20bd605e5750ab60e12..4202fea06e9cd5fcc13b23c9967498919340d2fa 100644 (file)
@@ -297,8 +297,7 @@ impl Perform<GetSiteResponse> for Oper<GetSite> {
   fn perform(&self, conn: &PgConnection) -> Result<GetSiteResponse, Error> {
     let _data: &GetSite = &self.data;
 
-    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 {
@@ -328,11 +327,16 @@ impl Perform<GetSiteResponse> for Oper<GetSite> {
     };
 
     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)?;
index 40e099694fb8399cc77d4f93548b9962f2295d4e..fbdead53b36e10e5910adfaa5a00163c1fab31dd 100644 (file)
@@ -1,4 +1,5 @@
 use super::*;
+use crate::apub::{gen_keypair_str, make_apub_endpoint, EndpointType};
 use crate::settings::Settings;
 use crate::{generate_random_string, send_email};
 use bcrypt::verify;
@@ -250,10 +251,11 @@ impl Perform<LoginResponse> for Oper<Register> {
       return Err(APIError::err("admin_already_created").into());
     }
 
+    let (user_public_key, user_private_key) = gen_keypair_str();
+
     // 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,
@@ -269,6 +271,12 @@ impl Perform<LoginResponse> for Oper<Register> {
       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_private_key),
+      public_key: Some(user_public_key),
+      last_refreshed_at: None,
     };
 
     // Create the user
@@ -287,12 +295,15 @@ impl Perform<LoginResponse> for Oper<Register> {
       }
     };
 
+    let (community_public_key, community_private_key) = gen_keypair_str();
+
     // 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,
@@ -301,6 +312,12 @@ impl Perform<LoginResponse> for Oper<Register> {
           removed: None,
           deleted: None,
           updated: None,
+          actor_id: make_apub_endpoint(EndpointType::Community, default_community_name).to_string(),
+          local: true,
+          private_key: Some(community_private_key),
+          public_key: Some(community_public_key),
+          last_refreshed_at: None,
+          published: None,
         };
         Community::create(&conn, &community_form).unwrap()
       }
@@ -387,7 +404,6 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
 
     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(),
@@ -403,6 +419,12 @@ impl Perform<LoginResponse> for Oper<SaveUserSettings> {
       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) {
@@ -540,30 +562,7 @@ impl Perform<AddAdminResponse> for Oper<AddAdmin> {
       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()),
     };
@@ -603,30 +602,7 @@ impl Perform<BanUserResponse> for Oper<BanUser> {
       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()),
     };
@@ -755,18 +731,7 @@ impl Perform<GetRepliesResponse> for Oper<MarkAllAsRead> {
       .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()),
       };
@@ -847,18 +812,7 @@ impl Perform<LoginResponse> for Oper<DeleteAccount> {
       .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()),
       };
@@ -872,25 +826,7 @@ impl Perform<LoginResponse> for Oper<DeleteAccount> {
       .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()),
       };
diff --git a/server/src/apub/activities.rs b/server/src/apub/activities.rs
new file mode 100644 (file)
index 0000000..a170726
--- /dev/null
@@ -0,0 +1,143 @@
+use crate::apub::{get_apub_protocol_string, get_following_instances};
+use crate::db::community::Community;
+use crate::db::post::Post;
+use crate::db::user::User_;
+use crate::db::Crud;
+use activitystreams::activity::{Accept, Create, Follow, Update};
+use activitystreams::object::properties::ObjectProperties;
+use activitystreams::BaseBox;
+use activitystreams::{context, public};
+use diesel::PgConnection;
+use failure::Error;
+use failure::_core::fmt::Debug;
+use isahc::prelude::*;
+use serde::Serialize;
+
+fn populate_object_props(
+  props: &mut ObjectProperties,
+  addressed_to: &str,
+  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_cc_xsd_any_uri(addressed_to)?;
+  Ok(())
+}
+
+fn send_activity<A>(activity: &A, to: Vec<String>) -> Result<(), Error>
+where
+  A: Serialize + Debug,
+{
+  let json = serde_json::to_string(&activity)?;
+  println!("sending data {}", json);
+  for t in to {
+    println!("to: {}", t);
+    let res = Request::post(t)
+      .header("Content-Type", "application/json")
+      .body(json.to_owned())?
+      .send()?;
+    dbg!(res);
+  }
+  Ok(())
+}
+
+fn get_followers(_community: &Community) -> Vec<String> {
+  // TODO: this is wrong, needs to go to the (non-local) followers of the community
+  get_following_instances()
+    .iter()
+    .map(|i| {
+      format!(
+        "{}://{}/federation/inbox",
+        get_apub_protocol_string(),
+        i.domain
+      )
+    })
+    .collect()
+}
+
+pub fn post_create(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+  let page = post.as_page(conn)?;
+  let community = Community::read(conn, post.community_id)?;
+  let mut create = Create::new();
+  populate_object_props(
+    &mut create.object_props,
+    &community.get_followers_url(),
+    &post.ap_id,
+  )?;
+  create
+    .create_props
+    .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+    .set_object_base_box(page)?;
+  send_activity(&create, get_followers(&community))?;
+  Ok(())
+}
+
+pub fn post_update(post: &Post, creator: &User_, conn: &PgConnection) -> Result<(), Error> {
+  let page = post.as_page(conn)?;
+  let community = Community::read(conn, post.community_id)?;
+  let mut update = Update::new();
+  populate_object_props(
+    &mut update.object_props,
+    &community.get_followers_url(),
+    &post.ap_id,
+  )?;
+  update
+    .update_props
+    .set_actor_xsd_any_uri(creator.actor_id.to_owned())?
+    .set_object_base_box(page)?;
+  send_activity(&update, get_followers(&community))?;
+  Ok(())
+}
+
+pub fn follow_community(
+  community: &Community,
+  user: &User_,
+  _conn: &PgConnection,
+) -> Result<(), Error> {
+  let mut follow = Follow::new();
+  follow
+    .object_props
+    .set_context_xsd_any_uri(context())?
+    // TODO: needs proper id
+    .set_id(user.actor_id.clone())?;
+  follow
+    .follow_props
+    .set_actor_xsd_any_uri(user.actor_id.clone())?
+    .set_object_xsd_any_uri(community.actor_id.clone())?;
+  let to = format!("{}/inbox", community.actor_id);
+  send_activity(&follow, vec![to])?;
+  Ok(())
+}
+
+pub fn accept_follow(follow: &Follow) -> Result<(), Error> {
+  let mut accept = Accept::new();
+  accept
+    .object_props
+    .set_context_xsd_any_uri(context())?
+    // TODO: needs proper id
+    .set_id(
+      follow
+        .follow_props
+        .get_actor_xsd_any_uri()
+        .unwrap()
+        .to_string(),
+    )?;
+  accept
+    .accept_props
+    .set_object_base_box(BaseBox::from_concrete(follow.clone())?)?;
+  let to = format!(
+    "{}/inbox",
+    follow
+      .follow_props
+      .get_actor_xsd_any_uri()
+      .unwrap()
+      .to_string()
+  );
+  send_activity(&accept, vec![to])?;
+  Ok(())
+}
index 32f14eeb28f7309b6832c9b51555f4aa7027505c..0bea47055c110712dbf694fcf8f949bb8220d9a7 100644 (file)
-use crate::apub::make_apub_endpoint;
-use crate::db::community::Community;
+use crate::apub::fetcher::{fetch_remote_object, fetch_remote_user};
+use crate::apub::signatures::PublicKey;
+use crate::apub::*;
+use crate::db::community::{Community, CommunityForm};
 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 crate::db::post::Post;
+use crate::db::user::User_;
+use crate::db::Crud;
+use crate::settings::Settings;
+use crate::{convert_datetime, naive_now};
+use activitystreams::actor::properties::ApActorProperties;
+use activitystreams::collection::OrderedCollection;
+use activitystreams::{
+  actor::Group, collection::UnorderedCollection, context, ext::Extensible,
+  object::properties::ObjectProperties,
+};
 use actix_web::body::Body;
 use actix_web::web::Path;
 use actix_web::HttpResponse;
+use actix_web::{web, Result};
+use diesel::r2d2::{ConnectionManager, Pool};
+use diesel::PgConnection;
+use failure::Error;
 use serde::Deserialize;
+use url::Url;
 
-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,
+}
 
-    let mut group = Group::default();
+pub async fn get_apub_community_list(
+  db: web::Data<Pool<ConnectionManager<PgConnection>>>,
+) -> Result<HttpResponse<Body>, Error> {
+  // TODO: implement pagination
+  let communities = Community::list_local(&db.get().unwrap())?
+    .iter()
+    .map(|c| c.as_group(&db.get().unwrap()))
+    .collect::<Result<Vec<GroupExt>, Error>>()?;
+  let mut collection = UnorderedCollection::default();
+  let oprops: &mut ObjectProperties = collection.as_mut();
+  oprops.set_context_xsd_any_uri(context())?.set_id(format!(
+    "{}://{}/federation/communities",
+    get_apub_protocol_string(),
+    Settings::get().hostname
+  ))?;
+
+  collection
+    .collection_props
+    .set_total_items(communities.len() as u64)?
+    .set_many_items_base_boxes(communities)?;
+  Ok(create_apub_response(&collection))
+}
 
-    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();
+impl Community {
+  fn as_group(&self, conn: &PgConnection) -> Result<GroupExt, Error> {
+    let mut group = Group::default();
+    let oprops: &mut ObjectProperties = group.as_mut();
+
+    let creator = User_::read(conn, self.creator_id)?;
+    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_attributed_to_xsd_any_uri(creator.actor_id)?;
+
+    if let Some(u) = self.updated.to_owned() {
+      oprops.set_updated(convert_datetime(u))?;
     }
-
-    if let Some(description) = &self.description {
-      group
-        .object_props
-        .set_summary_string(description.to_string())
-        .ok();
+    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_summary_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();
-
-    group
-  }
-
-  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_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 public_key = PublicKey {
+      id: format!("{}#main-key", self.actor_id),
+      owner: self.actor_id.to_owned(),
+      public_key_pem: self.public_key.to_owned().unwrap(),
+    };
 
-    let ap_followers = community_followers
-      .iter()
-      .map(|follower| make_apub_endpoint("u", &follower.user_name))
-      .collect();
+    Ok(group.extend(actor_props).extend(public_key.to_ext()))
+  }
 
-    collection
-      .collection_props
-      .set_items_string_vec(ap_followers)
-      .unwrap();
-    collection
+  pub fn get_followers_url(&self) -> String {
+    format!("{}/followers", &self.actor_id)
+  }
+  pub fn get_inbox_url(&self) -> String {
+    format!("{}/inbox", &self.actor_id)
+  }
+  pub fn get_outbox_url(&self) -> String {
+    format!("{}/outbox", &self.actor_id)
   }
 }
 
-#[derive(Deserialize)]
-pub struct CommunityQuery {
-  community_name: String,
+impl CommunityForm {
+  pub fn from_group(group: &GroupExt, conn: &PgConnection) -> Result<Self, Error> {
+    let oprops = &group.base.base.object_props;
+    let aprops = &group.base.extension;
+    let public_key: &PublicKey = &group.extension.public_key;
+
+    let followers_uri = Url::parse(&aprops.get_followers().unwrap().to_string())?;
+    let outbox_uri = Url::parse(&aprops.get_outbox().to_string())?;
+    let _outbox = fetch_remote_object::<OrderedCollection>(&outbox_uri)?;
+    let _followers = fetch_remote_object::<UnorderedCollection>(&followers_uri)?;
+    let apub_id = Url::parse(&oprops.get_attributed_to_xsd_any_uri().unwrap().to_string())?;
+    let creator = fetch_remote_user(&apub_id, conn)?;
+
+    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: 1, // -> peertube uses `"category": {"identifier": "9","name": "Comedy"},`
+      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: false,
+      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()),
+    })
+  }
 }
 
-pub async fn get_apub_community(info: Path<CommunityQuery>) -> HttpResponse<Body> {
-  let connection = establish_unpooled_connection();
-
-  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())
-  } else {
-    HttpResponse::NotFound().finish()
-  }
+pub async fn get_apub_community_http(
+  info: Path<CommunityQuery>,
+  db: web::Data<Pool<ConnectionManager<PgConnection>>>,
+) -> Result<HttpResponse<Body>, Error> {
+  let community = Community::read_from_name(&&db.get()?, info.community_name.to_owned())?;
+  let c = community.as_group(&db.get().unwrap())?;
+  Ok(create_apub_response(&c))
 }
 
-pub async fn get_apub_community_followers(info: Path<CommunityQuery>) -> HttpResponse<Body> {
+pub async fn get_apub_community_followers(
+  info: Path<CommunityQuery>,
+  db: web::Data<Pool<ConnectionManager<PgConnection>>>,
+) -> Result<HttpResponse<Body>, Error> {
+  let community = Community::read_from_name(&&db.get()?, info.community_name.to_owned())?;
+
   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, 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))
+}
 
-  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()
-  }
+pub async fn get_apub_community_outbox(
+  info: Path<CommunityQuery>,
+  db: web::Data<Pool<ConnectionManager<PgConnection>>>,
+) -> Result<HttpResponse<Body>, Error> {
+  let community = Community::read_from_name(&&db.get()?, info.community_name.to_owned())?;
+
+  let conn = establish_unpooled_connection();
+  //As we are an object, we validated that the community id was valid
+  let community_posts: Vec<Post> = Post::list_for_community(&conn, community.id)?;
+
+  let mut collection = OrderedCollection::default();
+  let oprops: &mut ObjectProperties = collection.as_mut();
+  oprops
+    .set_context_xsd_any_uri(context())?
+    .set_id(community.actor_id)?;
+  collection
+    .collection_props
+    .set_many_items_base_boxes(
+      community_posts
+        .iter()
+        .map(|c| c.as_page(&conn).unwrap())
+        .collect(),
+    )?
+    .set_total_items(community_posts.len() as u64)?;
+
+  Ok(create_apub_response(&collection))
 }
diff --git a/server/src/apub/fetcher.rs b/server/src/apub/fetcher.rs
new file mode 100644 (file)
index 0000000..53c97b6
--- /dev/null
@@ -0,0 +1,145 @@
+use crate::apub::*;
+use crate::db::community::{Community, CommunityForm};
+use crate::db::post::{Post, PostForm};
+use crate::db::user::{UserForm, User_};
+use crate::db::Crud;
+use crate::routes::nodeinfo::{NodeInfo, NodeInfoWellKnown};
+use crate::settings::Settings;
+use activitystreams::collection::{OrderedCollection, UnorderedCollection};
+use activitystreams::object::Page;
+use activitystreams::BaseBox;
+use diesel::result::Error::NotFound;
+use diesel::PgConnection;
+use failure::Error;
+use isahc::prelude::*;
+use log::warn;
+use serde::Deserialize;
+use std::time::Duration;
+use url::Url;
+
+fn fetch_node_info(instance: &Instance) -> Result<NodeInfo, Error> {
+  let well_known_uri = Url::parse(&format!(
+    "{}://{}/.well-known/nodeinfo",
+    get_apub_protocol_string(),
+    instance.domain
+  ))?;
+  let well_known = fetch_remote_object::<NodeInfoWellKnown>(&well_known_uri)?;
+  Ok(fetch_remote_object::<NodeInfo>(&well_known.links.href)?)
+}
+
+fn fetch_communities_from_instance(
+  community_list: &Url,
+  conn: &PgConnection,
+) -> Result<Vec<Community>, Error> {
+  fetch_remote_object::<UnorderedCollection>(community_list)?
+    .collection_props
+    .get_many_items_base_boxes()
+    .unwrap()
+    .map(|b| -> Result<CommunityForm, Error> {
+      let group = b.to_owned().to_concrete::<GroupExt>()?;
+      Ok(CommunityForm::from_group(&group, conn)?)
+    })
+    .map(
+      |cf: Result<CommunityForm, Error>| -> Result<Community, Error> {
+        let cf2 = cf?;
+        let existing = Community::read_from_actor_id(conn, &cf2.actor_id);
+        match existing {
+          Err(NotFound {}) => Ok(Community::create(conn, &cf2)?),
+          Ok(c) => Ok(Community::update(conn, c.id, &cf2)?),
+          Err(e) => Err(Error::from(e)),
+        }
+      },
+    )
+    .collect()
+}
+
+// TODO: add an optional param last_updated and only fetch if its too old
+pub fn fetch_remote_object<Response>(url: &Url) -> Result<Response, Error>
+where
+  Response: for<'de> Deserialize<'de>,
+{
+  if Settings::get().federation.tls_enabled && url.scheme() != "https" {
+    return Err(format_err!("Activitypub uri is insecure: {}", url));
+  }
+  // TODO: this function should return a future
+  let timeout = Duration::from_secs(60);
+  let text = Request::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)
+}
+
+fn fetch_remote_community_posts(
+  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: Result<PostForm, Error>| -> Result<Post, Error> {
+        let pf2 = pf?;
+        let existing = Post::read_from_apub_id(conn, &pf2.ap_id);
+        match existing {
+          Err(NotFound {}) => Ok(Post::create(conn, &pf2)?),
+          Ok(p) => Ok(Post::update(conn, p.id, &pf2)?),
+          Err(e) => Err(Error::from(e)),
+        }
+      })
+      .collect::<Result<Vec<Post>, Error>>()?,
+  )
+}
+
+// TODO: can probably merge these two methods?
+pub fn fetch_remote_user(apub_id: &Url, conn: &PgConnection) -> Result<User_, Error> {
+  let person = fetch_remote_object::<PersonExt>(apub_id)?;
+  let uf = UserForm::from_person(&person)?;
+  let existing = User_::read_from_apub_id(conn, &uf.actor_id);
+  Ok(match existing {
+    Err(NotFound {}) => User_::create(conn, &uf)?,
+    Ok(u) => User_::update(conn, u.id, &uf)?,
+    Err(e) => return Err(Error::from(e)),
+  })
+}
+pub fn fetch_remote_community(apub_id: &Url, conn: &PgConnection) -> Result<Community, Error> {
+  let group = fetch_remote_object::<GroupExt>(apub_id)?;
+  let cf = CommunityForm::from_group(&group, conn)?;
+  let existing = Community::read_from_actor_id(conn, &cf.actor_id);
+  Ok(match existing {
+    Err(NotFound {}) => Community::create(conn, &cf)?,
+    Ok(u) => Community::update(conn, u.id, &cf)?,
+    Err(e) => return Err(Error::from(e)),
+  })
+}
+
+// TODO: in the future, this should only be done when an instance is followed for the first time
+//       after that, we should rely in the inbox, and fetch on demand when needed
+pub fn fetch_all(conn: &PgConnection) -> Result<(), Error> {
+  for instance in &get_following_instances() {
+    let node_info = fetch_node_info(instance)?;
+    if let Some(community_list) = node_info.metadata.community_list_url {
+      let communities = fetch_communities_from_instance(&community_list, conn)?;
+      for c in communities {
+        fetch_remote_community_posts(&c, conn)?;
+      }
+    } else {
+      warn!(
+        "{} is not a Lemmy instance, federation is not supported",
+        instance.domain
+      );
+    }
+  }
+  Ok(())
+}
diff --git a/server/src/apub/inbox.rs b/server/src/apub/inbox.rs
new file mode 100644 (file)
index 0000000..a2db335
--- /dev/null
@@ -0,0 +1,101 @@
+use crate::apub::activities::accept_follow;
+use crate::apub::fetcher::fetch_remote_user;
+use crate::db::community::{Community, CommunityFollower, CommunityFollowerForm};
+use crate::db::post::{Post, PostForm};
+use crate::db::Crud;
+use crate::db::Followable;
+use activitystreams::activity::{Accept, Create, Follow, Update};
+use activitystreams::object::Page;
+use actix_web::{web, HttpResponse};
+use diesel::r2d2::{ConnectionManager, Pool};
+use diesel::PgConnection;
+use failure::Error;
+use url::Url;
+
+// TODO: need a proper actor that has this inbox
+
+pub async fn inbox(
+  input: web::Json<AcceptedObjects>,
+  db: web::Data<Pool<ConnectionManager<PgConnection>>>,
+) -> Result<HttpResponse, Error> {
+  // TODO: make sure that things are received in the correct inbox
+  //      (by using seperate handler functions and checking the user/community name in the path)
+  let input = input.into_inner();
+  let conn = &db.get().unwrap();
+  match input {
+    AcceptedObjects::Create(c) => handle_create(&c, conn),
+    AcceptedObjects::Update(u) => handle_update(&u, conn),
+    AcceptedObjects::Follow(f) => handle_follow(&f, conn),
+    AcceptedObjects::Accept(a) => handle_accept(&a, conn),
+  }
+}
+
+fn handle_create(create: &Create, conn: &PgConnection) -> Result<HttpResponse, Error> {
+  let page = create
+    .create_props
+    .get_object_base_box()
+    .to_owned()
+    .unwrap()
+    .to_owned()
+    .to_concrete::<Page>()?;
+  let post = PostForm::from_page(&page, conn)?;
+  Post::create(conn, &post)?;
+  // TODO: send the new post out via websocket
+  Ok(HttpResponse::Ok().finish())
+}
+
+fn handle_update(update: &Update, conn: &PgConnection) -> Result<HttpResponse, Error> {
+  let page = update
+    .update_props
+    .get_object_base_box()
+    .to_owned()
+    .unwrap()
+    .to_owned()
+    .to_concrete::<Page>()?;
+  let post = PostForm::from_page(&page, conn)?;
+  let id = Post::read_from_apub_id(conn, &post.ap_id)?.id;
+  Post::update(conn, id, &post)?;
+  // TODO: send the new post out via websocket
+  Ok(HttpResponse::Ok().finish())
+}
+
+fn handle_follow(follow: &Follow, conn: &PgConnection) -> Result<HttpResponse, Error> {
+  println!("received follow: {:?}", &follow);
+
+  // TODO: make sure this is a local community
+  let community_uri = follow
+    .follow_props
+    .get_object_xsd_any_uri()
+    .unwrap()
+    .to_string();
+  let community = Community::read_from_actor_id(conn, &community_uri)?;
+  let user_uri = follow
+    .follow_props
+    .get_actor_xsd_any_uri()
+    .unwrap()
+    .to_string();
+  let user = fetch_remote_user(&Url::parse(&user_uri)?, conn)?;
+  // TODO: insert ID of the user into follows of the community
+  let community_follower_form = CommunityFollowerForm {
+    community_id: community.id,
+    user_id: user.id,
+  };
+  CommunityFollower::follow(&conn, &community_follower_form)?;
+  accept_follow(&follow)?;
+  Ok(HttpResponse::Ok().finish())
+}
+
+fn handle_accept(accept: &Accept, _conn: &PgConnection) -> Result<HttpResponse, Error> {
+  println!("received accept: {:?}", &accept);
+  // TODO: at this point, indicate to the user that they are following the community
+  Ok(HttpResponse::Ok().finish())
+}
+
+#[serde(untagged)]
+#[derive(serde::Deserialize)]
+pub enum AcceptedObjects {
+  Create(Create),
+  Update(Update),
+  Follow(Follow),
+  Accept(Accept),
+}
index e224e2591636c033da7a4f65d0c02554b8cf2543..c1716ca274464df0466a0d0d690a9bbf48f049f3 100644 (file)
+pub mod activities;
 pub mod community;
+pub mod fetcher;
+pub mod inbox;
 pub mod post;
+pub mod signatures;
 pub mod user;
+use crate::apub::signatures::PublicKeyExtension;
 use crate::Settings;
+use activitystreams::actor::{properties::ApActorProperties, Group, Person};
+use activitystreams::ext::Ext;
+use actix_web::body::Body;
+use actix_web::HttpResponse;
+use openssl::{pkey::PKey, rsa::Rsa};
+use url::Url;
 
-use std::fmt::Display;
+type GroupExt = Ext<Ext<Group, ApActorProperties>, PublicKeyExtension>;
+type PersonExt = Ext<Ext<Person, ApActorProperties>, PublicKeyExtension>;
 
-#[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};
+static APUB_JSON_CONTENT_TYPE: &str = "application/activity+json";
 
-  #[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,
-    };
+pub enum EndpointType {
+  Community,
+  User,
+  Post,
+  Comment,
+}
 
-    let person = user.as_person();
-    assert_eq!(
-      format!("https://{}/federation/u/thom", Settings::get().hostname),
-      person.object_props.id_string().unwrap()
-    );
-  }
+pub struct Instance {
+  domain: String,
+}
 
-  #[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,
-    };
+fn create_apub_response<T>(json: &T) -> HttpResponse<Body>
+where
+  T: serde::ser::Serialize,
+{
+  HttpResponse::Ok()
+    .content_type(APUB_JSON_CONTENT_TYPE)
+    .json(json)
+}
 
-    let group = community.as_group();
-    assert_eq!(
-      format!("https://{}/federation/c/Test", Settings::get().hostname),
-      group.object_props.id_string().unwrap()
-    );
-  }
+// TODO: we will probably need to change apub endpoint urls so that html and activity+json content
+//       types are handled at the same endpoint, so that you can copy the url into mastodon search
+//       and have it fetch the object.
+pub fn make_apub_endpoint(endpoint_type: EndpointType, name: &str) -> Url {
+  let point = match endpoint_type {
+    EndpointType::Community => "c",
+    EndpointType::User => "u",
+    EndpointType::Post => "p",
+    // TODO I have to change this else my update advanced_migrations crashes the
+    // server if a comment exists.
+    EndpointType::Comment => "comment",
+  };
 
-  #[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,
-    };
+  Url::parse(&format!(
+    "{}://{}/federation/{}/{}",
+    get_apub_protocol_string(),
+    Settings::get().hostname,
+    point,
+    name
+  ))
+  .unwrap()
+}
 
-    let page = post.as_page();
-    assert_eq!(
-      format!("https://{}/federation/post/62", Settings::get().hostname),
-      page.object_props.id_string().unwrap()
-    );
+pub fn get_apub_protocol_string() -> &'static str {
+  if Settings::get().federation.tls_enabled {
+    "https"
+  } else {
+    "http"
   }
 }
 
-pub fn make_apub_endpoint<S: Display, T: Display>(point: S, value: T) -> String {
-  format!(
-    "https://{}/federation/{}/{}",
-    Settings::get().hostname,
-    point,
-    value
+pub fn gen_keypair() -> (Vec<u8>, Vec<u8>) {
+  let rsa = Rsa::generate(2048).expect("sign::gen_keypair: key generation error");
+  let pkey = PKey::from_rsa(rsa).expect("sign::gen_keypair: parsing error");
+  (
+    pkey
+      .public_key_to_pem()
+      .expect("sign::gen_keypair: public key encoding error"),
+    pkey
+      .private_key_to_pem_pkcs8()
+      .expect("sign::gen_keypair: private key encoding error"),
   )
 }
+
+pub fn gen_keypair_str() -> (String, String) {
+  let (public_key, private_key) = gen_keypair();
+  (vec_bytes_to_str(public_key), vec_bytes_to_str(private_key))
+}
+
+fn vec_bytes_to_str(bytes: Vec<u8>) -> String {
+  String::from_utf8_lossy(&bytes).into_owned()
+}
+
+/// If community is on local instance, don't include the @instance part. This is only for displaying
+/// to the user and should never be used otherwise.
+pub fn format_community_name(name: &str, instance: &str) -> String {
+  if instance == Settings::get().hostname {
+    format!("!{}", name)
+  } else {
+    format!("!{}@{}", name, instance)
+  }
+}
+
+pub fn get_following_instances() -> Vec<Instance> {
+  Settings::get()
+    .federation
+    .followed_instances
+    .split(',')
+    .map(|i| Instance {
+      domain: i.to_string(),
+    })
+    .collect()
+}
index ebb17129037eb0b6f30e1b383f46e9baa2d1a83b..b574d09c0b60eb99b5b5d947d9cc57dd50be7dc9 100644 (file)
-use crate::apub::make_apub_endpoint;
-use crate::db::post::Post;
-use crate::to_datetime_utc;
-use activitypub::{context, object::Page};
+use crate::apub::create_apub_response;
+use crate::apub::fetcher::{fetch_remote_community, fetch_remote_user};
+use crate::convert_datetime;
+use crate::db::community::Community;
+use crate::db::post::{Post, PostForm};
+use crate::db::user::User_;
+use crate::db::Crud;
+use activitystreams::{context, object::properties::ObjectProperties, object::Page};
+use actix_web::body::Body;
+use actix_web::web::Path;
+use actix_web::{web, HttpResponse};
+use diesel::r2d2::{ConnectionManager, Pool};
+use diesel::PgConnection;
+use failure::Error;
+use serde::Deserialize;
+use url::Url;
+
+#[derive(Deserialize)]
+pub struct PostQuery {
+  post_id: String,
+}
+
+pub async fn get_apub_post(
+  info: Path<PostQuery>,
+  db: web::Data<Pool<ConnectionManager<PgConnection>>>,
+) -> Result<HttpResponse<Body>, Error> {
+  let id = info.post_id.parse::<i32>()?;
+  let post = Post::read(&&db.get()?, id)?;
+  Ok(create_apub_response(&post.as_page(&db.get().unwrap())?))
+}
 
 impl Post {
-  pub fn as_page(&self) -> Page {
-    let base_url = make_apub_endpoint("post", self.id);
+  pub fn as_page(&self, conn: &PgConnection) -> Result<Page, 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)
+      .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/dessalines/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())?;
     }
 
-    //page.object_props.set_attributed_to_string
-
-    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();
+    if let Some(u) = self.updated {
+      oprops.set_updated(convert_datetime(u))?;
     }
 
-    page
+    Ok(page)
+  }
+}
+
+impl PostForm {
+  pub fn from_page(page: &Page, conn: &PgConnection) -> Result<PostForm, Error> {
+    let oprops = &page.object_props;
+    let creator_id = Url::parse(&oprops.get_attributed_to_xsd_any_uri().unwrap().to_string())?;
+    let creator = fetch_remote_user(&creator_id, conn)?;
+    let community_id = Url::parse(&oprops.get_to_xsd_any_uri().unwrap().to_string())?;
+    let community = fetch_remote_community(&community_id, conn)?;
+
+    Ok(PostForm {
+      name: oprops.get_summary_xsd_string().unwrap().to_string(),
+      url: oprops.get_url_xsd_any_uri().map(|u| u.to_string()),
+      body: oprops.get_content_xsd_string().map(|c| c.to_string()),
+      creator_id: creator.id,
+      community_id: community.id,
+      removed: None, // -> Delete activity / tombstone
+      locked: None,  // -> commentsEnabled
+      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,     // -> Delete activity / tombstone
+      nsfw: false,       // -> sensitive
+      stickied: None,    // -> put it in "featured" collection of the community
+      embed_title: None, // -> attachment?
+      embed_description: None,
+      embed_html: None,
+      thumbnail_url: None,
+      ap_id: oprops.get_id().unwrap().to_string(),
+      local: false,
+    })
   }
 }
diff --git a/server/src/apub/signatures.rs b/server/src/apub/signatures.rs
new file mode 100644 (file)
index 0000000..5bb3c53
--- /dev/null
@@ -0,0 +1,30 @@
+// For this example, we'll use the Extensible trait, the Extension trait, the Actor trait, and
+// the Person type
+use activitystreams::{actor::Actor, ext::Extension};
+
+// The following is taken from here:
+// https://docs.rs/activitystreams/0.5.0-alpha.17/activitystreams/ext/index.html
+
+#[derive(Clone, Debug, Default, serde::Deserialize, serde::Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct PublicKey {
+  pub id: String,
+  pub owner: String,
+  pub public_key_pem: String,
+}
+
+#[derive(Clone, Debug, Default, serde::Deserialize, serde::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: Actor {}
index 5f2421f11cdb57c9ae6b7997a6f993859b3c8111..d10093d4cb11c3377f7768173b8d7a99c5de48fd 100644 (file)
@@ -1,74 +1,99 @@
-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 crate::apub::signatures::PublicKey;
+use crate::apub::{create_apub_response, PersonExt};
+use crate::db::user::{UserForm, User_};
+use crate::{convert_datetime, naive_now};
+use activitystreams::{
+  actor::{properties::ApActorProperties, Person},
+  context,
+  ext::Extensible,
+  object::properties::ObjectProperties,
+};
 use actix_web::body::Body;
 use actix_web::web::Path;
 use actix_web::HttpResponse;
+use actix_web::{web, Result};
+use diesel::r2d2::{ConnectionManager, Pool};
+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);
-    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();
-    }
+#[derive(Deserialize)]
+pub struct UserQuery {
+  user_name: String,
+}
 
-    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();
-    }
+pub async fn get_apub_user(
+  info: Path<UserQuery>,
+  db: web::Data<Pool<ConnectionManager<PgConnection>>>,
+) -> Result<HttpResponse<Body>, Error> {
+  let user = User_::find_by_email_or_username(&&db.get()?, &info.user_name)?;
 
-    person
+  let mut person = Person::default();
+  let oprops: &mut ObjectProperties = person.as_mut();
+  oprops
+    .set_context_xsd_any_uri(context())?
+    .set_id(user.actor_id.to_string())?
+    .set_name_xsd_string(user.name.to_owned())?
+    .set_published(convert_datetime(user.published))?;
+
+  if let Some(u) = user.updated {
+    oprops.set_updated(convert_datetime(u))?;
   }
-}
 
-#[derive(Deserialize)]
-pub struct UserQuery {
-  user_name: String,
+  if let Some(i) = &user.preferred_username {
+    oprops.set_name_xsd_string(i.to_owned())?;
+  }
+
+  let mut actor_props = ApActorProperties::default();
+
+  actor_props
+    .set_inbox(format!("{}/inbox", &user.actor_id))?
+    .set_outbox(format!("{}/outbox", &user.actor_id))?
+    .set_following(format!("{}/following", &user.actor_id))?
+    .set_liked(format!("{}/liked", &user.actor_id))?;
+
+  let public_key = PublicKey {
+    id: format!("{}#main-key", user.actor_id),
+    owner: user.actor_id.to_owned(),
+    public_key_pem: user.public_key.unwrap(),
+  };
+
+  Ok(create_apub_response(
+    &person.extend(actor_props).extend(public_key.to_ext()),
+  ))
 }
 
-pub async fn get_apub_user(info: Path<UserQuery>) -> HttpResponse<Body> {
-  let connection = establish_unpooled_connection();
+impl UserForm {
+  pub fn from_person(person: &PersonExt) -> Result<Self, Error> {
+    let oprops = &person.base.base.object_props;
+    let aprops = &person.base.extension;
+    let public_key: &PublicKey = &person.extension.public_key;
 
-  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: None, // -> icon, image
+      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()),
+    })
   }
 }
diff --git a/server/src/db/code_migrations.rs b/server/src/db/code_migrations.rs
new file mode 100644 (file)
index 0000000..a13a996
--- /dev/null
@@ -0,0 +1,145 @@
+// This is for db migrations that require code
+use super::comment::Comment;
+use super::community::{Community, CommunityForm};
+use super::post::Post;
+use super::user::{UserForm, User_};
+use super::*;
+use crate::apub::{gen_keypair_str, make_apub_endpoint, EndpointType};
+use crate::naive_now;
+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)?;
+
+  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 (user_public_key, user_private_key) = gen_keypair_str();
+
+    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(user_private_key),
+      public_key: Some(user_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 (community_public_key, community_private_key) = gen_keypair_str();
+
+    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(community_private_key),
+      public_key: Some(community_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(())
+}
index c9bfbac6c313d17a862d4fa1833f26d43d80fd90..0b8a2e206e173f99695a87a804aaf8072af9aca9 100644 (file)
@@ -1,5 +1,7 @@
 use super::post::Post;
 use super::*;
+use crate::apub::{make_apub_endpoint, EndpointType};
+use crate::naive_now;
 use crate::schema::{comment, comment_like, comment_saved};
 
 // WITH RECURSIVE MyTree AS (
@@ -23,6 +25,8 @@ pub struct Comment {
   pub published: chrono::NaiveDateTime,
   pub updated: Option<chrono::NaiveDateTime>,
   pub deleted: bool,
+  pub ap_id: String,
+  pub local: bool,
 }
 
 #[derive(Insertable, AsChangeset, Clone)]
@@ -36,6 +40,8 @@ pub struct CommentForm {
   pub read: Option<bool>,
   pub updated: Option<chrono::NaiveDateTime>,
   pub deleted: Option<bool>,
+  pub ap_id: String,
+  pub local: bool,
 }
 
 impl Crud<CommentForm> for Comment {
@@ -68,6 +74,37 @@ 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 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"]
@@ -170,7 +207,6 @@ mod tests {
 
     let new_user = UserForm {
       name: "terry".into(),
-      fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
@@ -186,6 +222,12 @@ mod tests {
       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();
@@ -200,6 +242,12 @@ mod tests {
       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();
@@ -220,6 +268,9 @@ mod tests {
       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();
@@ -233,6 +284,8 @@ mod tests {
       read: None,
       parent_id: None,
       updated: None,
+      ap_id: "changeme".into(),
+      local: true,
     };
 
     let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
@@ -248,6 +301,8 @@ mod tests {
       parent_id: None,
       published: inserted_comment.published,
       updated: None,
+      ap_id: "changeme".into(),
+      local: true,
     };
 
     let child_comment_form = CommentForm {
@@ -259,6 +314,8 @@ mod tests {
       deleted: None,
       read: None,
       updated: None,
+      ap_id: "changeme".into(),
+      local: true,
     };
 
     let inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
index 85b41d826401bb97acbb002f839a7baae31bc17f..f0b97cb51e664f5ffba6d3d6219418d4f90057ea 100644 (file)
@@ -14,10 +14,16 @@ 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,
@@ -43,10 +49,16 @@ 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,
@@ -75,10 +87,16 @@ pub struct CommentView {
   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,
@@ -282,10 +300,16 @@ 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,
@@ -315,10 +339,16 @@ pub struct ReplyView {
   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,
@@ -434,7 +464,6 @@ mod tests {
 
     let new_user = UserForm {
       name: "timmy".into(),
-      fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
@@ -450,6 +479,12 @@ mod tests {
       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();
@@ -464,6 +499,12 @@ mod tests {
       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();
@@ -484,6 +525,9 @@ mod tests {
       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();
@@ -497,6 +541,8 @@ mod tests {
       deleted: None,
       read: None,
       updated: None,
+      ap_id: "changeme".into(),
+      local: true,
     };
 
     let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
@@ -535,6 +581,12 @@ mod tests {
       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 {
@@ -562,6 +614,12 @@ mod tests {
       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)
index 6350096358bd7a8f16092a0f35db1cedb90bfb53..ca2fc120a44bcf979f1b8639dd20c59232fc10cd 100644 (file)
@@ -15,9 +15,14 @@ pub struct 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)]
+#[derive(Insertable, AsChangeset, Clone, Serialize, Deserialize, Debug)]
 #[table_name = "community"]
 pub struct CommunityForm {
   pub name: String,
@@ -26,9 +31,15 @@ pub struct CommunityForm {
   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 {
@@ -69,6 +80,18 @@ impl Community {
       .first::<Self>(conn)
   }
 
+  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)
+  }
+
   pub fn get_url(&self) -> String {
     format!("https://{}/c/{}", Settings::get().hostname, self.name)
   }
@@ -216,7 +239,6 @@ mod tests {
 
     let new_user = UserForm {
       name: "bobbee".into(),
-      fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
@@ -232,6 +254,12 @@ mod tests {
       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();
@@ -246,6 +274,12 @@ mod tests {
       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();
@@ -262,6 +296,11 @@ mod tests {
       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 {
index f24c922c397db237173a4e3c04222899f675e6dc..3e946f472afb773a5fbe6c2310e9a5f8b4196529 100644 (file)
@@ -15,6 +15,11 @@ table! {
     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,
@@ -40,6 +45,11 @@ table! {
     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,
@@ -58,8 +68,12 @@ table! {
     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,
   }
 }
@@ -70,8 +84,12 @@ table! {
     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,
   }
 }
@@ -82,8 +100,12 @@ table! {
     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,
   }
 }
@@ -104,6 +126,11 @@ pub struct CommunityView {
   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,
@@ -257,8 +284,12 @@ pub struct CommunityModeratorView {
   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,
 }
 
@@ -287,8 +318,12 @@ pub struct CommunityFollowerView {
   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,
 }
 
@@ -317,8 +352,12 @@ pub struct CommunityUserBanView {
   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,
 }
 
index e0d358ffe7e286915493a024673d8126e5b571c8..f434f7288a95dcdd5cef2d93a240b761ef57e7b8 100644 (file)
@@ -5,6 +5,7 @@ use diesel::*;
 use serde::{Deserialize, Serialize};
 
 pub mod category;
+pub mod code_migrations;
 pub mod comment;
 pub mod comment_view;
 pub mod community;
index a8c3df4f15ba8302c901d90148cc329fe9c5cab9..d56e3914ca7e0e00bcf61d48b4b9d5a82beab2a5 100644 (file)
@@ -438,7 +438,6 @@ mod tests {
 
     let new_mod = UserForm {
       name: "the mod".into(),
-      fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
@@ -454,13 +453,18 @@ mod tests {
       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,
@@ -476,6 +480,12 @@ mod tests {
       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();
@@ -490,6 +500,12 @@ mod tests {
       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();
@@ -510,6 +526,9 @@ mod tests {
       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();
@@ -523,6 +542,8 @@ mod tests {
       read: None,
       parent_id: None,
       updated: None,
+      ap_id: "changeme".into(),
+      local: true,
     };
 
     let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
index 6951fd39936912c789866bd53d12c13c2554aa6f..ea8b2964a3bd2ce23c30c88445ebdd4fbabc9c07 100644 (file)
@@ -88,7 +88,6 @@ mod tests {
 
     let new_user = UserForm {
       name: "thommy prw".into(),
-      fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
@@ -104,6 +103,12 @@ mod tests {
       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();
index ffde14d3d702bbe427ca2b6afc933ae1ff6cfd35..bdd3306117afb1d2774ed4a3aa08622e72524587 100644 (file)
@@ -1,4 +1,6 @@
 use super::*;
+use crate::apub::{make_apub_endpoint, EndpointType};
+use crate::naive_now;
 use crate::schema::{post, post_like, post_read, post_saved};
 
 #[derive(Queryable, Identifiable, PartialEq, Debug, Serialize, Deserialize)]
@@ -21,9 +23,11 @@ pub struct 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,
@@ -33,6 +37,7 @@ pub struct PostForm {
   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,
@@ -41,6 +46,56 @@ pub struct PostForm {
   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 {
@@ -191,7 +246,6 @@ mod tests {
 
     let new_user = UserForm {
       name: "jim".into(),
-      fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
@@ -207,6 +261,12 @@ mod tests {
       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();
@@ -221,6 +281,12 @@ mod tests {
       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();
@@ -241,6 +307,9 @@ mod tests {
       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();
@@ -263,6 +332,8 @@ mod tests {
       embed_description: None,
       embed_html: None,
       thumbnail_url: None,
+      ap_id: "changeme".into(),
+      local: true,
     };
 
     // Post Like
index f48f4f680ccc8a80e65df1c2c8b66d8d26a965f6..a7ac58c034e6096e469b9c6fb962829fc25f1afc 100644 (file)
@@ -22,10 +22,16 @@ 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,
@@ -63,10 +69,16 @@ 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,
@@ -107,10 +119,16 @@ pub struct PostView {
   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,
@@ -359,7 +377,6 @@ mod tests {
 
     let new_user = UserForm {
       name: user_name.to_owned(),
-      fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
@@ -375,6 +392,12 @@ mod tests {
       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();
@@ -389,6 +412,12 @@ mod tests {
       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();
@@ -409,6 +438,9 @@ mod tests {
       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();
@@ -473,6 +505,12 @@ mod tests {
       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 {
@@ -512,6 +550,12 @@ mod tests {
       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)
index cc073b594e23befdb885b6b0250cea3a3b2e9394..63607547f72b720911aec5c98b05962da85f543d 100644 (file)
@@ -65,7 +65,6 @@ mod tests {
 
     let creator_form = UserForm {
       name: "creator_pm".into(),
-      fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
@@ -81,13 +80,18 @@ mod tests {
       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,
@@ -103,6 +107,12 @@ mod tests {
       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();
index e7c7696502b334a0123749f42d855615fa72f712..c7b1b2d3cb4bd237a56063162bc6fb76e451b797 100644 (file)
@@ -1,7 +1,7 @@
 use super::*;
 use crate::schema::user_;
 use crate::schema::user_::dsl::*;
-use crate::{is_email_regex, Settings};
+use crate::{is_email_regex, naive_now, Settings};
 use bcrypt::{hash, DEFAULT_COST};
 use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData, Validation};
 
@@ -10,7 +10,6 @@ use jsonwebtoken::{decode, encode, DecodingKey, EncodingKey, Header, TokenData,
 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>,
@@ -27,13 +26,18 @@ pub struct User_ {
   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,
@@ -49,6 +53,12 @@ pub struct UserForm {
   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_ {
@@ -78,6 +88,7 @@ impl User_ {
     Self::create(&conn, &edited_user)
   }
 
+  // TODO do more individual updates like these
   pub fn update_password(
     conn: &PgConnection,
     user_id: i32,
@@ -86,13 +97,33 @@ impl User_ {
     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> {
     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_apub_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)]
@@ -129,7 +160,7 @@ impl User_ {
     let my_claims = Claims {
       id: self.id,
       username: self.name.to_owned(),
-      iss: self.fedi_name.to_owned(),
+      iss: Settings::get().hostname.to_owned(),
       show_nsfw: self.show_nsfw,
       theme: self.theme.to_owned(),
       default_sort_type: self.default_sort_type,
@@ -186,7 +217,6 @@ mod tests {
 
     let new_user = UserForm {
       name: "thommy".into(),
-      fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
@@ -202,6 +232,12 @@ mod tests {
       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();
@@ -209,7 +245,6 @@ mod tests {
     let expected_user = User_ {
       id: inserted_user.id,
       name: "thommy".into(),
-      fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
@@ -226,6 +261,12 @@ mod tests {
       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();
index 0cf257955e749905716c1f5b7a15e18b6936f98d..aea1e2285acd4bc9c6acf348cd12525451d2a02a 100644 (file)
@@ -64,7 +64,6 @@ mod tests {
 
     let new_user = UserForm {
       name: "terrylake".into(),
-      fedi_name: "rrf".into(),
       preferred_username: None,
       password_encrypted: "nope".into(),
       email: None,
@@ -80,13 +79,18 @@ mod tests {
       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,
@@ -102,6 +106,12 @@ mod tests {
       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();
@@ -116,6 +126,12 @@ mod tests {
       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();
@@ -136,6 +152,9 @@ mod tests {
       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();
@@ -149,6 +168,8 @@ mod tests {
       read: None,
       parent_id: None,
       updated: None,
+      ap_id: "changeme".into(),
+      local: true,
     };
 
     let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
index 8046747e68ea48c79788ad20db9418679261ee75..85fd760fcd59c0cfd1a43fc4038ac06a7d92fbfb 100644 (file)
@@ -7,6 +7,8 @@ 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,
@@ -16,6 +18,8 @@ table! {
     updated -> Nullable<Timestamp>,
     deleted -> Bool,
     community_id -> Int4,
+    community_actor_id -> Text,
+    community_local -> Bool,
     community_name -> Varchar,
     banned -> Bool,
     banned_from_community -> Bool,
@@ -29,6 +33,8 @@ table! {
     my_vote -> Nullable<Int4>,
     saved -> Nullable<Bool>,
     recipient_id -> Int4,
+    recipient_actor_id -> Text,
+    recipient_local -> Bool,
   }
 }
 
@@ -37,6 +43,8 @@ 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,
@@ -46,6 +54,8 @@ table! {
     updated -> Nullable<Timestamp>,
     deleted -> Bool,
     community_id -> Int4,
+    community_actor_id -> Text,
+    community_local -> Bool,
     community_name -> Varchar,
     banned -> Bool,
     banned_from_community -> Bool,
@@ -59,6 +69,8 @@ table! {
     my_vote -> Nullable<Int4>,
     saved -> Nullable<Bool>,
     recipient_id -> Int4,
+    recipient_actor_id -> Text,
+    recipient_local -> Bool,
   }
 }
 
@@ -70,6 +82,8 @@ pub struct UserMentionView {
   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,
@@ -79,6 +93,8 @@ pub struct UserMentionView {
   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,
@@ -92,6 +108,8 @@ pub struct UserMentionView {
   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> {
index 2274ecbdffd2950dbdb78486c7d030811ec932b5..88cbb7fb60053f446bdc833914180702321d6ef2 100644 (file)
@@ -5,11 +5,13 @@ use diesel::pg::Pg;
 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,
@@ -25,11 +27,13 @@ table! {
 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,
@@ -48,11 +52,13 @@ table! {
 #[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,
index 9bbfe251a27372f04991944e4f987411f9d4988e..2c78cfc2643d3813a10027daf06dcf5868a2bce2 100644 (file)
@@ -16,6 +16,8 @@ pub extern crate dotenv;
 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;
@@ -34,7 +36,7 @@ pub mod version;
 pub mod websocket;
 
 use crate::settings::Settings;
-use chrono::{DateTime, NaiveDateTime, Utc};
+use chrono::{DateTime, FixedOffset, Local, NaiveDateTime};
 use isahc::prelude::*;
 use lettre::smtp::authentication::{Credentials, Mechanism};
 use lettre::smtp::extension::ClientId;
@@ -48,10 +50,6 @@ use rand::{thread_rng, Rng};
 use regex::{Regex, RegexBuilder};
 use serde::Deserialize;
 
-pub fn to_datetime_utc(ndt: NaiveDateTime) -> DateTime<Utc> {
-  DateTime::<Utc>::from_utc(ndt, Utc)
-}
-
 pub fn naive_now() -> NaiveDateTime {
   chrono::prelude::Utc::now().naive_utc()
 }
@@ -60,6 +58,11 @@ pub fn naive_from_unix(time: i64) -> NaiveDateTime {
   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)
 }
index f3887527571a43b7424295a5d599dff3eb26bd74..88d62eb997c9fd374a4a1fc3661257d1f64029a0 100644 (file)
@@ -6,15 +6,21 @@ use actix::prelude::*;
 use actix_web::*;
 use diesel::r2d2::{ConnectionManager, Pool};
 use diesel::PgConnection;
+use failure::Error;
+use lemmy_server::apub::fetcher::fetch_all;
+use lemmy_server::db::code_migrations::run_advanced_migrations;
 use lemmy_server::routes::{api, federation, feeds, index, nodeinfo, webfinger, websocket};
 use lemmy_server::settings::Settings;
 use lemmy_server::websocket::server::*;
-use std::io;
+use log::warn;
+use std::thread;
+use std::thread::sleep;
+use std::time::Duration;
 
 embed_migrations!();
 
 #[actix_rt::main]
-async fn main() -> io::Result<()> {
+async fn main() -> Result<(), Error> {
   env_logger::init();
   let settings = Settings::get();
 
@@ -28,41 +34,54 @@ async fn main() -> io::Result<()> {
   // Run the migrations from code
   let conn = pool.get().unwrap();
   embedded_migrations::run(&conn).unwrap();
+  run_advanced_migrations(&conn).unwrap();
 
   // Set up websocket server
   let server = ChatServer::startup(pool.clone()).start();
 
+  thread::spawn(move || {
+    // some work here
+    sleep(Duration::from_secs(5));
+    println!("Fetching apub data");
+    match fetch_all(&conn) {
+      Ok(_) => {}
+      Err(e) => warn!("Error during apub fetch: {}", e),
+    }
+  });
+
   println!(
     "Starting http server at {}:{}",
     settings.bind, settings.port
   );
 
   // Create Http server with websocket support
-  HttpServer::new(move || {
-    let settings = Settings::get();
-    App::new()
-      .wrap(middleware::Logger::default())
-      .data(pool.clone())
-      .data(server.clone())
-      // The routes
-      .configure(api::config)
-      .configure(federation::config)
-      .configure(feeds::config)
-      .configure(index::config)
-      .configure(nodeinfo::config)
-      .configure(webfinger::config)
-      .configure(websocket::config)
-      // static files
-      .service(actix_files::Files::new(
-        "/static",
-        settings.front_end_dir.to_owned(),
-      ))
-      .service(actix_files::Files::new(
-        "/docs",
-        settings.front_end_dir + "/documentation",
-      ))
-  })
-  .bind((settings.bind, settings.port))?
-  .run()
-  .await
+  Ok(
+    HttpServer::new(move || {
+      let settings = Settings::get();
+      App::new()
+        .wrap(middleware::Logger::default())
+        .data(pool.clone())
+        .data(server.clone())
+        // The routes
+        .configure(api::config)
+        .configure(federation::config)
+        .configure(feeds::config)
+        .configure(index::config)
+        .configure(nodeinfo::config)
+        .configure(webfinger::config)
+        .configure(websocket::config)
+        // static files
+        .service(actix_files::Files::new(
+          "/static",
+          settings.front_end_dir.to_owned(),
+        ))
+        .service(actix_files::Files::new(
+          "/docs",
+          settings.front_end_dir.to_owned() + "/documentation",
+        ))
+    })
+    .bind((settings.bind, settings.port))?
+    .run()
+    .await?,
+  )
 }
index ea6039d6bd34722d0cb306babe41fd3508562fbc..2798e7a9526a168e0260daf359bc52f9a0447a50 100644 (file)
@@ -1,18 +1,44 @@
 use crate::apub;
+use crate::settings::Settings;
 use actix_web::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
+      .route(
+        "/federation/communities",
+        web::get().to(apub::community::get_apub_community_list),
+      )
+      // TODO: this needs to be moved to the actors (eg /federation/u/{}/inbox)
+      .route("/federation/inbox", web::post().to(apub::inbox::inbox))
+      .route(
+        "/federation/c/{_}/inbox",
+        web::post().to(apub::inbox::inbox),
+      )
+      .route(
+        "/federation/u/{_}/inbox",
+        web::post().to(apub::inbox::inbox),
+      )
+      .route(
+        "/federation/c/{community_name}",
+        web::get().to(apub::community::get_apub_community_http),
+      )
+      .route(
+        "/federation/c/{community_name}/followers",
+        web::get().to(apub::community::get_apub_community_followers),
+      )
+      .route(
+        "/federation/c/{community_name}/outbox",
+        web::get().to(apub::community::get_apub_community_outbox),
+      )
+      .route(
+        "/federation/u/{user_name}",
+        web::get().to(apub::user::get_apub_user),
+      )
+      .route(
+        "/federation/p/{post_id}",
+        web::get().to(apub::user::get_apub_user),
+      );
+  }
 }
index 1263cef848076bc8fadf6e86baba98c92c3d2f70..a8ae213889f3bd49505f4a6d80e427ffe8a25d12 100644 (file)
@@ -1,3 +1,4 @@
+use crate::apub::get_apub_protocol_string;
 use crate::db::site_view::SiteView;
 use crate::version;
 use crate::Settings;
@@ -6,7 +7,10 @@ use actix_web::web;
 use actix_web::HttpResponse;
 use diesel::r2d2::{ConnectionManager, Pool};
 use diesel::PgConnection;
-use serde::Serialize;
+use failure::Error;
+use serde::{Deserialize, Serialize};
+use std::fmt::Debug;
+use url::Url;
 
 pub fn config(cfg: &mut web::ServiceConfig) {
   cfg
@@ -14,14 +18,18 @@ pub fn config(cfg: &mut web::ServiceConfig) {
     .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>, 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(
@@ -33,7 +41,7 @@ async fn node_info(
       Ok(site_view) => site_view,
       Err(_) => return Err(format_err!("not_found")),
     };
-    let protocols = if Settings::get().federation_enabled {
+    let protocols = if Settings::get().federation.enabled {
       vec!["activitypub".to_string()]
     } else {
       vec![]
@@ -53,6 +61,13 @@ async fn node_info(
         local_comments: site_view.number_of_comments,
         open_registrations: site_view.open_registration,
       },
+      metadata: NodeInfoMetadata {
+        community_list_url: Some(Url::parse(&format!(
+          "{}://{}/federation/communities",
+          get_apub_protocol_string(),
+          Settings::get().hostname
+        ))?),
+      },
     })
   })
   .await
@@ -61,41 +76,47 @@ async fn node_info(
   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,
+  pub metadata: NodeInfoMetadata,
 }
 
-#[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, Deserialize, Debug)]
+pub struct NodeInfoUsers {
+  pub total: i64,
 }
 
-#[derive(Serialize)]
-struct NodeInfoUsers {
-  total: i64,
+#[derive(Serialize, Deserialize, Debug)]
+pub struct NodeInfoMetadata {
+  pub community_list_url: Option<Url>,
 }
index 20f76a9ad0c898598ae0a975cf5cf42bacf2d015..7590cb43530d2658908c6db6b4acf26d8534fff3 100644 (file)
@@ -15,7 +15,7 @@ pub struct Params {
 }
 
 pub fn config(cfg: &mut web::ServiceConfig) {
-  if Settings::get().federation_enabled {
+  if Settings::get().federation.enabled {
     cfg.route(
       ".well-known/webfinger",
       web::get().to(get_webfinger_response),
@@ -46,13 +46,9 @@ async fn get_webfinger_response(
 
     let 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 {
+      .map(|c| c.get(1))
+      .flatten();
+    let community_name = match regex_parsed {
       Some(c) => c.as_str(),
       None => return Err(format_err!("not_found")),
     };
@@ -71,22 +67,21 @@ async fn get_webfinger_response(
       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}"
-    //}
+      {
+        "rel": "http://webfinger.net/rel/profile-page",
+        "type": "text/html",
+        "href": community.get_url(),
+      },
+      {
+        "rel": "self",
+        "type": "application/activity+json",
+        "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}"
+      //}
     ]
     }))
   })
index d9449fef97a019d7e246adc4f26bfbd778caa467..01c526c655463e96e4ceecafb3be950efeb97aa6 100644 (file)
 table! {
-    category (id) {
-        id -> Int4,
-        name -> Varchar,
-    }
+  activity (id) {
+    id -> Int4,
+    user_id -> Int4,
+    data -> Jsonb,
+    local -> Bool,
+    published -> Timestamp,
+    updated -> Nullable<Timestamp>,
+  }
 }
 
 table! {
-    comment (id) {
-        id -> Int4,
-        creator_id -> Int4,
-        post_id -> Int4,
-        parent_id -> Nullable<Int4>,
-        content -> Text,
-        removed -> Bool,
-        read -> Bool,
-        published -> Timestamp,
-        updated -> Nullable<Timestamp>,
-        deleted -> Bool,
-    }
+  category (id) {
+    id -> Int4,
+    name -> Varchar,
+  }
 }
 
 table! {
-    comment_like (id) {
-        id -> Int4,
-        user_id -> Int4,
-        comment_id -> Int4,
-        post_id -> Int4,
-        score -> Int2,
-        published -> Timestamp,
-    }
+  comment (id) {
+    id -> Int4,
+    creator_id -> Int4,
+    post_id -> Int4,
+    parent_id -> Nullable<Int4>,
+    content -> Text,
+    removed -> Bool,
+    read -> Bool,
+    published -> Timestamp,
+    updated -> Nullable<Timestamp>,
+    deleted -> Bool,
+    ap_id -> Varchar,
+    local -> Bool,
+  }
 }
 
 table! {
-    comment_saved (id) {
-        id -> Int4,
-        comment_id -> Int4,
-        user_id -> Int4,
-        published -> Timestamp,
-    }
+  comment_like (id) {
+    id -> Int4,
+    user_id -> Int4,
+    comment_id -> Int4,
+    post_id -> Int4,
+    score -> Int2,
+    published -> Timestamp,
+  }
 }
 
 table! {
-    community (id) {
-        id -> Int4,
-        name -> Varchar,
-        title -> Varchar,
-        description -> Nullable<Text>,
-        category_id -> Int4,
-        creator_id -> Int4,
-        removed -> Bool,
-        published -> Timestamp,
-        updated -> Nullable<Timestamp>,
-        deleted -> Bool,
-        nsfw -> Bool,
-    }
+  comment_saved (id) {
+    id -> Int4,
+    comment_id -> Int4,
+    user_id -> Int4,
+    published -> Timestamp,
+  }
 }
 
 table! {
-    community_follower (id) {
-        id -> Int4,
-        community_id -> Int4,
-        user_id -> Int4,
-        published -> Timestamp,
-    }
+  community (id) {
+    id -> Int4,
+    name -> Varchar,
+    title -> Varchar,
+    description -> Nullable<Text>,
+    category_id -> Int4,
+    creator_id -> Int4,
+    removed -> Bool,
+    published -> Timestamp,
+    updated -> Nullable<Timestamp>,
+    deleted -> Bool,
+    nsfw -> Bool,
+    actor_id -> Varchar,
+    local -> Bool,
+    private_key -> Nullable<Text>,
+    public_key -> Nullable<Text>,
+    last_refreshed_at -> Timestamp,
+  }
 }
 
 table! {
-    community_moderator (id) {
-        id -> Int4,
-        community_id -> Int4,
-        user_id -> Int4,
-        published -> Timestamp,
-    }
+  community_follower (id) {
+    id -> Int4,
+    community_id -> Int4,
+    user_id -> Int4,
+    published -> Timestamp,
+  }
 }
 
 table! {
-    community_user_ban (id) {
-        id -> Int4,
-        community_id -> Int4,
-        user_id -> Int4,
-        published -> Timestamp,
-    }
+  community_moderator (id) {
+    id -> Int4,
+    community_id -> Int4,
+    user_id -> Int4,
+    published -> Timestamp,
+  }
 }
 
 table! {
-    mod_add (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        other_user_id -> Int4,
-        removed -> Nullable<Bool>,
-        when_ -> Timestamp,
-    }
+  community_user_ban (id) {
+    id -> Int4,
+    community_id -> Int4,
+    user_id -> Int4,
+    published -> Timestamp,
+  }
 }
 
 table! {
-    mod_add_community (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        other_user_id -> Int4,
-        community_id -> Int4,
-        removed -> Nullable<Bool>,
-        when_ -> Timestamp,
-    }
+  mod_add (id) {
+    id -> Int4,
+    mod_user_id -> Int4,
+    other_user_id -> Int4,
+    removed -> Nullable<Bool>,
+    when_ -> Timestamp,
+  }
 }
 
 table! {
-    mod_ban (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        other_user_id -> Int4,
-        reason -> Nullable<Text>,
-        banned -> Nullable<Bool>,
-        expires -> Nullable<Timestamp>,
-        when_ -> Timestamp,
-    }
+  mod_add_community (id) {
+    id -> Int4,
+    mod_user_id -> Int4,
+    other_user_id -> Int4,
+    community_id -> Int4,
+    removed -> Nullable<Bool>,
+    when_ -> Timestamp,
+  }
 }
 
 table! {
-    mod_ban_from_community (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        other_user_id -> Int4,
-        community_id -> Int4,
-        reason -> Nullable<Text>,
-        banned -> Nullable<Bool>,
-        expires -> Nullable<Timestamp>,
-        when_ -> Timestamp,
-    }
+  mod_ban (id) {
+    id -> Int4,
+    mod_user_id -> Int4,
+    other_user_id -> Int4,
+    reason -> Nullable<Text>,
+    banned -> Nullable<Bool>,
+    expires -> Nullable<Timestamp>,
+    when_ -> Timestamp,
+  }
 }
 
 table! {
-    mod_lock_post (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        post_id -> Int4,
-        locked -> Nullable<Bool>,
-        when_ -> Timestamp,
-    }
+  mod_ban_from_community (id) {
+    id -> Int4,
+    mod_user_id -> Int4,
+    other_user_id -> Int4,
+    community_id -> Int4,
+    reason -> Nullable<Text>,
+    banned -> Nullable<Bool>,
+    expires -> Nullable<Timestamp>,
+    when_ -> Timestamp,
+  }
 }
 
 table! {
-    mod_remove_comment (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        comment_id -> Int4,
-        reason -> Nullable<Text>,
-        removed -> Nullable<Bool>,
-        when_ -> Timestamp,
-    }
+  mod_lock_post (id) {
+    id -> Int4,
+    mod_user_id -> Int4,
+    post_id -> Int4,
+    locked -> Nullable<Bool>,
+    when_ -> Timestamp,
+  }
 }
 
 table! {
-    mod_remove_community (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        community_id -> Int4,
-        reason -> Nullable<Text>,
-        removed -> Nullable<Bool>,
-        expires -> Nullable<Timestamp>,
-        when_ -> Timestamp,
-    }
+  mod_remove_comment (id) {
+    id -> Int4,
+    mod_user_id -> Int4,
+    comment_id -> Int4,
+    reason -> Nullable<Text>,
+    removed -> Nullable<Bool>,
+    when_ -> Timestamp,
+  }
 }
 
 table! {
-    mod_remove_post (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        post_id -> Int4,
-        reason -> Nullable<Text>,
-        removed -> Nullable<Bool>,
-        when_ -> Timestamp,
-    }
+  mod_remove_community (id) {
+    id -> Int4,
+    mod_user_id -> Int4,
+    community_id -> Int4,
+    reason -> Nullable<Text>,
+    removed -> Nullable<Bool>,
+    expires -> Nullable<Timestamp>,
+    when_ -> Timestamp,
+  }
 }
 
 table! {
-    mod_sticky_post (id) {
-        id -> Int4,
-        mod_user_id -> Int4,
-        post_id -> Int4,
-        stickied -> Nullable<Bool>,
-        when_ -> Timestamp,
-    }
+  mod_remove_post (id) {
+    id -> Int4,
+    mod_user_id -> Int4,
+    post_id -> Int4,
+    reason -> Nullable<Text>,
+    removed -> Nullable<Bool>,
+    when_ -> Timestamp,
+  }
 }
 
 table! {
-    password_reset_request (id) {
-        id -> Int4,
-        user_id -> Int4,
-        token_encrypted -> Text,
-        published -> Timestamp,
-    }
+  mod_sticky_post (id) {
+    id -> Int4,
+    mod_user_id -> Int4,
+    post_id -> Int4,
+    stickied -> Nullable<Bool>,
+    when_ -> Timestamp,
+  }
 }
 
 table! {
-    post (id) {
-        id -> Int4,
-        name -> Varchar,
-        url -> Nullable<Text>,
-        body -> Nullable<Text>,
-        creator_id -> Int4,
-        community_id -> Int4,
-        removed -> Bool,
-        locked -> Bool,
-        published -> Timestamp,
-        updated -> Nullable<Timestamp>,
-        deleted -> Bool,
-        nsfw -> Bool,
-        stickied -> Bool,
-        embed_title -> Nullable<Text>,
-        embed_description -> Nullable<Text>,
-        embed_html -> Nullable<Text>,
-        thumbnail_url -> Nullable<Text>,
-    }
+  password_reset_request (id) {
+    id -> Int4,
+    user_id -> Int4,
+    token_encrypted -> Text,
+    published -> Timestamp,
+  }
 }
 
 table! {
-    post_like (id) {
-        id -> Int4,
-        post_id -> Int4,
-        user_id -> Int4,
-        score -> Int2,
-        published -> Timestamp,
-    }
+  post (id) {
+    id -> Int4,
+    name -> Varchar,
+    url -> Nullable<Text>,
+    body -> Nullable<Text>,
+    creator_id -> Int4,
+    community_id -> Int4,
+    removed -> Bool,
+    locked -> Bool,
+    published -> Timestamp,
+    updated -> Nullable<Timestamp>,
+    deleted -> Bool,
+    nsfw -> Bool,
+    stickied -> Bool,
+    embed_title -> Nullable<Text>,
+    embed_description -> Nullable<Text>,
+    embed_html -> Nullable<Text>,
+    thumbnail_url -> Nullable<Text>,
+    ap_id -> Varchar,
+    local -> Bool,
+  }
 }
 
 table! {
-    post_read (id) {
-        id -> Int4,
-        post_id -> Int4,
-        user_id -> Int4,
-        published -> Timestamp,
-    }
+  post_like (id) {
+    id -> Int4,
+    post_id -> Int4,
+    user_id -> Int4,
+    score -> Int2,
+    published -> Timestamp,
+  }
 }
 
 table! {
-    post_saved (id) {
-        id -> Int4,
-        post_id -> Int4,
-        user_id -> Int4,
-        published -> Timestamp,
-    }
+  post_read (id) {
+    id -> Int4,
+    post_id -> Int4,
+    user_id -> Int4,
+    published -> Timestamp,
+  }
 }
 
 table! {
-    private_message (id) {
-        id -> Int4,
-        creator_id -> Int4,
-        recipient_id -> Int4,
-        content -> Text,
-        deleted -> Bool,
-        read -> Bool,
-        published -> Timestamp,
-        updated -> Nullable<Timestamp>,
-    }
+  post_saved (id) {
+    id -> Int4,
+    post_id -> Int4,
+    user_id -> Int4,
+    published -> Timestamp,
+  }
 }
 
 table! {
-    site (id) {
-        id -> Int4,
-        name -> Varchar,
-        description -> Nullable<Text>,
-        creator_id -> Int4,
-        published -> Timestamp,
-        updated -> Nullable<Timestamp>,
-        enable_downvotes -> Bool,
-        open_registration -> Bool,
-        enable_nsfw -> Bool,
-    }
+  private_message (id) {
+    id -> Int4,
+    creator_id -> Int4,
+    recipient_id -> Int4,
+    content -> Text,
+    deleted -> Bool,
+    read -> Bool,
+    published -> Timestamp,
+    updated -> Nullable<Timestamp>,
+  }
 }
 
 table! {
-    user_ (id) {
-        id -> Int4,
-        name -> Varchar,
-        fedi_name -> Varchar,
-        preferred_username -> Nullable<Varchar>,
-        password_encrypted -> Text,
-        email -> Nullable<Text>,
-        avatar -> Nullable<Text>,
-        admin -> Bool,
-        banned -> Bool,
-        published -> Timestamp,
-        updated -> Nullable<Timestamp>,
-        show_nsfw -> Bool,
-        theme -> Varchar,
-        default_sort_type -> Int2,
-        default_listing_type -> Int2,
-        lang -> Varchar,
-        show_avatars -> Bool,
-        send_notifications_to_email -> Bool,
-        matrix_user_id -> Nullable<Text>,
-    }
+  site (id) {
+    id -> Int4,
+    name -> Varchar,
+    description -> Nullable<Text>,
+    creator_id -> Int4,
+    published -> Timestamp,
+    updated -> Nullable<Timestamp>,
+    enable_downvotes -> Bool,
+    open_registration -> Bool,
+    enable_nsfw -> Bool,
+  }
 }
 
 table! {
-    user_ban (id) {
-        id -> Int4,
-        user_id -> Int4,
-        published -> Timestamp,
-    }
+  user_ (id) {
+    id -> Int4,
+    name -> Varchar,
+    preferred_username -> Nullable<Varchar>,
+    password_encrypted -> Text,
+    email -> Nullable<Text>,
+    avatar -> Nullable<Text>,
+    admin -> Bool,
+    banned -> Bool,
+    published -> Timestamp,
+    updated -> Nullable<Timestamp>,
+    show_nsfw -> Bool,
+    theme -> Varchar,
+    default_sort_type -> Int2,
+    default_listing_type -> Int2,
+    lang -> Varchar,
+    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,
+  }
 }
 
 table! {
-    user_mention (id) {
-        id -> Int4,
-        recipient_id -> Int4,
-        comment_id -> Int4,
-        read -> Bool,
-        published -> Timestamp,
-    }
+  user_ban (id) {
+    id -> Int4,
+    user_id -> Int4,
+    published -> Timestamp,
+  }
 }
 
+table! {
+  user_mention (id) {
+    id -> Int4,
+    recipient_id -> Int4,
+    comment_id -> Int4,
+    read -> Bool,
+    published -> Timestamp,
+  }
+}
+
+joinable!(activity -> user_ (user_id));
 joinable!(comment -> post (post_id));
 joinable!(comment -> user_ (creator_id));
 joinable!(comment_like -> comment (comment_id));
@@ -353,6 +379,7 @@ joinable!(user_mention -> comment (comment_id));
 joinable!(user_mention -> user_ (recipient_id));
 
 allow_tables_to_appear_in_same_query!(
+  activity,
   category,
   comment,
   comment_like,
index 6e5667cb2ee7fbc56a9a8394c3aedae18f07efa9..8c3cd6a1251d035b7220a43f5e7440e266220767 100644 (file)
@@ -20,7 +20,7 @@ pub struct Settings {
   pub front_end_dir: String,
   pub rate_limit: RateLimitConfig,
   pub email: Option<EmailConfig>,
-  pub federation_enabled: bool,
+  pub federation: Federation,
 }
 
 #[derive(Debug, Deserialize, Clone)]
@@ -60,6 +60,13 @@ pub struct Database {
   pub pool_size: u32,
 }
 
+#[derive(Debug, Deserialize)]
+pub struct Federation {
+  pub enabled: bool,
+  pub followed_instances: String,
+  pub tls_enabled: bool,
+}
+
 lazy_static! {
   static ref SETTINGS: RwLock<Settings> = RwLock::new(match Settings::init() {
     Ok(c) => c,
index 0f2d2d26fdb4d89700e1c34efcb9738eed8a6f6d..faa8041cea2d60ace233d074397516a2bbc428d6 100644 (file)
@@ -505,9 +505,6 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
 
   let user_operation: UserOperation = UserOperation::from_str(&op)?;
 
-  // TODO: none of the chat messages are going to work if stuff is submitted via http api,
-  //       need to move that handling elsewhere
-
   // A DDOS check
   chat.check_rate_limit_message(msg.id, false)?;
 
@@ -554,7 +551,9 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
     }
     UserOperation::GetCommunity => {
       let get_community: GetCommunity = serde_json::from_str(data)?;
+
       let mut res = Oper::new(get_community).perform(&conn)?;
+
       let community_id = res.community.id;
 
       chat.join_community_room(community_id, msg.id);
@@ -629,6 +628,7 @@ fn parse_json_message(chat: &mut ChatServer, msg: StandardMessage) -> Result<Str
     }
     UserOperation::GetPosts => {
       let get_posts: GetPosts = serde_json::from_str(data)?;
+
       if get_posts.community_id.is_none() {
         // 0 is the "all" community
         chat.join_community_room(0, msg.id);
index cc0ab540cf6c3e10587f607caf42c6149f99c2b7..bdd11a35d62235550e745a80d39e6c2603cbe911 100644 (file)
@@ -6,14 +6,11 @@ _site
 .git
 build
 .build
-.git
-.history
 .idea
 .jshintrc
 .nyc_output
 .sass-cache
 .vscode
-build
 coverage
 jsconfig.json
 Gemfile.lock
index 21458f0d2f20563e94f9467da69f25e3f0f3dc26..d2eb1de9ebd859e7590234d04439f272b44ae74f 100644 (file)
@@ -18,7 +18,7 @@
     "@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",
index 5239eb2c7a10660182154a1dd7f91b305ca367b7..b3c1a9a164fe093191dc8aa17ee4f401c19b5be5 100644 (file)
@@ -162,8 +162,9 @@ export class CommentForm extends Component<CommentFormProps, CommentFormState> {
               </button>
               {this.state.commentForm.content && (
                 <button
-                  className={`btn btn-sm mr-2 btn-secondary ${this.state
-                    .previewMode && 'active'}`}
+                  className={`btn btn-sm mr-2 btn-secondary ${
+                    this.state.previewMode && 'active'
+                  }`}
                   onClick={linkEvent(this, this.handlePreviewToggle)}
                 >
                   {i18n.t('preview')}
index ba4301e169a0475d9c69c6a9b71b48d4a7b01216..69a78f5015a4b0a5db4c0e7ecb0e5693a4a66818 100644 (file)
@@ -142,9 +142,11 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
           }
         >
           <div
-            class={`${!this.props.noIndent &&
+            class={`${
+              !this.props.noIndent &&
               this.props.node.comment.parent_id &&
-              'ml-2'}`}
+              'ml-2'
+            }`}
           >
             <div class="d-flex flex-wrap align-items-center mb-1 mt-1 text-muted small">
               <span class="mr-2">
@@ -249,8 +251,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                         this.loadingIcon
                       ) : (
                         <svg
-                          class={`icon icon-inline ${node.comment.read &&
-                            'text-success'}`}
+                          class={`icon icon-inline ${
+                            node.comment.read && 'text-success'
+                          }`}
                         >
                           <use xlinkHref="#icon-check"></use>
                         </svg>
@@ -302,8 +305,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                           this.loadingIcon
                         ) : (
                           <svg
-                            class={`icon icon-inline ${node.comment.saved &&
-                              'text-warning'}`}
+                            class={`icon icon-inline ${
+                              node.comment.saved && 'text-warning'
+                            }`}
                           >
                             <use xlinkHref="#icon-star"></use>
                           </svg>
@@ -350,8 +354,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                             data-tippy-content={i18n.t('view_source')}
                           >
                             <svg
-                              class={`icon icon-inline ${this.state
-                                .viewSource && 'text-success'}`}
+                              class={`icon icon-inline ${
+                                this.state.viewSource && 'text-success'
+                              }`}
                             >
                               <use xlinkHref="#icon-file-text"></use>
                             </svg>
@@ -380,8 +385,9 @@ export class CommentNode extends Component<CommentNodeProps, CommentNodeState> {
                                 }
                               >
                                 <svg
-                                  class={`icon icon-inline ${node.comment
-                                    .deleted && 'text-danger'}`}
+                                  class={`icon icon-inline ${
+                                    node.comment.deleted && 'text-danger'
+                                  }`}
                                 >
                                   <use xlinkHref="#icon-trash"></use>
                                 </svg>
index e0d8aff50ad98789073adc97bad7a170638787d9..f1936be1812fcd01af3898889821d8192540b14d 100644 (file)
@@ -381,7 +381,7 @@ export class Navbar extends Component<any, NavbarState> {
 
   requestNotificationPermission() {
     if (UserService.Instance.user) {
-      document.addEventListener('DOMContentLoaded', function() {
+      document.addEventListener('DOMContentLoaded', function () {
         if (!Notification) {
           toast(i18n.t('notifications_error'), 'danger');
           return;
index 912d8e5896157c89ae6e9be9925b08b10b2849c0..4dbc8b23a314b3d7bdd3d3e538f659b3ecbba9c8 100644 (file)
@@ -194,8 +194,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
               <form>
                 <label
                   htmlFor="file-upload"
-                  className={`${UserService.Instance.user &&
-                    'pointer'} d-inline-block float-right text-muted font-weight-bold`}
+                  className={`${
+                    UserService.Instance.user && 'pointer'
+                  } d-inline-block float-right text-muted font-weight-bold`}
                   data-tippy-content={i18n.t('upload_image')}
                 >
                   <svg class="icon icon-inline">
@@ -288,8 +289,9 @@ export class PostForm extends Component<PostFormProps, PostFormState> {
               )}
               {this.state.postForm.body && (
                 <button
-                  className={`mt-1 mr-2 btn btn-sm btn-secondary ${this.state
-                    .previewMode && 'active'}`}
+                  className={`mt-1 mr-2 btn btn-sm btn-secondary ${
+                    this.state.previewMode && 'active'
+                  }`}
                   onClick={linkEvent(this, this.handlePreviewToggle)}
                 >
                   {i18n.t('preview')}
index d0efa0437225e298c1b382071ea7521a85d7bc6a..497492010690049c18bc6b37156d558c32584821 100644 (file)
@@ -30,6 +30,7 @@ import {
   getUnixTime,
   pictshareImage,
   setupTippy,
+  hostname,
   previewLines,
 } from '../utils';
 import { i18n } from '../i18next';
@@ -150,9 +151,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
     let post = this.props.post;
     return (
       <img
-        className={`img-fluid thumbnail rounded ${(post.nsfw ||
-          post.community_nsfw) &&
-          'img-blur'}`}
+        className={`img-fluid thumbnail rounded ${
+          (post.nsfw || post.community_nsfw) && 'img-blur'
+        }`}
         src={src}
       />
     );
@@ -312,22 +313,21 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                     </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 ? (
@@ -440,9 +440,15 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                   {this.props.showCommunity && (
                     <span>
                       <span> {i18n.t('to')} </span>
-                      <Link to={`/c/${post.community_name}`}>
-                        {post.community_name}
-                      </Link>
+                      {post.local ? (
+                        <Link to={`/c/${post.community_name}`}>
+                          {post.community_name}
+                        </Link>
+                      ) : (
+                        <a href={post.community_actor_id} target="_blank">
+                          {hostname(post.ap_id)}/{post.community_name}
+                        </a>
+                      )}
                     </span>
                   )}
                 </li>
@@ -542,8 +548,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                             }
                           >
                             <svg
-                              class={`icon icon-inline ${post.saved &&
-                                'text-warning'}`}
+                              class={`icon icon-inline ${
+                                post.saved && 'text-warning'
+                              }`}
                             >
                               <use xlinkHref="#icon-star"></use>
                             </svg>
@@ -586,8 +593,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                             }
                           >
                             <svg
-                              class={`icon icon-inline ${post.deleted &&
-                                'text-danger'}`}
+                              class={`icon icon-inline ${
+                                post.deleted && 'text-danger'
+                              }`}
                             >
                               <use xlinkHref="#icon-trash"></use>
                             </svg>
@@ -618,8 +626,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                               data-tippy-content={i18n.t('view_source')}
                             >
                               <svg
-                                class={`icon icon-inline ${this.state
-                                  .viewSource && 'text-success'}`}
+                                class={`icon icon-inline ${
+                                  this.state.viewSource && 'text-success'
+                                }`}
                               >
                                 <use xlinkHref="#icon-file-text"></use>
                               </svg>
@@ -639,8 +648,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                                 }
                               >
                                 <svg
-                                  class={`icon icon-inline ${post.locked &&
-                                    'text-danger'}`}
+                                  class={`icon icon-inline ${
+                                    post.locked && 'text-danger'
+                                  }`}
                                 >
                                   <use xlinkHref="#icon-lock"></use>
                                 </svg>
@@ -657,8 +667,9 @@ export class PostListing extends Component<PostListingProps, PostListingState> {
                                 }
                               >
                                 <svg
-                                  class={`icon icon-inline ${post.stickied &&
-                                    'text-success'}`}
+                                  class={`icon icon-inline ${
+                                    post.stickied && 'text-success'
+                                  }`}
                                 >
                                   <use xlinkHref="#icon-pin"></use>
                                 </svg>
index de0f0e329c72bf60d24a7de43e2fcfaa89a427f1..cf9e748652e5241ddcaf9ba84f64da0d4f259ba4 100644 (file)
@@ -213,8 +213,9 @@ export class Post extends Component<any, PostState> {
     return (
       <div class="btn-group btn-group-toggle mb-2">
         <label
-          className={`btn btn-sm btn-secondary pointer ${this.state
-            .commentSort === CommentSortType.Hot && 'active'}`}
+          className={`btn btn-sm btn-secondary pointer ${
+            this.state.commentSort === CommentSortType.Hot && 'active'
+          }`}
         >
           {i18n.t('hot')}
           <input
@@ -225,8 +226,9 @@ export class Post extends Component<any, PostState> {
           />
         </label>
         <label
-          className={`btn btn-sm btn-secondary pointer ${this.state
-            .commentSort === CommentSortType.Top && 'active'}`}
+          className={`btn btn-sm btn-secondary pointer ${
+            this.state.commentSort === CommentSortType.Top && 'active'
+          }`}
         >
           {i18n.t('top')}
           <input
@@ -237,8 +239,9 @@ export class Post extends Component<any, PostState> {
           />
         </label>
         <label
-          className={`btn btn-sm btn-secondary pointer ${this.state
-            .commentSort === CommentSortType.New && 'active'}`}
+          className={`btn btn-sm btn-secondary pointer ${
+            this.state.commentSort === CommentSortType.New && 'active'
+          }`}
         >
           {i18n.t('new')}
           <input
@@ -249,8 +252,9 @@ export class Post extends Component<any, PostState> {
           />
         </label>
         <label
-          className={`btn btn-sm btn-secondary pointer ${this.state
-            .commentSort === CommentSortType.Old && 'active'}`}
+          className={`btn btn-sm btn-secondary pointer ${
+            this.state.commentSort === CommentSortType.Old && 'active'
+          }`}
         >
           {i18n.t('old')}
           <input
index 6b607654b62b943ae4e3a9e71da7d6a2f9ee4e0d..14abacf62086ca1e0fccab3a49275eabdf11bc4b 100644 (file)
@@ -222,8 +222,9 @@ export class PrivateMessageForm extends Component<
               </button>
               {this.state.privateMessageForm.content && (
                 <button
-                  className={`btn btn-secondary mr-2 ${this.state.previewMode &&
-                    'active'}`}
+                  className={`btn btn-secondary mr-2 ${
+                    this.state.previewMode && 'active'
+                  }`}
                   onClick={linkEvent(this, this.handlePreviewToggle)}
                 >
                   {i18n.t('preview')}
index 337b165012000f5598dcdd6ff4d670703aec765f..3acd6e19f06c1c40d2171866ee1f63d4830e38d4 100644 (file)
@@ -144,8 +144,9 @@ export class PrivateMessage extends Component<
                         }
                       >
                         <svg
-                          class={`icon icon-inline ${message.read &&
-                            'text-success'}`}
+                          class={`icon icon-inline ${
+                            message.read && 'text-success'
+                          }`}
                         >
                           <use xlinkHref="#icon-check"></use>
                         </svg>
@@ -188,8 +189,9 @@ export class PrivateMessage extends Component<
                         }
                       >
                         <svg
-                          class={`icon icon-inline ${message.deleted &&
-                            'text-danger'}`}
+                          class={`icon icon-inline ${
+                            message.deleted && 'text-danger'
+                          }`}
                         >
                           <use xlinkHref="#icon-trash"></use>
                         </svg>
@@ -204,8 +206,9 @@ export class PrivateMessage extends Component<
                     data-tippy-content={i18n.t('view_source')}
                   >
                     <svg
-                      class={`icon icon-inline ${this.state.viewSource &&
-                        'text-success'}`}
+                      class={`icon icon-inline ${
+                        this.state.viewSource && 'text-success'
+                      }`}
                     >
                       <use xlinkHref="#icon-file-text"></use>
                     </svg>
index d66266f6cab0e0e5a0769d8aa381d0c3ba6cb618..4b317aaa29880c957072d38945e019279658eb5c 100644 (file)
@@ -111,8 +111,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
                         }
                       >
                         <svg
-                          class={`icon icon-inline ${community.deleted &&
-                            'text-danger'}`}
+                          class={`icon icon-inline ${
+                            community.deleted && 'text-danger'
+                          }`}
                         >
                           <use xlinkHref="#icon-trash"></use>
                         </svg>
@@ -215,9 +216,9 @@ export class Sidebar extends Component<SidebarProps, SidebarState> {
               ))}
             </ul>
             <Link
-              class={`btn btn-sm btn-secondary btn-block mb-3 ${(community.deleted ||
-                community.removed) &&
-                'no-click'}`}
+              class={`btn btn-sm btn-secondary btn-block mb-3 ${
+                (community.deleted || community.removed) && 'no-click'
+              }`}
               to={`/create_post?community=${community.name}`}
             >
               {i18n.t('create_a_post')}
index b77ccac65b657f120df8ae416f7ac43fb988e268..12430836c45d1b0ad9f95f550bdc4a6d1ea2e210 100644 (file)
@@ -100,10 +100,13 @@ export interface User {
 
 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;
@@ -117,15 +120,21 @@ export interface UserView {
 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;
@@ -136,6 +145,9 @@ export interface Community {
   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;
@@ -161,13 +173,19 @@ export interface Post {
   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;
@@ -188,6 +206,8 @@ export interface Post {
 
 export interface Comment {
   id: number;
+  ap_id: string;
+  local: boolean;
   creator_id: number;
   post_id: number;
   parent_id?: number;
@@ -198,9 +218,13 @@ export interface Comment {
   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;
@@ -213,6 +237,8 @@ export interface Comment {
   saved?: boolean;
   user_mention_id?: number; // For mention type
   recipient_id?: number;
+  recipient_actor_id?: string;
+  recipient_local?: boolean;
   depth?: number;
 }
 
index 21a7fef83e83987fb5101d8b1531a5e8ff9bc5a9..480b41c7c313a6ffcd6403456583b4ee14fe2ee9 100644 (file)
@@ -118,11 +118,11 @@ export const md = new markdown_it({
   typographer: true,
 })
   .use(markdown_it_container, 'spoiler', {
-    validate: function(params: any) {
+    validate: function (params: any) {
       return params.trim().match(/^spoiler\s+(.*)$/);
     },
 
-    render: function(tokens: any, idx: any) {
+    render: function (tokens: any, idx: any) {
       var m = tokens[idx].info.trim().match(/^spoiler\s+(.*)$/);
 
       if (tokens[idx].nesting === 1) {
@@ -138,7 +138,7 @@ export const md = new markdown_it({
     defs: objectFlip(emojiShortName),
   });
 
-md.renderer.rules.emoji = function(token, idx) {
+md.renderer.rules.emoji = function (token, idx) {
   return twemoji.parse(token[idx].content);
 };
 
@@ -284,7 +284,7 @@ export function debounce(
   let timeout: any;
 
   // Calling debounce returns a new anonymous function
-  return function() {
+  return function () {
     // reference the context and args for the setTimeout function
     var context = this,
       args = arguments;
@@ -300,7 +300,7 @@ export function debounce(
     clearTimeout(timeout);
 
     // Set the new timeout
-    timeout = setTimeout(function() {
+    timeout = setTimeout(function () {
       // Inside the timeout function, clear the timeout variable
       // which will let the next execution run when in 'immediate' mode
       timeout = null;
@@ -841,3 +841,7 @@ export function previewLines(text: string, lines: number = 3): string {
     .slice(0, lines * 2)
     .join('\n');
 }
+
+export function hostname(url: string): string {
+  return new URL(url).hostname;
+}
index 35ad32a0ac2ea8390e621ed82083676bca5d49c0..4e94559eed7716121ba0f8da0b8e59849f4dadbd 100644 (file)
   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==
-  dependencies:
-    "@types/linkify-it" "*"
-    "@types/mdurl" "*"
-
-"@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@^13.11.1":
   version "13.11.1"
   resolved "https://registry.yarnpkg.com/@types/node/-/node-13.11.1.tgz#49a2a83df9d26daacead30d0ccc8762b128d53c7"