+# 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
+# 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/
--- /dev/null
+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"]
--- /dev/null
+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
--- /dev/null
+events {
+ worker_connections 1024;
+}
+
+http {
+ server {
+ listen 8540;
+ server_name 127.0.0.1;
+ access_log off;
+
+ # Upload limit for pictshare
+ client_max_body_size 50M;
+
+ location / {
+ proxy_pass http://lemmy_alpha:8540;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+
+ # WebSocket support
+ proxy_http_version 1.1;
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_set_header Connection "upgrade";
+ }
+
+ 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;
+ }
+ }
+}
--- /dev/null
+#!/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
# 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)",
"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"
"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)",
[[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)",
[[package]]
name = "arc-swap"
-version = "0.4.4"
+version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
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)",
]
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]]
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]]
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]]
"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"
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]]
[[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)",
[[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)",
[[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)",
]
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)",
"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)",
]
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)",
]
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]]
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)",
]
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)",
]
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]]
"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)",
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)",
"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)",
"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]]
"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]]
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]]
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)",
]
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]]
"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)",
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)",
]
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)",
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)",
]
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]]
"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)",
]
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)",
]
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)",
[[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)",
[[package]]
name = "proc-macro-nested"
-version = "0.1.3"
+version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
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)",
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)",
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)",
]
[[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)",
]
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)",
]
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)",
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)",
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)",
[[package]]
name = "ryu"
-version = "1.0.2"
+version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
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]]
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)",
]
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]]
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)",
]
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)",
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]]
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)",
]
"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)",
"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]]
]
[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"
"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"
"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"
"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"
"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"
"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"
"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"
"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"
"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"
"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"
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"]}
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"
--- /dev/null
+{
+ hostname: "localhost:8536"
+ federation_enabled: true
+}
\ No newline at end of file
pool_size: 5
}
# the domain name of your instance (eg "dev.lemmy.ml")
- hostname: "my_domain"
+ hostname: null
# address where lemmy should listen for incoming requests
bind: "0.0.0.0"
# port where lemmy should listen for incoming requests
jwt_secret: "changeme"
# The dir for the front end
front_end_dir: "../ui/dist"
- # whether to enable activitypub federation. this feature is in alpha, do not enable in production, as might
- # cause problems like remote instances fetching and permanently storing bad data.
- federation_enabled: false
# rate limits for various user actions, by user ip
rate_limit: {
# maximum number of messages created in interval
# interval length for registration limit
register_per_second: 3600
}
+ # settings related to activitypub federation
+ federation: {
+ # whether to enable activitypub federation. this feature is in alpha, do not enable in production.
+ enabled: false
+ # 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
--- /dev/null
+drop table activity;
+
+alter table user_
+drop column actor_id,
+drop column private_key,
+drop column public_key,
+drop column bio,
+drop column local,
+drop column last_refreshed_at;
+
+alter table community
+drop column actor_id,
+drop column private_key,
+drop column public_key,
+drop column local,
+drop column last_refreshed_at;
--- /dev/null
+-- The Activitypub activity table
+-- All user actions must create a row here.
+create table activity (
+ id serial primary key,
+ user_id int references user_ on update cascade on delete cascade not null, -- Ensures that the user is set up here.
+ data jsonb not null,
+ local boolean not null default true,
+ published timestamp not null default now(),
+ updated timestamp
+);
+
+-- Making sure that id is unique
+create unique index idx_activity_unique_apid on activity ((data ->> 'id'::text));
+
+-- Add federation columns to the two actor tables
+alter table user_
+-- TODO uniqueness constraints should be added on these 3 columns later
+add column actor_id character varying(255) not null default 'changeme', -- This needs to be checked and updated in code, building from the site url if local
+add column bio text, -- not on community, already has description
+add column local boolean not null default true,
+add column private_key text, -- These need to be generated from code
+add column public_key text,
+add column last_refreshed_at timestamp not null default now() -- Used to re-fetch federated actor periodically
+;
+
+-- Community
+alter table community
+add column actor_id character varying(255) not null default 'changeme', -- This needs to be checked and updated in code, building from the site url if local
+add column local boolean not null default true,
+add column private_key text, -- These need to be generated from code
+add column public_key text,
+add column last_refreshed_at timestamp not null default now() -- Used to re-fetch federated actor periodically
+;
+
+-- Don't worry about rebuilding the views right now.
+
--- /dev/null
+alter table post
+drop column ap_id,
+drop column local;
+
+alter table comment
+drop column ap_id,
+drop column local;
--- /dev/null
+-- Add federation columns to post, comment
+
+alter table post
+-- TODO uniqueness constraints should be added on these 3 columns later
+add column ap_id character varying(255) not null default 'changeme', -- This needs to be checked and updated in code, building from the site url if local
+add column local boolean not null default true
+;
+
+alter table comment
+-- TODO uniqueness constraints should be added on these 3 columns later
+add column ap_id character varying(255) not null default 'changeme', -- This needs to be checked and updated in code, building from the site url if local
+add column local boolean not null default true
+;
+
--- /dev/null
+-- User table
+drop view user_view cascade;
+
+alter table user_
+add column fedi_name varchar(40) not null default 'changeme';
+
+alter table user_
+add constraint user__name_fedi_name_key unique (name, fedi_name);
+
+-- Community
+alter table community
+add constraint community_name_key unique (name);
+
+
+create view user_view as
+select
+u.id,
+u.name,
+u.avatar,
+u.email,
+u.matrix_user_id,
+u.fedi_name,
+u.admin,
+u.banned,
+u.show_avatars,
+u.send_notifications_to_email,
+u.published,
+(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
+(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
+(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
+(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
+from user_ u;
+
+create materialized view user_mview as select * from user_view;
+
+create unique index idx_user_mview_id on user_mview (id);
--- /dev/null
+-- User table
+
+-- Need to regenerate user_view, user_mview
+drop view user_view cascade;
+
+-- Remove the fedi_name constraint, drop that useless column
+alter table user_
+drop constraint user__name_fedi_name_key;
+
+alter table user_
+drop column fedi_name;
+
+-- Community
+alter table community
+drop constraint community_name_key;
+
+create view user_view as
+select
+u.id,
+u.name,
+u.avatar,
+u.email,
+u.matrix_user_id,
+u.admin,
+u.banned,
+u.show_avatars,
+u.send_notifications_to_email,
+u.published,
+(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
+(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
+(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
+(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
+from user_ u;
+
+create materialized view user_mview as select * from user_view;
+
+create unique index idx_user_mview_id on user_mview (id);
+
--- /dev/null
+-- user_view
+drop view user_view cascade;
+
+create view user_view as
+select
+u.id,
+u.name,
+u.avatar,
+u.email,
+u.matrix_user_id,
+u.admin,
+u.banned,
+u.show_avatars,
+u.send_notifications_to_email,
+u.published,
+(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
+(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
+(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
+(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
+from user_ u;
+
+create materialized view user_mview as select * from user_view;
+
+create unique index idx_user_mview_id on user_mview (id);
+
+-- community_view
+drop view community_aggregates_view cascade;
+create view community_aggregates_view as
+select c.*,
+(select name from user_ u where c.creator_id = u.id) as creator_name,
+(select avatar from user_ u where c.creator_id = u.id) as creator_avatar,
+(select name from category ct where c.category_id = ct.id) as category_name,
+(select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers,
+(select count(*) from post p where p.community_id = c.id) as number_of_posts,
+(select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments,
+hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank
+from community c;
+
+create materialized view community_aggregates_mview as select * from community_aggregates_view;
+
+create unique index idx_community_aggregates_mview_id on community_aggregates_mview (id);
+
+create view community_view as
+with all_community as
+(
+ select
+ ca.*
+ from community_aggregates_view ca
+)
+
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join all_community ac
+
+union all
+
+select
+ac.*,
+null as user_id,
+null as subscribed
+from all_community ac
+;
+
+create view community_mview as
+with all_community as
+(
+ select
+ ca.*
+ from community_aggregates_mview ca
+)
+
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join all_community ac
+
+union all
+
+select
+ac.*,
+null as user_id,
+null as subscribed
+from all_community ac
+;
+
+-- community views
+drop view community_moderator_view;
+drop view community_follower_view;
+drop view community_user_ban_view;
+
+create view community_moderator_view as
+select *,
+(select name from user_ u where cm.user_id = u.id) as user_name,
+(select avatar from user_ u where cm.user_id = u.id),
+(select name from community c where cm.community_id = c.id) as community_name
+from community_moderator cm;
+
+create view community_follower_view as
+select *,
+(select name from user_ u where cf.user_id = u.id) as user_name,
+(select avatar from user_ u where cf.user_id = u.id),
+(select name from community c where cf.community_id = c.id) as community_name
+from community_follower cf;
+
+create view community_user_ban_view as
+select *,
+(select name from user_ u where cm.user_id = u.id) as user_name,
+(select avatar from user_ u where cm.user_id = u.id),
+(select name from community c where cm.community_id = c.id) as community_name
+from community_user_ban cm;
+
+-- post_view
+drop view post_view;
+drop view post_mview;
+drop materialized view post_aggregates_mview;
+drop view post_aggregates_view;
+
+-- regen post view
+create view post_aggregates_view as
+select
+p.*,
+(select u.banned from user_ u where p.creator_id = u.id) as banned,
+(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community,
+(select name from user_ where p.creator_id = user_.id) as creator_name,
+(select avatar from user_ where p.creator_id = user_.id) as creator_avatar,
+(select name from community where p.community_id = community.id) as community_name,
+(select removed from community c where p.community_id = c.id) as community_removed,
+(select deleted from community c where p.community_id = c.id) as community_deleted,
+(select nsfw from community c where p.community_id = c.id) as community_nsfw,
+(select count(*) from comment where comment.post_id = p.id) as number_of_comments,
+coalesce(sum(pl.score), 0) as score,
+count (case when pl.score = 1 then 1 else null end) as upvotes,
+count (case when pl.score = -1 then 1 else null end) as downvotes,
+hot_rank(coalesce(sum(pl.score) , 0),
+ (
+ case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps
+ else greatest(c.recent_comment_time, p.published)
+ end
+ )
+) as hot_rank,
+(
+ case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps
+ else greatest(c.recent_comment_time, p.published)
+ end
+) as newest_activity_time
+from post p
+left join post_like pl on p.id = pl.post_id
+left join (
+ select post_id,
+ max(published) as recent_comment_time
+ from comment
+ group by 1
+) c on p.id = c.post_id
+group by p.id, c.recent_comment_time;
+
+create materialized view post_aggregates_mview as select * from post_aggregates_view;
+
+create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id);
+
+create view post_view as
+with all_post as (
+ select
+ pa.*
+ from post_aggregates_view pa
+)
+select
+ap.*,
+u.id as user_id,
+coalesce(pl.score, 0) as my_vote,
+(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
+(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read,
+(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved
+from user_ u
+cross join all_post ap
+left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
+
+union all
+
+select
+ap.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from all_post ap
+;
+
+create view post_mview as
+with all_post as (
+ select
+ pa.*
+ from post_aggregates_mview pa
+)
+select
+ap.*,
+u.id as user_id,
+coalesce(pl.score, 0) as my_vote,
+(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
+(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read,
+(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved
+from user_ u
+cross join all_post ap
+left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
+
+union all
+
+select
+ap.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from all_post ap
+;
+
+
+-- reply_view, comment_view, user_mention
+drop view reply_view;
+drop view user_mention_view;
+drop view user_mention_mview;
+drop view comment_view;
+drop view comment_mview;
+drop materialized view comment_aggregates_mview;
+drop view comment_aggregates_view;
+
+-- reply and comment view
+create view comment_aggregates_view as
+select
+c.*,
+(select community_id from post p where p.id = c.post_id),
+(select co.name from post p, community co where p.id = c.post_id and p.community_id = co.id) as community_name,
+(select u.banned from user_ u where c.creator_id = u.id) as banned,
+(select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community,
+(select name from user_ where c.creator_id = user_.id) as creator_name,
+(select avatar from user_ where c.creator_id = user_.id) as creator_avatar,
+coalesce(sum(cl.score), 0) as score,
+count (case when cl.score = 1 then 1 else null end) as upvotes,
+count (case when cl.score = -1 then 1 else null end) as downvotes,
+hot_rank(coalesce(sum(cl.score) , 0), c.published) as hot_rank
+from comment c
+left join comment_like cl on c.id = cl.comment_id
+group by c.id;
+
+create materialized view comment_aggregates_mview as select * from comment_aggregates_view;
+
+create unique index idx_comment_aggregates_mview_id on comment_aggregates_mview (id);
+
+create view comment_view as
+with all_comment as
+(
+ select
+ ca.*
+ from comment_aggregates_view ca
+)
+
+select
+ac.*,
+u.id as user_id,
+coalesce(cl.score, 0) as my_vote,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.community_id = cf.community_id) as subscribed,
+(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+
+union all
+
+select
+ ac.*,
+ null as user_id,
+ null as my_vote,
+ null as subscribed,
+ null as saved
+from all_comment ac
+;
+
+create view comment_mview as
+with all_comment as
+(
+ select
+ ca.*
+ from comment_aggregates_mview ca
+)
+
+select
+ac.*,
+u.id as user_id,
+coalesce(cl.score, 0) as my_vote,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.community_id = cf.community_id) as subscribed,
+(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+
+union all
+
+select
+ ac.*,
+ null as user_id,
+ null as my_vote,
+ null as subscribed,
+ null as saved
+from all_comment ac
+;
+
+-- Do the reply_view referencing the comment_mview
+create view reply_view as
+with closereply as (
+ select
+ c2.id,
+ c2.creator_id as sender_id,
+ c.creator_id as recipient_id
+ from comment c
+ inner join comment c2 on c.id = c2.parent_id
+ where c2.creator_id != c.creator_id
+ -- Do union where post is null
+ union
+ select
+ c.id,
+ c.creator_id as sender_id,
+ p.creator_id as recipient_id
+ from comment c, post p
+ where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
+)
+select cv.*,
+closereply.recipient_id
+from comment_mview cv, closereply
+where closereply.id = cv.id
+;
+
+-- user mention
+create view user_mention_view as
+select
+ c.id,
+ um.id as user_mention_id,
+ c.creator_id,
+ c.post_id,
+ c.parent_id,
+ c.content,
+ c.removed,
+ um.read,
+ c.published,
+ c.updated,
+ c.deleted,
+ c.community_id,
+ c.community_name,
+ c.banned,
+ c.banned_from_community,
+ c.creator_name,
+ c.creator_avatar,
+ c.score,
+ c.upvotes,
+ c.downvotes,
+ c.hot_rank,
+ c.user_id,
+ c.my_vote,
+ c.saved,
+ um.recipient_id
+from user_mention um, comment_view c
+where um.comment_id = c.id;
+
+
+create view user_mention_mview as
+with all_comment as
+(
+ select
+ ca.*
+ from comment_aggregates_mview ca
+)
+
+select
+ ac.id,
+ um.id as user_mention_id,
+ ac.creator_id,
+ ac.post_id,
+ ac.parent_id,
+ ac.content,
+ ac.removed,
+ um.read,
+ ac.published,
+ ac.updated,
+ ac.deleted,
+ ac.community_id,
+ ac.community_name,
+ ac.banned,
+ ac.banned_from_community,
+ ac.creator_name,
+ ac.creator_avatar,
+ ac.score,
+ ac.upvotes,
+ ac.downvotes,
+ ac.hot_rank,
+ u.id as user_id,
+ coalesce(cl.score, 0) as my_vote,
+ (select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved,
+ um.recipient_id
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+left join user_mention um on um.comment_id = ac.id
+
+union all
+
+select
+ ac.id,
+ um.id as user_mention_id,
+ ac.creator_id,
+ ac.post_id,
+ ac.parent_id,
+ ac.content,
+ ac.removed,
+ um.read,
+ ac.published,
+ ac.updated,
+ ac.deleted,
+ ac.community_id,
+ ac.community_name,
+ ac.banned,
+ ac.banned_from_community,
+ ac.creator_name,
+ ac.creator_avatar,
+ ac.score,
+ ac.upvotes,
+ ac.downvotes,
+ ac.hot_rank,
+ null as user_id,
+ null as my_vote,
+ null as saved,
+ um.recipient_id
+from all_comment ac
+left join user_mention um on um.comment_id = ac.id
+;
+
--- /dev/null
+-- user_view
+drop view user_view cascade;
+
+create view user_view as
+select
+u.id,
+u.actor_id,
+u.name,
+u.avatar,
+u.email,
+u.matrix_user_id,
+u.bio,
+u.local,
+u.admin,
+u.banned,
+u.show_avatars,
+u.send_notifications_to_email,
+u.published,
+(select count(*) from post p where p.creator_id = u.id) as number_of_posts,
+(select coalesce(sum(score), 0) from post p, post_like pl where u.id = p.creator_id and p.id = pl.post_id) as post_score,
+(select count(*) from comment c where c.creator_id = u.id) as number_of_comments,
+(select coalesce(sum(score), 0) from comment c, comment_like cl where u.id = c.creator_id and c.id = cl.comment_id) as comment_score
+from user_ u;
+
+create materialized view user_mview as select * from user_view;
+
+create unique index idx_user_mview_id on user_mview (id);
+
+-- community_view
+drop view community_aggregates_view cascade;
+create view community_aggregates_view as
+-- Now that there's public and private keys, you have to be explicit here
+select c.id,
+c.name,
+c.title,
+c.description,
+c.category_id,
+c.creator_id,
+c.removed,
+c.published,
+c.updated,
+c.deleted,
+c.nsfw,
+c.actor_id,
+c.local,
+c.last_refreshed_at,
+(select actor_id from user_ u where c.creator_id = u.id) as creator_actor_id,
+(select local from user_ u where c.creator_id = u.id) as creator_local,
+(select name from user_ u where c.creator_id = u.id) as creator_name,
+(select avatar from user_ u where c.creator_id = u.id) as creator_avatar,
+(select name from category ct where c.category_id = ct.id) as category_name,
+(select count(*) from community_follower cf where cf.community_id = c.id) as number_of_subscribers,
+(select count(*) from post p where p.community_id = c.id) as number_of_posts,
+(select count(*) from comment co, post p where c.id = p.community_id and p.id = co.post_id) as number_of_comments,
+hot_rank((select count(*) from community_follower cf where cf.community_id = c.id), c.published) as hot_rank
+from community c;
+
+create materialized view community_aggregates_mview as select * from community_aggregates_view;
+
+create unique index idx_community_aggregates_mview_id on community_aggregates_mview (id);
+
+create view community_view as
+with all_community as
+(
+ select
+ ca.*
+ from community_aggregates_view ca
+)
+
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join all_community ac
+
+union all
+
+select
+ac.*,
+null as user_id,
+null as subscribed
+from all_community ac
+;
+
+create view community_mview as
+with all_community as
+(
+ select
+ ca.*
+ from community_aggregates_mview ca
+)
+
+select
+ac.*,
+u.id as user_id,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.id = cf.community_id) as subscribed
+from user_ u
+cross join all_community ac
+
+union all
+
+select
+ac.*,
+null as user_id,
+null as subscribed
+from all_community ac
+;
+
+-- community views
+drop view community_moderator_view;
+drop view community_follower_view;
+drop view community_user_ban_view;
+
+create view community_moderator_view as
+select *,
+(select actor_id from user_ u where cm.user_id = u.id) as user_actor_id,
+(select local from user_ u where cm.user_id = u.id) as user_local,
+(select name from user_ u where cm.user_id = u.id) as user_name,
+(select avatar from user_ u where cm.user_id = u.id),
+(select actor_id from community c where cm.community_id = c.id) as community_actor_id,
+(select local from community c where cm.community_id = c.id) as community_local,
+(select name from community c where cm.community_id = c.id) as community_name
+from community_moderator cm;
+
+create view community_follower_view as
+select *,
+(select actor_id from user_ u where cf.user_id = u.id) as user_actor_id,
+(select local from user_ u where cf.user_id = u.id) as user_local,
+(select name from user_ u where cf.user_id = u.id) as user_name,
+(select avatar from user_ u where cf.user_id = u.id),
+(select actor_id from community c where cf.community_id = c.id) as community_actor_id,
+(select local from community c where cf.community_id = c.id) as community_local,
+(select name from community c where cf.community_id = c.id) as community_name
+from community_follower cf;
+
+create view community_user_ban_view as
+select *,
+(select actor_id from user_ u where cm.user_id = u.id) as user_actor_id,
+(select local from user_ u where cm.user_id = u.id) as user_local,
+(select name from user_ u where cm.user_id = u.id) as user_name,
+(select avatar from user_ u where cm.user_id = u.id),
+(select actor_id from community c where cm.community_id = c.id) as community_actor_id,
+(select local from community c where cm.community_id = c.id) as community_local,
+(select name from community c where cm.community_id = c.id) as community_name
+from community_user_ban cm;
+
+-- post_view
+drop view post_view;
+drop view post_mview;
+drop materialized view post_aggregates_mview;
+drop view post_aggregates_view;
+
+-- regen post view
+create view post_aggregates_view as
+select
+p.*,
+(select u.banned from user_ u where p.creator_id = u.id) as banned,
+(select cb.id::bool from community_user_ban cb where p.creator_id = cb.user_id and p.community_id = cb.community_id) as banned_from_community,
+(select actor_id from user_ where p.creator_id = user_.id) as creator_actor_id,
+(select local from user_ where p.creator_id = user_.id) as creator_local,
+(select name from user_ where p.creator_id = user_.id) as creator_name,
+(select avatar from user_ where p.creator_id = user_.id) as creator_avatar,
+(select actor_id from community where p.community_id = community.id) as community_actor_id,
+(select local from community where p.community_id = community.id) as community_local,
+(select name from community where p.community_id = community.id) as community_name,
+(select removed from community c where p.community_id = c.id) as community_removed,
+(select deleted from community c where p.community_id = c.id) as community_deleted,
+(select nsfw from community c where p.community_id = c.id) as community_nsfw,
+(select count(*) from comment where comment.post_id = p.id) as number_of_comments,
+coalesce(sum(pl.score), 0) as score,
+count (case when pl.score = 1 then 1 else null end) as upvotes,
+count (case when pl.score = -1 then 1 else null end) as downvotes,
+hot_rank(coalesce(sum(pl.score) , 0),
+ (
+ case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps
+ else greatest(c.recent_comment_time, p.published)
+ end
+ )
+) as hot_rank,
+(
+ case when (p.published < ('now'::timestamp - '1 month'::interval)) then p.published -- Prevents necro-bumps
+ else greatest(c.recent_comment_time, p.published)
+ end
+) as newest_activity_time
+from post p
+left join post_like pl on p.id = pl.post_id
+left join (
+ select post_id,
+ max(published) as recent_comment_time
+ from comment
+ group by 1
+) c on p.id = c.post_id
+group by p.id, c.recent_comment_time;
+
+create materialized view post_aggregates_mview as select * from post_aggregates_view;
+
+create unique index idx_post_aggregates_mview_id on post_aggregates_mview (id);
+
+create view post_view as
+with all_post as (
+ select
+ pa.*
+ from post_aggregates_view pa
+)
+select
+ap.*,
+u.id as user_id,
+coalesce(pl.score, 0) as my_vote,
+(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
+(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read,
+(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved
+from user_ u
+cross join all_post ap
+left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
+
+union all
+
+select
+ap.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from all_post ap
+;
+
+create view post_mview as
+with all_post as (
+ select
+ pa.*
+ from post_aggregates_mview pa
+)
+select
+ap.*,
+u.id as user_id,
+coalesce(pl.score, 0) as my_vote,
+(select cf.id::bool from community_follower cf where u.id = cf.user_id and cf.community_id = ap.community_id) as subscribed,
+(select pr.id::bool from post_read pr where u.id = pr.user_id and pr.post_id = ap.id) as read,
+(select ps.id::bool from post_saved ps where u.id = ps.user_id and ps.post_id = ap.id) as saved
+from user_ u
+cross join all_post ap
+left join post_like pl on u.id = pl.user_id and ap.id = pl.post_id
+
+union all
+
+select
+ap.*,
+null as user_id,
+null as my_vote,
+null as subscribed,
+null as read,
+null as saved
+from all_post ap
+;
+
+
+-- reply_view, comment_view, user_mention
+drop view reply_view;
+drop view user_mention_view;
+drop view user_mention_mview;
+drop view comment_view;
+drop view comment_mview;
+drop materialized view comment_aggregates_mview;
+drop view comment_aggregates_view;
+
+-- reply and comment view
+create view comment_aggregates_view as
+select
+c.*,
+(select community_id from post p where p.id = c.post_id),
+(select co.actor_id from post p, community co where p.id = c.post_id and p.community_id = co.id) as community_actor_id,
+(select co.local from post p, community co where p.id = c.post_id and p.community_id = co.id) as community_local,
+(select co.name from post p, community co where p.id = c.post_id and p.community_id = co.id) as community_name,
+(select u.banned from user_ u where c.creator_id = u.id) as banned,
+(select cb.id::bool from community_user_ban cb, post p where c.creator_id = cb.user_id and p.id = c.post_id and p.community_id = cb.community_id) as banned_from_community,
+(select actor_id from user_ where c.creator_id = user_.id) as creator_actor_id,
+(select local from user_ where c.creator_id = user_.id) as creator_local,
+(select name from user_ where c.creator_id = user_.id) as creator_name,
+(select avatar from user_ where c.creator_id = user_.id) as creator_avatar,
+coalesce(sum(cl.score), 0) as score,
+count (case when cl.score = 1 then 1 else null end) as upvotes,
+count (case when cl.score = -1 then 1 else null end) as downvotes,
+hot_rank(coalesce(sum(cl.score) , 0), c.published) as hot_rank
+from comment c
+left join comment_like cl on c.id = cl.comment_id
+group by c.id;
+
+create materialized view comment_aggregates_mview as select * from comment_aggregates_view;
+
+create unique index idx_comment_aggregates_mview_id on comment_aggregates_mview (id);
+
+create view comment_view as
+with all_comment as
+(
+ select
+ ca.*
+ from comment_aggregates_view ca
+)
+
+select
+ac.*,
+u.id as user_id,
+coalesce(cl.score, 0) as my_vote,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.community_id = cf.community_id) as subscribed,
+(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+
+union all
+
+select
+ ac.*,
+ null as user_id,
+ null as my_vote,
+ null as subscribed,
+ null as saved
+from all_comment ac
+;
+
+create view comment_mview as
+with all_comment as
+(
+ select
+ ca.*
+ from comment_aggregates_mview ca
+)
+
+select
+ac.*,
+u.id as user_id,
+coalesce(cl.score, 0) as my_vote,
+(select cf.id::boolean from community_follower cf where u.id = cf.user_id and ac.community_id = cf.community_id) as subscribed,
+(select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+
+union all
+
+select
+ ac.*,
+ null as user_id,
+ null as my_vote,
+ null as subscribed,
+ null as saved
+from all_comment ac
+;
+
+-- Do the reply_view referencing the comment_mview
+create view reply_view as
+with closereply as (
+ select
+ c2.id,
+ c2.creator_id as sender_id,
+ c.creator_id as recipient_id
+ from comment c
+ inner join comment c2 on c.id = c2.parent_id
+ where c2.creator_id != c.creator_id
+ -- Do union where post is null
+ union
+ select
+ c.id,
+ c.creator_id as sender_id,
+ p.creator_id as recipient_id
+ from comment c, post p
+ where c.post_id = p.id and c.parent_id is null and c.creator_id != p.creator_id
+)
+select cv.*,
+closereply.recipient_id
+from comment_mview cv, closereply
+where closereply.id = cv.id
+;
+
+-- user mention
+create view user_mention_view as
+select
+ c.id,
+ um.id as user_mention_id,
+ c.creator_id,
+ c.creator_actor_id,
+ c.creator_local,
+ c.post_id,
+ c.parent_id,
+ c.content,
+ c.removed,
+ um.read,
+ c.published,
+ c.updated,
+ c.deleted,
+ c.community_id,
+ c.community_actor_id,
+ c.community_local,
+ c.community_name,
+ c.banned,
+ c.banned_from_community,
+ c.creator_name,
+ c.creator_avatar,
+ c.score,
+ c.upvotes,
+ c.downvotes,
+ c.hot_rank,
+ c.user_id,
+ c.my_vote,
+ c.saved,
+ um.recipient_id,
+ (select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
+ (select local from user_ u where u.id = um.recipient_id) as recipient_local
+from user_mention um, comment_view c
+where um.comment_id = c.id;
+
+
+create view user_mention_mview as
+with all_comment as
+(
+ select
+ ca.*
+ from comment_aggregates_mview ca
+)
+
+select
+ ac.id,
+ um.id as user_mention_id,
+ ac.creator_id,
+ ac.creator_actor_id,
+ ac.creator_local,
+ ac.post_id,
+ ac.parent_id,
+ ac.content,
+ ac.removed,
+ um.read,
+ ac.published,
+ ac.updated,
+ ac.deleted,
+ ac.community_id,
+ ac.community_actor_id,
+ ac.community_local,
+ ac.community_name,
+ ac.banned,
+ ac.banned_from_community,
+ ac.creator_name,
+ ac.creator_avatar,
+ ac.score,
+ ac.upvotes,
+ ac.downvotes,
+ ac.hot_rank,
+ u.id as user_id,
+ coalesce(cl.score, 0) as my_vote,
+ (select cs.id::bool from comment_saved cs where u.id = cs.user_id and cs.comment_id = ac.id) as saved,
+ um.recipient_id,
+ (select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
+ (select local from user_ u where u.id = um.recipient_id) as recipient_local
+from user_ u
+cross join all_comment ac
+left join comment_like cl on u.id = cl.user_id and ac.id = cl.comment_id
+left join user_mention um on um.comment_id = ac.id
+
+union all
+
+select
+ ac.id,
+ um.id as user_mention_id,
+ ac.creator_id,
+ ac.creator_actor_id,
+ ac.creator_local,
+ ac.post_id,
+ ac.parent_id,
+ ac.content,
+ ac.removed,
+ um.read,
+ ac.published,
+ ac.updated,
+ ac.deleted,
+ ac.community_id,
+ ac.community_actor_id,
+ ac.community_local,
+ ac.community_name,
+ ac.banned,
+ ac.banned_from_community,
+ ac.creator_name,
+ ac.creator_avatar,
+ ac.score,
+ ac.upvotes,
+ ac.downvotes,
+ ac.hot_rank,
+ null as user_id,
+ null as my_vote,
+ null as saved,
+ um.recipient_id,
+ (select actor_id from user_ u where u.id = um.recipient_id) as recipient_actor_id,
+ (select local from user_ u where u.id = um.recipient_id) as recipient_local
+from all_comment ac
+left join user_mention um on um.comment_id = ac.id
+;
+
deleted: None,
read: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_comment = match Comment::create(&conn, &comment_form) {
Err(_e) => return Err(APIError::err("couldnt_create_comment").into()),
};
+ 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
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,
} 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) {
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,
}
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)]
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()),
};
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,
}
// 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(),
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) {
return Err(APIError::err("no_community_edit_allowed").into());
}
+ let read_community = Community::read(&conn, data.edit_id)?;
+
let community_form = CommunityForm {
name: data.name.to_owned(),
title: data.title.to_owned(),
deleted: data.deleted.to_owned(),
nsfw: data.nsfw,
updated: Some(naive_now()),
+ actor_id: read_community.actor_id,
+ local: read_community.local,
+ private_key: read_community.private_key,
+ public_key: read_community.public_key,
+ last_refreshed_at: None,
+ published: None,
};
let _updated_community = match Community::update(&conn, data.edit_id, &community_form) {
let 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))?;
deleted: None,
nsfw: read_community.nsfw,
updated: Some(naive_now()),
+ actor_id: read_community.actor_id,
+ local: read_community.local,
+ private_key: read_community.private_key,
+ public_key: read_community.public_key,
+ last_refreshed_at: None,
+ published: None,
};
let _updated_community = match Community::update(&conn, data.community_id, &community_form) {
use 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>,
pub online: usize,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct GetPosts {
type_: String,
sort: String,
auth: Option<String>,
}
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct GetPostsResponse {
- posts: Vec<PostView>,
+ pub posts: Vec<PostView>,
}
#[derive(Serialize, Deserialize)]
}
// Check for a site ban
- if UserView::read(&conn, user_id)?.banned {
+ let user = User_::read(&conn, user_id)?;
+ if user.banned {
return Err(APIError::err("site_ban").into());
}
embed_description: iframely_description,
embed_html: iframely_html,
thumbnail_url: pictshare_thumbnail,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = match Post::create(&conn, &post_form) {
}
};
+ let updated_post = match Post::update_ap_id(&conn, inserted_post.id) {
+ Ok(post) => post,
+ Err(_e) => return Err(APIError::err("couldnt_create_post").into()),
+ };
+
+ post_create(&updated_post, &user, conn)?;
+
// They like their own post by default
let like_form = PostLikeForm {
post_id: inserted_post.id,
}
// Check for a site ban
- if UserView::read(&conn, user_id)?.banned {
+ let user = User_::read(&conn, user_id)?;
+ if user.banned {
return Err(APIError::err("site_ban").into());
}
let (iframely_title, iframely_description, iframely_html, 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(),
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)" {
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 })
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 {
};
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)?;
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;
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,
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
}
};
+ 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,
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()
}
let user_form = UserForm {
name: read_user.name,
- fedi_name: read_user.fedi_name,
email,
matrix_user_id: data.matrix_user_id.to_owned(),
avatar: data.avatar.to_owned(),
lang: data.lang.to_owned(),
show_avatars: data.show_avatars,
send_notifications_to_email: data.send_notifications_to_email,
+ actor_id: read_user.actor_id,
+ bio: read_user.bio,
+ local: read_user.local,
+ private_key: read_user.private_key,
+ public_key: read_user.public_key,
+ last_refreshed_at: None,
};
let updated_user = match User_::update(&conn, user_id, &user_form) {
return Err(APIError::err("not_an_admin").into());
}
- let read_user = User_::read(&conn, data.user_id)?;
-
- // TODO make addadmin easier
- let user_form = UserForm {
- name: read_user.name,
- fedi_name: read_user.fedi_name,
- email: read_user.email,
- matrix_user_id: read_user.matrix_user_id,
- avatar: read_user.avatar,
- password_encrypted: read_user.password_encrypted,
- preferred_username: read_user.preferred_username,
- updated: Some(naive_now()),
- admin: data.added,
- banned: read_user.banned,
- show_nsfw: read_user.show_nsfw,
- theme: read_user.theme,
- default_sort_type: read_user.default_sort_type,
- default_listing_type: read_user.default_listing_type,
- lang: read_user.lang,
- show_avatars: read_user.show_avatars,
- send_notifications_to_email: read_user.send_notifications_to_email,
- };
-
- match User_::update(&conn, data.user_id, &user_form) {
+ match User_::add_admin(&conn, user_id, data.added) {
Ok(user) => user,
Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
};
return Err(APIError::err("not_an_admin").into());
}
- let read_user = User_::read(&conn, data.user_id)?;
-
- // TODO make bans and addadmins easier
- let user_form = UserForm {
- name: read_user.name,
- fedi_name: read_user.fedi_name,
- email: read_user.email,
- matrix_user_id: read_user.matrix_user_id,
- avatar: read_user.avatar,
- password_encrypted: read_user.password_encrypted,
- preferred_username: read_user.preferred_username,
- updated: Some(naive_now()),
- admin: read_user.admin,
- banned: data.ban,
- show_nsfw: read_user.show_nsfw,
- theme: read_user.theme,
- default_sort_type: read_user.default_sort_type,
- default_listing_type: read_user.default_listing_type,
- lang: read_user.lang,
- show_avatars: read_user.show_avatars,
- send_notifications_to_email: read_user.send_notifications_to_email,
- };
-
- match User_::update(&conn, data.user_id, &user_form) {
+ match User_::ban_user(&conn, user_id, data.ban) {
Ok(user) => user,
Err(_e) => return Err(APIError::err("couldnt_update_user").into()),
};
.list()?;
for reply in &replies {
- let comment_form = CommentForm {
- content: reply.to_owned().content,
- parent_id: reply.to_owned().parent_id,
- post_id: reply.to_owned().post_id,
- creator_id: reply.to_owned().creator_id,
- removed: None,
- deleted: None,
- read: Some(true),
- updated: reply.to_owned().updated,
- };
-
- let _updated_comment = match Comment::update(&conn, reply.id, &comment_form) {
+ match Comment::mark_as_read(&conn, reply.id) {
Ok(comment) => comment,
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
};
.list()?;
for comment in &comments {
- let comment_form = CommentForm {
- content: "*Permananently Deleted*".to_string(),
- parent_id: comment.to_owned().parent_id,
- post_id: comment.to_owned().post_id,
- creator_id: comment.to_owned().creator_id,
- removed: None,
- deleted: Some(true),
- read: None,
- updated: Some(naive_now()),
- };
-
- let _updated_comment = match Comment::update(&conn, comment.id, &comment_form) {
+ let _updated_comment = match Comment::permadelete(&conn, comment.id) {
Ok(comment) => comment,
Err(_e) => return Err(APIError::err("couldnt_update_comment").into()),
};
.list()?;
for post in &posts {
- let post_form = PostForm {
- name: "*Permananently Deleted*".to_string(),
- url: Some("https://deleted.com".to_string()),
- body: Some("*Permananently Deleted*".to_string()),
- creator_id: post.to_owned().creator_id,
- community_id: post.to_owned().community_id,
- removed: None,
- deleted: Some(true),
- nsfw: post.to_owned().nsfw,
- locked: None,
- stickied: None,
- updated: Some(naive_now()),
- embed_title: None,
- embed_description: None,
- embed_html: None,
- thumbnail_url: None,
- };
-
- let _updated_post = match Post::update(&conn, post.id, &post_form) {
+ let _updated_post = match Post::permadelete(&conn, post.id) {
Ok(post) => post,
Err(_e) => return Err(APIError::err("couldnt_update_post").into()),
};
--- /dev/null
+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(())
+}
-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))
}
--- /dev/null
+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(())
+}
--- /dev/null
+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),
+}
+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()
+}
-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,
+ })
}
}
--- /dev/null
+// 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 {}
-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()),
+ })
}
}
--- /dev/null
+// 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(())
+}
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 (
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
+ pub ap_id: String,
+ pub local: bool,
}
#[derive(Insertable, AsChangeset, Clone)]
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 {
}
}
+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"]
let new_user = UserForm {
name: "terry".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
deleted: None,
updated: None,
nsfw: false,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
read: None,
parent_id: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
parent_id: None,
published: inserted_comment.published,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let child_comment_form = CommentForm {
deleted: None,
read: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_child_comment = Comment::create(&conn, &child_comment_form).unwrap();
published -> Timestamp,
updated -> Nullable<Timestamp>,
deleted -> Bool,
+ ap_id -> Text,
+ local -> Bool,
community_id -> Int4,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
banned -> Bool,
banned_from_community -> Bool,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
score -> BigInt,
published -> Timestamp,
updated -> Nullable<Timestamp>,
deleted -> Bool,
+ ap_id -> Text,
+ local -> Bool,
community_id -> Int4,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
banned -> Bool,
banned_from_community -> Bool,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
score -> BigInt,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
+ pub ap_id: String,
+ pub local: bool,
pub community_id: i32,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
pub banned: bool,
pub banned_from_community: bool,
+ pub creator_actor_id: String,
+ pub creator_local: bool,
pub creator_name: String,
pub creator_avatar: Option<String>,
pub score: i64,
published -> Timestamp,
updated -> Nullable<Timestamp>,
deleted -> Bool,
+ ap_id -> Text,
+ local -> Bool,
community_id -> Int4,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
banned -> Bool,
banned_from_community -> Bool,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
score -> BigInt,
pub published: chrono::NaiveDateTime,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
+ pub ap_id: String,
+ pub local: bool,
pub community_id: i32,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
pub banned: bool,
pub banned_from_community: bool,
+ pub creator_actor_id: String,
+ pub creator_local: bool,
pub creator_name: String,
pub creator_avatar: Option<String>,
pub score: i64,
let new_user = UserForm {
name: "timmy".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
deleted: None,
updated: None,
nsfw: false,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
deleted: None,
read: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
my_vote: None,
subscribed: None,
saved: None,
+ ap_id: "changeme".to_string(),
+ local: true,
+ community_actor_id: inserted_community.actor_id.to_owned(),
+ community_local: true,
+ creator_actor_id: inserted_user.actor_id.to_owned(),
+ creator_local: true,
};
let expected_comment_view_with_user = CommentView {
my_vote: Some(1),
subscribed: None,
saved: None,
+ ap_id: "changeme".to_string(),
+ local: true,
+ community_actor_id: inserted_community.actor_id.to_owned(),
+ community_local: true,
+ creator_actor_id: inserted_user.actor_id.to_owned(),
+ creator_local: true,
};
let mut read_comment_views_no_user = CommentQueryBuilder::create(&conn)
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,
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 {
.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)
}
let new_user = UserForm {
name: "bobbee".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
removed: None,
deleted: None,
updated: None,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
deleted: false,
published: inserted_community.published,
updated: None,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: inserted_community.published,
};
let community_follower_form = CommunityFollowerForm {
updated -> Nullable<Timestamp>,
deleted -> Bool,
nsfw -> Bool,
+ actor_id -> Text,
+ local -> Bool,
+ last_refreshed_at -> Timestamp,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
category_name -> Varchar,
updated -> Nullable<Timestamp>,
deleted -> Bool,
nsfw -> Bool,
+ actor_id -> Text,
+ local -> Bool,
+ last_refreshed_at -> Timestamp,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
category_name -> Varchar,
community_id -> Int4,
user_id -> Int4,
published -> Timestamp,
+ user_actor_id -> Text,
+ user_local -> Bool,
user_name -> Varchar,
avatar -> Nullable<Text>,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
}
}
community_id -> Int4,
user_id -> Int4,
published -> Timestamp,
+ user_actor_id -> Text,
+ user_local -> Bool,
user_name -> Varchar,
avatar -> Nullable<Text>,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
}
}
community_id -> Int4,
user_id -> Int4,
published -> Timestamp,
+ user_actor_id -> Text,
+ user_local -> Bool,
user_name -> Varchar,
avatar -> Nullable<Text>,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
}
}
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
pub nsfw: bool,
+ pub actor_id: String,
+ pub local: bool,
+ pub last_refreshed_at: chrono::NaiveDateTime,
+ pub creator_actor_id: String,
+ pub creator_local: bool,
pub creator_name: String,
pub creator_avatar: Option<String>,
pub category_name: String,
pub community_id: i32,
pub user_id: i32,
pub published: chrono::NaiveDateTime,
+ pub user_actor_id: String,
+ pub user_local: bool,
pub user_name: String,
pub avatar: Option<String>,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
}
pub community_id: i32,
pub user_id: i32,
pub published: chrono::NaiveDateTime,
+ pub user_actor_id: String,
+ pub user_local: bool,
pub user_name: String,
pub avatar: Option<String>,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
}
pub community_id: i32,
pub user_id: i32,
pub published: chrono::NaiveDateTime,
+ pub user_actor_id: String,
+ pub user_local: bool,
pub user_name: String,
pub avatar: Option<String>,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
}
use serde::{Deserialize, Serialize};
pub mod category;
+pub mod code_migrations;
pub mod comment;
pub mod comment_view;
pub mod community;
let new_mod = UserForm {
name: "the mod".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_mod = User_::create(&conn, &new_mod).unwrap();
let new_user = UserForm {
name: "jim2".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
deleted: None,
updated: None,
nsfw: false,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
read: None,
parent_id: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
let new_user = UserForm {
name: "thommy prw".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
use super::*;
+use crate::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)]
pub embed_description: Option<String>,
pub embed_html: Option<String>,
pub thumbnail_url: Option<String>,
+ pub ap_id: String,
+ pub local: bool,
}
-#[derive(Insertable, AsChangeset, Clone)]
+#[derive(Insertable, AsChangeset, Clone, Debug)]
#[table_name = "post"]
pub struct PostForm {
pub name: String,
pub community_id: i32,
pub removed: Option<bool>,
pub locked: Option<bool>,
+ pub published: Option<chrono::NaiveDateTime>,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: Option<bool>,
pub nsfw: bool,
pub embed_description: Option<String>,
pub embed_html: Option<String>,
pub thumbnail_url: Option<String>,
+ pub ap_id: String,
+ pub local: bool,
+}
+
+impl Post {
+ pub fn read(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
+ use crate::schema::post::dsl::*;
+ post.filter(id.eq(post_id)).first::<Self>(conn)
+ }
+
+ pub fn list_for_community(
+ conn: &PgConnection,
+ the_community_id: i32,
+ ) -> Result<Vec<Self>, Error> {
+ use crate::schema::post::dsl::*;
+ post
+ .filter(community_id.eq(the_community_id))
+ .load::<Self>(conn)
+ }
+
+ pub fn read_from_apub_id(conn: &PgConnection, object_id: &str) -> Result<Self, Error> {
+ use crate::schema::post::dsl::*;
+ post.filter(ap_id.eq(object_id)).first::<Self>(conn)
+ }
+
+ pub fn update_ap_id(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
+ use crate::schema::post::dsl::*;
+
+ let apid = make_apub_endpoint(EndpointType::Post, &post_id.to_string()).to_string();
+ diesel::update(post.find(post_id))
+ .set(ap_id.eq(apid))
+ .get_result::<Self>(conn)
+ }
+
+ pub fn permadelete(conn: &PgConnection, post_id: i32) -> Result<Self, Error> {
+ use crate::schema::post::dsl::*;
+
+ let perma_deleted = "*Permananently Deleted*";
+ let perma_deleted_url = "https://deleted.com";
+
+ diesel::update(post.find(post_id))
+ .set((
+ name.eq(perma_deleted),
+ url.eq(perma_deleted_url),
+ body.eq(perma_deleted),
+ deleted.eq(true),
+ updated.eq(naive_now()),
+ ))
+ .get_result::<Self>(conn)
+ }
}
impl Crud<PostForm> for Post {
let new_user = UserForm {
name: "jim".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
deleted: None,
updated: None,
nsfw: false,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
};
// Post Like
embed_description -> Nullable<Text>,
embed_html -> Nullable<Text>,
thumbnail_url -> Nullable<Text>,
+ ap_id -> Text,
+ local -> Bool,
banned -> Bool,
banned_from_community -> Bool,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
community_removed -> Bool,
community_deleted -> Bool,
embed_description -> Nullable<Text>,
embed_html -> Nullable<Text>,
thumbnail_url -> Nullable<Text>,
+ ap_id -> Text,
+ local -> Bool,
banned -> Bool,
banned_from_community -> Bool,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
creator_name -> Varchar,
creator_avatar -> Nullable<Text>,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
community_removed -> Bool,
community_deleted -> Bool,
pub embed_description: Option<String>,
pub embed_html: Option<String>,
pub thumbnail_url: Option<String>,
+ pub ap_id: String,
+ pub local: bool,
pub banned: bool,
pub banned_from_community: bool,
+ pub creator_actor_id: String,
+ pub creator_local: bool,
pub creator_name: String,
pub creator_avatar: Option<String>,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
pub community_removed: bool,
pub community_deleted: bool,
let new_user = UserForm {
name: user_name.to_owned(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
deleted: None,
updated: None,
nsfw: false,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".to_string(),
+ local: true,
+ creator_actor_id: inserted_user.actor_id.to_owned(),
+ creator_local: true,
+ community_actor_id: inserted_community.actor_id.to_owned(),
+ community_local: true,
};
let expected_post_listing_with_user = PostView {
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".to_string(),
+ local: true,
+ creator_actor_id: inserted_user.actor_id.to_owned(),
+ creator_local: true,
+ community_actor_id: inserted_community.actor_id.to_owned(),
+ community_local: true,
};
let read_post_listings_with_user = PostQueryBuilder::create(&conn)
let creator_form = UserForm {
name: "creator_pm".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_creator = User_::create(&conn, &creator_form).unwrap();
let recipient_form = UserForm {
name: "recipient_pm".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();
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};
pub struct User_ {
pub id: i32,
pub name: String,
- pub fedi_name: String,
pub preferred_username: Option<String>,
pub password_encrypted: String,
pub email: Option<String>,
pub show_avatars: bool,
pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>,
+ pub actor_id: String,
+ pub bio: Option<String>,
+ pub local: bool,
+ pub private_key: Option<String>,
+ pub public_key: Option<String>,
+ pub last_refreshed_at: chrono::NaiveDateTime,
}
-#[derive(Insertable, AsChangeset, Clone)]
+#[derive(Insertable, AsChangeset, Clone, Debug)]
#[table_name = "user_"]
pub struct UserForm {
pub name: String,
- pub fedi_name: String,
pub preferred_username: Option<String>,
pub password_encrypted: String,
pub admin: bool,
pub show_avatars: bool,
pub send_notifications_to_email: bool,
pub matrix_user_id: Option<String>,
+ pub actor_id: String,
+ pub bio: Option<String>,
+ pub local: bool,
+ pub private_key: Option<String>,
+ pub public_key: Option<String>,
+ pub last_refreshed_at: Option<chrono::NaiveDateTime>,
}
impl Crud<UserForm> for User_ {
Self::create(&conn, &edited_user)
}
+ // TODO do more individual updates like these
pub fn update_password(
conn: &PgConnection,
user_id: i32,
let password_hash = hash(new_password, DEFAULT_COST).expect("Couldn't hash password");
diesel::update(user_.find(user_id))
- .set(password_encrypted.eq(password_hash))
+ .set((
+ password_encrypted.eq(password_hash),
+ updated.eq(naive_now()),
+ ))
.get_result::<Self>(conn)
}
pub fn read_from_name(conn: &PgConnection, from_user_name: String) -> Result<Self, Error> {
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)]
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,
let new_user = UserForm {
name: "thommy".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
let expected_user = User_ {
id: inserted_user.id,
name: "thommy".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: inserted_user.published,
};
let read_user = User_::read(&conn, inserted_user.id).unwrap();
let new_user = UserForm {
name: "terrylake".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_user = User_::create(&conn, &new_user).unwrap();
let recipient_form = UserForm {
name: "terrylakes recipient".into(),
- fedi_name: "rrf".into(),
preferred_username: None,
password_encrypted: "nope".into(),
email: None,
lang: "browser".into(),
show_avatars: true,
send_notifications_to_email: false,
+ actor_id: "changeme".into(),
+ bio: None,
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
};
let inserted_recipient = User_::create(&conn, &recipient_form).unwrap();
deleted: None,
updated: None,
nsfw: false,
+ actor_id: "changeme".into(),
+ local: true,
+ private_key: None,
+ public_key: None,
+ last_refreshed_at: None,
+ published: None,
};
let inserted_community = Community::create(&conn, &new_community).unwrap();
embed_description: None,
embed_html: None,
thumbnail_url: None,
+ ap_id: "changeme".into(),
+ local: true,
+ published: None,
};
let inserted_post = Post::create(&conn, &new_post).unwrap();
read: None,
parent_id: None,
updated: None,
+ ap_id: "changeme".into(),
+ local: true,
};
let inserted_comment = Comment::create(&conn, &comment_form).unwrap();
id -> Int4,
user_mention_id -> Int4,
creator_id -> Int4,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
post_id -> Int4,
parent_id -> Nullable<Int4>,
content -> Text,
updated -> Nullable<Timestamp>,
deleted -> Bool,
community_id -> Int4,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
banned -> Bool,
banned_from_community -> Bool,
my_vote -> Nullable<Int4>,
saved -> Nullable<Bool>,
recipient_id -> Int4,
+ recipient_actor_id -> Text,
+ recipient_local -> Bool,
}
}
id -> Int4,
user_mention_id -> Int4,
creator_id -> Int4,
+ creator_actor_id -> Text,
+ creator_local -> Bool,
post_id -> Int4,
parent_id -> Nullable<Int4>,
content -> Text,
updated -> Nullable<Timestamp>,
deleted -> Bool,
community_id -> Int4,
+ community_actor_id -> Text,
+ community_local -> Bool,
community_name -> Varchar,
banned -> Bool,
banned_from_community -> Bool,
my_vote -> Nullable<Int4>,
saved -> Nullable<Bool>,
recipient_id -> Int4,
+ recipient_actor_id -> Text,
+ recipient_local -> Bool,
}
}
pub id: i32,
pub user_mention_id: i32,
pub creator_id: i32,
+ pub creator_actor_id: String,
+ pub creator_local: bool,
pub post_id: i32,
pub parent_id: Option<i32>,
pub content: String,
pub updated: Option<chrono::NaiveDateTime>,
pub deleted: bool,
pub community_id: i32,
+ pub community_actor_id: String,
+ pub community_local: bool,
pub community_name: String,
pub banned: bool,
pub banned_from_community: bool,
pub my_vote: Option<i32>,
pub saved: Option<bool>,
pub recipient_id: i32,
+ pub recipient_actor_id: String,
+ pub recipient_local: bool,
}
pub struct UserMentionQueryBuilder<'a> {
table! {
user_view (id) {
id -> Int4,
+ actor_id -> Text,
name -> Varchar,
avatar -> Nullable<Text>,
email -> Nullable<Text>,
matrix_user_id -> Nullable<Text>,
- fedi_name -> Varchar,
+ bio -> Nullable<Text>,
+ local -> Bool,
admin -> Bool,
banned -> Bool,
show_avatars -> Bool,
table! {
user_mview (id) {
id -> Int4,
+ actor_id -> Text,
name -> Varchar,
avatar -> Nullable<Text>,
email -> Nullable<Text>,
matrix_user_id -> Nullable<Text>,
- fedi_name -> Varchar,
+ bio -> Nullable<Text>,
+ local -> Bool,
admin -> Bool,
banned -> Bool,
show_avatars -> Bool,
#[table_name = "user_view"]
pub struct UserView {
pub id: i32,
+ pub actor_id: String,
pub name: String,
pub avatar: Option<String>,
pub email: Option<String>,
pub matrix_user_id: Option<String>,
- pub fedi_name: String,
+ pub bio: Option<String>,
+ pub local: bool,
pub admin: bool,
pub banned: bool,
pub show_avatars: bool,
pub extern crate jsonwebtoken;
pub extern crate lettre;
pub extern crate lettre_email;
+extern crate log;
+pub extern crate openssl;
pub extern crate rand;
pub extern crate regex;
pub extern crate rss;
pub mod 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;
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()
}
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)
}
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();
// 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?,
+ )
}
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),
+ );
+ }
}
+use crate::apub::get_apub_protocol_string;
use crate::db::site_view::SiteView;
use crate::version;
use crate::Settings;
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
.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(
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![]
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
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>,
}
}
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),
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")),
};
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}"
+ //}
]
}))
})
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));
joinable!(user_mention -> user_ (recipient_id));
allow_tables_to_appear_in_same_query!(
+ activity,
category,
comment,
comment_like,
pub front_end_dir: String,
pub rate_limit: RateLimitConfig,
pub email: Option<EmailConfig>,
- pub federation_enabled: bool,
+ pub federation: Federation,
}
#[derive(Debug, Deserialize, Clone)]
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,
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)?;
}
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);
}
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);
.git
build
.build
-.git
-.history
.idea
.jshintrc
.nyc_output
.sass-cache
.vscode
-build
coverage
jsconfig.json
Gemfile.lock
"@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",
</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')}
}
>
<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">
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>
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>
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>
}
>
<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>
requestNotificationPermission() {
if (UserService.Instance.user) {
- document.addEventListener('DOMContentLoaded', function() {
+ document.addEventListener('DOMContentLoaded', function () {
if (!Notification) {
toast(i18n.t('notifications_error'), 'danger');
return;
<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">
)}
{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')}
getUnixTime,
pictshareImage,
setupTippy,
+ hostname,
previewLines,
} from '../utils';
import { i18n } from '../i18next';
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}
/>
);
</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 ? (
{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>
}
>
<svg
- class={`icon icon-inline ${post.saved &&
- 'text-warning'}`}
+ class={`icon icon-inline ${
+ post.saved && 'text-warning'
+ }`}
>
<use xlinkHref="#icon-star"></use>
</svg>
}
>
<svg
- class={`icon icon-inline ${post.deleted &&
- 'text-danger'}`}
+ class={`icon icon-inline ${
+ post.deleted && 'text-danger'
+ }`}
>
<use xlinkHref="#icon-trash"></use>
</svg>
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>
}
>
<svg
- class={`icon icon-inline ${post.locked &&
- 'text-danger'}`}
+ class={`icon icon-inline ${
+ post.locked && 'text-danger'
+ }`}
>
<use xlinkHref="#icon-lock"></use>
</svg>
}
>
<svg
- class={`icon icon-inline ${post.stickied &&
- 'text-success'}`}
+ class={`icon icon-inline ${
+ post.stickied && 'text-success'
+ }`}
>
<use xlinkHref="#icon-pin"></use>
</svg>
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
/>
</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
/>
</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
/>
</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
</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')}
}
>
<svg
- class={`icon icon-inline ${message.read &&
- 'text-success'}`}
+ class={`icon icon-inline ${
+ message.read && 'text-success'
+ }`}
>
<use xlinkHref="#icon-check"></use>
</svg>
}
>
<svg
- class={`icon icon-inline ${message.deleted &&
- 'text-danger'}`}
+ class={`icon icon-inline ${
+ message.deleted && 'text-danger'
+ }`}
>
<use xlinkHref="#icon-trash"></use>
</svg>
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>
}
>
<svg
- class={`icon icon-inline ${community.deleted &&
- 'text-danger'}`}
+ class={`icon icon-inline ${
+ community.deleted && 'text-danger'
+ }`}
>
<use xlinkHref="#icon-trash"></use>
</svg>
))}
</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')}
export interface UserView {
id: number;
+ actor_id: string;
name: string;
avatar?: string;
email?: string;
matrix_user_id?: string;
+ bio?: string;
+ local: boolean;
published: string;
number_of_posts: number;
post_score: number;
export interface CommunityUser {
id: number;
user_id: number;
+ user_actor_id: string;
+ user_local: boolean;
user_name: string;
avatar?: string;
community_id: number;
+ community_actor_id: string;
+ community_local: boolean;
community_name: string;
published: string;
}
export interface Community {
id: number;
+ actor_id: string;
+ local: boolean;
name: string;
title: string;
description?: string;
nsfw: boolean;
published: string;
updated?: string;
+ creator_actor_id: string;
+ creator_local: boolean;
+ last_refreshed_at: string;
creator_name: string;
creator_avatar?: string;
category_name: string;
embed_description?: string;
embed_html?: string;
thumbnail_url?: string;
+ ap_id: string;
+ local: boolean;
nsfw: boolean;
banned: boolean;
banned_from_community: boolean;
published: string;
updated?: string;
+ creator_actor_id: string;
+ creator_local: boolean;
creator_name: string;
creator_avatar?: string;
+ community_actor_id: string;
+ community_local: boolean;
community_name: string;
community_removed: boolean;
community_deleted: boolean;
export interface Comment {
id: number;
+ ap_id: string;
+ local: boolean;
creator_id: number;
post_id: number;
parent_id?: number;
published: string;
updated?: string;
community_id: number;
+ community_actor_id: string;
+ community_local: boolean;
community_name: string;
banned: boolean;
banned_from_community: boolean;
+ creator_actor_id: string;
+ creator_local: boolean;
creator_name: string;
creator_avatar?: string;
score: number;
saved?: boolean;
user_mention_id?: number; // For mention type
recipient_id?: number;
+ recipient_actor_id?: string;
+ recipient_local?: boolean;
depth?: number;
}
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) {
defs: objectFlip(emojiShortName),
});
-md.renderer.rules.emoji = function(token, idx) {
+md.renderer.rules.emoji = function (token, idx) {
return twemoji.parse(token[idx].content);
};
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;
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;
.slice(0, lines * 2)
.join('\n');
}
+
+export function hostname(url: string): string {
+ return new URL(url).hostname;
+}
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"